summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorStephen Gallagher <sgallagh@redhat.com>2010-02-18 07:49:04 -0500
committerStephen Gallagher <sgallagh@redhat.com>2010-02-18 13:48:45 -0500
commit1c48b5a62f73234ed26bb20f0ab345ab61cda0ab (patch)
tree0b6cddd567a862e1a7b5df23764869782a62ca78 /src
parent8c56df3176f528fe0260974b3bf934173c4651ea (diff)
downloadsssd_unused-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.gz
sssd_unused-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.xz
sssd_unused-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.zip
Rename server/ directory to src/
Also update BUILD.txt
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am941
-rw-r--r--src/build_macros.m421
-rw-r--r--src/conf_macros.m4207
-rw-r--r--src/confdb/confdb.c908
-rw-r--r--src/confdb/confdb.h364
-rw-r--r--src/confdb/confdb_private.h35
-rw-r--r--src/confdb/confdb_setup.c423
-rw-r--r--src/confdb/confdb_setup.h52
-rw-r--r--src/config/SSSDConfig.py1664
-rwxr-xr-xsrc/config/SSSDConfigTest.py1521
-rw-r--r--src/config/etc/sssd.api.conf66
-rw-r--r--src/config/etc/sssd.api.d/sssd-ipa.conf77
-rw-r--r--src/config/etc/sssd.api.d/sssd-krb5.conf13
-rw-r--r--src/config/etc/sssd.api.d/sssd-ldap.conf68
-rw-r--r--src/config/etc/sssd.api.d/sssd-local.conf10
-rw-r--r--src/config/etc/sssd.api.d/sssd-proxy.conf7
-rw-r--r--src/config/ipachangeconf.py588
-rw-r--r--src/config/setup.py35
-rw-r--r--src/config/testconfigs/noparse.api.conf7
-rw-r--r--src/config/testconfigs/sssd-badversion.conf42
-rw-r--r--src/config/testconfigs/sssd-invalid-badbool.conf43
-rw-r--r--src/config/testconfigs/sssd-invalid.conf3
-rw-r--r--src/config/testconfigs/sssd-noversion.conf41
-rw-r--r--src/config/testconfigs/sssd-valid.conf43
-rw-r--r--src/config/upgrade_config.py405
-rw-r--r--src/configure.ac145
-rw-r--r--src/db/sysdb.c1883
-rw-r--r--src/db/sysdb.h650
-rw-r--r--src/db/sysdb_ops.c5059
-rw-r--r--src/db/sysdb_private.h107
-rw-r--r--src/db/sysdb_search.c691
-rw-r--r--src/doxy.config.in1538
-rw-r--r--src/examples/sssd.conf81
-rw-r--r--src/examples/sssdproxytest5
-rw-r--r--src/examples/sudo6
-rw-r--r--src/external/crypto.m413
-rw-r--r--src/external/docbook.m435
-rw-r--r--src/external/krb5.m462
-rw-r--r--src/external/ldap.m463
-rw-r--r--src/external/libcares.m420
-rw-r--r--src/external/libcollection.m412
-rw-r--r--src/external/libdhash.m412
-rw-r--r--src/external/libini_config.m412
-rw-r--r--src/external/libldb.m428
-rw-r--r--src/external/libpcre.m415
-rw-r--r--src/external/libpopt.m49
-rw-r--r--src/external/libtalloc.m47
-rw-r--r--src/external/libtdb.m48
-rw-r--r--src/external/libtevent.m47
-rw-r--r--src/external/pam.m46
-rw-r--r--src/external/pkg.m4156
-rw-r--r--src/external/platform.m429
-rw-r--r--src/external/python.m458
-rw-r--r--src/external/selinux.m413
-rw-r--r--src/external/sizes.m444
-rw-r--r--src/krb5_plugin/sssd_krb5_locator_plugin.c289
-rw-r--r--src/ldb_modules/memberof.c3612
-rw-r--r--src/m4/.dir0
-rw-r--r--src/man/include/failover.xml42
-rw-r--r--src/man/include/param_help.xml10
-rw-r--r--src/man/include/upstream.xml4
-rw-r--r--src/man/sss_groupadd.8.xml81
-rw-r--r--src/man/sss_groupdel.8.xml69
-rw-r--r--src/man/sss_groupmod.8.xml95
-rw-r--r--src/man/sss_groupshow.8.xml76
-rw-r--r--src/man/sss_useradd.8.xml191
-rw-r--r--src/man/sss_userdel.8.xml105
-rw-r--r--src/man/sss_usermod.8.xml150
-rw-r--r--src/man/sssd-ipa.5.xml159
-rw-r--r--src/man/sssd-krb5.5.xml250
-rw-r--r--src/man/sssd-ldap.5.xml688
-rw-r--r--src/man/sssd.8.xml148
-rw-r--r--src/man/sssd.conf.5.xml808
-rw-r--r--src/man/sssd_krb5_locator_plugin.8.xml89
-rw-r--r--src/monitor/monitor.c2631
-rw-r--r--src/monitor/monitor.h36
-rw-r--r--src/monitor/monitor_interfaces.h55
-rw-r--r--src/monitor/monitor_sbus.c195
-rw-r--r--src/po/LINGUAS10
-rw-r--r--src/po/Makevars41
-rw-r--r--src/po/POTFILES.in16
-rw-r--r--src/po/de.po692
-rw-r--r--src/po/es.po720
-rw-r--r--src/po/fr.po692
-rw-r--r--src/po/it.po702
-rw-r--r--src/po/ja.po692
-rw-r--r--src/po/nl.po693
-rw-r--r--src/po/pl.po718
-rw-r--r--src/po/pt.po712
-rw-r--r--src/po/sss_daemon.pot691
-rw-r--r--src/po/sv.po711
-rw-r--r--src/providers/child_common.c416
-rw-r--r--src/providers/child_common.h73
-rw-r--r--src/providers/data_provider.h219
-rw-r--r--src/providers/data_provider_be.c1235
-rw-r--r--src/providers/data_provider_fo.c356
-rw-r--r--src/providers/data_provider_opts.c384
-rw-r--r--src/providers/dp_auth_util.c414
-rw-r--r--src/providers/dp_backend.h142
-rw-r--r--src/providers/dp_sbus.c45
-rw-r--r--src/providers/fail_over.c651
-rw-r--r--src/providers/fail_over.h108
-rw-r--r--src/providers/ipa/ipa_access.c1823
-rw-r--r--src/providers/ipa/ipa_access.h66
-rw-r--r--src/providers/ipa/ipa_auth.c313
-rw-r--r--src/providers/ipa/ipa_auth.h32
-rw-r--r--src/providers/ipa/ipa_common.c597
-rw-r--r--src/providers/ipa/ipa_common.h83
-rw-r--r--src/providers/ipa/ipa_init.c293
-rw-r--r--src/providers/ipa/ipa_timerules.c1186
-rw-r--r--src/providers/ipa/ipa_timerules.h56
-rw-r--r--src/providers/krb5/krb5_auth.c1193
-rw-r--r--src/providers/krb5/krb5_auth.h91
-rw-r--r--src/providers/krb5/krb5_become_user.c61
-rw-r--r--src/providers/krb5/krb5_child.c1030
-rw-r--r--src/providers/krb5/krb5_common.c356
-rw-r--r--src/providers/krb5/krb5_common.h72
-rw-r--r--src/providers/krb5/krb5_init.c152
-rw-r--r--src/providers/krb5/krb5_utils.c145
-rw-r--r--src/providers/krb5/krb5_utils.h39
-rw-r--r--src/providers/ldap/ldap_auth.c1055
-rw-r--r--src/providers/ldap/ldap_child.c429
-rw-r--r--src/providers/ldap/ldap_common.c589
-rw-r--r--src/providers/ldap/ldap_common.h115
-rw-r--r--src/providers/ldap/ldap_id.c795
-rw-r--r--src/providers/ldap/ldap_id_cleanup.c555
-rw-r--r--src/providers/ldap/ldap_id_enum.c608
-rw-r--r--src/providers/ldap/ldap_init.c179
-rw-r--r--src/providers/ldap/sdap.c388
-rw-r--r--src/providers/ldap/sdap.h258
-rw-r--r--src/providers/ldap/sdap_async.c1018
-rw-r--r--src/providers/ldap/sdap_async.h126
-rw-r--r--src/providers/ldap/sdap_async_accounts.c2065
-rw-r--r--src/providers/ldap/sdap_async_connection.c1141
-rw-r--r--src/providers/ldap/sdap_async_private.h68
-rw-r--r--src/providers/ldap/sdap_child_helpers.c462
-rw-r--r--src/providers/providers.h24
-rw-r--r--src/providers/proxy.c2521
-rw-r--r--src/providers/sssd_be.exports4
-rw-r--r--src/python/pysss.c937
-rw-r--r--src/resolv/ares/ares_data.c140
-rw-r--r--src/resolv/ares/ares_data.h68
-rw-r--r--src/resolv/ares/ares_dns.h91
-rw-r--r--src/resolv/ares/ares_parse_srv_reply.c183
-rw-r--r--src/resolv/ares/ares_parse_srv_reply.h35
-rw-r--r--src/resolv/ares/ares_parse_txt_reply.c204
-rw-r--r--src/resolv/ares/ares_parse_txt_reply.h33
-rw-r--r--src/resolv/async_resolv.c1062
-rw-r--r--src/resolv/async_resolv.h95
-rw-r--r--src/responder/common/responder.h152
-rw-r--r--src/responder/common/responder_cmd.c103
-rw-r--r--src/responder/common/responder_common.c589
-rw-r--r--src/responder/common/responder_dp.c590
-rw-r--r--src/responder/common/responder_packet.c253
-rw-r--r--src/responder/common/responder_packet.h43
-rw-r--r--src/responder/nss/nsssrv.c367
-rw-r--r--src/responder/nss/nsssrv.h70
-rw-r--r--src/responder/nss/nsssrv_cmd.c3182
-rw-r--r--src/responder/nss/nsssrv_nc.c321
-rw-r--r--src/responder/nss/nsssrv_nc.h51
-rw-r--r--src/responder/pam/pam_LOCAL_domain.c476
-rw-r--r--src/responder/pam/pamsrv.c224
-rw-r--r--src/responder/pam/pamsrv.h57
-rw-r--r--src/responder/pam/pamsrv_cmd.c1181
-rw-r--r--src/responder/pam/pamsrv_dp.c142
-rw-r--r--src/sbus/sbus_client.c57
-rw-r--r--src/sbus/sbus_client.h36
-rw-r--r--src/sbus/sssd_dbus.h153
-rw-r--r--src/sbus/sssd_dbus_common.c444
-rw-r--r--src/sbus/sssd_dbus_connection.c692
-rw-r--r--src/sbus/sssd_dbus_private.h98
-rw-r--r--src/sbus/sssd_dbus_server.c171
-rw-r--r--src/sss_client/common.c669
-rw-r--r--src/sss_client/group.c435
-rw-r--r--src/sss_client/man/pam_sss.8.xml97
-rw-r--r--src/sss_client/pam_sss.c1166
-rw-r--r--src/sss_client/pam_test_client.c95
-rw-r--r--src/sss_client/passwd.c373
-rw-r--r--src/sss_client/protos.h137
-rw-r--r--src/sss_client/sss_cli.h220
-rw-r--r--src/sss_client/sss_nss.exports73
-rw-r--r--src/sss_client/sss_pam.exports4
-rw-r--r--src/sss_client/sss_pam_macros.h30
-rw-r--r--src/sysv/SUSE/sssd78
-rw-r--r--src/sysv/sssd121
-rw-r--r--src/tests/auth-tests.c343
-rw-r--r--src/tests/check_and_open-tests.c218
-rw-r--r--src/tests/common.c109
-rw-r--r--src/tests/common.h21
-rw-r--r--src/tests/fail_over-tests.c304
-rw-r--r--src/tests/files-tests.c323
-rw-r--r--src/tests/find_uid-tests.c125
-rw-r--r--src/tests/ipa_ldap_opt-tests.c59
-rw-r--r--src/tests/ipa_timerules-tests.c580
-rw-r--r--src/tests/krb5_utils-tests.c307
-rw-r--r--src/tests/python-test.py445
-rw-r--r--src/tests/refcount-tests.c232
-rw-r--r--src/tests/resolv-tests.c598
-rw-r--r--src/tests/stress-tests.c328
-rw-r--r--src/tests/strtonum-tests.c455
-rw-r--r--src/tests/sysdb-tests.c3330
-rw-r--r--src/tools/files.c736
-rw-r--r--src/tools/sss_groupadd.c155
-rw-r--r--src/tools/sss_groupdel.c155
-rw-r--r--src/tools/sss_groupmod.c246
-rw-r--r--src/tools/sss_groupshow.c944
-rw-r--r--src/tools/sss_sync_ops.c1838
-rw-r--r--src/tools/sss_sync_ops.h125
-rw-r--r--src/tools/sss_useradd.c349
-rw-r--r--src/tools/sss_userdel.c205
-rw-r--r--src/tools/sss_usermod.c265
-rw-r--r--src/tools/tools_util.c520
-rw-r--r--src/tools/tools_util.h108
-rw-r--r--src/util/backup_file.c122
-rw-r--r--src/util/check_and_open.c89
-rw-r--r--src/util/crypto_sha512crypt.c382
-rw-r--r--src/util/debug.c154
-rw-r--r--src/util/dlinklist.h116
-rw-r--r--src/util/find_uid.c296
-rw-r--r--src/util/find_uid.h36
-rw-r--r--src/util/memory.c67
-rw-r--r--src/util/nss_sha512crypt.c419
-rw-r--r--src/util/refcount.c88
-rw-r--r--src/util/refcount.h39
-rw-r--r--src/util/server.c433
-rw-r--r--src/util/sha512crypt.h4
-rw-r--r--src/util/signal.c146
-rw-r--r--src/util/signal.m41
-rw-r--r--src/util/sss_krb5.c196
-rw-r--r--src/util/sss_krb5.h50
-rw-r--r--src/util/sss_ldap.c70
-rw-r--r--src/util/sss_ldap.h30
-rw-r--r--src/util/strtonum.c65
-rw-r--r--src/util/strtonum.h32
-rw-r--r--src/util/user_info_msg.c51
-rw-r--r--src/util/user_info_msg.h33
-rw-r--r--src/util/usertools.c175
-rw-r--r--src/util/util.c138
-rw-r--r--src/util/util.h256
239 files changed, 91971 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..eeb8cfef
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,941 @@
+SUBDIRS = po
+topdir=.
+
+# Some old versions of automake don't define builddir
+builddir ?= .
+
+DOXYGEN = @DOXYGEN@
+
+sssdlibexecdir = $(libexecdir)/sssd
+sssdlibdir = $(libdir)/sssd
+ldblibdir = @ldblibdir@
+if BUILD_KRB5_LOCATOR_PLUGIN
+krb5plugindir = @krb5pluginpath@
+endif
+sssdconfdir = $(sysconfdir)/sssd
+sssdapiplugindir = $(sssdconfdir)/sssd.api.d
+dbusintrospectdir = $(datarootdir)/sssd/introspect
+dbuspolicydir = $(sysconfdir)/dbus-1/system.d
+localedir = @localedir@
+nsslibdir = @nsslibdir@
+pamlibdir = $(nsslibdir)/security
+
+dbpath = @dbpath@
+pluginpath = @pluginpath@
+pidpath = @pidpath@
+pipepath = @pipepath@
+initdir = @initdir@
+logpath = @logpath@
+pubconfpath = @pubconfpath@
+
+AM_CFLAGS =
+if WANT_AUX_INFO
+ AM_CFLAGS += -aux-info $@.X
+endif
+if HAVE_GCC
+ AM_CFLAGS += -Wall -Wshadow -Wstrict-prototypes -Wpointer-arith \
+ -Wcast-qual -Wcast-align -Wwrite-strings \
+ -Werror-implicit-function-declaration
+endif
+
+ACLOCAL_AMFLAGS = -I m4 -I .
+
+sbin_PROGRAMS = \
+ sssd \
+ sss_useradd \
+ sss_userdel \
+ sss_groupadd \
+ sss_groupdel \
+ sss_usermod \
+ sss_groupmod \
+ sss_groupshow
+
+sssdlibexec_PROGRAMS = \
+ sssd_nss \
+ sssd_pam \
+ sssd_be \
+ krb5_child \
+ ldap_child \
+ $(sssd_pk) \
+ $(sssd_info)
+
+dist_sssdlibexec_SCRIPTS = \
+ config/upgrade_config.py
+
+if HAVE_CHECK
+ non_interactive_check_based_tests = \
+ sysdb-tests \
+ strtonum-tests \
+ resolv-tests \
+ krb5-utils-tests \
+ check_and_open-tests \
+ ipa_timerules-tests \
+ files-tests \
+ refcount-tests \
+ fail_over-tests \
+ find_uid-tests \
+ auth-tests \
+ ipa_ldap_opt-tests
+endif
+
+check_PROGRAMS = \
+ stress-tests \
+ $(non_interactive_check_based_tests)
+
+TESTS = \
+ $(srcdir)/config/SSSDConfigTest.py \
+ $(non_interactive_check_based_tests)
+
+sssdlib_LTLIBRARIES = \
+ libsss_ldap.la \
+ libsss_krb5.la \
+ libsss_proxy.la \
+ libsss_ipa.la
+
+ldblib_LTLIBRARIES = \
+ memberof.la
+
+if BUILD_KRB5_LOCATOR_PLUGIN
+krb5plugin_LTLIBRARIES = \
+ sssd_krb5_locator_plugin.la
+endif
+
+noinst_LTLIBRARIES = \
+ libsss_crypt.la
+
+if HAVE_NSS
+ SSS_CRYPT_SOURCES = util/nss_sha512crypt.c
+ SSS_CRYPT_CFLAGS = $(NSS_CFLAGS)
+ SSS_CRYPT_LIBS = $(NSS_LIBS)
+else
+ SSS_CRYPT_SOURCES = util/crypto_sha512crypt.c
+ SSS_CRYPT_CFLAGS = $(CRYPTO_CFLAGS)
+ SSS_CRYPT_LIBS = $(CRYPTO_LIBS)
+endif
+
+libsss_crypt_la_SOURCES = \
+ $(SSS_CRYPT_SOURCES)
+libsss_crypt_la_CPPFLAGS = \
+ $(SSS_CRYPT_CFLAGS)
+libsss_crypt_la_LIBADD = \
+ $(SSS_CRYPT_LIBS)
+
+if BUILD_PYTHON_BINDINGS
+pyexec_LTLIBRARIES = \
+ pysss.la
+endif
+
+dist_noinst_SCRIPTS = \
+ $(EXTRA_SCRIPTS) \
+ config/setup.py \
+ config/ipachangeconf.py \
+ config/SSSDConfig.py \
+ config/SSSDConfigTest.py
+
+dist_noinst_DATA = \
+ config/testconfigs/sssd-valid.conf \
+ config/testconfigs/noparse.api.conf \
+ config/testconfigs/sssd-noversion.conf \
+ config/testconfigs/sssd-badversion.conf \
+ config/testconfigs/sssd-invalid.conf \
+ config/testconfigs/sssd-invalid-badbool.conf
+
+###############################
+# Global compilation settings #
+###############################
+
+if HAVE_SYSTEM_COLLECTION
+ COLLECTION_CFLAGS = $(SYSTEM_COLLECTION_CFLAGS)
+ COLLECTION_LIBS = $(SYSTEM_COLLECTION_LIBS)
+else
+ COLLECTION_CFLAGS = \
+ -I$(srcdir)/../common/collection
+ COLLECTION_LIBS = \
+ -L$(builddir)/../common/collection \
+ -lcollection
+endif
+
+if HAVE_SYSTEM_INI_CONFIG
+ INI_CFG_CFLAGS = $(SYSTEM_INI_CONFIG_CFLAGS)
+ INI_CFG_LIBS = $(SYSTEM_INI_CONFIG_LIBS)
+else
+ INI_CFG_CFLAGS = \
+ -I$(srcdir)/../common/ini
+ INI_CFG_LIBS = \
+ -L$(builddir)/../common/ini/ \
+ -lini_config
+endif
+
+if HAVE_SYSTEM_DHASH
+ DHASH_CFLAGS = $(SYSTEM_DHASH_CFLAGS)
+ DHASH_LIBS = $(SYSTEM_DHASH_LIBS)
+else
+ DHASH_CFLAGS = \
+ -I$(srcdir)/../common/dhash
+ DHASH_LIBS = \
+ -L$(builddir)/../common/dhash/ \
+ -ldhash
+endif
+
+AM_CPPFLAGS = -Wall \
+ -Iinclude \
+ -I.. \
+ -I$(srcdir)/include \
+ -I$(srcdir)/sss_client \
+ -Iinclude \
+ -I. \
+ $(POPT_CFLAGS) \
+ $(TALLOC_CFLAGS) \
+ $(TDB_CFLAGS) \
+ $(TEVENT_CFLAGS) \
+ $(LDB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(PCRE_CFLAGS) \
+ $(COLLECTION_CFLAGS) \
+ $(INI_CFG_CFLAGS) \
+ $(DHASH_CFLAGS) \
+ -DLIBDIR=\"$(libdir)\" \
+ -DVARDIR=\"$(localstatedir)\" \
+ -DSHLIBEXT=\"$(SHLIBEXT)\" \
+ -DSSSD_LIBEXEC_PATH=\"$(sssdlibexecdir)\" \
+ -DSSSD_INTROSPECT_PATH=\"$(dbusinstropectdir)\" \
+ -DSSSD_CONF_DIR=\"$(sssdconfdir)\" \
+ -DSSS_NSS_SOCKET_NAME=\"$(pipepath)/nss\" \
+ -DSSS_PAM_SOCKET_NAME=\"$(pipepath)/pam\" \
+ -DSSS_PAM_PRIV_SOCKET_NAME=\"$(pipepath)/private/pam\" \
+ -DUSE_MMAP=1 \
+ -DTEVENT_DEPRECATED=1\
+ -DLOCALEDIR=\"$(localedir)\"
+
+EXTRA_DIST = build/config.rpath
+
+SSSD_DEBUG_OBJ = \
+ util/debug.c
+
+SSSD_UTIL_OBJ = \
+ confdb/confdb.c \
+ db/sysdb.c \
+ db/sysdb_ops.c \
+ db/sysdb_search.c \
+ monitor/monitor_sbus.c \
+ providers/dp_auth_util.c \
+ providers/dp_sbus.c \
+ sbus/sbus_client.c \
+ sbus/sssd_dbus_common.c \
+ sbus/sssd_dbus_connection.c \
+ sbus/sssd_dbus_server.c \
+ util/util.c \
+ util/memory.c \
+ util/server.c \
+ util/signal.c \
+ util/usertools.c \
+ util/backup_file.c \
+ util/strtonum.c \
+ util/check_and_open.c \
+ util/refcount.c \
+ $(SSSD_DEBUG_OBJ)
+
+SSSD_RESPONDER_OBJ = \
+ responder/common/responder_cmd.c \
+ responder/common/responder_common.c \
+ responder/common/responder_dp.c \
+ responder/common/responder_packet.c
+
+SSSD_TOOLS_OBJ = \
+ tools/sss_sync_ops.c \
+ tools/tools_util.c \
+ tools/files.c
+
+SSSD_RESOLV_OBJ = \
+ resolv/async_resolv.c
+
+SSSD_FAILOVER_OBJ = \
+ providers/fail_over.c \
+ $(SSSD_RESOLV_OBJ)
+
+SSSD_LIBS = \
+ $(TALLOC_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(LDB_LIBS) \
+ $(DBUS_LIBS) \
+ $(PCRE_LIBS) \
+ $(INI_CFG_LIBS) \
+ $(COLLECTION_LIBS) \
+ $(DHASH_LIBS) \
+ $(SSS_CRYPT_LIBS) \
+ libsss_crypt.la
+
+PYTHON_BINDINGS_LIBS = \
+ $(TALLOC_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(LDB_LIBS) \
+ $(DBUS_LIBS) \
+ $(PCRE_LIBS) \
+ $(SSS_CRYPT_LIBS) \
+ libsss_crypt.la
+
+TOOLS_LIBS = \
+ $(TALLOC_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(LDB_LIBS) \
+ $(DBUS_LIBS) \
+ $(PCRE_LIBS) \
+ $(INI_CFG_LIBS) \
+ $(COLLECTION_LIBS) \
+ $(DHASH_LIBS) \
+ libsss_crypt.la
+
+if BUILD_SELINUX
+ TOOLS_LIBS += $(SELINUX_LIBS)
+endif
+
+dist_noinst_HEADERS = \
+ monitor/monitor.h \
+ util/sha512crypt.h \
+ util/dlinklist.h \
+ util/util.h \
+ util/strtonum.h \
+ util/sss_ldap.h \
+ util/sss_krb5.h \
+ util/refcount.h \
+ util/find_uid.h \
+ util/user_info_msg.h \
+ config.h \
+ monitor/monitor.h \
+ monitor/monitor_interfaces.h \
+ responder/common/responder.h \
+ responder/common/responder_packet.h \
+ responder/pam/pamsrv.h \
+ responder/nss/nsssrv.h \
+ responder/nss/nsssrv_nc.h \
+ sbus/sbus_client.h \
+ sbus/sssd_dbus.h \
+ sbus/sssd_dbus_private.h \
+ db/sysdb.h \
+ db/sysdb_private.h \
+ confdb/confdb.h \
+ confdb/confdb_private.h \
+ confdb/confdb_setup.h \
+ providers/data_provider.h \
+ providers/dp_backend.h \
+ providers/fail_over.h \
+ providers/providers.h \
+ providers/child_common.h \
+ providers/krb5/krb5_auth.h \
+ providers/krb5/krb5_common.h \
+ providers/krb5/krb5_utils.h \
+ providers/ldap/ldap_common.h \
+ providers/ldap/sdap.h \
+ providers/ldap/sdap_async.h \
+ providers/ldap/sdap_async_private.h \
+ providers/ipa/ipa_common.h \
+ providers/ipa/ipa_access.h \
+ providers/ipa/ipa_timerules.h \
+ providers/ipa/ipa_auth.h \
+ tools/tools_util.h \
+ tools/sss_sync_ops.h \
+ resolv/async_resolv.h \
+ resolv/ares/ares_parse_srv_reply.h \
+ resolv/ares/ares_parse_txt_reply.h \
+ resolv/ares/ares_data.h \
+ tests/common.h
+
+
+####################
+# Program Binaries #
+####################
+sssd_SOURCES = \
+ monitor/monitor.c \
+ confdb/confdb_setup.c \
+ $(SSSD_UTIL_OBJ)
+sssd_LDADD = \
+ $(SSSD_LIBS)
+
+sssd_nss_SOURCES = \
+ responder/nss/nsssrv.c \
+ responder/nss/nsssrv_cmd.c \
+ responder/nss/nsssrv_nc.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_RESPONDER_OBJ)
+sssd_nss_LDADD = \
+ $(TDB_LIBS) \
+ $(SSSD_LIBS)
+
+sssd_pam_SOURCES = \
+ responder/pam/pam_LOCAL_domain.c \
+ responder/pam/pamsrv.c \
+ responder/pam/pamsrv_cmd.c \
+ responder/pam/pamsrv_dp.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_RESPONDER_OBJ)
+sssd_pam_LDADD = \
+ $(SSSD_LIBS)
+
+sssd_be_SOURCES = \
+ providers/data_provider_be.c \
+ providers/data_provider_fo.c \
+ providers/data_provider_opts.c \
+ $(SSSD_FAILOVER_OBJ) \
+ $(SSSD_UTIL_OBJ)
+sssd_be_LDADD = $(SSSD_LIBS) $(CARES_LIBS)
+sssd_be_LDFLAGS = \
+ -Wl,--version-script,$(srcdir)/providers/sssd_be.exports \
+ -export-dynamic
+
+dist_noinst_DATA += \
+ examples/sssd.conf \
+ examples/sssdproxytest \
+ examples/sudo \
+ providers/sssd_be.exports \
+ m4
+
+######################
+# Command-line Tools #
+######################
+sss_useradd_SOURCES = \
+ tools/sss_useradd.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_useradd_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_userdel_SOURCES = \
+ tools/sss_userdel.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_userdel_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_groupadd_SOURCES = \
+ tools/sss_groupadd.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_groupadd_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_groupdel_SOURCES = \
+ tools/sss_groupdel.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_groupdel_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_usermod_SOURCES = \
+ tools/sss_usermod.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_usermod_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_groupmod_SOURCES = \
+ tools/sss_groupmod.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_groupmod_LDADD = \
+ $(TOOLS_LIBS)
+
+sss_groupshow_SOURCES = \
+ tools/sss_groupshow.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ)
+sss_groupshow_LDADD = \
+ $(TOOLS_LIBS)
+
+#################
+# Feature Tests #
+#################
+if HAVE_CHECK
+sysdb_tests_DEPENDENCIES = \
+ $(ldblib_LTLIBRARIES)
+sysdb_tests_SOURCES = \
+ tests/sysdb-tests.c \
+ $(SSSD_UTIL_OBJ)
+sysdb_tests_CFLAGS = \
+ -DSYSDB_TEST \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+sysdb_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS)
+
+strtonum_tests_SOURCES = \
+ tests/strtonum-tests.c \
+ util/debug.c \
+ util/strtonum.c
+strtonum_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+strtonum_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS)
+
+krb5_utils_tests_SOURCES = \
+ tests/krb5_utils-tests.c \
+ providers/krb5/krb5_utils.c \
+ providers/krb5/krb5_common.c \
+ providers/data_provider_fo.c \
+ providers/data_provider_opts.c \
+ $(SSSD_FAILOVER_OBJ) \
+ $(SSSD_UTIL_OBJ)
+krb5_utils_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+krb5_utils_tests_LDADD = \
+ $(SSSD_LIBS)\
+ $(CARES_LIBS) \
+ $(CHECK_LIBS)
+
+
+check_and_open_tests_SOURCES = \
+ $(SSSD_DEBUG_OBJ) \
+ tests/check_and_open-tests.c \
+ util/check_and_open.c
+check_and_open_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+check_and_open_tests_LDADD = \
+ $(CHECK_LIBS)
+
+FILES_TESTS_LIBS = \
+ $(CHECK_LIBS) \
+ $(POPT_LIBS) \
+ $(TALLOC_LIBS)
+if BUILD_SELINUX
+ FILES_TESTS_LIBS += $(SELINUX_LIBS)
+endif
+
+files_tests_SOURCES = \
+ $(SSSD_DEBUG_OBJ) \
+ tests/files-tests.c \
+ util/check_and_open.c \
+ tools/files.c
+files_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+files_tests_LDADD = \
+ $(FILES_TESTS_LIBS)
+
+SSSD_RESOLV_TESTS_OBJ = \
+ $(SSSD_RESOLV_OBJ)
+if BUILD_ARES_DATA
+ SSSD_RESOLV_TESTS_OBJ += \
+ resolv/ares/ares_parse_srv_reply.c \
+ resolv/ares/ares_parse_txt_reply.c \
+ resolv/ares/ares_data.c
+endif
+
+resolv_tests_SOURCES = \
+ tests/resolv-tests.c \
+ tests/common.c \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_RESOLV_TESTS_OBJ)
+resolv_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS) \
+ -DBUILD_TXT_SRV
+resolv_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS) \
+ $(CARES_LIBS)
+
+refcount_tests_SOURCES = \
+ tests/refcount-tests.c \
+ tests/common.c \
+ $(CHECK_OBJ) \
+ $(SSSD_UTIL_OBJ)
+refcount_tests_CFLAGS = \
+ $(CHECK_CFLAGS)
+refcount_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS)
+
+fail_over_tests_SOURCES = \
+ tests/fail_over-tests.c \
+ tests/common.c \
+ $(SSSD_FAILOVER_OBJ) \
+ $(CHECK_OBJ) \
+ $(SSSD_UTIL_OBJ)
+fail_over_tests_CFLAGS = \
+ $(CHECK_CFLAGS)
+fail_over_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS) \
+ $(CARES_LIBS)
+
+ipa_timerules_tests_SOURCES = \
+ providers/ipa/ipa_timerules.c \
+ tests/ipa_timerules-tests.c \
+ tests/common.c \
+ $(SSSD_DEBUG_OBJ)
+ipa_timerules_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(POPT_CFLAGS) \
+ $(TALLOC_CFLAGS) \
+ $(PCRE_CFLAGS) \
+ $(CHECK_CFLAGS)
+ipa_timerules_tests_LDADD = \
+ $(POPT_LIBS) \
+ $(PCRE_LIBS) \
+ $(TALLOC_LIBS) \
+ $(CHECK_LIBS)
+
+find_uid_tests_SOURCES = \
+ tests/find_uid-tests.c \
+ util/find_uid.c \
+ $(SSSD_DEBUG_OBJ)
+find_uid_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(TALLOC_CFLAGS) \
+ $(DHASH_CFLAGS) \
+ $(CHECK_CFLAGS)
+find_uid_tests_LDADD = \
+ $(TALLOC_LIBS) \
+ $(DHASH_LIBS) \
+ $(CHECK_LIBS)
+
+auth_tests_SOURCES = \
+ tests/auth-tests.c \
+ $(SSSD_UTIL_OBJ)
+auth_tests_CFLAG = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+auth_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS)
+
+ipa_ldap_opt_tests_SOURCES = \
+ tests/ipa_ldap_opt-tests.c
+ipa_ldap_opt_tests_CFLAG = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+ipa_ldap_opt_tests_LDADD = \
+ $(CHECK_LIBS)
+
+endif
+
+stress_tests_SOURCES = \
+ tests/stress-tests.c \
+ $(SSSD_UTIL_OBJ)
+stress_tests_LDADD = \
+ $(SSSD_LIBS)
+
+noinst_PROGRAMS = pam_test_client
+pam_test_client_SOURCES = sss_client/pam_test_client.c
+pam_test_client_LDFLAGS = -lpam -lpam_misc
+
+####################
+# Client Libraries #
+####################
+
+nsslib_LTLIBRARIES = libnss_sss.la
+libnss_sss_la_SOURCES = \
+ sss_client/common.c \
+ sss_client/passwd.c \
+ sss_client/group.c \
+ sss_client/sss_cli.h
+libnss_sss_la_LDFLAGS = \
+ -module \
+ -version-info 2:0:0 \
+ -Wl,--version-script,$(srcdir)/sss_client/sss_nss.exports
+
+pamlib_LTLIBRARIES = pam_sss.la
+pam_sss_la_SOURCES = \
+ sss_client/pam_sss.c \
+ sss_client/common.c \
+ sss_client/sss_cli.h \
+ sss_client/sss_pam_macros.h
+
+pam_sss_la_LDFLAGS = \
+ -lpam \
+ -module \
+ -avoid-version \
+ -Wl,--version-script,$(srcdir)/sss_client/sss_pam.exports
+
+dist_noinst_DATA += \
+ sss_client/sss_nss.exports \
+ sss_client/sss_pam.exports
+
+####################
+# Plugin Libraries #
+####################
+libsss_ldap_la_SOURCES = \
+ providers/child_common.c \
+ providers/ldap/ldap_id.c \
+ providers/ldap/ldap_id_enum.c \
+ providers/ldap/ldap_id_cleanup.c \
+ providers/ldap/ldap_auth.c \
+ providers/ldap/ldap_init.c \
+ providers/ldap/ldap_common.c \
+ providers/ldap/sdap_async.c \
+ providers/ldap/sdap_async_accounts.c \
+ providers/ldap/sdap_async_connection.c \
+ providers/ldap/sdap_child_helpers.c \
+ providers/ldap/sdap.c \
+ util/user_info_msg.c \
+ util/sss_ldap.c \
+ util/sss_krb5.c
+libsss_ldap_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(LDAP_CFLAGS) \
+ $(KRB5_CFLAGS)
+libsss_ldap_la_LIBADD = \
+ $(OPENLDAP_LIBS) \
+ $(KRB5_LIBS)
+libsss_ldap_la_LDFLAGS = \
+ -version-info 1:0:0 \
+ -module
+
+libsss_proxy_la_SOURCES = \
+ providers/proxy.c
+libsss_proxy_la_CFLAGS = \
+ $(AM_CFLAGS)
+libsss_proxy_la_LIBADD = \
+ $(PAM_LIBS)
+libsss_proxy_la_LDFLAGS = \
+ -version-info 1:0:0 \
+ -module
+
+libsss_krb5_la_SOURCES = \
+ util/find_uid.c \
+ providers/child_common.c \
+ providers/krb5/krb5_utils.c \
+ providers/krb5/krb5_become_user.c \
+ providers/krb5/krb5_auth.c \
+ providers/krb5/krb5_common.c \
+ providers/krb5/krb5_init.c \
+ util/sss_krb5.c
+libsss_krb5_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(DHASH_CFLAGS)
+libsss_krb5_la_LIBADD = \
+ $(DHASH_LIBS) \
+ $(KRB5_LIBS)
+libsss_krb5_la_LDFLAGS = \
+ -version-info 1:0:0 \
+ -module
+
+libsss_ipa_la_SOURCES = \
+ providers/child_common.c \
+ providers/ipa/ipa_init.c \
+ providers/ipa/ipa_common.c \
+ providers/ipa/ipa_auth.c \
+ providers/ipa/ipa_access.c \
+ providers/ipa/ipa_timerules.c \
+ providers/ldap/ldap_id.c \
+ providers/ldap/ldap_id_enum.c \
+ providers/ldap/ldap_id_cleanup.c \
+ providers/ldap/ldap_auth.c \
+ providers/ldap/ldap_common.c \
+ providers/ldap/sdap_async.c \
+ providers/ldap/sdap_async_accounts.c \
+ providers/ldap/sdap_async_connection.c \
+ providers/ldap/sdap_child_helpers.c \
+ providers/ldap/sdap.c \
+ util/user_info_msg.c \
+ util/sss_ldap.c \
+ util/sss_krb5.c \
+ util/find_uid.c \
+ providers/krb5/krb5_utils.c \
+ providers/krb5/krb5_become_user.c \
+ providers/krb5/krb5_common.c \
+ providers/krb5/krb5_auth.c
+libsss_ipa_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(LDAP_CFLAGS) \
+ $(DHASH_CFLAGS) \
+ $(KRB5_CFLAGS)
+libsss_ipa_la_LIBADD = \
+ $(OPENLDAP_LIBS) \
+ $(DHASH_LIBS) \
+ $(KRB5_LIBS)
+libsss_ipa_la_LDFLAGS = \
+ -version-info 1:0:0 \
+ -module
+
+krb5_child_SOURCES = \
+ $(SSSD_DEBUG_OBJ) \
+ providers/krb5/krb5_become_user.c \
+ providers/krb5/krb5_child.c \
+ providers/child_common.c \
+ util/user_info_msg.c \
+ util/sss_krb5.c
+krb5_child_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(POPT_CFLAGS) \
+ $(KRB5_CFLAGS)
+krb5_child_LDADD = \
+ $(TALLOC_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(KRB5_LIBS)
+
+ldap_child_SOURCES = \
+ $(SSSD_DEBUG_OBJ) \
+ providers/ldap/ldap_child.c \
+ providers/child_common.c \
+ util/sss_krb5.c
+ldap_child_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(POPT_CFLAGS) \
+ $(KRB5_CFLAGS)
+ldap_child_LDADD = \
+ $(TALLOC_LIBS) \
+ $(TEVENT_LIBS) \
+ $(POPT_LIBS) \
+ $(OPENLDAP_LIBS) \
+ $(KRB5_LIBS)
+
+memberof_la_SOURCES = \
+ ldb_modules/memberof.c
+memberof_la_CFLAGS = \
+ $(AM_CFLAGS)
+memberof_la_LIBADD = $(LDB_LIBS) $(DHASH_LIBS)
+memberof_la_LDFLAGS = \
+ -avoid-version \
+ -module
+
+if BUILD_KRB5_LOCATOR_PLUGIN
+sssd_krb5_locator_plugin_la_SOURCES = \
+ krb5_plugin/sssd_krb5_locator_plugin.c
+sssd_krb5_locator_plugin_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(KRB5_CFLAGS)
+sssd_krb5_locator_plugin_la_LDFLAGS = \
+ -avoid-version \
+ -module
+endif
+
+if BUILD_PYTHON_BINDINGS
+pysss_la_SOURCES = \
+ $(SSSD_UTIL_OBJ) \
+ $(SSSD_TOOLS_OBJ) \
+ python/pysss.c
+pysss_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(PYTHON_CFLAGS)
+pysss_la_LIBADD = \
+ $(PYTHON_BINDINGS_LIBS) \
+ $(PYTHON_LIBS)
+pysss_la_LDFLAGS = \
+ -avoid-version \
+ -module
+endif
+
+############
+# MANPAGES #
+############
+
+#Special Rules:
+export SGML_CATALOG_FILES
+DOCBOOK_XSLT = http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
+XMLLINT_FLAGS = --catalogs --postvalid --nonet --xinclude --noout
+XSLTPROC_FLAGS = --catalogs --xinclude --nonet
+
+dist_man_MANS = man/sss_useradd.8 man/sss_userdel.8 man/sss_usermod.8 \
+ man/sss_groupadd.8 man/sss_groupdel.8 man/sss_groupmod.8 \
+ man/sssd.8 man/sssd.conf.5 man/sssd-ldap.5 man/sssd-krb5.5 \
+ man/sssd-ipa.5 man/sssd_krb5_locator_plugin.8 \
+ man/sss_groupshow.8 sss_client/man/pam_sss.8
+
+SUFFIXES = .1.xml .1 .3.xml .3 .5.xml .5 .8.xml .8
+.1.xml.1:
+ $(XMLLINT) $(XMLLINT_FLAGS) $<
+ $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $<
+
+.3.xml.3:
+ $(XMLLINT) $(XMLLINT_FLAGS) $<
+ $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $<
+
+.5.xml.5:
+ $(XMLLINT) $(XMLLINT_FLAGS) $<
+ $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $<
+
+.8.xml.8:
+ $(XMLLINT) $(XMLLINT_FLAGS) $<
+ $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $<
+
+#######################
+# Installation Extras #
+#######################
+
+dist_init_SCRIPTS =
+if HAVE_SUSE
+ dist_init_SCRIPTS += \
+ sysv/SUSE/sssd
+else
+ dist_init_SCRIPTS += \
+ sysv/sssd
+endif
+
+
+dist_sssdconf_DATA = \
+ config/etc/sssd.api.conf
+dist_sssdapiplugin_DATA = \
+ config/etc/sssd.api.d/sssd-ipa.conf \
+ config/etc/sssd.api.d/sssd-krb5.conf \
+ config/etc/sssd.api.d/sssd-ldap.conf \
+ config/etc/sssd.api.d/sssd-local.conf \
+ config/etc/sssd.api.d/sssd-proxy.conf
+
+installsssddirs::
+ mkdir -p \
+ $(DESTDIR)$(includedir) \
+ $(DESTDIR)$(libdir) \
+ $(DESTDIR)$(sbindir) \
+ $(DESTDIR)$(initdir) \
+ $(DESTDIR)$(mandir) \
+ $(DESTDIR)$(pluginpath) \
+ $(DESTDIR)$(libdir)/ldb \
+ $(DESTDIR)$(dbuspolicydir) \
+ $(DESTDIR)$(infpintrospectdir) \
+ $(DESTDIR)$(dbusintrospectdir) \
+ $(DESTDIR)$(pipepath)/private \
+ $(DESTDIR)$(sssdlibdir) \
+ $(DESTDIR)$(sssdconfdir) \
+ $(DESTDIR)$(dbpath) \
+ $(DESTDIR)$(pidpath) \
+ $(DESTDIR)$(initdir) \
+ $(DESTDIR)$(logpath) \
+ $(DESTDIR)$(pubconfpath)
+
+if HAVE_DOXYGEN
+docs:
+ $(DOXYGEN) doxy.config
+else
+docs:
+ @echo "Doxygen not installed, cannot generate documentation"
+ @exit 1
+endif
+all-local:
+ cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config
+
+install-exec-hook: installsssddirs
+ if [ "$(DESTDIR)" = "" ]; then \
+ cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config install --prefix=$(PYTHON_PREFIX) --record=$(abs_builddir)/config/.files; \
+ else \
+ cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config install --prefix=$(PYTHON_PREFIX) --root=$(DESTDIR) --record=$(abs_builddir)/config/.files; \
+ fi
+ mkdir -p doc $(DESTDIR)/$(docdir); cp -a doc $(DESTDIR)/$(docdir)/
+
+install-data-hook:
+ rm $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2 \
+ $(DESTDIR)/$(nsslibdir)/libnss_sss.so
+ mv $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2.0.0 $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2
+
+uninstall-hook:
+ if [ -f $(abs_builddir)/config/.files ]; then \
+ cat $(abs_builddir)/config/.files | xargs -iq rm -f $(DESTDIR)/q; \
+ rm $(abs_builddir)/config/.files ; \
+ fi
+ rm -Rf $(DESTDIR)/$(docdir)/doc
+
+clean-local:
+ cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config clean --all
+ rm -Rf doc
+
+CLEANFILES = *.X */*.X */*/*.X
+
+tests: all $(check_PROGRAMS)
diff --git a/src/build_macros.m4 b/src/build_macros.m4
new file mode 100644
index 00000000..37d96439
--- /dev/null
+++ b/src/build_macros.m4
@@ -0,0 +1,21 @@
+AC_DEFUN([BUILD_WITH_SHARED_BUILD_DIR],
+ [ AC_ARG_WITH([shared-build-dir],
+ [AC_HELP_STRING([--with-shared-build-dir=DIR],
+ [temporary build directory where libraries are installed [$srcdir/sharedbuild]])])
+
+ sharedbuilddir="$srcdir/sharedbuild"
+ if test x"$with_shared_build_dir" != x; then
+ sharedbuilddir=$with_shared_build_dir
+ CFLAGS="$CFLAGS -I$with_shared_build_dir/include"
+ CPPFLAGS="$CPPFLAGS -I$with_shared_build_dir/include"
+ LDFLAGS="$LDFLAGS -L$with_shared_build_dir/lib"
+ fi
+ AC_SUBST(sharedbuilddir)
+ ])
+
+AC_DEFUN([BUILD_WITH_AUX_INFO],
+ [ AC_ARG_WITH([aux-info],
+ [AC_HELP_STRING([--with-aux-info],
+ [Build with -aux-info output])])
+ ])
+AM_CONDITIONAL([WANT_AUX_INFO], [test x$with_aux_info = xyes])
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
new file mode 100644
index 00000000..86ccf5d9
--- /dev/null
+++ b/src/conf_macros.m4
@@ -0,0 +1,207 @@
+AC_DEFUN([WITH_DB_PATH],
+ [ AC_ARG_WITH([db-path],
+ [AC_HELP_STRING([--with-db-path=PATH],
+ [Path to the SSSD databases [/var/lib/sss/db]]
+ )
+ ]
+ )
+ config_dbpath="\"VARDIR\"/lib/sss/db"
+ dbpath="${localstatedir}/lib/sss/db"
+ if test x"$with_db_path" != x; then
+ config_dbpath=$with_db_path
+ dbpath=$with_db_path
+ fi
+ AC_SUBST(dbpath)
+ AC_DEFINE_UNQUOTED(DB_PATH, "$config_dbpath", [Path to the SSSD databases])
+ ])
+
+AC_DEFUN([WITH_PLUGIN_PATH],
+ [ AC_ARG_WITH([plugin-path],
+ [AC_HELP_STRING([--with-plugin-path=PATH],
+ [Path to the SSSD data provider plugins [/usr/lib/sssd]]
+ )
+ ]
+ )
+ pluginpath="${libdir}/sssd"
+ config_pluginpath="\"LIBDIR\"/sssd"
+ if test x"$with_plugin_path" != x; then
+ pluginpath=$with_plugin_path
+ config_pluginpath=$with_plugin_path
+ fi
+ AC_SUBST(pluginpath)
+ AC_DEFINE_UNQUOTED(DATA_PROVIDER_PLUGINS_PATH, "$config_pluginpath", [Path to the SSSD data provider plugins])
+ ])
+
+AC_DEFUN([WITH_PID_PATH],
+ [ AC_ARG_WITH([pid-path],
+ [AC_HELP_STRING([--with-pid-path=PATH],
+ [Where to store pid files for the SSSD [/var/run]]
+ )
+ ]
+ )
+ config_pidpath="\"VARDIR\"/run"
+ pidpath="${localstatedir}/run"
+ if test x"$with_pid_path" != x; then
+ config_pidpath=$with_pid_path
+ pidpath=$with_pid_path
+ fi
+ AC_SUBST(pidpath)
+ AC_DEFINE_UNQUOTED(PID_PATH, "$config_pidpath", [Where to store pid files for the SSSD])
+ ])
+
+AC_DEFUN([WITH_LOG_PATH],
+ [ AC_ARG_WITH([log-path],
+ [AC_HELP_STRING([--with-log-path=PATH],
+ [Where to store log files for the SSSD [/var/log/sssd]]
+ )
+ ]
+ )
+ config_logpath="\"VARDIR\"/log/sssd"
+ logpath="${localstatedir}/log/sssd"
+ if test x"$with_log_path" != x; then
+ config_logpath=$with_log_path
+ logpath=$with_log_path
+ fi
+ AC_SUBST(logpath)
+ AC_DEFINE_UNQUOTED(LOG_PATH, "$config_logpath", [Where to store log files for the SSSD])
+ ])
+
+AC_DEFUN([WITH_PUBCONF_PATH],
+ [ AC_ARG_WITH([pubconf-path],
+ [AC_HELP_STRING([--with-pubconf-path=PATH],
+ [Where to store pubconf files for the SSSD [/var/lib/sss/pubconf]]
+ )
+ ]
+ )
+ config_pubconfpath="\"VARDIR\"/lib/sss/pubconf"
+ pubconfpath="${localstatedir}/lib/sss/pubconf"
+ if test x"$with_pubconf_path" != x; then
+ config_pubconfpath=$with_pubconf_path
+ pubconfpath=$with_pubconf_path
+ fi
+ AC_SUBST(pubconfpath)
+ AC_DEFINE_UNQUOTED(PUBCONF_PATH, "$config_pubconfpath", [Where to store pubconf files for the SSSD])
+ ])
+
+AC_DEFUN([WITH_PIPE_PATH],
+ [ AC_ARG_WITH([pipe-path],
+ [AC_HELP_STRING([--with-pipe-path=PATH],
+ [Where to store pipe files for the SSSD interconnects [/var/lib/sss/pipes]]
+ )
+ ]
+ )
+ config_pipepath="\"VARDIR\"/lib/sss/pipes"
+ pipepath="${localstatedir}/lib/sss/pipes"
+ if test x"$with_pipe_path" != x; then
+ config_pipepath=$with_pipe_path
+ pipepath=$with_pipe_path
+ fi
+ AC_SUBST(pipepath)
+ AC_DEFINE_UNQUOTED(PIPE_PATH, "$config_pipepath", [Where to store pipe files for the SSSD interconnects])
+ ])
+
+AC_DEFUN([WITH_INIT_DIR],
+ [ AC_ARG_WITH([init-dir],
+ [AC_HELP_STRING([--with-init-dir=DIR],
+ [Where to store init script for sssd [/etc/rc.d/init.d]]
+ )
+ ]
+ )
+ initdir="${sysconfdir}/rc.d/init.d"
+ if test x"$with_init_dir" != x; then
+ initdir=$with_init_dir
+ fi
+ AC_SUBST(initdir)
+ ])
+
+AC_DEFUN([WITH_SHADOW_UTILS_PATH],
+ [ AC_ARG_WITH([shadow-utils-path],
+ [AC_HELP_STRING([--with-shadow-utils-path=PATH],
+ [Where to look for shadow-utils binaries [/usr/sbin]]
+ )
+ ]
+ )
+ shadow_utils_path="${sbindir}"
+ if test x"$with_shadow_utils_path" != x; then
+ shadow_utils_path=$with_shadow_utils_path
+ fi
+ AC_SUBST(shadow_utils_path)
+ ])
+
+AC_DEFUN([WITH_MANPAGES],
+ [ AC_ARG_WITH([manpages],
+ [AC_HELP_STRING([--with-manpages],
+ [Whether to regenerate man pages from DocBook sources [yes]]
+ )
+ ],
+ [],
+ with_manpages=yes
+ )
+ if test x"$with_manpages" == xyes; then
+ HAVE_MANPAGES=1
+ AC_SUBST(HAVE_MANPAGES)
+ fi
+ ])
+AM_CONDITIONAL([BUILD_MANPAGES], [test x$with_manpages = xyes])
+
+AC_DEFUN([WITH_XML_CATALOG],
+ [ AC_ARG_WITH([xml-catalog-path],
+ [AC_HELP_STRING([--with-xml-catalog-path=PATH],
+ [Where to look for XML catalog [/etc/xml/catalog]]
+ )
+ ]
+ )
+ SGML_CATALOG_FILES="/etc/xml/catalog"
+ if test x"$with_xml_catalog_path" != x; then
+ SGML_CATALOG_FILES="$with_xml_catalog_path"
+ fi
+ AC_SUBST([SGML_CATALOG_FILES])
+ ])
+
+AC_DEFUN([WITH_KRB5_PLUGIN_PATH],
+ [ AC_ARG_WITH([krb5-plugin-path],
+ [AC_HELP_STRING([--with-krb5-plugin-path=PATH],
+ [Path to kerberos plugin store [/usr/lib/krb5/plugins/libkrb5]]
+ )
+ ]
+ )
+ krb5pluginpath="${libdir}/krb5/plugins/libkrb5"
+ if test x"$with_krb5_plugin_path" != x; then
+ krb5pluginpath=$with_krb5_plugin_path
+ fi
+ AC_SUBST(krb5pluginpath)
+ ])
+
+AC_DEFUN([WITH_PYTHON_BINDINGS],
+ [ AC_ARG_WITH([python-bindings],
+ [AC_HELP_STRING([--with-python-bindings],
+ [Whether to build python bindings [yes]]
+ )
+ ],
+ [],
+ with_python_bindings=yes
+ )
+ if test x"$with_python_bindings" == xyes; then
+ HAVE_PYTHON_BINDINGS=1
+ AC_SUBST(HAVE_PYTHON_BINDINGS)
+ fi
+ AM_CONDITIONAL([BUILD_PYTHON_BINDINGS], [test x"$with_python_bindings" = xyes])
+ ])
+
+AC_DEFUN([WITH_SELINUX],
+ [ AC_ARG_WITH([selinux],
+ [AC_HELP_STRING([--with-selinux],
+ [Whether to build with SELinux support [yes]]
+ )
+ ],
+ [],
+ with_selinux=yes
+ )
+ if test x"$with_selinux" == xyes; then
+ HAVE_SELINUX=1
+ AC_SUBST(HAVE_SELINUX)
+ AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1, [Build with SELinux support])
+ fi
+ AM_CONDITIONAL([BUILD_SELINUX], [test x"$with_selinux" = xyes])
+ ])
+
diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
new file mode 100644
index 00000000..6981baa5
--- /dev/null
+++ b/src/confdb/confdb.c
@@ -0,0 +1,908 @@
+/*
+ SSSD
+
+ NSS Configuratoin DB
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include "config.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "confdb/confdb_private.h"
+#include "util/strtonum.h"
+#include "db/sysdb.h"
+
+#define CONFDB_ZERO_CHECK_OR_JUMP(var, ret, err, label) do { \
+ if (!var) { \
+ ret = err; \
+ goto label; \
+ } \
+} while(0)
+
+static char *prepend_cn(char *str, int *slen, const char *comp, int clen)
+{
+ char *ret;
+
+ ret = talloc_realloc(NULL, str, char, *slen + 4 + clen + 1);
+ if (!ret)
+ return NULL;
+
+ /* move current string to the end */
+ memmove(&ret[clen +4], ret, *slen+1); /* includes termination */
+ memcpy(ret, "cn=", 3);
+ memcpy(&ret[3], comp, clen);
+ ret[clen+3] = ',';
+
+ *slen = *slen + 4 + clen;
+
+ return ret;
+}
+
+int parse_section(TALLOC_CTX *mem_ctx, const char *section,
+ char **sec_dn, const char **rdn_name)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *dn = NULL;
+ char *p;
+ const char *s;
+ int l, ret;
+
+ /* section must be a non null string and must not start with '/' */
+ if (!section || !*section || *section == '/') return EINVAL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) return ENOMEM;
+
+ s = section;
+ l = 0;
+ while ((p = strchrnul(s, '/'))) {
+ if (l == 0) {
+ dn = talloc_asprintf(tmp_ctx, "cn=%s", s);
+ l = 3 + (p-s);
+ dn[l] = '\0';
+ } else {
+ dn = prepend_cn(dn, &l, s, p-s);
+ }
+ if (!dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ if (*p == '\0') {
+ if (rdn_name) *rdn_name = s;
+ break; /* reached end */
+ }
+ s = p+1;
+ if (*s == '\0') { /* a section cannot end in '.' */
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ *sec_dn = talloc_steal(mem_ctx, dn);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int confdb_add_param(struct confdb_ctx *cdb,
+ bool replace,
+ const char *section,
+ const char *attribute,
+ const char **values)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message *msg;
+ struct ldb_result *res;
+ struct ldb_dn *dn;
+ char *secdn;
+ const char *rdn_name;
+ int ret, i;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = parse_section(tmp_ctx, section, &secdn, &rdn_name);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
+ CONFDB_ZERO_CHECK_OR_JUMP(dn, ret, EIO, done);
+
+ ret = ldb_search(cdb->ldb, tmp_ctx, &res,
+ dn, LDB_SCOPE_BASE, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ CONFDB_ZERO_CHECK_OR_JUMP(msg, ret, ENOMEM, done);
+
+ msg->dn = talloc_steal(msg, dn);
+ CONFDB_ZERO_CHECK_OR_JUMP(msg->dn, ret, ENOMEM, done);
+
+ if (res->count == 0) { /* add a new message */
+ errno = 0;
+
+ /* cn first */
+ ret = ldb_msg_add_string(msg, "cn", rdn_name);
+ if (ret != LDB_SUCCESS) {
+ if (errno) ret = errno;
+ else ret = EIO;
+ goto done;
+ }
+
+ /* now the requested attribute */
+ for (i = 0; values[i]; i++) {
+ ret = ldb_msg_add_string(msg, attribute, values[i]);
+ if (ret != LDB_SUCCESS) {
+ if (errno) ret = errno;
+ else ret = EIO;
+ goto done;
+ }
+ }
+
+ ret = ldb_add(cdb->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ } else {
+ int optype;
+ errno = 0;
+
+ /* mark this as a replacement */
+ if (replace) optype = LDB_FLAG_MOD_REPLACE;
+ else optype = LDB_FLAG_MOD_ADD;
+ ret = ldb_msg_add_empty(msg, attribute, optype, NULL);
+ if (ret != LDB_SUCCESS) {
+ if (errno) ret = errno;
+ else ret = EIO;
+ goto done;
+ }
+
+ /* now the requested attribute */
+ for (i = 0; values[i]; i++) {
+ ret = ldb_msg_add_string(msg, attribute, values[i]);
+ if (ret != LDB_SUCCESS) {
+ if (errno) ret = errno;
+ else ret = EIO;
+ goto done;
+ }
+ }
+
+ ret = ldb_modify(cdb->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add [%s] to [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ }
+ return ret;
+}
+
+int confdb_get_param(struct confdb_ctx *cdb,
+ TALLOC_CTX *mem_ctx,
+ const char *section,
+ const char *attribute,
+ char ***values)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res;
+ struct ldb_dn *dn;
+ char *secdn;
+ const char *attrs[] = { attribute, NULL };
+ char **vals;
+ struct ldb_message_element *el;
+ int ret, i;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx)
+ return ENOMEM;
+
+ ret = parse_section(tmp_ctx, section, &secdn, NULL);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn);
+ if (!dn) {
+ ret = EIO;
+ goto done;
+ }
+
+ ret = ldb_search(cdb->ldb, tmp_ctx, &res,
+ dn, LDB_SCOPE_BASE, attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ if (res->count > 1) {
+ ret = EIO;
+ goto done;
+ }
+
+ vals = talloc_zero(mem_ctx, char *);
+ ret = EOK;
+
+ if (res->count > 0) {
+ el = ldb_msg_find_element(res->msgs[0], attribute);
+ if (el && el->num_values > 0) {
+ vals = talloc_realloc(mem_ctx, vals, char *, el->num_values +1);
+ if (!vals) {
+ ret = ENOMEM;
+ goto done;
+ }
+ /* should always be strings so this should be safe */
+ for (i = 0; i < el->num_values; i++) {
+ struct ldb_val v = el->values[i];
+ vals[i] = talloc_strndup(vals, (char *)v.data, v.length);
+ if (!vals[i]) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ vals[i] = NULL;
+ }
+ }
+
+ *values = vals;
+
+done:
+ talloc_free(tmp_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to get [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ }
+ return ret;
+}
+
+int confdb_get_string(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ const char *defstr, char **result)
+{
+ char **values = NULL;
+ char *restr;
+ int ret;
+
+ ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ if (values[0]) {
+ if (values[1] != NULL) {
+ /* too many values */
+ ret = EINVAL;
+ goto failed;
+ }
+ restr = talloc_steal(ctx, values[0]);
+ } else {
+ /* Did not return a value, so use the default */
+
+ if (defstr == NULL) { /* No default given */
+ *result = NULL;
+ talloc_free(values);
+ return EOK;
+ }
+
+ /* Copy the default string */
+ restr = talloc_strdup(ctx, defstr);
+ }
+ if (!restr) {
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ talloc_free(values);
+
+ *result = restr;
+ return EOK;
+
+failed:
+ talloc_free(values);
+ DEBUG(1, ("Failed to get [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ return ret;
+}
+
+int confdb_get_int(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ int defval, int *result)
+{
+ char **values = NULL;
+ long val;
+ int ret;
+
+ ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ if (values[0]) {
+ if (values[1] != NULL) {
+ /* too many values */
+ ret = EINVAL;
+ goto failed;
+ }
+
+ errno = 0;
+ val = strtol(values[0], NULL, 0);
+ if (errno) {
+ ret = errno;
+ goto failed;
+ }
+
+ if (val < INT_MIN || val > INT_MAX) {
+ ret = ERANGE;
+ goto failed;
+ }
+
+ } else {
+ val = defval;
+ }
+
+ talloc_free(values);
+
+ *result = (int)val;
+ return EOK;
+
+failed:
+ talloc_free(values);
+ DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ return ret;
+}
+
+long confdb_get_long(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ long defval, long *result)
+{
+ char **values = NULL;
+ long val;
+ int ret;
+
+ ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ if (values[0]) {
+ if (values[1] != NULL) {
+ /* too many values */
+ ret = EINVAL;
+ goto failed;
+ }
+
+ errno = 0;
+ val = strtol(values[0], NULL, 0);
+ if (errno) {
+ ret = errno;
+ goto failed;
+ }
+
+ } else {
+ val = defval;
+ }
+
+ talloc_free(values);
+
+ *result = val;
+ return EOK;
+
+failed:
+ talloc_free(values);
+ DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ return ret;
+}
+
+int confdb_get_bool(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ bool defval, bool *result)
+{
+ char **values = NULL;
+ bool val;
+ int ret;
+
+ ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ if (values[0]) {
+ if (values[1] != NULL) {
+ /* too many values */
+ ret = EINVAL;
+ goto failed;
+ }
+
+ if (strcasecmp(values[0], "FALSE") == 0) {
+ val = false;
+
+ } else if (strcasecmp(values[0], "TRUE") == 0) {
+ val = true;
+
+ } else {
+
+ DEBUG(2, ("Value is not a boolean!\n"));
+ ret = EINVAL;
+ goto failed;
+ }
+
+ } else {
+ val = defval;
+ }
+
+ talloc_free(values);
+
+ *result = val;
+ return EOK;
+
+failed:
+ talloc_free(values);
+ DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ return ret;
+}
+
+/* WARNING: Unlike other similar functions, this one does NOT take a default,
+ * and returns ENOENT if the attribute was not found ! */
+int confdb_get_string_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ char ***result)
+{
+ char **values = NULL;
+ int ret;
+
+ ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (values && values[0]) {
+ if (values[1] != NULL) {
+ /* too many values */
+ ret = EINVAL;
+ goto done;
+ }
+ } else {
+ /* Did not return a value */
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = split_on_separator(ctx, values[0], ',', true, result, NULL);
+
+done:
+ talloc_free(values);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(2, ("Failed to get [%s] from [%s], error [%d] (%s)",
+ attribute, section, ret, strerror(ret)));
+ }
+ return ret;
+}
+
+int confdb_init(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx **cdb_ctx,
+ char *confdb_location)
+{
+ struct confdb_ctx *cdb;
+ int ret = EOK;
+
+ cdb = talloc_zero(mem_ctx, struct confdb_ctx);
+ if (!cdb)
+ return ENOMEM;
+
+ /* Because confdb calls use sync ldb calls, we create a separate event
+ * context here. This will prevent the ldb sync calls to start nested
+ * events.
+ * NOTE: this means that we *cannot* do async calls and return in confdb
+ * unless we convert all calls and hook back to the main event context.
+ */
+
+ cdb->pev = tevent_context_init(cdb);
+ if (!cdb->pev) {
+ talloc_free(cdb);
+ return EIO;
+ }
+
+ cdb->ldb = ldb_init(cdb, cdb->pev);
+ if (!cdb->ldb) {
+ talloc_free(cdb);
+ return EIO;
+ }
+
+ ret = ldb_set_debug(cdb->ldb, ldb_debug_messages, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Could not set up debug fn.\n"));
+ talloc_free(cdb);
+ return EIO;
+ }
+
+ ret = ldb_connect(cdb->ldb, confdb_location, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Unable to open config database [%s]\n",
+ confdb_location));
+ talloc_free(cdb);
+ return EIO;
+ }
+
+ *cdb_ctx = cdb;
+
+ return EOK;
+}
+
+static errno_t get_entry_as_uint32(struct ldb_message *msg,
+ uint32_t *return_value,
+ const char *entry,
+ uint32_t default_value)
+{
+ const char *tmp = NULL;
+ char *endptr;
+ uint32_t u32ret = 0;
+
+ *return_value = 0;
+
+ if (!msg || !entry) {
+ return EFAULT;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(msg, entry, NULL);
+ if (tmp == NULL) {
+ *return_value = default_value;
+ return EOK;
+ }
+
+ if ((*tmp == '-') || (*tmp == '\0')) {
+ return EINVAL;
+ }
+
+ u32ret = strtouint32 (tmp, &endptr, 10);
+ if (errno) {
+ return errno;
+ }
+
+ if (*endptr != '\0') {
+ /* Not all of the string was a valid number */
+ return EINVAL;
+ }
+
+ *return_value = u32ret;
+ return EOK;
+}
+
+static errno_t get_entry_as_bool(struct ldb_message *msg,
+ bool *return_value,
+ const char *entry,
+ bool default_value)
+{
+ const char *tmp = NULL;
+
+ *return_value = 0;
+
+ if (!msg || !entry) {
+ return EFAULT;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(msg, entry, NULL);
+ if (tmp == NULL || *tmp == '\0') {
+ *return_value = default_value;
+ return EOK;
+ }
+
+ if (strcasecmp(tmp, "FALSE") == 0) {
+ *return_value = 0;
+ }
+ else if (strcasecmp(tmp, "TRUE") == 0) {
+ *return_value = 1;
+ }
+ else {
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+static int confdb_get_domain_internal(struct confdb_ctx *cdb,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct sss_domain_info **_domain)
+{
+ struct sss_domain_info *domain;
+ struct ldb_result *res;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *dn;
+ const char *tmp;
+ int ret, val;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) return ENOMEM;
+
+ dn = ldb_dn_new_fmt(tmp_ctx, cdb->ldb,
+ "cn=%s,%s", name, CONFDB_DOMAIN_BASEDN);
+ if (!dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn,
+ LDB_SCOPE_BASE, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ if (res->count != 1) {
+ DEBUG(0, ("Unknown domain [%s]\n", name));
+ ret = ENOENT;
+ goto done;
+ }
+
+ domain = talloc_zero(mem_ctx, struct sss_domain_info);
+ if (!domain) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0], "cn", NULL);
+ if (!tmp) {
+ DEBUG(0, ("Invalid configuration entry, fatal error!\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ domain->name = talloc_strdup(domain, tmp);
+ if (!domain->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0],
+ CONFDB_DOMAIN_ID_PROVIDER,
+ NULL);
+ if (tmp) {
+ domain->provider = talloc_strdup(domain, tmp);
+ if (!domain->provider) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ else {
+ DEBUG(0, ("Domain [%s] does not specify an ID provider, disabling!\n",
+ domain->name));
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (strcasecmp(domain->provider, "files") == 0) {
+ /* The files provider is not valid anymore */
+ DEBUG(0, ("The \"files\" provider is invalid\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (strcasecmp(domain->provider, "local") == 0) {
+ /* If this is the local provider, we need to ensure that
+ * no other provider was specified for other types, since
+ * the local provider cannot load them.
+ */
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0],
+ CONFDB_DOMAIN_AUTH_PROVIDER,
+ NULL);
+ if (tmp && strcasecmp(tmp, "local") != 0) {
+ DEBUG(0, ("Local ID provider does not support [%s] as an AUTH provider.\n", tmp));
+ ret = EINVAL;
+ goto done;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0],
+ CONFDB_DOMAIN_ACCESS_PROVIDER,
+ NULL);
+ if (tmp && strcasecmp(tmp, "local") != 0) {
+ DEBUG(0, ("Local ID provider does not support [%s] as an ACCESS provider.\n", tmp));
+ ret = EINVAL;
+ goto done;
+ }
+
+ tmp = ldb_msg_find_attr_as_string(res->msgs[0],
+ CONFDB_DOMAIN_CHPASS_PROVIDER,
+ NULL);
+ if (tmp && strcasecmp(tmp, "local") != 0) {
+ DEBUG(0, ("Local ID provider does not support [%s] as a CHPASS provider.\n", tmp));
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ domain->timeout = ldb_msg_find_attr_as_int(res->msgs[0],
+ CONFDB_DOMAIN_TIMEOUT, 0);
+
+ /* Determine if this domain can be enumerated */
+
+ /* TEMP: test if the old bitfield conf value is used and warn it has been
+ * superceeded. */
+ val = ldb_msg_find_attr_as_int(res->msgs[0], CONFDB_DOMAIN_ENUMERATE, 0);
+ if (val > 0) { /* ok there was a number in here */
+ DEBUG(0, ("Warning: enumeration parameter in %s still uses integers! "
+ "Enumeration is now a boolean and takes true/false values. "
+ "Interpreting as true\n", domain->name));
+ domain->enumerate = true;
+ } else { /* assume the new format */
+ ret = get_entry_as_bool(res->msgs[0], &domain->enumerate,
+ CONFDB_DOMAIN_ENUMERATE, 1);
+ if(ret != EOK) {
+ DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_ENUMERATE));
+ goto done;
+ }
+ }
+ if (!domain->enumerate) {
+ DEBUG(1, ("No enumeration for [%s]!\n", domain->name));
+ }
+
+ /* Determine if user/group names will be Fully Qualified
+ * in NSS interfaces */
+ ret = get_entry_as_bool(res->msgs[0], &domain->fqnames, CONFDB_DOMAIN_FQ, 0);
+ if(ret != EOK) {
+ DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_FQ));
+ goto done;
+ }
+
+ ret = get_entry_as_uint32(res->msgs[0], &domain->id_min,
+ CONFDB_DOMAIN_MINID, SSSD_MIN_ID);
+ if (ret != EOK) {
+ DEBUG(0, ("Invalid value for minId\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = get_entry_as_uint32(res->msgs[0], &domain->id_max,
+ CONFDB_DOMAIN_MAXID, 0);
+ if (ret != EOK) {
+ DEBUG(0, ("Invalid value for maxId\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ if (domain->id_max && (domain->id_max < domain->id_min)) {
+ DEBUG(0, ("Invalid domain range\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Do we allow to cache credentials */
+ ret = get_entry_as_bool(res->msgs[0], &domain->cache_credentials,
+ CONFDB_DOMAIN_CACHE_CREDS, 0);
+ if(ret != EOK) {
+ DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_CACHE_CREDS));
+ goto done;
+ }
+
+ ret = get_entry_as_bool(res->msgs[0], &domain->legacy_passwords,
+ CONFDB_DOMAIN_LEGACY_PASS, 0);
+ if(ret != EOK) {
+ DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_LEGACY_PASS));
+ goto done;
+ }
+
+ *_domain = domain;
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int confdb_get_domains(struct confdb_ctx *cdb,
+ struct sss_domain_info **domains)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct sss_domain_info *domain, *prevdom = NULL;
+ char **domlist;
+ int ret, i;
+
+ if (cdb->doms) {
+ *domains = cdb->doms;
+ return EOK;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ ret = confdb_get_string_as_list(cdb, tmp_ctx,
+ CONFDB_MONITOR_CONF_ENTRY,
+ CONFDB_MONITOR_ACTIVE_DOMAINS,
+ &domlist);
+ if (ret == ENOENT) {
+ DEBUG(0, ("No domains configured, fatal error!\n"));
+ goto done;
+ }
+ if (ret != EOK ) {
+ DEBUG(0, ("Fatal error retrieving domains list!\n"));
+ goto done;
+ }
+
+ for (i = 0; domlist[i]; i++) {
+ ret = confdb_get_domain_internal(cdb, cdb, domlist[i], &domain);
+ if (ret) {
+ DEBUG(0, ("Error (%d [%s]) retrieving domain [%s], skipping!\n",
+ ret, strerror(ret), domlist[i]));
+ ret = EOK;
+ continue;
+ }
+
+ if (cdb->doms == NULL) {
+ cdb->doms = domain;
+ prevdom = cdb->doms;
+ } else {
+ prevdom->next = domain;
+ prevdom = domain;
+ }
+ }
+
+ if (cdb->doms == NULL) {
+ DEBUG(0, ("No properly configured domains, fatal error!\n"));
+ ret = ENOENT;
+ goto done;
+ }
+
+ *domains = cdb->doms;
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int confdb_get_domain(struct confdb_ctx *cdb,
+ const char *name,
+ struct sss_domain_info **_domain)
+{
+ struct sss_domain_info *dom, *doms;
+ int ret;
+
+ ret = confdb_get_domains(cdb, &doms);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ for (dom = doms; dom; dom = dom->next) {
+ if (strcasecmp(dom->name, name) == 0) {
+ *_domain = dom;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
new file mode 100644
index 00000000..e848e8bc
--- /dev/null
+++ b/src/confdb/confdb.h
@@ -0,0 +1,364 @@
+/*
+ SSSD
+
+ NSS Configuratoin DB
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _CONF_DB_H
+#define _CONF_DB_H
+
+#include <stdbool.h>
+#include "talloc.h"
+#include "tevent.h"
+#include "ldb.h"
+#include "ldb_errors.h"
+#include "config.h"
+
+/**
+ * @defgroup sss_confdb The ConfDB API
+ * The ConfDB is an interface for data providers to
+ * access the configuration information provided in
+ * the sssd.conf
+ * @{
+ */
+
+#define CONFDB_FILE "config.ldb"
+#define CONFDB_DEFAULT_CONFIG_FILE SSSD_CONF_DIR"/sssd.conf"
+#define SSSD_MIN_ID 1000
+
+
+/* Configuration options */
+
+/* Services */
+#define CONFDB_SERVICE_PATH_TMPL "config/%s"
+#define CONFDB_SERVICE_COMMAND "command"
+#define CONFDB_SERVICE_DEBUG_LEVEL "debug_level"
+#define CONFDB_SERVICE_DEBUG_TIMESTAMPS "debug_timestamps"
+#define CONFDB_SERVICE_DEBUG_TO_FILES "debug_to_files"
+#define CONFDB_SERVICE_TIMEOUT "timeout"
+#define CONFDB_SERVICE_RECON_RETRIES "reconnection_retries"
+
+/* Monitor */
+#define CONFDB_MONITOR_CONF_ENTRY "config/sssd"
+#define CONFDB_MONITOR_SBUS_TIMEOUT "sbus_timeout"
+#define CONFDB_MONITOR_ACTIVE_SERVICES "services"
+#define CONFDB_MONITOR_ACTIVE_DOMAINS "domains"
+#define CONFDB_MONITOR_NAME_REGEX "re_expression"
+#define CONFDB_MONITOR_FULL_NAME_FORMAT "full_name_format"
+
+/* NSS */
+#define CONFDB_NSS_CONF_ENTRY "config/nss"
+#define CONFDB_NSS_ENUM_CACHE_TIMEOUT "enum_cache_timeout"
+#define CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE "entry_cache_nowait_percentage"
+#define CONFDB_NSS_ENTRY_NEG_TIMEOUT "entry_negative_timeout"
+#define CONFDB_NSS_FILTER_USERS_IN_GROUPS "filter_users_in_groups"
+#define CONFDB_NSS_FILTER_USERS "filter_users"
+#define CONFDB_NSS_FILTER_GROUPS "filter_groups"
+#define CONFDB_NSS_PWFIELD "pwfield"
+
+/* PAM */
+#define CONFDB_PAM_CONF_ENTRY "config/pam"
+#define CONFDB_PAM_CRED_TIMEOUT "offline_credentials_expiration"
+#define CONFDB_PAM_FAILED_LOGIN_ATTEMPTS "offline_failed_login_attempts"
+#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS 0
+#define CONFDB_PAM_FAILED_LOGIN_DELAY "offline_failed_login_delay"
+#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY 5
+
+/* Data Provider */
+#define CONFDB_DP_CONF_ENTRY "config/dp"
+
+/* Domains */
+#define CONFDB_DOMAIN_PATH_TMPL "config/domain/%s"
+#define CONFDB_DOMAIN_BASEDN "cn=domain,cn=config"
+#define CONFDB_DOMAIN_ID_PROVIDER "id_provider"
+#define CONFDB_DOMAIN_AUTH_PROVIDER "auth_provider"
+#define CONFDB_DOMAIN_ACCESS_PROVIDER "access_provider"
+#define CONFDB_DOMAIN_CHPASS_PROVIDER "chpass_provider"
+#define CONFDB_DOMAIN_COMMAND "command"
+#define CONFDB_DOMAIN_TIMEOUT "timeout"
+#define CONFDB_DOMAIN_ATTR "cn"
+#define CONFDB_DOMAIN_ENUMERATE "enumerate"
+#define CONFDB_DOMAIN_MINID "min_id"
+#define CONFDB_DOMAIN_MAXID "max_id"
+#define CONFDB_DOMAIN_CACHE_CREDS "cache_credentials"
+#define CONFDB_DOMAIN_LEGACY_PASS "store_legacy_passwords"
+#define CONFDB_DOMAIN_MPG "magic_private_groups"
+#define CONFDB_DOMAIN_FQ "use_fully_qualified_names"
+#define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout"
+
+/* Local Provider */
+#define CONFDB_LOCAL_DEFAULT_SHELL "default_shell"
+#define CONFDB_LOCAL_DEFAULT_BASEDIR "base_directory"
+#define CONFDB_LOCAL_CREATE_HOMEDIR "create_homedir"
+#define CONFDB_LOCAL_REMOVE_HOMEDIR "remove_homedir"
+#define CONFDB_LOCAL_UMASK "homedir_umask"
+#define CONFDB_LOCAL_SKEL_DIR "skel_dir"
+#define CONFDB_LOCAL_MAIL_DIR "mail_dir"
+
+/* Proxy Provider */
+#define CONFDB_PROXY_LIBNAME "proxy_lib_name"
+#define CONFDB_PROXY_PAM_TARGET "proxy_pam_target"
+
+/* KRB5 Provider */
+#define CONFDB_KRB5_KDCIP "krb5_kdcip"
+#define CONFDB_KRB5_REALM "krb5_realm"
+#define CONFDB_KRB5_CCACHEDIR "krb5_ccachedir"
+#define CONFDB_KRB5_CCNAME_TMPL "krb5_ccname_template"
+#define CONFDB_KRB5_CHANGEPW_PRINC "krb5_changepw_principal"
+#define CONFDB_KRB5_AUTH_TIMEOUT "krb5_auth_timeout"
+
+struct confdb_ctx;
+struct config_file_ctx;
+
+/**
+ * Data structure storing all of the basic features
+ * of a domain.
+ */
+struct sss_domain_info {
+ char *name;
+ char *provider;
+ int timeout;
+ bool enumerate;
+ bool fqnames;
+ uint32_t id_min;
+ uint32_t id_max;
+
+ bool cache_credentials;
+ bool legacy_passwords;
+
+ struct sss_domain_info *next;
+};
+
+/**
+ * Initialize the connection to the ConfDB
+ *
+ * @param[in] mem_ctx The parent memory context for the confdb_ctx
+ * @param[out] cdb_ctx The newly-created connection object
+ * @param[in] confdb_location The absolute path to the ConfDB file on the
+ * filesystem
+ *
+ * @return 0 - Connection succeeded and cdb_ctx was populated
+ * @return ENOMEM - There was not enough memory to create the cdb_ctx
+ * @return EIO - There was an I/O error communicating with the ConfDB file
+ */
+int confdb_init(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx **cdb_ctx,
+ char *confdb_location);
+
+/**
+ * Get a domain object for the named domain
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] name The name of the domain to retrieve
+ * @param[out] domain A pointer to a domain object for the domain given by
+ * name
+ *
+ * @return 0 - Lookup succeeded and domain was populated
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return ENOENT - The named domain does not exist or is not set active
+ */
+int confdb_get_domain(struct confdb_ctx *cdb,
+ const char *name,
+ struct sss_domain_info **domain);
+
+/**
+ * Get a null-terminated linked-list of active domain objects
+ * @param[in] cdb The connection object to the confdb
+ * @param[out] domains A pointer to the first entry of a linked-list of domain
+ * objects
+ *
+ * @return 0 - Lookup succeeded and all active domains are in the list
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return ENOENT - No active domains are configured
+ */
+int confdb_get_domains(struct confdb_ctx *cdb,
+ struct sss_domain_info **domains);
+
+
+/**
+ * @brief Add an arbitrary parameter to the confdb.
+ *
+ * This is mostly useful
+ * for testing, as they will not persist between SSSD restarts. For
+ * persistence, make changes to the sssd.conf file.
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] replace If replace is set to true, pre-existing values will be
+ * overwritten.
+ * If it is false, the provided values will be added to the
+ * attribute.
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[in] values A null-terminated array of values to add to the attribute
+ *
+ * @return 0 - Successfully added the provided value(s)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed
+ * @return EIO - An I/O error occurred communicating with the ConfDB
+ */
+int confdb_add_param(struct confdb_ctx *cdb,
+ bool replace,
+ const char *section,
+ const char *attribute,
+ const char **values);
+
+/**
+ * @brief Retrieve all values for an attribute
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] mem_ctx The parent memory context for the value list
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[out] values A null-terminated array of cstrings containing all
+ * values for this attribute
+ *
+ * @return 0 - Successfully retrieved the value(s)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed
+ * @return EIO - An I/O error occurred while communicating with the ConfDB
+ */
+int confdb_get_param(struct confdb_ctx *cdb,
+ TALLOC_CTX *mem_ctx,
+ const char *section,
+ const char *attribute,
+ char ***values);
+
+/**
+ * @brief Convenience function to retrieve a single-valued attribute as a
+ * string
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] ctx The parent memory context for the returned string
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[in] defstr If not NULL, the string to use if the attribute does not
+ * exist in the ConfDB
+ * @param[out] result A pointer to the retrieved (or default) string
+ *
+ * @return 0 - Successfully retrieved the entry (or used the default)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed, or the attribute was not
+ * single-valued.
+ * @return EIO - An I/O error occurred while communicating with the ConfDB
+ */
+int confdb_get_string(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ const char *defstr, char **result);
+
+/**
+ * @brief Convenience function to retrieve a single-valued attribute as an
+ * integer
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] ctx The parent memory context for the returned string
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[in] defval If not NULL, the integer to use if the attribute does not
+ * exist in the ConfDB
+ * @param[out] result A pointer to the retrieved (or default) integer
+ *
+ * @return 0 - Successfully retrieved the entry (or used the default)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed, or the attribute was not
+ * single-valued.
+ * @return EIO - An I/O error occurred while communicating with the ConfDB
+ * @return ERANGE - The value stored in the ConfDB was outside the range
+ * [INT_MIN..INT_MAX]
+ */
+int confdb_get_int(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ int defval, int *result);
+
+/**
+ * @brief Convenience function to retrieve a single-valued attribute as a
+ * boolean
+ *
+ * This function will read (in a case-insensitive manner) a "true" or "false"
+ * value from the ConfDB and convert it to an integral bool value.
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] ctx The parent memory context for the returned string
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[in] defval If not NULL, the boolean state to use if the attribute
+ * does not exist in the ConfDB
+ * @param[out] result A pointer to the retrieved (or default) bool
+ *
+ * @return 0 - Successfully retrieved the entry (or used the default)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed, the attribute was not
+ * single-valued, or the value was not a boolean.
+ * @return EIO - An I/O error occurred while communicating with the ConfDB
+ */
+int confdb_get_bool(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ bool defval, bool *result);
+
+/**
+ * @brief Convenience function to retrieve a single-valued attribute as a
+ * null-terminated array of strings
+ *
+ * This function will automatically split a comma-separated string in an
+ * attribute into a null-terminated array of strings. This is useful for
+ * storing and retrieving ordered lists, as ConfDB multivalued attributes do
+ * not guarantee retrieval order.
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] ctx The parent memory context for the returned string
+ * @param[in] section The ConfDB section to update. This is constructed from
+ * the format of the sssd.conf file. All sections start
+ * with 'config/'. Subsections are separated by slashes.
+ * e.g. [domain/LDAP] in sssd.conf would translate to
+ * config/domain/LDAP
+ * @param[in] attribute The name of the attribute to update
+ * @param[out] result A pointer to the retrieved array of strings
+ *
+ * @return 0 - Successfully retrieved the entry (or used the default)
+ * @return ENOMEM - There was insufficient memory to complete the operation
+ * @return EINVAL - The section could not be parsed, or the attribute was not
+ * single-valued.
+ * @return ENOENT - The attribute was not found.
+ * @return EIO - An I/O error occurred while communicating with the ConfDB
+ */
+int confdb_get_string_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+ const char *section, const char *attribute,
+ char ***result);
+/**
+ * @}
+ */
+#endif
diff --git a/src/confdb/confdb_private.h b/src/confdb/confdb_private.h
new file mode 100644
index 00000000..1bab99ca
--- /dev/null
+++ b/src/confdb/confdb_private.h
@@ -0,0 +1,35 @@
+/*
+ SSSD
+
+ Configuration Database
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef CONFDB_PRIVATE_H_
+#define CONFDB_PRIVATE_H_
+
+struct confdb_ctx {
+ struct tevent_context *pev;
+ struct ldb_context *ldb;
+
+ struct sss_domain_info *doms;
+};
+
+int parse_section(TALLOC_CTX *mem_ctx, const char *section,
+ char **sec_dn, const char **rdn_name);
+
+#endif /* CONFDB_PRIVATE_H_ */
diff --git a/src/confdb/confdb_setup.c b/src/confdb/confdb_setup.c
new file mode 100644
index 00000000..3c10c06c
--- /dev/null
+++ b/src/confdb/confdb_setup.c
@@ -0,0 +1,423 @@
+/*
+ SSSD
+
+ Configuration Database
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include <sys/stat.h>
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "confdb.h"
+#include "confdb_private.h"
+#include "confdb_setup.h"
+#include "collection.h"
+#include "collection_tools.h"
+#include "ini_config.h"
+
+
+int confdb_test(struct confdb_ctx *cdb)
+{
+ char **values;
+ int ret;
+
+ ret = confdb_get_param(cdb, cdb,
+ "config",
+ "version",
+ &values);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ if (values[0] == NULL) {
+ /* empty database, will need to init */
+ talloc_free(values);
+ return ENOENT;
+ }
+
+ if (values[1] != NULL) {
+ /* more than 1 value ?? */
+ talloc_free(values);
+ return EIO;
+ }
+
+ if (strcmp(values[0], CONFDB_VERSION) != 0) {
+ /* Existing version does not match executable version */
+ DEBUG(1, ("Upgrading confdb version from %s to %s\n",
+ values[0], CONFDB_VERSION));
+
+ /* This is recoverable, since we purge the confdb file
+ * when we re-initialize it.
+ */
+ talloc_free(values);
+ return ENOENT;
+ }
+
+ talloc_free(values);
+ return EOK;
+}
+
+static int confdb_purge(struct confdb_ctx *cdb)
+{
+ int ret, i;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res;
+ struct ldb_dn *dn;
+ const char *attrs[] = { "dn", NULL };
+
+ tmp_ctx = talloc_new(NULL);
+
+ dn = ldb_dn_new(tmp_ctx, cdb->ldb, "cn=config");
+
+ /* Get the list of all DNs */
+ ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn,
+ LDB_SCOPE_SUBTREE, attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ for(i=0; i<res->count; i++) {
+ /* Delete this DN */
+ ret = ldb_delete(cdb->ldb, res->msgs[i]->dn);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int confdb_create_base(struct confdb_ctx *cdb)
+{
+ int ret;
+ struct ldb_ldif *ldif;
+
+ const char *base_ldif = CONFDB_BASE_LDIF;
+
+ while ((ldif = ldb_ldif_read_string(cdb->ldb, &base_ldif))) {
+ ret = ldb_add(cdb->ldb, ldif->msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d,[%s]), aborting!\n",
+ ret, ldb_errstring(cdb->ldb)));
+ return EIO;
+ }
+ ldb_ldif_read_free(cdb->ldb, ldif);
+ }
+
+ return EOK;
+}
+
+static int confdb_create_ldif(TALLOC_CTX *mem_ctx,
+ struct collection_item *sssd_config,
+ char **config_ldif)
+{
+ int ret, i, j;
+ char *ldif;
+ char *tmp_ldif;
+ char *writer;
+ char **sections;
+ int section_count;
+ char *dn;
+ char *tmp_dn;
+ char *sec_dn;
+ char **attrs;
+ int attr_count;
+ char *ldif_attr;
+ struct collection_item *attr;
+ TALLOC_CTX *tmp_ctx;
+ size_t dn_size;
+ size_t ldif_len;
+ size_t attr_len;
+
+ ldif_len = strlen(CONFDB_INTERNAL_LDIF);
+ ldif = talloc_array(mem_ctx, char, ldif_len+1);
+ if (!ldif) return ENOMEM;
+
+ tmp_ctx = talloc_new(ldif);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto error;
+ }
+
+ memcpy(ldif, CONFDB_INTERNAL_LDIF, ldif_len);
+ writer = ldif+ldif_len;
+
+ /* Read in the collection and convert it to an LDIF */
+ /* Get the list of sections */
+ sections = get_section_list(sssd_config, &section_count, &ret);
+ if (ret != EOK) {
+ goto error;
+ }
+
+ for(i = 0; i < section_count; i++) {
+ const char *rdn = NULL;
+ DEBUG(6,("Processing config section [%s]\n", sections[i]));
+ ret = parse_section(tmp_ctx, sections[i], &sec_dn, &rdn);
+ if (ret != EOK) {
+ goto error;
+ }
+
+ dn = talloc_asprintf(tmp_ctx,
+ "dn: %s,cn=config\n"
+ "cn: %s\n",
+ sec_dn, rdn);
+ if(!dn) {
+ ret = ENOMEM;
+ free_section_list(sections);
+ goto error;
+ }
+ dn_size = strlen(dn);
+
+ /* Get all of the attributes and their values as LDIF */
+ attrs = get_attribute_list(sssd_config, sections[i],
+ &attr_count, &ret);
+ if (ret != EOK) {
+ free_section_list(sections);
+ goto error;
+ }
+
+ for(j = 0; j < attr_count; j++) {
+ DEBUG(6, ("Processing attribute [%s]\n", attrs[j]));
+ ret = get_config_item(sections[i], attrs[j], sssd_config,
+ &attr);
+ if (ret != EOK) goto error;
+
+ const char *value = get_const_string_config_value(attr, &ret);
+ if (ret != EOK) goto error;
+
+ ldif_attr = talloc_asprintf(tmp_ctx,
+ "%s: %s\n", attrs[j], value);
+ DEBUG(9, ("%s", ldif_attr));
+
+ attr_len = strlen(ldif_attr);
+
+ tmp_dn = talloc_realloc(tmp_ctx, dn, char,
+ dn_size+attr_len+1);
+ if(!tmp_dn) {
+ ret = ENOMEM;
+ free_attribute_list(attrs);
+ free_section_list(sections);
+ goto error;
+ }
+ dn = tmp_dn;
+ memcpy(dn+dn_size, ldif_attr, attr_len+1);
+ dn_size += attr_len;
+ }
+
+ dn_size ++;
+ tmp_dn = talloc_realloc(tmp_ctx, dn, char,
+ dn_size+1);
+ if(!tmp_dn) {
+ ret = ENOMEM;
+ free_attribute_list(attrs);
+ free_section_list(sections);
+ goto error;
+ }
+ dn = tmp_dn;
+ dn[dn_size-1] = '\n';
+ dn[dn_size] = '\0';
+
+ DEBUG(9, ("Section dn\n%s", dn));
+
+ tmp_ldif = talloc_realloc(mem_ctx, ldif, char,
+ ldif_len+dn_size+1);
+ if(!tmp_ldif) {
+ ret = ENOMEM;
+ free_attribute_list(attrs);
+ free_section_list(sections);
+ goto error;
+ }
+ ldif = tmp_ldif;
+ memcpy(ldif+ldif_len, dn, dn_size);
+ ldif_len += dn_size;
+
+ free_attribute_list(attrs);
+ talloc_free(dn);
+ }
+
+ ldif[ldif_len] = '\0';
+
+ free_section_list(sections);
+
+ *config_ldif = ldif;
+ talloc_free(tmp_ctx);
+ return EOK;
+
+error:
+ talloc_free(ldif);
+ return ret;
+}
+
+int confdb_init_db(const char *config_file, struct confdb_ctx *cdb)
+{
+ int ret, i;
+ int fd = -1;
+ struct collection_item *sssd_config = NULL;
+ struct collection_item *error_list = NULL;
+ struct collection_item *item = NULL;
+ char *config_ldif;
+ struct ldb_ldif *ldif;
+ TALLOC_CTX *tmp_ctx;
+ char *lasttimestr, timestr[21];
+ const char *vals[2] = { timestr, NULL };
+ struct stat cstat;
+ int version;
+
+ tmp_ctx = talloc_new(cdb);
+ if (tmp_ctx == NULL) return ENOMEM;
+
+ ret = check_and_open_readonly(config_file, &fd, 0, 0, (S_IRUSR|S_IWUSR));
+ if (ret != EOK) {
+ DEBUG(1, ("Permission check on config file failed.\n"));
+ talloc_zfree(tmp_ctx);
+ return EIO;
+ }
+
+ /* Determine if the conf file has changed since we last updated
+ * the confdb
+ */
+ ret = fstat(fd, &cstat);
+ if (ret != 0) {
+ DEBUG(0, ("Unable to stat config file [%s]! (%d [%s])\n",
+ config_file, errno, strerror(errno)));
+ close(fd);
+ talloc_zfree(tmp_ctx);
+ return errno;
+ }
+ ret = snprintf(timestr, 21, "%llu", (long long unsigned)cstat.st_mtime);
+ if (ret <= 0 || ret >= 21) {
+ DEBUG(0, ("Failed to convert time_t to string ??\n"));
+ close(fd);
+ talloc_zfree(tmp_ctx);
+ return errno ? errno: EFAULT;
+ }
+
+ /* check if we need to re-init the db */
+ ret = confdb_get_string(cdb, tmp_ctx, "config", "lastUpdate", NULL, &lasttimestr);
+ if (ret == EOK && lasttimestr != NULL) {
+
+ /* now check if we lastUpdate and last file modification change differ*/
+ if (strcmp(lasttimestr, timestr) == 0) {
+ /* not changed, get out, nothing more to do */
+ close(fd);
+ talloc_zfree(tmp_ctx);
+ return EOK;
+ }
+ }
+
+ /* Set up a transaction to replace the configuration */
+ ret = ldb_transaction_start(cdb->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to start a transaction for updating the configuration\n"));
+ talloc_zfree(tmp_ctx);
+ close(fd);
+ return sysdb_error_to_errno(ret);
+ }
+
+ /* Purge existing database */
+ ret = confdb_purge(cdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not purge existing configuration\n"));
+ close(fd);
+ goto done;
+ }
+
+ /* Read the configuration into a collection */
+ ret = config_from_fd("sssd", fd, config_file, &sssd_config,
+ INI_STOP_ON_ANY, &error_list);
+ close(fd);
+ if (ret != EOK) {
+ DEBUG(0, ("Parse error reading configuration file [%s]\n",
+ config_file));
+ print_file_parsing_errors(stderr, error_list);
+ free_ini_config_errors(error_list);
+ free_ini_config(sssd_config);
+ goto done;
+ }
+
+ /* Make sure that the config file version matches the confdb version */
+ ret = get_config_item("sssd", "config_file_version",
+ sssd_config, &item);
+ if (ret != EOK) {
+ DEBUG(0, ("Internal error determining config_file_version\n"));
+ goto done;
+ }
+ if (item == NULL) {
+ /* No known version. Assumed to be version 1 */
+ DEBUG(0, ("Config file is an old version. "
+ "Please run configuration upgrade script.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ version = get_int_config_value(item, 1, -1, &ret);
+ if (ret != EOK) {
+ DEBUG(0, ("Config file version could not be determined\n"));
+ goto done;
+ } else if (version < CONFDB_VERSION_INT) {
+ DEBUG(0, ("Config file is an old version. "
+ "Please run configuration upgrade script.\n"));
+ ret = EINVAL;
+ goto done;
+ } else if (version > CONFDB_VERSION_INT) {
+ DEBUG(0, ("Config file version is newer than confdb\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = confdb_create_ldif(tmp_ctx, sssd_config, &config_ldif);
+ free_ini_config(sssd_config);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not create LDIF for confdb\n"));
+ goto done;
+ }
+
+ DEBUG(7, ("LDIF file to import: \n%s", config_ldif));
+
+ i=0;
+ while ((ldif = ldb_ldif_read_string(cdb->ldb, (const char **)&config_ldif))) {
+ ret = ldb_add(cdb->ldb, ldif->msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d,[%s]), aborting!\n",
+ ret, ldb_errstring(cdb->ldb)));
+ ret = EIO;
+ goto done;
+ }
+ ldb_ldif_read_free(cdb->ldb, ldif);
+ }
+
+ /* now store the lastUpdate time so that we do not re-init if nothing
+ * changed on restart */
+
+ ret = confdb_add_param(cdb, true, "config", "lastUpdate", vals);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to set last update time on db!\n"));
+ }
+
+ ret = EOK;
+
+done:
+ ret == EOK ?
+ ldb_transaction_commit(cdb->ldb) :
+ ldb_transaction_cancel(cdb->ldb);
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
diff --git a/src/confdb/confdb_setup.h b/src/confdb/confdb_setup.h
new file mode 100644
index 00000000..2b8802f6
--- /dev/null
+++ b/src/confdb/confdb_setup.h
@@ -0,0 +1,52 @@
+/*
+ SSSD
+
+ Configuration Database
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef CONFDB_SETUP_H_
+#define CONFDB_SETUP_H_
+
+#define CONFDB_VERSION "2"
+#define CONFDB_VERSION_INT 2
+
+#define CONFDB_BASE_LDIF \
+ "dn: @ATTRIBUTES\n" \
+ "cn: CASE_INSENSITIVE\n" \
+ "dc: CASE_INSENSITIVE\n" \
+ "dn: CASE_INSENSITIVE\n" \
+ "name: CASE_INSENSITIVE\n" \
+ "objectclass: CASE_INSENSITIVE\n" \
+ "\n" \
+ "dn: @INDEXLIST\n" \
+ "@IDXATTR: cn\n" \
+ "\n" \
+ "dn: @MODULES\n" \
+ "@LIST: server_sort\n" \
+ "\n"
+
+#define CONFDB_INTERNAL_LDIF \
+ "dn: cn=config\n" \
+ "version: "CONFDB_VERSION"\n" \
+ "\n"
+
+int confdb_create_base(struct confdb_ctx *cdb);
+int confdb_test(struct confdb_ctx *cdb);
+int confdb_init_db(const char *config_file, struct confdb_ctx *cdb);
+
+#endif /* CONFDB_SETUP_H_ */
diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
new file mode 100644
index 00000000..a004c33b
--- /dev/null
+++ b/src/config/SSSDConfig.py
@@ -0,0 +1,1664 @@
+'''
+Created on Sep 18, 2009
+
+@author: sgallagh
+'''
+
+import os
+import gettext
+import exceptions
+from ipachangeconf import SSSDChangeConf
+
+# Exceptions
+class SSSDConfigException(Exception): pass
+class ParsingError(Exception): pass
+class AlreadyInitializedError(SSSDConfigException): pass
+class NotInitializedError(SSSDConfigException): pass
+class NoOutputFileError(SSSDConfigException): pass
+class NoServiceError(SSSDConfigException): pass
+class NoSectionError(SSSDConfigException): pass
+class NoOptionError(SSSDConfigException): pass
+class ServiceNotRecognizedError(SSSDConfigException): pass
+class ServiceAlreadyExists(SSSDConfigException): pass
+class NoDomainError(SSSDConfigException): pass
+class DomainNotRecognized(SSSDConfigException): pass
+class DomainAlreadyExistsError(SSSDConfigException): pass
+class NoSuchProviderError(SSSDConfigException): pass
+class NoSuchProviderSubtypeError(SSSDConfigException): pass
+class ProviderSubtypeInUse(SSSDConfigException): pass
+
+PACKAGE = 'sss_daemon'
+LOCALEDIR = '/usr/share/locale'
+
+translation = gettext.translation(PACKAGE, LOCALEDIR, fallback=True)
+_ = translation.ugettext
+
+# TODO: This needs to be made external
+option_strings = {
+ # [service]
+ 'debug_level' : _('Set the verbosity of the debug logging'),
+ 'debug_timestamps' : _('Include timestamps in debug logs'),
+ 'debug_to_files' : _('Write debug messages to logfiles'),
+ 'timeout' : _('Ping timeout before restarting service'),
+ 'command' : _('Command to start service'),
+ 'reconnection_retries' : _('Number of times to attempt connection to Data Providers'),
+
+ # [sssd]
+ 'services' : _('SSSD Services to start'),
+ 'domains' : _('SSSD Domains to start'),
+ 'sbus_timeout' : _('Timeout for messages sent over the SBUS'),
+ 're_expression' : _('Regex to parse username and domain'),
+ 'full_name_format' : _('Printf-compatible format for displaying fully-qualified names'),
+
+ # [nss]
+ 'enum_cache_timeout' : _('Enumeration cache timeout length (seconds)'),
+ 'entry_cache_no_wait_timeout' : _('Entry cache background update timeout length (seconds)'),
+ 'entry_negative_timeout' : _('Negative cache timeout length (seconds)'),
+ 'filter_users' : _('Users that SSSD should explicitly ignore'),
+ 'filter_groups' : _('Groups that SSSD should explicitly ignore'),
+ 'filter_users_in_groups' : _('Should filtered users appear in groups'),
+ 'pwfield' : _('The value of the password field the NSS provider should return'),
+
+ # [pam]
+ 'offline_credentials_expiration' : _('How long to allow cached logins between online logins (days)'),
+ 'offline_failed_login_attempts' : _('How many failed logins attempts are allowed when offline'),
+ 'offline_failed_login_delay' : _('How long (minutes) to deny login after offline_failed_login_attempts has been reached'),
+
+ # [provider]
+ 'id_provider' : _('Identity provider'),
+ 'auth_provider' : _('Authentication provider'),
+ 'access_provider' : _('Access control provider'),
+ 'chpass_provider' : _('Password change provider'),
+
+ # [domain]
+ 'min_id' : _('Minimum user ID'),
+ 'max_id' : _('Maximum user ID'),
+ 'timeout' : _('Ping timeout before restarting domain'),
+ 'enumerate' : _('Enable enumerating all users/groups'),
+ 'cache_credentials' : _('Cache credentials for offline login'),
+ 'store_legacy_passwords' : _('Store password hashes'),
+ 'use_fully_qualified_names' : _('Display users/groups in fully-qualified form'),
+ 'entry_cache_timeout' : _('Entry cache timeout length (seconds)'),
+
+ # [provider/ipa]
+ 'ipa_domain' : _('IPA domain'),
+ 'ipa_server' : _('IPA server address'),
+ 'ipa_hostname' : _('IPA client hostname'),
+
+ # [provider/krb5]
+ 'krb5_kdcip' : _('Kerberos server address'),
+ 'krb5_realm' : _('Kerberos realm'),
+ 'krb5_auth_timeout' : _('Authentication timeout'),
+
+ # [provider/krb5/auth]
+ 'krb5_ccachedir' : _('Directory to store credential caches'),
+ 'krb5_ccname_template' : _("Location of the user's credential cache"),
+ 'krb5_keytab' : _("Location of the keytab to validate credentials"),
+ 'krb5_validate' : _("Enable credential validation"),
+
+ # [provider/krb5/chpass]
+ 'krb5_changepw_principal' : _('The principal of the change password service'),
+
+ # [provider/ldap]
+ 'ldap_uri' : _('ldap_uri, The URI of the LDAP server'),
+ 'ldap_search_base' : _('The default base DN'),
+ 'ldap_schema' : _('The Schema Type in use on the LDAP server, rfc2307'),
+ 'ldap_default_bind_dn' : _('The default bind DN'),
+ 'ldap_default_authtok_type' : _('The type of the authentication token of the default bind DN'),
+ 'ldap_default_authtok' : _('The authentication token of the default bind DN'),
+ 'ldap_network_timeout' : _('Length of time to attempt connection'),
+ 'ldap_opt_timeout' : _('Length of time to attempt synchronous LDAP operations'),
+ 'ldap_offline_timeout' : _('Length of time between attempts to reconnect while offline'),
+ 'ldap_tls_cacert' : _('file that contains CA certificates'),
+ 'ldap_tls_reqcert' : _('Require TLS certificate verification'),
+ 'ldap_sasl_mech' : _('Specify the sasl mechanism to use'),
+ 'ldap_sasl_authid' : _('Specify the sasl authorization id to use'),
+ 'krb5_kdcip' : _('Kerberos server address'),
+ 'krb5_realm' : _('Kerberos realm'),
+ 'ldap_krb5_keytab' : _('Kerberos service keytab'),
+ 'ldap_krb5_init_creds' : _('Use Kerberos auth for LDAP connection'),
+ 'ldap_referrals' : _('Follow LDAP referrals'),
+
+ # [provider/ldap/id]
+ 'ldap_search_timeout' : _('Length of time to wait for a search request'),
+ 'ldap_enumeration_refresh_timeout' : _('Length of time between enumeration updates'),
+ 'ldap_id_use_start_tls' : _('Require TLS for ID lookups, false'),
+ 'ldap_user_search_base' : _('Base DN for user lookups'),
+ 'ldap_user_search_scope' : _('Scope of user lookups'),
+ 'ldap_user_search_filter' : _('Filter for user lookups'),
+ 'ldap_user_object_class' : _('Objectclass for users'),
+ 'ldap_user_name' : _('Username attribute'),
+ 'ldap_user_uid_number' : _('UID attribute'),
+ 'ldap_user_gid_number' : _('Primary GID attribute'),
+ 'ldap_user_gecos' : _('GECOS attribute'),
+ 'ldap_user_homedir' : _('Home directory attribute'),
+ 'ldap_user_shell' : _('Shell attribute'),
+ 'ldap_user_uuid' : _('UUID attribute'),
+ 'ldap_user_principal' : _('User principal attribute (for Kerberos)'),
+ 'ldap_user_fullname' : _('Full Name'),
+ 'ldap_user_member_of' : _('memberOf attribute'),
+ 'ldap_user_modify_timestamp' : _('Modification time attribute'),
+
+ # [provider/ldap/auth]
+ 'ldap_pwd_policy' : _('Policy to evaluate the password expiration'),
+
+ # [provider/local/id]
+ 'default_shell' : _('Default shell, /bin/bash'),
+ 'base_directory' : _('Base for home directories'),
+
+ # [provider/proxy/id]
+ 'proxy_lib_name' : _('The name of the NSS library to use'),
+
+ # [provider/proxy/auth]
+ 'proxy_pam_target' : _('PAM stack to use')
+}
+
+def striplist(l):
+ return([x.strip() for x in l])
+
+def options_overlap(options1, options2):
+ overlap = []
+ for option in options1:
+ if option in options2:
+ overlap.append(option)
+ return overlap
+
+class SSSDConfigSchema(SSSDChangeConf):
+ def __init__(self, schemafile, schemaplugindir):
+ SSSDChangeConf.__init__(self)
+ #TODO: get these from a global setting
+ if not schemafile:
+ schemafile = '/etc/sssd/sssd.api.conf'
+ if not schemaplugindir:
+ schemaplugindir = '/etc/sssd/sssd.api.d'
+
+ try:
+ #Read the primary config file
+ fd = open(schemafile, 'r')
+ self.readfp(fd)
+ fd.close()
+ # Read in the provider files
+ for file in os.listdir(schemaplugindir):
+ fd = open(schemaplugindir+ "/" + file)
+ self.readfp(fd)
+ fd.close()
+ except IOError:
+ raise
+ except SyntaxError: # can be raised with readfp
+ raise ParsingError
+
+ # Set up lookup table for types
+ self.type_lookup = {
+ 'bool' : bool,
+ 'int' : int,
+ 'long' : long,
+ 'float': float,
+ 'str' : str,
+ 'list' : list,
+ 'None' : None
+ }
+
+ # Lookup table for acceptable boolean values
+ self.bool_lookup = {
+ 'false' : False,
+ 'true' : True,
+ }
+
+ def get_options(self, section):
+ if not self.has_section(section):
+ raise NoSectionError
+ options = self.options(section)
+
+ # Indexes
+ PRIMARY_TYPE = 0
+ SUBTYPE = 1
+ MANDATORY = 2
+ DEFAULT = 3
+
+ # Parse values
+ parsed_options = {}
+ for option in self.strip_comments_empty(options):
+ unparsed_option = option['value']
+ split_option = striplist(unparsed_option.split(','))
+ optionlen = len(split_option)
+
+ primarytype = self.type_lookup[split_option[PRIMARY_TYPE]]
+ subtype = self.type_lookup[split_option[SUBTYPE]]
+ mandatory = self.bool_lookup[split_option[MANDATORY]]
+
+ if option_strings.has_key(option['name']):
+ desc = option_strings[option['name']]
+ else:
+ desc = None
+
+ if optionlen == 3:
+ # This option has no defaults
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ None)
+ elif optionlen == 4:
+ if type(split_option[DEFAULT]) == primarytype:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ split_option[DEFAULT])
+ elif primarytype == list:
+ if (type(split_option[DEFAULT]) == subtype):
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [split_option[DEFAULT]])
+ else:
+ try:
+ if subtype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [self.bool_lookup[split_option[DEFAULT].lower()]])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ [subtype(split_option[DEFAULT])])
+ except ValueError, KeyError:
+ raise ParsingError
+ else:
+ try:
+ if primarytype == bool and \
+ type(split_option[DEFAULT]) == str:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ self.bool_lookup[split_option[DEFAULT].lower()])
+ else:
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ primarytype(split_option[DEFAULT]))
+ except ValueError, KeyError:
+ raise ParsingError
+
+ elif optionlen > 4:
+ if (primarytype != list):
+ raise ParsingError
+ fixed_options = []
+ for x in split_option[DEFAULT:]:
+ if type(x) != subtype:
+ try:
+ if (subtype == bool and type(x) == str):
+ newvalue = self.bool_lookup[x.lower()]
+ else:
+ newvalue = subtype(x)
+ fixed_options.extend([newvalue])
+ except ValueError, KeyError:
+ raise ParsingError
+ else:
+ fixed_options.extend([x])
+ parsed_options[option['name']] = \
+ (primarytype,
+ subtype,
+ mandatory,
+ desc,
+ fixed_options)
+ else:
+ # Bad config file
+ raise ParsingError
+
+ return parsed_options
+
+ def get_option(self, section, option):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+ if not self.has_option(section, option):
+ raise NoOptionError("Section [%s] has no option [%s]" %
+ (section, option))
+
+ return self.get_options(section)[option]
+
+ def get_defaults(self, section):
+ if not self.has_section(section):
+ raise NoSectionError(section)
+
+ schema_options = self.get_options(section)
+ defaults = dict([(x,schema_options[x][4])
+ for x in schema_options.keys()
+ if schema_options[x][4] != None])
+
+ return defaults
+
+ def get_services(self):
+ service_list = [x['name'] for x in self.sections()
+ if x['name'] != 'service' and
+ not x['name'].startswith('domain') and
+ not x['name'].startswith('provider')]
+ return service_list
+
+ def get_providers(self):
+ providers = {}
+ for section in self.sections():
+ splitsection = section['name'].split('/')
+ if (splitsection[0] == 'provider'):
+ if(len(splitsection) == 3):
+ if not providers.has_key(splitsection[1]):
+ providers[splitsection[1]] = []
+ providers[splitsection[1]].extend([splitsection[2]])
+ for key in providers.keys():
+ providers[key] = tuple(providers[key])
+ return providers
+
+class SSSDConfigObject(object):
+ def __init__(self):
+ self.name = None
+ self.options = {}
+
+ def get_name(self):
+ """
+ Return the name of the this object
+
+ === Returns ===
+ The domain name
+
+ === Errors ===
+ No errors
+ """
+ return self.name
+
+ def get_option(self, optionname):
+ """
+ Return the value of an service option
+
+ optionname:
+ The option to get.
+
+ === Returns ===
+ The value for the requested option.
+
+ === Errors ===
+ NoOptionError:
+ The specified option was not listed in the service
+ """
+ if optionname in self.options.keys():
+ return self.options[optionname]
+ raise NoOptionError(optionname)
+
+ def get_all_options(self):
+ """
+ Return a dictionary of name/value pairs for this object
+
+ === Returns ===
+ A dictionary of name/value pairs currently in use for this object
+
+ === Errors ===
+ No errors
+ """
+ return self.options
+
+ def remove_option(self, optionname):
+ """
+ Remove an option from the object. If the option does not exist, it is ignored.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No errors
+ """
+ if self.options.has_key(optionname):
+ del self.options[optionname]
+
+class SSSDService(SSSDConfigObject):
+ '''
+ Object to manipulate SSSD service options
+ '''
+
+ def __init__(self, servicename, apischema):
+ """
+ Create a new SSSDService, setting its defaults to those found in the
+ schema. This constructor should not be used directly. Use
+ SSSDConfig.new_service() instead.
+
+ name:
+ The service name
+ apischema:
+ An SSSDConfigSchema? object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDService object.
+
+ === Errors ===
+ TypeError:
+ The API schema passed in was unusable or the name was not a string.
+ ServiceNotRecognizedError:
+ The service was not listed in the schema
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(servicename) != str:
+ raise TypeError
+
+ if not apischema.has_section(servicename):
+ raise ServiceNotRecognizedError(servicename)
+
+ self.name = servicename
+ self.schema = apischema
+
+ # Set up the service object with any known defaults
+ self.options = {}
+
+ # Include a list of hidden options
+ self.hidden_options = []
+
+ # Set up default options for all services
+ self.options.update(self.schema.get_defaults('service'))
+
+ # Set up default options for this service
+ self.options.update(self.schema.get_defaults(self.name))
+
+ # For the [sssd] service, force the config file version
+ if servicename == 'sssd':
+ self.options['config_file_version'] = 2
+ self.hidden_options.append('config_file_version')
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the service, including the mandatory flag.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+
+ # Get the list of available options for all services
+ schema_options = self.schema.get_options('service')
+ options.update(schema_options)
+
+ schema_options = self.schema.get_options(self.name)
+ options.update(schema_options)
+
+ return options
+
+ def list_options(self):
+ """
+ List all options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List all mandatory options that apply to this service
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'services' :
+ (list, str, u'SSSD Services to start', ['nss', 'pam']) }
+
+ === Errors ===
+ No Errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def set_option(self, optionname, value):
+ """
+ Set a service option to the specified value (or values)
+
+ optionname:
+ The option to change
+ value:
+ The value to set. This may be a single value or a list of values. If
+ it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ if self.schema.has_option(self.name, optionname):
+ option_schema = self.schema.get_option(self.name, optionname)
+ elif self.schema.has_option('service', optionname):
+ option_schema = self.schema.get_option('service', optionname)
+ elif optionname in self.hidden_options:
+ # Set this option and do not add it to the list of changeable values
+ self.options[optionname] = value
+ return
+ else:
+ raise NoOptionError('Section [%s] has no option [%s]' % (self.name, optionname))
+
+ if value == None:
+ self.remove_option(optionname)
+ return
+
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and \
+ type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], optionname, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+
+ value = newvalue
+
+ self.options[optionname] = value
+
+class SSSDDomain(SSSDConfigObject):
+ """
+ Object to manipulate SSSD domain options
+ """
+ def __init__(self, domainname, apischema):
+ """
+ Creates a new, empty SSSDDomain. This domain is inactive by default.
+ This constructor should not be used directly. Use
+ SSSDConfig.new_domain() instead.
+
+ name:
+ The domain name.
+ apischema:
+ An SSSDConfigSchema object created by SSSDConfig.__init__()
+
+ === Returns ===
+ The newly-created SSSDDomain object.
+
+ === Errors ===
+ TypeError:
+ apischema was not an SSSDConfigSchema object or domainname was not
+ a string
+ """
+ SSSDConfigObject.__init__(self)
+
+ if not isinstance(apischema, SSSDConfigSchema) or type(domainname) != str:
+ raise TypeError
+
+ self.name = domainname
+ self.schema = apischema
+ self.active = False
+ self.oldname = None
+ self.providers = []
+
+ # Set up the domain object with any known defaults
+ self.options = {}
+
+ # Set up default options for all domains
+ self.options.update(self.schema.get_defaults('provider'))
+ self.options.update(self.schema.get_defaults('domain'))
+
+ def set_active(self, active):
+ """
+ Enable or disable this domain
+
+ active:
+ Boolean value. If True, this domain will be added to the active
+ domains list when it is saved. If False, it will be removed from the
+ active domains list when it is saved.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ No errors
+ """
+ self.active = bool(active)
+
+ def list_options_with_mandatory(self):
+ """
+ List options for the currently-configured providers, including the
+ mandatory flag
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), whether it is mandatory, the
+ translated option description, and the default value (or 'None') as
+ the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, False, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = {}
+ # Get the list of available options for all domains
+ options.update(self.schema.get_options('provider'))
+
+ options.update(self.schema.get_options('domain'))
+
+ # Candidate for future optimization: will update primary type
+ # for each subtype
+ for (provider, providertype) in self.providers:
+ schema_options = self.schema.get_options('provider/%s'
+ % provider)
+ options.update(schema_options)
+ schema_options = self.schema.get_options('provider/%s/%s'
+ % (provider, providertype))
+ options.update(schema_options)
+ return options
+
+ def list_options(self):
+ """
+ List options available for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_mandatory_options(self):
+ """
+ List mandatory options for the currently-configured providers.
+
+ === Returns ===
+ A dictionary of configurable options. This dictionary is keyed on the
+ option name with a tuple of the variable type, subtype ('None' if the
+ type is not a collection type), the translated option description, and
+ the default value (or 'None') as the value.
+
+ Example:
+ { 'enumerate' :
+ (bool, None, u'Enable enumerating all users/groups', True) }
+
+ === Errors ===
+ No errors
+ """
+ options = self.list_options_with_mandatory()
+
+ # Filter out the mandatory field to maintain compatibility
+ # with older versions of the API
+ filtered_options = {}
+ for key in options.keys():
+ if options[key][2]:
+ filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4])
+
+ return filtered_options
+
+ def list_provider_options(self, provider, provider_type=None):
+ """
+ If provider_type is specified, list all options applicable to that
+ target, otherwise list all possible options available for a provider.
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, access, chpass)
+
+ === Returns ===
+
+ A dictionary of configurable options for the specified provider type.
+ This dictionary is keyed on the option name with a tuple of the
+ variable type, subtype ('None' if the type is not a collection type),
+ the translated option description, and the default value (or 'None')
+ as the value.
+
+ === Errors ===
+
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ #TODO section checking
+
+ options = self.schema.get_options('provider/%s' % provider)
+ if(provider_type):
+ options.update(self.schema.get_options('provider/%s/%s' %
+ (provider, provider_type)))
+ else:
+ # Add options from all provider subtypes
+ known_providers = self.list_providers()
+ for provider_type in known_providers[provider]:
+ options.update(self.list_provider_options(provider,
+ provider_type))
+ return options
+
+ def list_providers(self):
+ """
+ Return a dictionary of providers.
+
+ === Returns ===
+ Returns a dictionary of providers, keyed on the primary type, with the
+ value being a tuple of the subtypes it supports.
+
+ Example:
+ { 'ldap' : ('id', 'auth', 'chpass') }
+
+ === Errors ===
+ No Errors
+ """
+ return self.schema.get_providers()
+
+ def set_option(self, option, value):
+ """
+ Set a domain option to the specified value (or values)
+
+ option:
+ The option to change.
+ value:
+ The value to set. This may be a single value or a list of values.
+ If it is set to None, it resets the option to its default.
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ NoOptionError:
+ The specified option is not listed in the schema
+ TypeError:
+ The value specified was not of the expected type
+ """
+ options = self.list_options()
+ if (option not in options.keys()):
+ raise NoOptionError('Section [%s] has no option [%s]' %
+ (self.name, option))
+
+ if value == None:
+ self.remove_option(option)
+ return
+
+ option_schema = options[option]
+ raise_error = False
+
+ # If we were expecting a list and didn't get one,
+ # Create a list with a single entry. If it's the
+ # wrong subtype, it will fail below
+ if option_schema[0] == list and type(value) != list:
+ if type(value) == str:
+ value = striplist(value.split(','))
+ else:
+ value = [value]
+
+ if type(value) != option_schema[0]:
+ # If it's possible to convert it, do so
+ try:
+ if option_schema[0] == bool and \
+ type(value) == str:
+ value = self.schema.bool_lookup[value.lower()]
+ else:
+ value = option_schema[0](value)
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s for %s, received %s' %
+ (option_schema[0], option, type(value)))
+
+ if type(value) == list:
+ # Iterate through the list an ensure that all members
+ # are of the appropriate subtype
+ try:
+ newvalue = []
+ for x in value:
+ if option_schema[1] == bool and \
+ type(x) == str:
+ newvalue.extend([self.schema.bool_lookup[x.lower()]])
+ else:
+ newvalue.extend([option_schema[1](x)])
+ except ValueError:
+ raise_error = True
+ except KeyError:
+ raise_error = True
+
+ if raise_error:
+ raise TypeError('Expected %s' % option_schema[1])
+ value = newvalue
+
+ # Check whether we're adding a provider entry.
+ is_provider = option.rfind('_provider')
+ if (is_provider > 0):
+ provider = option[:is_provider]
+ self.add_provider(value, provider)
+ else:
+ self.options[option] = value
+
+ def set_name(self, newname):
+ """
+ Change the name of the domain
+
+ newname:
+ New name for this domain
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ TypeError:
+ newname was not a string
+ """
+
+ if type(newname) != str:
+ raise TypeError
+
+ if not self.oldname:
+ # Only set the oldname once
+ self.oldname = self.name
+ self.name = newname
+
+ def add_provider(self, provider, provider_type):
+ """
+ Add a new provider type to the domain
+
+ type:
+ Provider backend type. (e.g. local, ldap, krb5, etc.)
+ subtype:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ ProviderSubtypeInUse:
+ Another backend is already providing this subtype
+ NoSuchProviderError:
+ The specified provider is not listed in the schema or plugins
+ NoSuchProviderSubtypeError:
+ The specified provider subtype is not listed in the schema
+ """
+ # Check that provider and provider_type are valid
+ configured_providers = self.list_providers()
+ if provider in configured_providers.keys():
+ if provider_type not in configured_providers[provider]:
+ raise NoSuchProviderSubtypeError(provider_type)
+ else:
+ raise NoSuchProviderError
+
+ # Don't add a provider twice
+ with_this_type = [x for x in self.providers if x[1] == provider_type]
+ if len(with_this_type) > 1:
+ # This should never happen!
+ raise ProviderSubtypeInUse
+ if len(with_this_type) == 1:
+ if with_this_type[0][0] != provider:
+ raise ProviderSubtypeInUse(with_this_type[0][0])
+ else:
+ self.providers.extend([(provider, provider_type)])
+
+ option_name = '%s_provider' % provider_type
+ self.options[option_name] = provider
+
+ # Add defaults for this provider
+ self.options.update(self.schema.get_defaults('provider/%s' %
+ provider))
+ self.options.update(self.schema.get_defaults('provider/%s/%s' %
+ (provider,
+ provider_type)))
+
+ def remove_provider(self, provider_type):
+ """
+ Remove a provider from the domain. If the provider is not present, it
+ is ignored.
+
+ provider_type:
+ Subtype of the backend type. (e.g. id, auth, chpass)
+
+ === Returns ===
+ No return value.
+
+ === Errors ===
+ No Errors
+ """
+
+ provider = None
+ for (provider, ptype) in self.providers:
+ if ptype == provider_type:
+ break
+ provider = None
+
+ # Check whether the provider_type was found
+ if not provider:
+ return
+
+ # Remove any unused options when removing the provider.
+ options = self.list_provider_options(provider, provider_type)
+
+ # Trim any options that are used by other providers,
+ # if that provider is in use
+ for (prov, ptype) in self.providers:
+ # Ignore the one being removed
+ if (prov, ptype) == (provider, provider_type):
+ continue
+
+ provider_options = self.list_provider_options(prov, ptype)
+ overlap = options_overlap(options.keys(), provider_options.keys())
+ for opt in overlap:
+ del options[opt]
+
+ # We should now have a list of options used only by this
+ # provider. So we remove them.
+ for option in options:
+ if self.options.has_key(option):
+ del self.options[option]
+
+ self.providers.remove((provider, provider_type))
+
+class SSSDConfig(SSSDChangeConf):
+ """
+ class SSSDConfig
+ Primary class for operating on SSSD configurations
+ """
+ def __init__(self, schemafile=None, schemaplugindir=None):
+ """
+ Initialize the SSSD config parser/editor. This constructor does not
+ open or create a config file. If the schemafile and schemaplugindir
+ are not passed, it will use the system defaults.
+
+ schemafile:
+ The path to the api schema config file. Usually
+ /etc/sssd/sssd.api.conf
+ schemaplugindir:
+ The path the directory containing the provider schema config files.
+ Usually /etc/sssd/sssd.api.d
+
+ === Returns ===
+ The newly-created SSSDConfig object.
+
+ === Errors ===
+ IOError:
+ Exception raised when the schema file could not be opened for
+ reading.
+ ParsingError:
+ The main schema file or one of those in the plugin directory could
+ not be parsed.
+ """
+ SSSDChangeConf.__init__(self)
+ self.schema = SSSDConfigSchema(schemafile, schemaplugindir)
+ self.configfile = None
+ self.initialized = False
+ self.API_VERSION = 2
+
+ def import_config(self,configfile=None):
+ """
+ Read in a config file, populating all of the service and domain
+ objects with the read values.
+
+ configfile:
+ The path to the SSSD config file. If not specified, use the system
+ default, usually /etc/sssd/sssd.conf
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for reading
+ ParsingError:
+ Exception raised when errors occur attempting to parse a file.
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ if not configfile:
+ #TODO: get this from a global setting
+ configfile = '/etc/sssd/sssd.conf'
+ # open will raise an IOError if it fails
+ fd = open(configfile, 'r')
+
+ try:
+ self.readfp(fd)
+ except:
+ raise ParsingError
+
+ fd.close()
+ self.configfile = configfile
+ self.initialized = True
+
+ try:
+ if int(self.get('sssd', 'config_file_version')) != self.API_VERSION:
+ raise ParsingError("Wrong config_file_version")
+ except:
+ # Either the 'sssd' section or the 'config_file_version' was not
+ # present in the config file
+ raise ParsingError("File contains no config_file_version")
+
+ def new_config(self):
+ """
+ Initialize the SSSDConfig object with the defaults from the schema.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ AlreadyInitializedError:
+ This SSSDConfig object was already initialized by a call to
+ import_config() or new_config()
+ """
+ if self.initialized:
+ raise AlreadyInitializedError
+
+ self.initialized = True
+
+ #Initialize all services
+ for servicename in self.schema.get_services():
+ service = self.new_service(servicename)
+
+ def write(self, outputfile=None):
+ """
+ Write out the configuration to a file.
+
+ outputfile:
+ The path to write the new config file. If it is not specified, it
+ will use the path specified by the import() call.
+ === Returns ===
+ No return value
+
+ === Errors ===
+ IOError:
+ Exception raised when the file could not be opened for writing
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoOutputFileError:
+ No outputfile was specified and this SSSDConfig object was not
+ initialized by import()
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if outputfile == None:
+ if(self.configfile == None):
+ raise NoOutputFileError
+
+ outputfile = self.configfile
+
+ # open() will raise IOError if it fails
+ of = open(outputfile, "wb")
+ output = self.dump(self.opts)
+ of.write(output)
+ of.close()
+
+ def list_services(self):
+ """
+ Retrieve a list of known services.
+
+ === Returns ===
+ The list of known services.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ service_list = [x['name'] for x in self.sections()
+ if not x['name'].startswith('domain') ]
+ return service_list
+
+ def get_service(self, name):
+ """
+ Get an SSSDService object to edit a service.
+
+ name:
+ The name of the service to return.
+
+ === Returns ===
+ An SSSDService instance containing the current state of a service in
+ the SSSDConfig
+
+ === Errors ===
+ NoServiceError:
+ There is no such service with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section(name):
+ raise NoServiceError
+
+ service = SSSDService(name, self.schema)
+ [service.set_option(opt['name'], opt['value'])
+ for opt in self.strip_comments_empty(self.options(name)) ]
+
+ return service
+
+ def new_service(self, name):
+ """
+ Create a new service from the defaults and return the SSSDService
+ object for it. This function will also add this service to the list of
+ active services in the [SSSD] section.
+
+ name:
+ The name of the service to create and return.
+
+ === Returns ===
+ The newly-created SSSDService object
+
+ === Errors ===
+ ServiceNotRecognizedError:
+ There is no such service in the schema.
+ ServiceAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if (self.has_section(name)):
+ raise ServiceAlreadyExists(name)
+
+ service = SSSDService(name, self.schema)
+ self.save_service(service)
+ return service
+
+ def delete_service(self, name):
+ """
+ Remove a service from the SSSDConfig object. This function will also
+ remove this service from the list of active services in the [SSSD]
+ section. Has no effect if the service does not exist.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ self.delete_option('section', name)
+
+ def save_service(self, service):
+ """
+ Save the changes made to the service object back to the SSSDConfig
+ object.
+
+ service_object:
+ The SSSDService object to save to the configuration.
+
+ === Returns ===
+ No return value
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ service_object was not of the type SSSDService
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(service, SSSDService):
+ raise TypeError
+
+ name = service.get_name()
+ # Ensure that the existing section is removed
+ # This way we ensure that we are getting a
+ # complete copy of the service.
+ # delete_option() is a noop if the section
+ # does not exist.
+ index = self.delete_option('section', name)
+
+ addkw = []
+ for option,value in service.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ addkw.append( { 'type' : 'option',
+ 'name' : option,
+ 'value' : str(value) } )
+
+ self.add_section(name, addkw, index)
+
+ def list_active_domains(self):
+ """
+ Return a list of all active domains.
+
+ === Returns ===
+ The list of configured, active domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ active_domains = striplist(self.get('sssd', 'domains').split(','))
+ domain_dict = dict.fromkeys(active_domains)
+ if domain_dict.has_key(''):
+ del domain_dict['']
+
+ # Remove any entries in this list that don't
+ # correspond to an active domain, for integrity
+ configured_domains = self.list_domains()
+ for dom in domain_dict.keys():
+ if dom not in configured_domains:
+ del domain_dict[dom]
+
+ active_domains = domain_dict.keys()
+ else:
+ active_domains = []
+
+ return active_domains
+
+ def list_inactive_domains(self):
+ """
+ Return a list of all configured, but disabled domains.
+
+ === Returns ===
+ The list of configured, inactive domains.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ if (self.has_option('sssd', 'domains')):
+ active_domains = striplist(self.get('sssd', 'domains').split(','))
+ else:
+ active_domains = []
+
+ domains = [x for x in self.list_domains()
+ if x not in active_domains]
+ return domains
+
+ def list_domains(self):
+ """
+ Return a list of all configured domains, including inactive domains.
+
+ === Returns ===
+ The list of configured domains, both active and inactive.
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ domains = [x['name'][7:] for x in self.sections() if x['name'].startswith('domain/')]
+ return domains
+
+ def get_domain(self, name):
+ """
+ Get an SSSDDomain object to edit a domain.
+
+ name:
+ The name of the domain to return.
+
+ === Returns ===
+ An SSSDDomain instance containing the current state of a domain in the
+ SSSDConfig
+
+ === Errors ===
+ NoDomainError:
+ There is no such domain with the specified name in the SSSDConfig.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not self.has_section('domain/%s' % name):
+ raise NoDomainError(name)
+
+ domain = SSSDDomain(name, self.schema)
+
+ # Read in the providers first or we may have type
+ # errors trying to read in their options
+ providers = [ (x['name'],x['value']) for x in self.strip_comments_empty(self.options('domain/%s' % name))
+ if x['name'].rfind('_provider') > 0]
+ [domain.set_option(option, value)
+ for (option, value) in providers]
+
+ [domain.set_option(opt['name'], opt['value'])
+ for opt in self.strip_comments_empty(self.options('domain/%s' % name))
+ if opt not in providers]
+
+ # Determine if this domain is currently active
+ domain.active = self.is_domain_active(name)
+
+ return domain
+
+ def new_domain(self, name):
+ """
+ Create a new, empty domain and return the SSSDDomain object for it.
+
+ name:
+ The name of the domain to create and return.
+
+ === Returns ===
+ The newly-created SSSDDomain object
+
+ === Errors ===
+ DomainAlreadyExistsError:
+ The service being created already exists in the SSSDConfig object.
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if self.has_section('domain/%s' % name):
+ raise DomainAlreadyExistsError
+
+ domain = SSSDDomain(name, self.schema)
+ self.save_domain(domain)
+ return domain
+
+ def is_domain_active(self, name):
+ """
+ Is a particular domain set active
+
+ name:
+ The name of the configured domain to check
+
+ === Returns ===
+ True if the domain is active, False if it is inactive
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ return name in self.list_active_domains()
+
+ def activate_domain(self, name):
+ """
+ Activate a configured domain
+
+ name:
+ The name of the configured domain to activate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', name)
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to add a new value
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if domain_dict.has_key(''):
+ del domain_dict['']
+
+ # Add a new key for the domain being activated
+ domain_dict[name] = None
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def deactivate_domain(self, name):
+ """
+ Deactivate a configured domain
+
+ name:
+ The name of the configured domain to deactivate
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ NoDomainError:
+ No domain by this name is configured
+ """
+
+ if not self.initialized:
+ raise NotInitializedError
+
+ if name not in self.list_domains():
+ raise NoDomainError
+ item = self.get_option_index('sssd', 'domains')[1]
+ if not item:
+ self.set('sssd','domains', '')
+ return
+
+ # Turn the items into a set of dictionary keys
+ # This guarantees uniqueness and makes it easy
+ # to remove the one unwanted value.
+ domain_dict = dict.fromkeys(striplist(item['value'].split(',')))
+ if domain_dict.has_key(''):
+ del domain_dict['']
+
+ # Remove the unwanted domain from the lest
+ if domain_dict.has_key(name):
+ del domain_dict[name]
+
+ # Write out the joined keys
+ self.set('sssd','domains', ", ".join(domain_dict.keys()))
+
+ def delete_domain(self, name):
+ """
+ Remove a domain from the SSSDConfig object. This function will also
+ remove this domain from the list of active domains in the [SSSD]
+ section, if it is there.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ """
+ if not self.initialized:
+ raise NotInitializedError
+
+ # Remove the domain from the active domains list if applicable
+ self.deactivate_domain(name)
+ self.delete_option('section', 'domain/%s' % name)
+
+ def save_domain(self, domain):
+ """
+ Save the changes made to the domain object back to the SSSDConfig
+ object. If this domain is marked active, ensure it is present in the
+ active domain list in the [SSSD] section
+
+ domain_object:
+ The SSSDDomain object to save to the configuration.
+
+ === Returns ===
+ No return value
+
+ === Errors ===
+ NotInitializedError:
+ This SSSDConfig object has not had import_config() or new_config()
+ run on it yet.
+ TypeError:
+ domain_object was not of type SSSDDomain
+ """
+ if not self.initialized:
+ raise NotInitializedError
+ if not isinstance(domain, SSSDDomain):
+ raise TypeError
+
+ name = domain.get_name()
+
+ oldindex = None
+ if domain.oldname and domain.oldname != name:
+ # We are renaming this domain
+ # Remove the old section
+
+ self.deactivate_domain(domain.oldname)
+ oldindex = self.delete_option('section', 'domain/%s' %
+ domain.oldname)
+
+ # Reset the oldname, in case we're not done with
+ # this domain object.
+ domain.oldname = None;
+
+ sectionname = 'domain/%s' % name
+ # Ensure that the existing section is removed
+ # This way we ensure that we are getting a
+ # complete copy of the service.
+ # delete_option() is a noop if the section
+ # does not exist.
+ index = self.delete_option('section', sectionname)
+ addkw = []
+ for option,value in domain.get_all_options().items():
+ if (type(value) == list):
+ value = ', '.join(value)
+ addkw.append( { 'type' : 'option',
+ 'name' : option,
+ 'value' : str(value) } )
+ if oldindex:
+ self.add_section(sectionname, addkw, oldindex)
+ else:
+ self.add_section(sectionname, addkw, index)
+
+ if domain.active:
+ self.activate_domain(name)
+ else:
+ self.deactivate_domain(name)
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
new file mode 100755
index 00000000..153146f8
--- /dev/null
+++ b/src/config/SSSDConfigTest.py
@@ -0,0 +1,1521 @@
+#!/usr/bin/python
+'''
+Created on Sep 18, 2009
+
+@author: sgallagh
+'''
+import unittest
+
+import SSSDConfig
+
+class SSSDConfigTestValid(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Validate services
+ services = sssdconfig.list_services()
+ self.assertTrue('sssd' in services)
+ self.assertTrue('nss' in services)
+ self.assertTrue('pam' in services)
+ self.assertTrue('dp' in services)
+
+ #Verify service attributes
+ sssd_service = sssdconfig.get_service('sssd')
+ service_opts = sssd_service.list_options()
+
+
+ self.assertTrue('services' in service_opts.keys())
+ service_list = sssd_service.get_option('services')
+ self.assertTrue('nss' in service_list)
+ self.assertTrue('pam' in service_list)
+
+ self.assertTrue('domains' in service_opts)
+
+ self.assertTrue('reconnection_retries' in service_opts)
+
+ del sssdconfig
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.new_config()
+ sssdconfig.delete_service('sssd')
+ new_sssd_service = sssdconfig.new_service('sssd');
+ new_options = new_sssd_service.list_options();
+
+ self.assertTrue('debug_level' in new_options)
+ self.assertEquals(new_options['debug_level'][0], int)
+
+ self.assertTrue('command' in new_options)
+ self.assertEquals(new_options['command'][0], str)
+
+ self.assertTrue('reconnection_retries' in new_options)
+ self.assertEquals(new_options['reconnection_retries'][0], int)
+
+ self.assertTrue('services' in new_options)
+ self.assertEquals(new_options['debug_level'][0], int)
+
+ self.assertTrue('domains' in new_options)
+ self.assertEquals(new_options['domains'][0], list)
+ self.assertEquals(new_options['domains'][1], str)
+
+ self.assertTrue('sbus_timeout' in new_options)
+ self.assertEquals(new_options['sbus_timeout'][0], int)
+
+ self.assertTrue('re_expression' in new_options)
+ self.assertEquals(new_options['re_expression'][0], str)
+
+ self.assertTrue('full_name_format' in new_options)
+ self.assertEquals(new_options['full_name_format'][0], str)
+
+ del sssdconfig
+
+ def testDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ #Validate domain list
+ domains = sssdconfig.list_domains()
+ self.assertTrue('LOCAL' in domains)
+ self.assertTrue('LDAP' in domains)
+ self.assertTrue('PROXY' in domains)
+ self.assertTrue('IPA' in domains)
+
+ #Verify domain attributes
+ ipa_domain = sssdconfig.get_domain('IPA')
+ domain_opts = ipa_domain.list_options()
+ self.assertTrue('debug_level' in domain_opts.keys())
+ self.assertTrue('id_provider' in domain_opts.keys())
+ self.assertTrue('auth_provider' in domain_opts.keys())
+
+ del sssdconfig
+
+ def testListProviders(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ sssdconfig.new_config()
+ junk_domain = sssdconfig.new_domain('junk')
+ providers = junk_domain.list_providers()
+ self.assertTrue('ldap' in providers.keys())
+
+ def testCreateNewLocalConfig(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ sssdconfig.new_config()
+
+ local_domain = sssdconfig.new_domain('LOCAL')
+ local_domain.add_provider('local', 'id')
+ local_domain.set_option('debug_level', 1)
+ local_domain.set_option('default_shell', '/bin/tcsh')
+ local_domain.set_active(True)
+ sssdconfig.save_domain(local_domain)
+
+ sssdconfig.write('/tmp/testCreateNewLocalConfig.conf')
+
+ def testCreateNewLDAPConfig(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ sssdconfig.new_config()
+
+ ldap_domain = sssdconfig.new_domain('LDAP')
+ ldap_domain.add_provider('ldap', 'id')
+ ldap_domain.set_option('debug_level', 1)
+ ldap_domain.set_active(True)
+ sssdconfig.save_domain(ldap_domain)
+
+ sssdconfig.write('/tmp/testCreateNewLDAPConfig.conf')
+
+ def testModifyExistingConfig(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ ldap_domain = sssdconfig.get_domain('LDAP')
+ ldap_domain.set_option('debug_level', 3)
+
+ ldap_domain.remove_provider('auth')
+ ldap_domain.add_provider('krb5', 'auth')
+ ldap_domain.set_active(True)
+ sssdconfig.save_domain(ldap_domain)
+
+ sssdconfig.write('/tmp/testModifyExistingConfig.conf')
+
+ def testSpaces(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+ ldap_domain = sssdconfig.get_domain('LDAP')
+ self.assertEqual(ldap_domain.get_option('auth_provider'), 'ldap')
+ self.assertEqual(ldap_domain.get_option('id_provider'), 'ldap')
+
+class SSSDConfigTestInvalid(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testBadBool(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-invalid-badbool.conf")
+ self.assertRaises(TypeError,
+ sssdconfig.get_domain,'IPA')
+
+class SSSDConfigTestSSSDService(unittest.TestCase):
+ def setUp(self):
+ self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ def tearDown(self):
+ pass
+
+ def testInit(self):
+ # Positive test
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Type Error test
+ # Name is not a string
+ self.assertRaises(TypeError, SSSDConfig.SSSDService, 3, self.schema)
+
+ # TypeError test
+ # schema is not an SSSDSchema
+ self.assertRaises(TypeError, SSSDConfig.SSSDService, '3', self)
+
+ # ServiceNotRecognizedError test
+ self.assertRaises(SSSDConfig.ServiceNotRecognizedError,
+ SSSDConfig.SSSDService, 'ssd', self.schema)
+
+ def testListOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ options = service.list_options()
+ control_list = [
+ 'services',
+ 'domains',
+ 'timeout',
+ 'sbus_timeout',
+ 're_expression',
+ 'full_name_format',
+ 'debug_level',
+ 'debug_timestamps',
+ 'debug_to_files',
+ 'command',
+ 'reconnection_retries']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['reconnection_retries']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['reconnection_retries'][0] == int,
+ "reconnection_retries should require an int. " +
+ "list_options is requiring a %s" %
+ options['reconnection_retries'][0])
+
+ self.assertTrue(options['reconnection_retries'][1] == None,
+ "reconnection_retries should not require a subtype. " +
+ "list_options is requiring a %s" %
+ options['reconnection_retries'][1])
+
+ self.assertTrue(options['reconnection_retries'][3] == None,
+ "reconnection_retries should have no default")
+
+ self.assertTrue(type(options['services']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['services'][0] == list,
+ "services should require an list. " +
+ "list_options is requiring a %s" %
+ options['services'][0])
+
+ self.assertTrue(options['services'][1] == str,
+ "services should require a subtype of str. " +
+ "list_options is requiring a %s" %
+ options['services'][1])
+
+ def testListMandatoryOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ options = service.list_mandatory_options()
+ control_list = [
+ 'services',
+ 'domains']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['services']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['services'][0] == list,
+ "services should require an list. " +
+ "list_options is requiring a %s" %
+ options['services'][0])
+
+ self.assertTrue(options['services'][1] == str,
+ "services should require a subtype of str. " +
+ "list_options is requiring a %s" %
+ options['services'][1])
+
+ def testSetOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - Exactly right
+ service.set_option('debug_level', 2)
+ self.assertEqual(service.get_option('debug_level'), 2)
+
+ # Positive test - Allow converting "safe" values
+ service.set_option('debug_level', '2')
+ self.assertEqual(service.get_option('debug_level'), 2)
+
+ # Positive test - Remove option if value is None
+ service.set_option('debug_level', None)
+ self.assertTrue('debug_level' not in service.options.keys())
+
+ # Negative test - Nonexistent Option
+ self.assertRaises(SSSDConfig.NoOptionError, service.set_option, 'nosuchoption', 1)
+
+ # Negative test - Incorrect type
+ self.assertRaises(TypeError, service.set_option, 'debug_level', 'two')
+
+ def testGetOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - Single-valued
+ self.assertEqual(service.get_option('config_file_version'), 2)
+
+ # Positive test - List of values
+ self.assertEqual(service.get_option('services'), ['nss', 'pam'])
+
+ # Negative Test - Bad Option
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchoption')
+
+ def testGetAllOptions(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ #Positive test
+ options = service.get_all_options()
+ control_list = [
+ 'config_file_version',
+ 'services']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testRemoveOption(self):
+ service = SSSDConfig.SSSDService('sssd', self.schema)
+
+ # Positive test - Remove an option that exists
+ self.assertEqual(service.get_option('services'), ['nss', 'pam'])
+ service.remove_option('services')
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'debug_level')
+
+ # Positive test - Remove an option that doesn't exist
+ self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchentry')
+ service.remove_option('nosuchentry')
+
+class SSSDConfigTestSSSDDomain(unittest.TestCase):
+ def setUp(self):
+ self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ def tearDown(self):
+ pass
+
+ def testInit(self):
+ # Positive Test
+ domain = SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ # Negative Test - Name not a string
+ self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 2, self.schema)
+
+ # Negative Test - Schema is not an SSSDSchema
+ self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 'mydomain', self)
+
+ def testGetName(self):
+ # Positive Test
+ domain = SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ self.assertEqual(domain.get_name(), 'mydomain')
+
+ def testSetActive(self):
+ #Positive Test
+ domain = SSSDConfig.SSSDDomain('mydomain', self.schema)
+
+ # Should default to inactive
+ self.assertFalse(domain.active)
+ domain.set_active(True)
+ self.assertTrue(domain.active)
+ domain.set_active(False)
+ self.assertFalse(domain.active)
+
+ def testListOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_options()
+ control_list = [
+ 'debug_level',
+ 'debug_timestamps',
+ 'min_id',
+ 'max_id',
+ 'timeout',
+ 'command',
+ 'enumerate',
+ 'cache_credentials',
+ 'store_legacy_passwords',
+ 'use_fully_qualified_names',
+ 'entry_cache_timeout',
+ 'id_provider',
+ 'auth_provider',
+ 'access_provider',
+ 'chpass_provider']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['max_id']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['max_id'][0] == int,
+ "max_id should require an int. " +
+ "list_options is requiring a %s" %
+ options['max_id'][0])
+
+ self.assertTrue(options['max_id'][1] == None,
+ "max_id should not require a subtype. " +
+ "list_options is requiring a %s" %
+ options['max_id'][1])
+
+ # Add a provider and verify that the new options appear
+ domain.add_provider('local', 'id')
+ control_list.extend(
+ ['default_shell',
+ 'base_directory'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(
+ ['krb5_kdcip',
+ 'krb5_realm',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_auth_timeout'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testListMandatoryOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_mandatory_options()
+ control_list = [
+ 'cache_credentials',
+ 'min_id',
+ 'id_provider',
+ 'auth_provider']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add a provider and verify that the new options appear
+ domain.add_provider('local', 'id')
+ control_list.extend(
+ ['default_shell',
+ 'base_directory'])
+
+ options = domain.list_mandatory_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(
+ ['krb5_kdcip',
+ 'krb5_realm'])
+
+ options = domain.list_mandatory_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ options = domain.list_mandatory_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testListProviders(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ control_provider_dict = {
+ 'ipa': ['id', 'auth', 'access', 'chpass'],
+ 'local': ['id', 'auth', 'chpass'],
+ 'ldap': ['id', 'auth', 'chpass'],
+ 'krb5': ['auth', 'chpass'],
+ 'proxy': ['id', 'auth'],
+ 'permit': ['access'],
+ 'deny': ['access']}
+
+ providers = domain.list_providers()
+
+ # Ensure that all of the expected defaults are there
+ for provider in control_provider_dict.keys():
+ for ptype in control_provider_dict[provider]:
+ self.assertTrue(providers.has_key(provider))
+ self.assertTrue(ptype in providers[provider])
+
+ for provider in providers.keys():
+ for ptype in providers[provider]:
+ self.assertTrue(control_provider_dict.has_key(provider))
+ self.assertTrue(ptype in control_provider_dict[provider])
+
+ def testListProviderOptions(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Test looking up a specific provider type
+ options = domain.list_provider_options('krb5', 'auth')
+ control_list = [
+ 'krb5_kdcip',
+ 'krb5_realm',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_auth_timeout']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ #Test looking up all provider values
+ options = domain.list_provider_options('krb5')
+ control_list.extend(['krb5_changepw_principal'])
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ def testAddProvider(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive Test
+ domain.add_provider('local', 'id')
+
+ # Negative Test - No such backend type
+ self.assertRaises(SSSDConfig.NoSuchProviderError,
+ domain.add_provider, 'nosuchbackend', 'auth')
+
+ # Negative Test - No such backend subtype
+ self.assertRaises(SSSDConfig.NoSuchProviderSubtypeError,
+ domain.add_provider, 'ldap', 'nosuchsubtype')
+
+ # Negative Test - Try to add a second provider of the same type
+ self.assertRaises(SSSDConfig.ProviderSubtypeInUse,
+ domain.add_provider, 'ldap', 'id')
+
+ def testRemoveProvider(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # First test default options
+ options = domain.list_options()
+ control_list = [
+ 'debug_level',
+ 'debug_timestamps',
+ 'min_id',
+ 'max_id',
+ 'timeout',
+ 'command',
+ 'enumerate',
+ 'cache_credentials',
+ 'store_legacy_passwords',
+ 'use_fully_qualified_names',
+ 'entry_cache_timeout',
+ 'id_provider',
+ 'auth_provider',
+ 'access_provider',
+ 'chpass_provider']
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ self.assertTrue(type(options['max_id']) == tuple,
+ "Option values should be a tuple")
+
+ self.assertTrue(options['max_id'][0] == int,
+ "config_file_version should require an int. " +
+ "list_options is requiring a %s" %
+ options['max_id'][0])
+
+ self.assertTrue(options['max_id'][1] == None,
+ "config_file_version should not require a subtype. " +
+ "list_options is requiring a %s" %
+ options['max_id'][1])
+
+ # Add a provider and verify that the new options appear
+ domain.add_provider('local', 'id')
+ control_list.extend(
+ ['default_shell',
+ 'base_directory'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Add a provider that has global options and verify that
+ # The new options appear.
+ domain.add_provider('krb5', 'auth')
+
+ backup_list = control_list[:]
+ control_list.extend(
+ ['krb5_kdcip',
+ 'krb5_realm',
+ 'krb5_ccachedir',
+ 'krb5_ccname_template',
+ 'krb5_keytab',
+ 'krb5_validate',
+ 'krb5_auth_timeout'])
+
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in control_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in control_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Remove the local ID provider and add an LDAP one
+ # LDAP ID providers can also use the krb5_realm
+ domain.remove_provider('id')
+
+ domain.add_provider('ldap', 'id')
+
+ # Set the krb5_realm option and the ldap_uri option
+ domain.set_option('krb5_realm', 'EXAMPLE.COM')
+ domain.set_option('ldap_uri', 'ldap://ldap.example.com')
+
+ self.assertEquals(domain.get_option('krb5_realm'),
+ 'EXAMPLE.COM')
+ self.assertEquals(domain.get_option('ldap_uri'),
+ 'ldap://ldap.example.com')
+
+ # Remove the LDAP provider and verify that krb5_realm remains
+ domain.remove_provider('id')
+ self.assertEquals(domain.get_option('krb5_realm'),
+ 'EXAMPLE.COM')
+ self.assertFalse(domain.options.has_key('ldap_uri'))
+
+ # Put the LOCAL provider back
+ domain.add_provider('local', 'id')
+
+ # Remove the auth domain and verify that the options
+ # revert to the backup_list
+ domain.remove_provider('auth')
+ options = domain.list_options()
+
+ self.assertTrue(type(options) == dict,
+ "Options should be a dictionary")
+
+ # Ensure that all of the expected defaults are there
+ for option in backup_list:
+ self.assertTrue(option in options.keys(),
+ "Option [%s] missing" %
+ option)
+
+ # Ensure that there aren't any unexpected options listed
+ for option in options.keys():
+ self.assertTrue(option in backup_list,
+ 'Option [%s] unexpectedly found' %
+ option)
+
+ # Ensure that the krb5_realm option is now gone
+ self.assertFalse(domain.options.has_key('krb5_realm'))
+
+ # Test removing nonexistent provider - Real
+ domain.remove_provider('id')
+
+ # Test removing nonexistent provider - Bad backend type
+ # Should pass without complaint
+ domain.remove_provider('id')
+
+ # Test removing nonexistent provider - Bad provider type
+ # Should pass without complaint
+ domain.remove_provider('nosuchprovider')
+
+ def testGetOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive Test - Ensure that we can get a valid option
+ self.assertEqual(domain.get_option('debug_level'), 0)
+
+ # Negative Test - Try to get valid option that is not set
+ self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'max_id')
+
+ # Positive Test - Set the above option and get it
+ domain.set_option('max_id', 10000)
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ # Negative Test - Try yo get invalid option
+ self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'nosuchoption')
+
+ def testSetOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive Test
+ domain.set_option('max_id', 10000)
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ # Positive Test - Remove option if value is None
+ domain.set_option('max_id', None)
+ self.assertTrue('max_id' not in domain.get_all_options().keys())
+
+ # Negative Test - invalid option
+ self.assertRaises(SSSDConfig.NoOptionError, domain.set_option, 'nosuchoption', 1)
+
+ # Negative Test - incorrect type
+ self.assertRaises(TypeError, domain.set_option, 'max_id', 'a string')
+
+ # Positive Test - Coax options to appropriate type
+ domain.set_option('max_id', '10000')
+ self.assertEqual(domain.get_option('max_id'), 10000)
+
+ domain.set_option('max_id', 30.2)
+ self.assertEqual(domain.get_option('max_id'), 30)
+
+ def testRemoveOption(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive test - Remove existing option
+ self.assertTrue('min_id' in domain.get_all_options().keys())
+ domain.remove_option('min_id')
+ self.assertFalse('min_id' in domain.get_all_options().keys())
+
+ # Positive test - Remove unset but valid option
+ self.assertFalse('max_id' in domain.get_all_options().keys())
+ domain.remove_option('max_id')
+ self.assertFalse('max_id' in domain.get_all_options().keys())
+
+ # Positive test - Remove unset and unknown option
+ self.assertFalse('nosuchoption' in domain.get_all_options().keys())
+ domain.remove_option('nosuchoption')
+ self.assertFalse('nosuchoption' in domain.get_all_options().keys())
+
+ def testSetName(self):
+ domain = SSSDConfig.SSSDDomain('sssd', self.schema)
+
+ # Positive test - Change the name once
+ domain.set_name('sssd2');
+ self.assertEqual(domain.get_name(), 'sssd2')
+ self.assertEqual(domain.oldname, 'sssd')
+
+ # Positive test - Change the name a second time
+ domain.set_name('sssd3')
+ self.assertEqual(domain.get_name(), 'sssd3')
+ self.assertEqual(domain.oldname, 'sssd')
+
+ # Negative test - try setting the name to a non-string
+ self.assertRaises(TypeError,
+ domain.set_name, 4)
+
+class SSSDConfigTestSSSDConfig(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testInit(self):
+ # Positive test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - No Such File
+ self.assertRaises(IOError,
+ SSSDConfig.SSSDConfig, "nosuchfile.api.conf", srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Schema is not parsable
+ self.assertRaises(SSSDConfig.ParsingError,
+ SSSDConfig.SSSDConfig, srcdir + "/testconfigs/noparse.api.conf", srcdir + "/etc/sssd.api.d")
+
+ def testImportConfig(self):
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Verify that all sections were imported
+ control_list = [
+ 'sssd',
+ 'nss',
+ 'pam',
+ 'dp',
+ 'domain/PROXY',
+ 'domain/IPA',
+ 'domain/LOCAL',
+ 'domain/LDAP',
+ ]
+
+ for section in control_list:
+ self.assertTrue(sssdconfig.has_section(section),
+ "Section [%s] missing" %
+ section)
+ for section in sssdconfig.sections():
+ self.assertTrue(section['name'] in control_list)
+
+ # Verify that all options were imported for a section
+ control_list = [
+ 'services',
+ 'reconnection_retries',
+ 'domains',
+ 'debug_timestamps',
+ 'config_file_version']
+
+ for option in control_list:
+ self.assertTrue(sssdconfig.has_option('sssd', option),
+ "Option [%s] missing from [sssd]" %
+ option)
+ for option in sssdconfig.options('sssd'):
+ if option['type'] in ('empty', 'comment'):
+ continue
+ self.assertTrue(option['name'] in control_list,
+ "Option [%s] unexpectedly found" %
+ option)
+
+ #TODO: Check the types and values of the settings
+
+ # Negative Test - Missing config file
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(IOError, sssdconfig.import_config, "nosuchfile.conf")
+
+ # Negative Test - Invalid config file
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-invalid.conf")
+
+ # Negative Test - Invalid config file version
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-badversion.conf")
+
+ # Negative Test - No config file version
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-noversion.conf")
+
+ # Negative Test - Already initialized
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+ self.assertRaises(SSSDConfig.AlreadyInitializedError,
+ sssdconfig.import_config, srcdir + "/testconfigs/sssd-valid.conf")
+
+ def testNewConfig(self):
+ # Positive Test
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ sssdconfig.new_config()
+
+ # Check that the defaults were set
+ control_list = [
+ 'sssd',
+ 'nss',
+ 'pam']
+ for section in control_list:
+ self.assertTrue(sssdconfig.has_section(section),
+ "Section [%s] missing" %
+ section)
+ for section in sssdconfig.sections():
+ self.assertTrue(section['name'] in control_list)
+
+ control_list = [
+ 'config_file_version',
+ 'services']
+ for option in control_list:
+ self.assertTrue(sssdconfig.has_option('sssd', option),
+ "Option [%s] missing from [sssd]" %
+ option)
+ for option in sssdconfig.options('sssd'):
+ if option['type'] in ('empty', 'comment'):
+ continue
+ self.assertTrue(option['name'] in control_list,
+ "Option [%s] unexpectedly found" %
+ option)
+
+ # Negative Test - Already Initialized
+ self.assertRaises(SSSDConfig.AlreadyInitializedError, sssdconfig.new_config)
+
+ def testWrite(self):
+ #TODO Write tests to compare output files
+ pass
+
+ def testListServices(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - sssdconfig not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_services)
+
+ sssdconfig.new_config()
+
+ control_list = [
+ 'sssd',
+ 'pam',
+ 'nss']
+ service_list = sssdconfig.list_services()
+ for service in control_list:
+ self.assertTrue(service in service_list,
+ "Service [%s] missing" %
+ service)
+ for service in service_list:
+ self.assertTrue(service in control_list,
+ "Service [%s] unexpectedly found" %
+ service)
+
+ def testGetService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_service, 'sssd')
+
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ service = sssdconfig.get_service('sssd')
+ self.assertTrue(isinstance(service, SSSDConfig.SSSDService))
+
+ # Verify the contents of this service
+ self.assertEqual(type(service.get_option('debug_timestamps')), bool)
+ self.assertFalse(service.get_option('debug_timestamps'))
+
+ # Negative Test - No such service
+ self.assertRaises(SSSDConfig.NoServiceError, sssdconfig.get_service, 'nosuchservice')
+
+ def testNewService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_service, 'sssd')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ # First need to remove the existing service
+ sssdconfig.delete_service('sssd')
+ service = sssdconfig.new_service('sssd')
+ self.failUnless(service.get_name() in sssdconfig.list_services())
+
+ # TODO: check that the values of this new service
+ # are set to the defaults from the schema
+
+ def testDeleteService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_service, 'sssd')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ service = sssdconfig.delete_service('sssd')
+
+ def testSaveService(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ new_service = SSSDConfig.SSSDService('sssd', sssdconfig.schema)
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_service, new_service)
+
+ # Positive Test
+ sssdconfig.new_config()
+ sssdconfig.save_service(new_service)
+
+ # TODO: check that all entries were saved correctly (change a few)
+
+ # Negative Test - Type Error
+ self.assertRaises(TypeError, sssdconfig.save_service, self)
+
+ def testListActiveDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_active_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'IPA',
+ 'LOCAL']
+ active_domains = sssdconfig.list_active_domains()
+
+ for domain in control_list:
+ self.assertTrue(domain in active_domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in active_domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testListInactiveDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_inactive_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'PROXY',
+ 'LDAP']
+ inactive_domains = sssdconfig.list_inactive_domains()
+
+ for domain in control_list:
+ self.assertTrue(domain in inactive_domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in inactive_domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testListDomains(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not Initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_domains)
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ control_list = [
+ 'IPA',
+ 'LOCAL',
+ 'PROXY',
+ 'LDAP']
+ domains = sssdconfig.list_domains()
+
+ for domain in control_list:
+ self.assertTrue(domain in domains,
+ "Domain [%s] missing" %
+ domain)
+ for domain in domains:
+ self.assertTrue(domain in control_list,
+ "Domain [%s] unexpectedly found" %
+ domain)
+
+ def testGetDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_domain, 'sssd')
+
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ domain = sssdconfig.get_domain('IPA')
+ self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain))
+ self.assertTrue(domain.active)
+
+ # TODO verify the contents of this domain
+
+ # Negative Test - No such domain
+ self.assertRaises(SSSDConfig.NoDomainError, sssdconfig.get_domain, 'nosuchdomain')
+
+ def testNewDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_domain, 'example.com')
+
+ sssdconfig.new_config()
+
+ # Positive Test
+ domain = sssdconfig.new_domain('example.com')
+ self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain))
+ self.failUnless(domain.get_name() in sssdconfig.list_domains())
+ self.failUnless(domain.get_name() in sssdconfig.list_inactive_domains())
+
+ # TODO: check that the values of this new domain
+ # are set to the defaults from the schema
+
+ def testDeleteDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_domain, 'IPA')
+
+ # Positive Test
+ sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf')
+
+ self.assertTrue('IPA' in sssdconfig.list_domains())
+ self.assertTrue('IPA' in sssdconfig.list_active_domains())
+ self.assertTrue(sssdconfig.has_section('domain/IPA'))
+ sssdconfig.delete_domain('IPA')
+ self.assertFalse('IPA' in sssdconfig.list_domains())
+ self.assertFalse('IPA' in sssdconfig.list_active_domains())
+ self.assertFalse(sssdconfig.has_section('domain/IPA'))
+
+ def testSaveDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+ # Negative Test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_domain, 'IPA')
+
+ # Positive Test
+ sssdconfig.new_config()
+ domain = sssdconfig.new_domain('example.com')
+ domain.add_provider('ldap', 'id')
+ domain.set_option('ldap_uri', 'ldap://ldap.example.com')
+ domain.set_active(True)
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com' in sssdconfig.list_domains())
+ self.assertTrue('example.com' in sssdconfig.list_active_domains())
+ self.assertEqual(sssdconfig.get('domain/example.com', 'ldap_uri'),
+ 'ldap://ldap.example.com')
+
+ # Negative Test - Type Error
+ self.assertRaises(TypeError, sssdconfig.save_domain, self)
+
+ # Positive test - Change the domain name and save it
+ domain.set_name('example.com2')
+ self.assertEqual(domain.name,'example.com2')
+ self.assertEqual(domain.oldname,'example.com')
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com2' in sssdconfig.list_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue(sssdconfig.has_section('domain/example.com2'))
+ self.assertEqual(sssdconfig.get('domain/example.com2',
+ 'ldap_uri'),
+ 'ldap://ldap.example.com')
+ self.assertFalse('example.com' in sssdconfig.list_domains())
+ self.assertFalse('example.com' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com' in sssdconfig.list_inactive_domains())
+ self.assertFalse(sssdconfig.has_section('domain/example.com'))
+ self.assertEquals(domain.oldname, None)
+
+ # Positive test - Set the domain inactive and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ domain.set_active(False)
+ sssdconfig.save_domain(domain)
+
+ self.assertFalse('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEquals(len(sssdconfig.list_active_domains()),
+ len(activelist)-1)
+ self.assertEquals(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist)+1)
+
+ # Positive test - Set the domain active and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+ domain.set_active(True)
+ sssdconfig.save_domain(domain)
+
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEquals(len(sssdconfig.list_active_domains()),
+ len(activelist)+1)
+ self.assertEquals(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist)-1)
+
+ # Positive test - Set the domain inactive and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ sssdconfig.deactivate_domain(domain.get_name())
+
+ self.assertFalse('example.com2' in sssdconfig.list_active_domains())
+ self.assertTrue('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEquals(len(sssdconfig.list_active_domains()),
+ len(activelist)-1)
+ self.assertEquals(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist)+1)
+
+ # Positive test - Set the domain active and save it
+ activelist = sssdconfig.list_active_domains()
+ inactivelist = sssdconfig.list_inactive_domains()
+
+ sssdconfig.activate_domain(domain.get_name())
+
+ self.assertTrue('example.com2' in sssdconfig.list_active_domains())
+ self.assertFalse('example.com2' in sssdconfig.list_inactive_domains())
+
+ self.assertEquals(len(sssdconfig.list_active_domains()),
+ len(activelist)+1)
+ self.assertEquals(len(sssdconfig.list_inactive_domains()),
+ len(inactivelist)-1)
+
+ # Positive test - Ensure that saved domains retain values
+ domain.set_option('ldap_krb5_init_creds', True)
+ domain.set_option('ldap_id_use_start_tls', False)
+ domain.set_option('ldap_user_search_base',
+ 'cn=accounts, dc=example, dc=com')
+ self.assertTrue(domain.get_option('ldap_krb5_init_creds'))
+ self.assertFalse(domain.get_option('ldap_id_use_start_tls'))
+ self.assertEqual(domain.get_option('ldap_user_search_base'),
+ 'cn=accounts, dc=example, dc=com')
+
+ sssdconfig.save_domain(domain)
+ sssdconfig.write('/tmp/testSaveDomain.out')
+
+ domain2 = sssdconfig.get_domain('example.com2')
+ self.assertTrue(domain2.get_option('ldap_krb5_init_creds'))
+ self.assertFalse(domain2.get_option('ldap_id_use_start_tls'))
+
+ def testActivateDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ domain_name = 'PROXY'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_domain, domain_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test - Activate an inactive domain
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ sssdconfig.activate_domain('PROXY')
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ # Positive test - Activate an active domain
+ # This should succeed
+ sssdconfig.activate_domain('PROXY')
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ # Negative test - Invalid domain name
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, 'nosuchdomain')
+
+ # Negative test - Invalid domain name type
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, self)
+
+ def testDeactivateDomain(self):
+ sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf",
+ srcdir + "/etc/sssd.api.d")
+
+ domain_name = 'IPA'
+
+ # Negative test - Not initialized
+ self.assertRaises(SSSDConfig.NotInitializedError,
+ sssdconfig.activate_domain, domain_name)
+
+ sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf")
+
+ # Positive test -Deactivate an active domain
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertTrue(domain_name in sssdconfig.list_active_domains())
+ self.assertFalse(domain_name in sssdconfig.list_inactive_domains())
+
+ sssdconfig.deactivate_domain(domain_name)
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ # Positive test - Deactivate an inactive domain
+ # This should succeed
+ sssdconfig.deactivate_domain(domain_name)
+ self.assertTrue(domain_name in sssdconfig.list_domains())
+ self.assertFalse(domain_name in sssdconfig.list_active_domains())
+ self.assertTrue(domain_name in sssdconfig.list_inactive_domains())
+
+ # Negative test - Invalid domain name
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, 'nosuchdomain')
+
+ # Negative test - Invalid domain name type
+ self.assertRaises(SSSDConfig.NoDomainError,
+ sssdconfig.activate_domain, self)
+
+if __name__ == "__main__":
+ error = 0
+
+ import os
+ import sys
+ srcdir = os.getenv('srcdir')
+ if srcdir:
+ srcdir = srcdir + "/config"
+ else:
+ srcdir = "."
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDService)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x1
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDDomain)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x2
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDConfig)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x4
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestValid)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x8
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestInvalid)
+ res = unittest.TextTestRunner().run(suite)
+ if not res.wasSuccessful():
+ error |= 0x10
+
+ sys.exit(error)
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
new file mode 100644
index 00000000..19053538
--- /dev/null
+++ b/src/config/etc/sssd.api.conf
@@ -0,0 +1,66 @@
+# Format:
+# option = type, subtype, mandatory[, default]
+
+[service]
+# Options available to all services
+debug_level = int, None, false
+debug_timestamps = bool, None, false
+debug_to_files = bool, None, false
+command = str, None, false
+reconnection_retries = int, None, false
+
+[sssd]
+# Monitor service
+services = list, str, true, nss, pam
+domains = list, str, true
+timeout = int, None, false
+sbus_timeout = int, None, false
+re_expression = str, None, false
+full_name_format = str, None, false
+
+[nss]
+# Name service
+enum_cache_timeout = int, None, false
+entry_cache_no_wait_percentage = int, None, false
+entry_negative_timeout = int, None, false
+filter_users = list, str, false
+filter_groups = list, str, false
+filter_users_in_groups = bool, None, false
+pwfield = str, None, false
+
+[pam]
+# Authentication service
+offline_credentials_expiration = int, None, false
+offline_failed_login_attempts = int, None, false
+offline_failed_login_delay = int, None, false
+
+[provider]
+#Available provider types
+id_provider = str, None, true
+auth_provider = str, None, true
+access_provider = str, None, false
+chpass_provider = str, None, false
+
+[domain]
+# Options available to all domains
+debug_level = int, None, false, 0
+debug_timestamps = bool, None, false
+command = str, None, false
+min_id = int, None, true, 1000
+max_id = int, None, false
+timeout = int, None, false
+enumerate = bool, None, false
+cache_credentials = bool, None, true, false
+store_legacy_passwords = bool, None, false
+use_fully_qualified_names = bool, None, false
+entry_cache_timeout = int, None, false
+
+# Special providers
+[provider/permit]
+
+[provider/permit/access]
+
+[provider/deny]
+
+[provider/deny/access]
+
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
new file mode 100644
index 00000000..c2a12d5a
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -0,0 +1,77 @@
+[provider/ipa]
+ipa_domain = str, None, true
+ipa_server = str, None, true
+ipa_hostname = str, None, false
+ldap_uri = str, None, false
+ldap_search_base = str, None, false
+ldap_schema = str, None, false
+ldap_default_bind_dn = str, None, false
+ldap_default_authtok_type = str, None, false
+ldap_default_authtok = str, None, false
+ldap_network_timeout = int, None, false
+ldap_opt_timeout = int, None, false
+ldap_offline_timeout = int, None, false
+ldap_tls_cacert = str, None, false
+ldap_tls_reqcert = str, None, false
+ldap_sasl_mech = str, None, false
+ldap_sasl_authid = str, None, false
+krb5_kdcip = str, None, false
+krb5_realm = str, None, false
+krb5_auth_timeout = int, None, false
+ldap_krb5_keytab = str, None, false
+ldap_krb5_init_creds = bool, None, false
+ldap_entry_usn = str, None, false
+ldap_rootdse_last_usn = str, None, false
+ldap_referrals = bool, None, false
+
+[provider/ipa/id]
+ldap_search_timeout = int, None, false
+ldap_enumeration_refresh_timeout = int, None, false
+ldap_purge_cache_timeout = int, None, false
+ldap_id_use_start_tls = bool, None, false
+ldap_user_search_base = str, None, false
+ldap_user_search_scope = str, None, false
+ldap_user_search_filter = str, None, false
+ldap_user_object_class = str, None, false
+ldap_user_name = str, None, false
+ldap_user_uid_number = str, None, false
+ldap_user_gid_number = str, None, false
+ldap_user_gecos = str, None, false
+ldap_user_homedir = str, None, false
+ldap_user_shell = str, None, false
+ldap_user_uuid = str, None, false
+ldap_user_principal = str, None, false
+ldap_user_fullname = str, None, false
+ldap_user_member_of = str, None, false
+ldap_user_modify_timestamp = str, None, false
+ldap_user_shadow_last_change = str, None, false
+ldap_user_shadow_min = str, None, false
+ldap_user_shadow_max = str, None, false
+ldap_user_shadow_warning = str, None, false
+ldap_user_shadow_inactive = str, None, false
+ldap_user_shadow_expire = str, None, false
+ldap_user_shadow_flag = str, None, false
+ldap_user_krb_last_pwd_change = str, None, false
+ldap_user_krb_password_expiration = str, None, false
+ldap_pwd_attribute = str, None, false
+ldap_group_search_base = str, None, false
+ldap_group_search_scope = str, None, false
+ldap_group_search_filter = str, None, false
+ldap_group_object_class = str, None, false
+ldap_group_name = str, None, false
+ldap_group_gid_number = str, None, false
+ldap_group_member = str, None, false
+ldap_group_uuid = str, None, false
+ldap_group_modify_timestamp = str, None, false
+ldap_force_upper_case_realm = bool, None, false
+
+[provider/ipa/auth]
+krb5_ccachedir = str, None, false
+krb5_ccname_template = str, None, false
+krb5_keytab = str, None, false
+krb5_validate = bool, None, false
+
+[provider/ipa/access]
+
+[provider/ipa/chpass]
+krb5_changepw_principal = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-krb5.conf b/src/config/etc/sssd.api.d/sssd-krb5.conf
new file mode 100644
index 00000000..7ba0ab32
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-krb5.conf
@@ -0,0 +1,13 @@
+[provider/krb5]
+krb5_kdcip = str, None, true
+krb5_realm = str, None, true
+krb5_auth_timeout = int, None, false
+
+[provider/krb5/auth]
+krb5_ccachedir = str, None, false
+krb5_ccname_template = str, None, false
+krb5_keytab = str, None, false
+krb5_validate = bool, None, false
+
+[provider/krb5/chpass]
+krb5_changepw_principal = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
new file mode 100644
index 00000000..6758ab49
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -0,0 +1,68 @@
+[provider/ldap]
+ldap_uri = str, None, true
+ldap_search_base = str, None, true
+ldap_schema = str, None, true, rfc2307
+ldap_default_bind_dn = str, None, false
+ldap_default_authtok_type = str, None, false
+ldap_default_authtok = str, None, false
+ldap_network_timeout = int, None, false
+ldap_opt_timeout = int, None, false
+ldap_offline_timeout = int, None, false
+ldap_tls_cacert = str, None, false
+ldap_tls_reqcert = str, None, false
+ldap_sasl_mech = str, None, false
+ldap_sasl_authid = str, None, false
+krb5_kdcip = str, None, false
+krb5_realm = str, None, false
+ldap_krb5_keytab = str, None, false
+ldap_krb5_init_creds = bool, None, false
+ldap_entry_usn = str, None, false
+ldap_rootdse_last_usn = str, None, false
+ldap_referrals = bool, None, false
+
+[provider/ldap/id]
+ldap_search_timeout = int, None, false
+ldap_enumeration_refresh_timeout = int, None, false
+ldap_purge_cache_timeout = int, None, false
+ldap_id_use_start_tls = bool, None, true, false
+ldap_user_search_base = str, None, false
+ldap_user_search_scope = str, None, false
+ldap_user_search_filter = str, None, false
+ldap_user_object_class = str, None, false
+ldap_user_name = str, None, false
+ldap_user_uid_number = str, None, false
+ldap_user_gid_number = str, None, false
+ldap_user_gecos = str, None, false
+ldap_user_homedir = str, None, false
+ldap_user_shell = str, None, false
+ldap_user_uuid = str, None, false
+ldap_user_principal = str, None, false
+ldap_user_fullname = str, None, false
+ldap_user_member_of = str, None, false
+ldap_user_modify_timestamp = str, None, false
+ldap_user_shadow_last_change = str, None, false
+ldap_user_shadow_min = str, None, false
+ldap_user_shadow_max = str, None, false
+ldap_user_shadow_warning = str, None, false
+ldap_user_shadow_inactive = str, None, false
+ldap_user_shadow_expire = str, None, false
+ldap_user_shadow_flag = str, None, false
+ldap_user_krb_last_pwd_change = str, None, false
+ldap_user_krb_password_expiration = str, None, false
+ldap_pwd_attribute = str, None, false
+ldap_group_search_base = str, None, false
+ldap_group_search_scope = str, None, false
+ldap_group_search_filter = str, None, false
+ldap_group_object_class = str, None, false
+ldap_group_name = str, None, false
+ldap_group_gid_number = str, None, false
+ldap_group_member = str, None, false
+ldap_group_uuid = str, None, false
+ldap_group_modify_timestamp = str, None, false
+ldap_force_upper_case_realm = bool, None, false
+
+[provider/ldap/auth]
+ldap_pwd_policy = str, None, false
+
+[provider/ldap/chpass]
+
diff --git a/src/config/etc/sssd.api.d/sssd-local.conf b/src/config/etc/sssd.api.d/sssd-local.conf
new file mode 100644
index 00000000..0686f082
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-local.conf
@@ -0,0 +1,10 @@
+[provider/local]
+
+[provider/local/id]
+default_shell = str, None, true, /bin/bash
+base_directory = str, None, true, /home
+
+[provider/local/auth]
+
+[provider/local/chpass]
+
diff --git a/src/config/etc/sssd.api.d/sssd-proxy.conf b/src/config/etc/sssd.api.d/sssd-proxy.conf
new file mode 100644
index 00000000..7ecf6b33
--- /dev/null
+++ b/src/config/etc/sssd.api.d/sssd-proxy.conf
@@ -0,0 +1,7 @@
+[provider/proxy]
+
+[provider/proxy/id]
+proxy_lib_name = str, None, true
+
+[provider/proxy/auth]
+proxy_pam_target = str, None, true
diff --git a/src/config/ipachangeconf.py b/src/config/ipachangeconf.py
new file mode 100644
index 00000000..ea73a9b9
--- /dev/null
+++ b/src/config/ipachangeconf.py
@@ -0,0 +1,588 @@
+#
+# ipachangeconf - configuration file manipulation classes and functions
+# partially based on authconfig code
+# Copyright (c) 1999-2007 Red Hat, Inc.
+# Author: Simo Sorce <ssorce@redhat.com>
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+import fcntl
+import os
+import string
+import time
+import shutil
+import re
+
+def openLocked(filename, perms, create = True):
+ fd = -1
+
+ flags = os.O_RDWR
+ if create:
+ flags = flags | os.O_CREAT
+
+ try:
+ fd = os.open(filename, flags, perms)
+ fcntl.lockf(fd, fcntl.LOCK_EX)
+ except OSError, (errno, strerr):
+ if fd != -1:
+ try:
+ os.close(fd)
+ except OSError:
+ pass
+ raise IOError(errno, strerr)
+ return os.fdopen(fd, "r+")
+
+
+ #TODO: add subsection as a concept
+ # (ex. REALM.NAME = { foo = x bar = y } )
+ #TODO: put section delimiters as separating element of the list
+ # so that we can process multiple sections in one go
+ #TODO: add a comment all but provided options as a section option
+class IPAChangeConf:
+
+ def __init__(self, name):
+ self.progname = name
+ self.indent = ("","","")
+ self.assign = (" = ","=")
+ self.dassign = self.assign[0]
+ self.comment = ("#",)
+ self.dcomment = self.comment[0]
+ self.eol = ("\n",)
+ self.deol = self.eol[0]
+ self.sectnamdel = ("[","]")
+ self.subsectdel = ("{","}")
+ self.backup_suffix = ".ipabkp"
+
+ def setProgName(self, name):
+ self.progname = name
+
+ def setIndent(self, indent):
+ if type(indent) is tuple:
+ self.indent = indent
+ elif type(indent) is str:
+ self.indent = (indent, )
+ else:
+ raise ValueError, 'Indent must be a list of strings'
+
+ def setOptionAssignment(self, assign):
+ if type(assign) is tuple:
+ self.assign = assign
+ else:
+ self.assign = (assign, )
+ self.dassign = self.assign[0]
+
+ def setCommentPrefix(self, comment):
+ if type(comment) is tuple:
+ self.comment = comment
+ else:
+ self.comment = (comment, )
+ self.dcomment = self.comment[0]
+
+ def setEndLine(self, eol):
+ if type(eol) is tuple:
+ self.eol = eol
+ else:
+ self.eol = (eol, )
+ self.deol = self.eol[0]
+
+ def setSectionNameDelimiters(self, delims):
+ self.sectnamdel = delims
+
+ def setSubSectionDelimiters(self, delims):
+ self.subsectdel = delims
+
+ def matchComment(self, line):
+ for v in self.comment:
+ if line.lstrip().startswith(v):
+ return line.lstrip()[len(v):]
+ return False
+
+ def matchEmpty(self, line):
+ if line.strip() == "":
+ return True
+ return False
+
+ def matchSection(self, line):
+ cl = "".join(line.strip().split())
+ if len(self.sectnamdel) != 2:
+ return False
+ if not cl.startswith(self.sectnamdel[0]):
+ return False
+ if not cl.endswith(self.sectnamdel[1]):
+ return False
+ return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
+
+ def matchSubSection(self, line):
+ if self.matchComment(line):
+ return False
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ return False
+
+ if parts[1].strip() == self.subsectdel[0]:
+ return parts[0].strip()
+
+ return False
+
+ def matchSubSectionEnd(self, line):
+ if self.matchComment(line):
+ return False
+
+ if line.strip() == self.subsectdel[1]:
+ return True
+
+ return False
+
+ def getSectionLine(self, section):
+ if len(self.sectnamdel) != 2:
+ return section
+ return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
+
+ def dump(self, options, level=0):
+ output = ""
+ if level >= len(self.indent):
+ level = len(self.indent)-1
+
+ for o in options:
+ if o['type'] == "section":
+ output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol
+ output += self.dump(o['value'], level+1)
+ continue
+ if o['type'] == "subsection":
+ output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol
+ output += self.dump(o['value'], level+1)
+ output += self.indent[level]+self.subsectdel[1]+self.deol
+ continue
+ if o['type'] == "option":
+ output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
+ continue
+ if o['type'] == "comment":
+ output += self.dcomment+o['value']+self.deol
+ continue
+ if o['type'] == "empty":
+ output += self.deol
+ continue
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
+
+ return output
+
+ def parseLine(self, line):
+
+ if self.matchEmpty(line):
+ return {'name':'empty', 'type':'empty'}
+
+ value = self.matchComment(line)
+ if value:
+ return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
+
+ parts = line.split(self.dassign, 1)
+ if len(parts) < 2:
+ raise SyntaxError, 'Syntax Error: Unknown line format'
+
+ return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
+
+ def findOpts(self, opts, type, name, exclude_sections=False):
+
+ num = 0
+ for o in opts:
+ if o['type'] == type and o['name'] == name:
+ return (num, o)
+ if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
+ return (num, None)
+ num += 1
+ return (num, None)
+
+ def commentOpts(self, inopts, level = 0):
+
+ opts = []
+
+ if level >= len(self.indent):
+ level = len(self.indent)-1
+
+ for o in inopts:
+ if o['type'] == 'section':
+ no = self.commentOpts(o['value'], level+1)
+ val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ for n in no:
+ opts.append(n)
+ continue
+ if o['type'] == 'subsection':
+ no = self.commentOpts(o['value'], level+1)
+ val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ for n in no:
+ opts.append(n)
+ val = self.indent[level]+self.subsectdel[1]
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ continue
+ if o['type'] == 'option':
+ val = self.indent[level]+o['name']+self.dassign+o['value']
+ opts.append({'name':'comment', 'type':'comment', 'value':val})
+ continue
+ if o['type'] == 'comment':
+ opts.append(o)
+ continue
+ if o['type'] == 'empty':
+ opts.append({'name':'comment', 'type':'comment', 'value':''})
+ continue
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
+
+ return opts
+
+ def mergeOld(self, oldopts, newopts):
+
+ opts = []
+
+ for o in oldopts:
+ if o['type'] == "section" or o['type'] == "subsection":
+ (num, no) = self.findOpts(newopts, o['type'], o['name'])
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == "set":
+ mo = self.mergeOld(o['value'], no['value'])
+ opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
+ continue
+ if no['action'] == "comment":
+ co = self.commentOpts(o['value'])
+ for c in co:
+ opts.append(c)
+ continue
+ if no['action'] == "remove":
+ continue
+ raise SyntaxError, 'Unknown action: ['+no['action']+']'
+
+ if o['type'] == "comment" or o['type'] == "empty":
+ opts.append(o)
+ continue
+
+ if o['type'] == "option":
+ (num, no) = self.findOpts(newopts, 'option', o['name'], True)
+ if not no:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment' or no['action'] == 'remove':
+ if no['value'] != None and o['value'] != no['value']:
+ opts.append(o)
+ continue
+ if no['action'] == 'comment':
+ opts.append({'name':'comment', 'type':'comment',
+ 'value':self.dcomment+o['name']+self.dassign+o['value']})
+ continue
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ raise SyntaxError, 'Unknown action: ['+o['action']+']'
+
+ raise SyntaxError, 'Unknown type: ['+o['type']+']'
+
+ return opts
+
+ def mergeNew(self, opts, newopts):
+
+ cline = 0
+
+ for no in newopts:
+
+ if no['type'] == "section" or no['type'] == "subsection":
+ (num, o) = self.findOpts(opts, no['type'], no['name'])
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ if no['action'] == "set":
+ self.mergeNew(o['value'], no['value'])
+ continue
+ cline = num+1
+ continue
+
+ if no['type'] == "option":
+ (num, o) = self.findOpts(opts, no['type'], no['name'], True)
+ if not o:
+ if no['action'] == 'set':
+ opts.append(no)
+ continue
+ cline = num+1
+ continue
+
+ if no['type'] == "comment" or no['type'] == "empty":
+ opts.insert(cline, no)
+ cline += 1
+ continue
+
+ raise SyntaxError, 'Unknown type: ['+no['type']+']'
+
+
+ def merge(self, oldopts, newopts):
+
+ #Use a two pass strategy
+ #First we create a new opts tree from oldopts removing/commenting
+ # the options as indicated by the contents of newopts
+ #Second we fill in the new opts tree with options as indicated
+ # in the newopts tree (this is becaus eentire (sub)sections may
+ # exist in the newopts that do not exist in oldopts)
+
+ opts = self.mergeOld(oldopts, newopts)
+ self.mergeNew(opts, newopts)
+ return opts
+
+ #TODO: Make parse() recursive?
+ def parse(self, f):
+
+ opts = []
+ sectopts = []
+ section = None
+ subsectopts = []
+ subsection = None
+ curopts = opts
+ fatheropts = opts
+
+ # Read in the old file.
+ for line in f:
+
+ # It's a section start.
+ value = self.matchSection(line)
+ if value:
+ if section is not None:
+ opts.append({'name':section, 'type':'section', 'value':sectopts})
+ sectopts = []
+ curopts = sectopts
+ fatheropts = sectopts
+ section = value
+ continue
+
+ # It's a subsection start.
+ value = self.matchSubSection(line)
+ if value:
+ if subsection is not None:
+ raise SyntaxError, 'nested subsections are not supported yet'
+ subsectopts = []
+ curopts = subsectopts
+ subsection = value
+ continue
+
+ value = self.matchSubSectionEnd(line)
+ if value:
+ if subsection is None:
+ raise SyntaxError, 'Unmatched end subsection terminator found'
+ fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
+ subsection = None
+ curopts = fatheropts
+ continue
+
+ # Copy anything else as is.
+ curopts.append(self.parseLine(line))
+
+ #Add last section if any
+ if len(sectopts) is not 0:
+ opts.append({'name':section, 'type':'section', 'value':sectopts})
+
+ return opts
+
+ # Write settings to configuration file
+ # file is a path
+ # options is a set of dictionaries in the form:
+ # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
+ # section is a section name like 'global'
+ def changeConf(self, file, newopts):
+ autosection = False
+ savedsection = None
+ done = False
+ output = ""
+ f = None
+ try:
+ #Do not catch an unexisting file error, we want to fail in that case
+ shutil.copy2(file, file+self.backup_suffix)
+
+ f = openLocked(file, 0644)
+
+ oldopts = self.parse(f)
+
+ options = self.merge(oldopts, newopts)
+
+ output = self.dump(options)
+
+ # Write it out and close it.
+ f.seek(0)
+ f.truncate(0)
+ f.write(output)
+ finally:
+ try:
+ if f:
+ f.close()
+ except IOError:
+ pass
+ return True
+
+ # Write settings to new file, backup old
+ # file is a path
+ # options is a set of dictionaries in the form:
+ # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
+ # section is a section name like 'global'
+ def newConf(self, file, options):
+ autosection = False
+ savedsection = None
+ done = False
+ output = ""
+ f = None
+ try:
+ try:
+ shutil.copy2(file, file+self.backup_suffix)
+ except IOError, err:
+ if err.errno == 2:
+ # The orign file did not exist
+ pass
+
+ f = openLocked(file, 0644)
+
+ # Trunkate
+ f.seek(0)
+ f.truncate(0)
+
+ output = self.dump(options)
+
+ f.write(output)
+ finally:
+ try:
+ if f:
+ f.close()
+ except IOError:
+ pass
+ return True
+
+# A SSSD-specific subclass of IPAChangeConf
+class SSSDChangeConf(IPAChangeConf):
+ OPTCRE = re.compile(
+ r'(?P<option>[^:=\s][^:=]*)' # very permissive!
+ r'\s*=\s*' # any number of space/tab,
+ # followed by separator
+ # followed by any # space/tab
+ r'(?P<value>.*)$' # everything up to eol
+ )
+
+ def __init__(self):
+ IPAChangeConf.__init__(self, "SSSD")
+ self.comment = ("#",";")
+ self.backup_suffix = ".bak"
+ self.opts = []
+
+ def parseLine(self, line):
+ """
+ Overrides IPAChangeConf parseLine so that lines are splitted
+ using any separator in self.assign, not just the default one
+ """
+
+ if self.matchEmpty(line):
+ return {'name':'empty', 'type':'empty'}
+
+ value = self.matchComment(line)
+ if value:
+ return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
+
+ mo = self.OPTCRE.match(line)
+ if not mo:
+ raise SyntaxError, 'Syntax Error: Unknown line format'
+
+ try:
+ name, value = mo.group('option', 'value')
+ except IndexError:
+ raise SyntaxError, 'Syntax Error: Unknown line format'
+
+ return {'name':name.strip(), 'type':'option', 'value':value.strip()}
+
+ def readfp(self, fd):
+ self.opts.extend(self.parse(fd))
+
+ def read(self, filename):
+ fd = open(filename, 'r')
+ self.readfp(fd)
+ fd.close()
+
+ def get(self, section, name):
+ index, item = self.get_option_index(section, name)
+ if item:
+ return item['value']
+
+ def set(self, section, name, value):
+ modkw = { 'type' : 'section',
+ 'name' : section,
+ 'value' : [{
+ 'type' : 'option',
+ 'name' : name,
+ 'value' : value,
+ 'action': 'set',
+ }],
+ 'action': 'set',
+ }
+ self.opts = self.merge(self.opts, [ modkw ])
+
+ def add_section(self, name, optkw, index=0):
+ optkw.append({'type':'empty', 'value':'empty'})
+ addkw = { 'type' : 'section',
+ 'name' : name,
+ 'value' : optkw,
+ }
+ self.opts.insert(index, addkw)
+
+ def delete_section(self, name):
+ self.delete_option('section', name)
+
+ def sections(self):
+ return [ o for o in self.opts if o['type'] == 'section' ]
+
+ def has_section(self, section):
+ return len([ o for o in self.opts if o['type'] == 'section' if o['name'] == section ]) > 0
+
+ def options(self, section):
+ for opt in self.opts:
+ if opt['type'] == 'section' and opt['name'] == section:
+ return opt['value']
+
+ def delete_option(self, type, name, exclude_sections=False):
+ return self.delete_option_subtree(self.opts, type, name)
+
+ def delete_option_subtree(self, subtree, type, name, exclude_sections=False):
+ index, item = self.findOpts(subtree, type, name, exclude_sections)
+ if item:
+ del subtree[index]
+ return index
+
+ def has_option(self, section, name):
+ index, item = self.get_option_index(section, name)
+ if index != -1 and item != None:
+ return True
+ return False
+
+ def strip_comments_empty(self, optlist):
+ retlist = []
+ for opt in optlist:
+ if opt['type'] in ('comment', 'empty'):
+ continue
+ retlist.append(opt)
+ return retlist
+
+ def get_option_index(self, parent_name, name, type='option'):
+ subtree = None
+ if parent_name:
+ pindex, pdata = self.findOpts(self.opts, 'section', parent_name)
+ if not pdata:
+ return (-1, None)
+ subtree = pdata['value']
+ else:
+ subtree = self.opts
+ return self.findOpts(subtree, type, name)
+
diff --git a/src/config/setup.py b/src/config/setup.py
new file mode 100644
index 00000000..46a81060
--- /dev/null
+++ b/src/config/setup.py
@@ -0,0 +1,35 @@
+# Authors:
+# Stephen Gallagher <sgallagh@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Python-level packaging using distutils.
+"""
+
+from distutils.core import setup
+
+setup(
+ name='SSSDConfig',
+ version='1',
+ license='GPLv3+',
+ url='http://fedorahosted.org/sssd',
+ py_modules=[
+ 'SSSDConfig',
+ 'ipachangeconf',
+ ],
+)
diff --git a/src/config/testconfigs/noparse.api.conf b/src/config/testconfigs/noparse.api.conf
new file mode 100644
index 00000000..50651001
--- /dev/null
+++ b/src/config/testconfigs/noparse.api.conf
@@ -0,0 +1,7 @@
+# Format:
+# option = type, subtype[, default]
+
+[service]
+# Options available to all services
+debug_level = int, None, 0
+command \ No newline at end of file
diff --git a/src/config/testconfigs/sssd-badversion.conf b/src/config/testconfigs/sssd-badversion.conf
new file mode 100644
index 00000000..75d8c484
--- /dev/null
+++ b/src/config/testconfigs/sssd-badversion.conf
@@ -0,0 +1,42 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = LOCAL, IPA
+config_file_version = 1
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0
+
+[domain/LOCAL]
+id_provider = local
+auth_provider = local
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider = ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/testconfigs/sssd-invalid-badbool.conf b/src/config/testconfigs/sssd-invalid-badbool.conf
new file mode 100644
index 00000000..25c27f49
--- /dev/null
+++ b/src/config/testconfigs/sssd-invalid-badbool.conf
@@ -0,0 +1,43 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = LOCAL, IPA
+config_file_version = 2
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+ldap_id_use_start_tls = Fal
+auth_provider = krb5
+debug_level = 0
+
+[domain/LOCAL]
+id_provider = local
+auth_provider = local
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/testconfigs/sssd-invalid.conf b/src/config/testconfigs/sssd-invalid.conf
new file mode 100644
index 00000000..3a84ae11
--- /dev/null
+++ b/src/config/testconfigs/sssd-invalid.conf
@@ -0,0 +1,3 @@
+[sssd]
+services
+config_file_version = 2
diff --git a/src/config/testconfigs/sssd-noversion.conf b/src/config/testconfigs/sssd-noversion.conf
new file mode 100644
index 00000000..71af85cc
--- /dev/null
+++ b/src/config/testconfigs/sssd-noversion.conf
@@ -0,0 +1,41 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = LOCAL, IPA
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0
+
+[domain/LOCAL]
+id_provider = local
+auth_provider = local
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider = ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/testconfigs/sssd-valid.conf b/src/config/testconfigs/sssd-valid.conf
new file mode 100644
index 00000000..79016eb4
--- /dev/null
+++ b/src/config/testconfigs/sssd-valid.conf
@@ -0,0 +1,43 @@
+[nss]
+nss_filter_groups = root
+nss_entry_negative_timeout = 15
+debug_level = 0
+nss_filter_users_in_groups = true
+nss_filter_users = root
+nss_entry_cache_no_wait_timeout = 60
+nss_entry_cache_timeout = 600
+nss_enum_cache_timeout = 120
+
+[sssd]
+services = nss, pam
+reconnection_retries = 3
+domains = LOCAL, IPA
+config_file_version = 2
+debug_timestamps = False
+
+[domain/PROXY]
+id_provider = proxy
+auth_provider = proxy
+debug_level = 0
+
+[domain/IPA]
+id_provider = ldap
+auth_provider = krb5
+debug_level = 0
+
+[domain/LOCAL]
+id_provider = local
+auth_provider = local
+debug_level = 0
+
+[domain/LDAP]
+id_provider = ldap
+auth_provider=ldap
+debug_level = 0
+
+[pam]
+debug_level = 0
+
+[dp]
+debug_level = 0
+
diff --git a/src/config/upgrade_config.py b/src/config/upgrade_config.py
new file mode 100644
index 00000000..d47fcd38
--- /dev/null
+++ b/src/config/upgrade_config.py
@@ -0,0 +1,405 @@
+#!/usr/bin/python
+#coding=utf-8
+
+# SSSD
+#
+# upgrade_config.py
+#
+# Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import shutil
+import traceback
+from optparse import OptionParser
+
+from ipachangeconf import openLocked
+from ipachangeconf import SSSDChangeConf
+
+class SSSDConfigFile(SSSDChangeConf):
+ def __init__(self, filename):
+ SSSDChangeConf.__init__(self)
+ self.filename = filename
+
+ f = openLocked(self.filename, 0600, False)
+ self.opts = self.parse(f)
+ f.close()
+
+ def _backup_file(self, file_name):
+ " Copy the file we operate on to a backup location "
+ shutil.copy(file_name, file_name + self.backup_suffix)
+ # make sure we don't leak data, force permissions on the backup
+ os.chmod(file_name + self.backup_suffix, 0600)
+
+ def get_version(self):
+ ver = self.get_option_index('sssd', 'config_file_version')[1]
+ if not ver:
+ return 1
+ try:
+ return int(ver['value'])
+ except ValueError:
+ raise SyntaxError, 'config_file_version not an integer'
+
+ def rename_opts(self, parent_name, rename_kw, type='option'):
+ for new_name, old_name in rename_kw.items():
+ index, item = self.get_option_index(parent_name, old_name, type)
+ if item:
+ item['name'] = new_name
+
+ def _do_v2_changes(self):
+ # remove Data Provider
+ srvlist = self.get_option_index('sssd', 'services')[1]
+ if srvlist:
+ services = [ srv.strip() for srv in srvlist['value'].split(',') ]
+ if 'dp' in services:
+ services.remove('dp')
+ srvlist['value'] = ", ".join([srv for srv in services])
+ self.delete_option('section', 'dp')
+
+ # remove magic_private_groups from all domains
+ for domain in [ s for s in self.sections() if s['name'].startswith("domain/") ]:
+ self.delete_option_subtree(domain['value'], 'option', 'magic_private_groups')
+
+ def _update_option(self, to_section_name, from_section_name, opts):
+ to_section = [ s for s in self.sections() if s['name'].strip() == to_section_name ]
+ from_section = [ s for s in self.sections() if s['name'].strip() == from_section_name ]
+
+ if len(to_section) > 0 and len(from_section) > 0:
+ vals = to_section[0]['value']
+ for o in [one_opt for one_opt in from_section[0]['value'] if one_opt['name'] in opts]:
+ updated = False
+ for v in vals:
+ if v['type'] == 'empty':
+ continue
+ # if already in list, just update
+ if o['name'] == v['name']:
+ o['value'] = v['value']
+ updated = True
+ # not in list, add there
+ if not updated:
+ vals.insert(0, { 'name' : o['name'], 'type' : o['type'], 'value' : o['value'] })
+
+ def _migrate_enumerate(self, domain):
+ " Enumerate was special as it turned into bool from (0,1,2,3) enum "
+ enum = self.findOpts(domain, 'option', 'enumerate')[1]
+ if enum:
+ if enum['value'].upper() not in ['TRUE', 'FALSE']:
+ try:
+ enum['value'] = int(enum['value'])
+ except ValueError:
+ raise ValueError('Cannot convert value %s in domain %s' % (enum['value'], domain['name']))
+
+ if enum['value'] == 0:
+ enum['value'] = 'FALSE'
+ elif enum['value'] > 0:
+ enum['value'] = 'TRUE'
+ else:
+ raise ValueError('Cannot convert value %s in domain %s' % (enum['value'], domain['name']))
+
+ def _migrate_domain(self, domain):
+ # rename the section
+ domain['name'] = domain['name'].strip().replace('domains', 'domain')
+
+ # Generic options - new:old
+ generic_kw = { 'min_id' : 'minId',
+ 'max_id': 'maxId',
+ 'timeout': 'timeout',
+ 'magic_private_groups' : 'magicPrivateGroups',
+ 'cache_credentials' : 'cache-credentials',
+ 'id_provider' : 'provider',
+ 'auth_provider' : 'auth-module',
+ 'access_provider' : 'access-module',
+ 'chpass_provider' : 'chpass-module',
+ 'use_fully_qualified_names' : 'useFullyQualifiedNames',
+ 'store_legacy_passwords' : 'store-legacy-passwords',
+ }
+ # Proxy options
+ proxy_kw = { 'proxy_pam_target' : 'pam-target',
+ 'proxy_lib_name' : 'libName',
+ }
+ # LDAP options - new:old
+ ldap_kw = { 'ldap_uri' : 'ldapUri',
+ 'ldap_schema' : 'ldapSchema',
+ 'ldap_default_bind_dn' : 'defaultBindDn',
+ 'ldap_default_authtok_type' : 'defaultAuthtokType',
+ 'ldap_default_authtok' : 'defaultAuthtok',
+ 'ldap_user_search_base' : 'userSearchBase',
+ 'ldap_user_search_scope' : 'userSearchScope',
+ 'ldap_user_search_filter' : 'userSearchFilter',
+ 'ldap_user_object_class' : 'userObjectClass',
+ 'ldap_user_name' : 'userName',
+ 'ldap_user_pwd' : 'userPassword',
+ 'ldap_user_uid_number' : 'userUidNumber',
+ 'ldap_user_gid_number' : 'userGidNumber',
+ 'ldap_user_gecos' : 'userGecos',
+ 'ldap_user_home_directory' : 'userHomeDirectory',
+ 'ldap_user_shell' : 'userShell',
+ 'ldap_user_uuid' : 'userUUID',
+ 'ldap_user_principal' : 'userPrincipal',
+ 'ldap_force_upper_case_realm' : 'force_upper_case_realm',
+ 'ldap_user_fullname' : 'userFullname',
+ 'ldap_user_member_of' : 'userMemberOf',
+ 'ldap_user_modify_timestamp' : 'modifyTimestamp',
+ 'ldap_group_search_base' : 'groupSearchBase',
+ 'ldap_group_search_scope' : 'groupSearchScope',
+ 'ldap_group_search_filter' : 'groupSearchFilter',
+ 'ldap_group_object_class' : 'groupObjectClass',
+ 'ldap_group_name' : 'groupName',
+ 'ldap_group_pwd' : 'userPassword',
+ 'ldap_group_gid_number' : 'groupGidNumber',
+ 'ldap_group_member' : 'groupMember',
+ 'ldap_group_uuid' : 'groupUUID',
+ 'ldap_group_modify_timestamp' : 'modifyTimestamp',
+ 'ldap_network_timeout' : 'network_timeout',
+ 'ldap_offline_timeout' : 'offline_timeout',
+ 'ldap_enumeration_refresh_timeout' : 'enumeration_refresh_timeout',
+ 'ldap_stale_time' : 'stale_time',
+ 'ldap_opt_timeout' : 'opt_timeout',
+ 'ldap_tls_reqcert' : 'tls_reqcert',
+ }
+ krb5_kw = { 'krb5_kdcip' : 'krb5KDCIP',
+ 'krb5_realm' : 'krb5REALM',
+ 'krb5_try_simple_upn' : 'krb5try_simple_upn',
+ 'krb5_changepw_principal' : 'krb5changepw_principle',
+ 'krb5_ccachedir' : 'krb5ccache_dir',
+ 'krb5_auth_timeout' : 'krb5auth_timeout',
+ 'krb5_ccname_template' : 'krb5ccname_template',
+ }
+ user_defaults_kw = { 'default_shell' : 'defaultShell',
+ 'base_directory' : 'baseDirectory',
+ }
+
+ self._migrate_enumerate(domain['value'])
+ self.rename_opts(domain['name'], generic_kw)
+ self.rename_opts(domain['name'], proxy_kw)
+ self.rename_opts(domain['name'], ldap_kw)
+ self.rename_opts(domain['name'], krb5_kw)
+
+ # remove obsolete libPath option
+ self.delete_option_subtree(domain['value'], 'option', 'libPath')
+
+ # configuration files before 0.5.0 did not enforce provider= in local domains
+ # it did special-case by domain name (LOCAL)
+ prvindex, prv = self.findOpts(domain['value'], 'option', 'id_provider')
+ if not prv and domain['name'] == 'domain/LOCAL':
+ prv = { 'type' : 'option',
+ 'name' : 'id_provider',
+ 'value' : 'local',
+ }
+ domain['value'].insert(0, prv)
+
+ # if domain was local, update with parameters from [user_defaults]
+ if prv['value'] == 'local':
+ self._update_option(domain['name'], 'user_defaults', user_defaults_kw.values())
+ self.delete_option('section', 'user_defaults')
+ self.rename_opts(domain['name'], user_defaults_kw)
+
+ # if domain had provider = files, unroll that into provider=proxy, proxy_lib_name=files
+ if prv['value'] == 'files':
+ prv['value'] = 'proxy'
+ libkw = { 'type' : 'option',
+ 'name' : 'proxy_lib_name',
+ 'value' : 'files',
+ }
+ domain['value'].insert(prvindex+1, libkw)
+
+ def _migrate_domains(self):
+ for domain in [ s for s in self.sections() if s['name'].startswith("domains/") ]:
+ self._migrate_domain(domain)
+
+ def _update_if_exists(self, opt, to_name, from_section, from_name):
+ index, item = self.get_option_index(from_section, from_name)
+ if item:
+ item['name'] = to_name
+ opt.append(item)
+
+ def _migrate_services(self):
+ # [service] - options common to all services, no section as in v1
+ service_kw = { 'reconnection_retries' : 'reconnection_retries',
+ 'debug_level' : 'debug-level',
+ 'debug_timestamps' : 'debug-timestamps',
+ 'command' : 'command',
+ 'timeout' : 'timeout',
+ }
+
+ # rename services sections
+ names_kw = { 'nss' : 'services/nss',
+ 'pam' : 'services/pam',
+ 'dp' : 'services/dp',
+ }
+ self.rename_opts(None, names_kw, 'section')
+
+ # [sssd] - monitor service
+ sssd_kw = [
+ { 'type' : 'option',
+ 'name' : 'config_file_version',
+ 'value' : '2',
+ 'action': 'set',
+ }
+ ]
+ self._update_if_exists(sssd_kw, 'domains',
+ 'domains', 'domains')
+ self._update_if_exists(sssd_kw, 'services',
+ 'services', 'activeServices')
+ self._update_if_exists(sssd_kw, 'sbus_timeout',
+ 'services/monitor', 'sbusTimeout')
+ self._update_if_exists(sssd_kw, 're_expression',
+ 'names', 're-expression')
+ self._update_if_exists(sssd_kw, 're_expression',
+ 'names', 'full-name-format')
+ self.add_section('sssd', sssd_kw)
+ # update from general services section and monitor
+ self._update_option('sssd', 'services', service_kw.values())
+ self._update_option('sssd', 'services/monitor', service_kw.values())
+
+ # [nss] - Name service
+ nss_kw = { 'enum_cache_timeout' : 'EnumCacheTimeout',
+ 'entry_cache_timeout' : 'EntryCacheTimeout',
+ 'entry_cache_nowait_timeout' : 'EntryCacheNoWaitRefreshTimeout',
+ 'entry_negative_timeout ' : 'EntryNegativeTimeout',
+ 'filter_users' : 'filterUsers',
+ 'filter_groups' : 'filterGroups',
+ 'filter_users_in_groups' : 'filterUsersInGroups',
+ }
+ nss_kw.update(service_kw)
+ self._update_option('nss', 'services', service_kw.values())
+ self.rename_opts('nss', nss_kw)
+
+ # [pam] - Authentication service
+ pam_kw = {}
+ pam_kw.update(service_kw)
+ self._update_option('pam', 'services', service_kw.values())
+ self.rename_opts('pam', pam_kw)
+
+ # remove obsolete sections
+ self.delete_option('section', 'services')
+ self.delete_option('section', 'names')
+ self.delete_option('section', 'domains')
+ self.delete_option('section', 'services/monitor')
+
+ def v2_changes(self, out_file_name, backup=True):
+ # read in the old file, make backup if needed
+ if backup:
+ self._backup_file(self.filename)
+
+ self._do_v2_changes()
+
+ # all done, write the file
+ of = open(out_file_name, "wb")
+ output = self.dump(self.opts)
+ of.write(output)
+ of.close()
+ # make sure it has the right permissions too
+ os.chmod(out_file_name, 0600)
+
+ def upgrade_v2(self, out_file_name, backup=True):
+ # read in the old file, make backup if needed
+ if backup:
+ self._backup_file(self.filename)
+
+ # do the migration to v2 format
+ # do the upgrade
+ self._migrate_services()
+ self._migrate_domains()
+ # also include any changes in the v2 format
+ self._do_v2_changes()
+
+ # all done, write the file
+ of = open(out_file_name, "wb")
+ output = self.dump(self.opts)
+ of.write(output)
+ of.close()
+ # make sure it has the right permissions too
+ os.chmod(out_file_name, 0600)
+
+def parse_options():
+ parser = OptionParser()
+ parser.add_option("-f", "--file",
+ dest="filename", default="/etc/sssd/sssd.conf",
+ help="Set input file to FILE", metavar="FILE")
+ parser.add_option("-o", "--outfile",
+ dest="outfile", default=None,
+ help="Set output file to OUTFILE", metavar="OUTFILE")
+ parser.add_option("", "--no-backup", action="store_false",
+ dest="backup", default=True,
+ help="""Do not provide backup file after conversion.
+The script copies the original file with the suffix .bak
+by default""")
+ parser.add_option("-v", "--verbose", action="store_true",
+ dest="verbose", default=False,
+ help="Be verbose")
+ (options, args) = parser.parse_args()
+ if len(args) > 0:
+ print >>sys.stderr, "Stray arguments: %s" % ' '.join([a for a in args])
+ return None
+
+ # do the conversion in place by default
+ if not options.outfile:
+ options.outfile = options.filename
+
+ return options
+
+def verbose(msg, verbose):
+ if verbose:
+ print msg
+
+def main():
+ options = parse_options()
+ if not options:
+ print >>sys.stderr, "Cannot parse options"
+ return 1
+
+ try:
+ config = SSSDConfigFile(options.filename)
+ except SyntaxError:
+ verbose(traceback.format_exc(), options.verbose)
+ print >>sys.stderr, "Cannot parse config file %s" % options.filename
+ return 1
+ except Exception, e:
+ print "ERROR: %s" % e
+ verbose(traceback.format_exc(), options.verbose)
+ return 1
+
+ # make sure we keep strict settings when creating new files
+ os.umask(0077)
+
+ version = config.get_version()
+ if version == 2:
+ verbose("Looks like v2, only checking changes", options.verbose)
+ try:
+ config.v2_changes(options.outfile, options.backup)
+ except Exception, e:
+ print "ERROR: %s" % e
+ verbose(traceback.format_exc(), options.verbose)
+ return 1
+ elif version == 1:
+ verbose("Looks like v1, performing full upgrade", options.verbose)
+ try:
+ config.upgrade_v2(options.outfile, options.backup)
+ except Exception, e:
+ print "ERROR: %s" % e
+ verbose(traceback.format_exc(), options.verbose)
+ return 1
+ else:
+ print >>sys.stderr, "Can only upgrade from v1 to v2, file %s looks like version %d" % (options.filename, config.get_version())
+ return 1
+
+ return 0
+
+if __name__ == "__main__":
+ ret = main()
+ sys.exit(ret)
+
diff --git a/src/configure.ac b/src/configure.ac
new file mode 100644
index 00000000..4198ee95
--- /dev/null
+++ b/src/configure.ac
@@ -0,0 +1,145 @@
+AC_PREREQ(2.59)
+AC_DEFUN([AC_CHECK_LIB_EXT], [
+ AC_CHECK_LIB([$1],[$3],[$4],[$5],[$7])
+ ac_cv_lib_ext_$1_$3=$ac_cv_lib_$1_$3
+])
+AC_DEFUN([AC_CHECK_FUNC_EXT], [
+ AC_CHECK_FUNC([$1],[$3],[$4])
+ ac_cv_func_ext_$1=$ac_cv_func_$1
+])
+AC_DEFUN([SMB_MODULE_DEFAULT], [echo -n ""])
+AC_DEFUN([SMB_LIBRARY_ENABLE], [echo -n ""])
+AC_DEFUN([SMB_EXT_LIB], [echo -n ""])
+AC_DEFUN([SMB_ENABLE], [echo -n ""])
+
+m4_include([../version.m4])
+AC_INIT([sss_daemon],
+ VERSION_NUMBER,
+ [sssd-devel@lists.fedorahosted.org])
+
+CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE"
+
+AC_CONFIG_SRCDIR([conf_macros.m4])
+AC_CONFIG_AUX_DIR([build])
+
+AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
+AM_PROG_CC_C_O
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+AC_CONFIG_MACRO_DIR([m4])
+AM_GNU_GETTEXT([external])
+AM_GNU_GETTEXT_VERSION([0.14])
+
+m4_pattern_allow([AM_SILENT_RULES])
+AM_SILENT_RULES
+
+AM_CONDITIONAL([HAVE_GCC], [test "$ac_cv_prog_gcc" = yes])
+
+AC_CHECK_HEADERS(stdint.h dlfcn.h)
+AC_CONFIG_HEADER(config.h)
+
+AC_CHECK_TYPES([errno_t], [], [], [[#include <errno.h>]])
+
+m4_include([build_macros.m4])
+BUILD_WITH_SHARED_BUILD_DIR
+
+#Check for PAM headers
+AC_CHECK_HEADERS([security/pam_appl.h security/pam_misc.h security/pam_modules.h],
+ [AC_CHECK_LIB(pam, pam_get_item, [ PAM_LIBS="-lpam" ], [AC_MSG_ERROR([PAM must support pam_get_item])])],
+ [AC_MSG_ERROR([PAM development libraries not installed])]
+)
+
+#Set the NSS library install path
+AC_ARG_ENABLE([nsslibdir], [AS_HELP_STRING([--enable-nsslibdir],
+ [Where to install nss libraries ($libdir)])],
+ [nsslibdir=$enableval],
+ [nsslibdir=$libdir])
+AC_SUBST(nsslibdir)
+
+m4_include(conf_macros.m4)
+WITH_DB_PATH
+WITH_PLUGIN_PATH
+WITH_PID_PATH
+WITH_LOG_PATH
+WITH_PUBCONF_PATH
+WITH_PIPE_PATH
+WITH_INIT_DIR
+WITH_SHADOW_UTILS_PATH
+WITH_MANPAGES
+WITH_XML_CATALOG
+WITH_KRB5_PLUGIN_PATH
+WITH_PYTHON_BINDINGS
+WITH_SELINUX
+
+m4_include([external/platform.m4])
+m4_include([external/pkg.m4])
+m4_include([external/libpopt.m4])
+m4_include([external/libtalloc.m4])
+m4_include([external/libtdb.m4])
+m4_include([external/libtevent.m4])
+m4_include([external/libldb.m4])
+m4_include([external/libdhash.m4])
+m4_include([external/libcollection.m4])
+m4_include([external/libini_config.m4])
+m4_include([external/pam.m4])
+m4_include([external/ldap.m4])
+m4_include([external/libpcre.m4])
+m4_include([external/krb5.m4])
+m4_include([external/libcares.m4])
+m4_include([external/docbook.m4])
+m4_include([external/sizes.m4])
+m4_include([external/python.m4])
+m4_include([external/selinux.m4])
+m4_include([external/crypto.m4])
+m4_include([util/signal.m4])
+
+PKG_CHECK_MODULES([DBUS],[dbus-1])
+dnl if test -n "`$PKG_CONFIG --modversion dbus-1 | grep '^0\.'`" ; then
+if ! $PKG_CONFIG --atleast-version 1.0.0 dbus-1; then
+ DBUS_CFLAGS="$DBUS_CFLAGS -DDBUS_API_SUBJECT_TO_CHANGE"
+ AC_MSG_RESULT([setting -DDBUS_API_SUBJECT_TO_CHANGE])
+fi
+
+if test x$has_dbus != xno; then
+ SAFE_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$DBUS_LIBS"
+ AC_CHECK_FUNC([dbus_watch_get_unix_fd],
+ AC_DEFINE([HAVE_DBUS_WATCH_GET_UNIX_FD], [1],
+ [Define if dbus_watch_get_unix_fd exists]))
+ LDFLAGS="$SAFE_LDFLAGS"
+fi
+
+if test x$HAVE_MANPAGES != x; then
+ CHECK_XML_TOOLS
+ CHECK_STYLESHEET([$SGML_CATALOG_FILES],
+ [http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl],
+ [Docbook XSL templates])
+fi
+
+if test x$HAVE_PYTHON_BINDINGS != x; then
+ AM_PATH_PYTHON([2.4])
+ AM_CHECK_PYTHON_HEADERS([],
+ AC_MSG_ERROR([Could not find python headers]))
+ AM_PYTHON_CONFIG
+fi
+
+if test x$HAVE_SELINUX != x; then
+ AM_CHECK_SELINUX
+fi
+
+AC_CHECK_HEADERS([sys/inotify.h])
+
+AC_CHECK_HEADERS([sasl/sasl.h],,AC_MSG_ERROR([Could not find SASL headers]))
+
+PKG_CHECK_MODULES([CHECK], [check >= 0.9.5], [have_check=1], [have_check=])
+if test x$have_check = x; then
+ AC_MSG_WARN([Without the 'CHECK' libraries, you will be unable to run all tests in the 'make check' suite])
+fi
+
+AC_PATH_PROG([DOXYGEN], [doxygen], [false])
+AM_CONDITIONAL([HAVE_DOXYGEN], [test x$DOXYGEN != xfalse ])
+
+AM_CONDITIONAL([HAVE_CHECK], [test x$have_check != x])
+
+AC_CONFIG_FILES([Makefile doxy.config po/Makefile.in])
+AC_OUTPUT
diff --git a/src/db/sysdb.c b/src/db/sysdb.c
new file mode 100644
index 00000000..b3f81a08
--- /dev/null
+++ b/src/db/sysdb.c
@@ -0,0 +1,1883 @@
+/*
+ SSSD
+
+ System Database
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "db/sysdb_private.h"
+#include "confdb/confdb.h"
+#include <time.h>
+
+
+struct ldb_dn *sysdb_custom_subtree_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain,
+ const char *subtree_name)
+{
+ return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_CUSTOM_SUBTREE,
+ subtree_name, domain);
+}
+struct ldb_dn *sysdb_custom_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *object_name,
+ const char *subtree_name)
+{
+ return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_CUSTOM, object_name,
+ subtree_name, domain);
+}
+
+struct ldb_dn *sysdb_user_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *name)
+{
+ return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_USER, name, domain);
+}
+
+struct ldb_dn *sysdb_group_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *name)
+{
+ return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_TMPL_GROUP, name, domain);
+}
+
+struct ldb_dn *sysdb_domain_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain)
+{
+ return ldb_dn_new_fmt(memctx, ctx->ldb, SYSDB_DOM_BASE, domain);
+}
+
+struct ldb_context *sysdb_ctx_get_ldb(struct sysdb_ctx *ctx)
+{
+ return ctx->ldb;
+}
+
+struct ldb_context *sysdb_handle_get_ldb(struct sysdb_handle *handle)
+{
+ return handle->ctx->ldb;
+}
+
+struct sysdb_ctx *sysdb_handle_get_ctx(struct sysdb_handle *handle)
+{
+ return handle->ctx;
+}
+
+struct sysdb_attrs *sysdb_new_attrs(TALLOC_CTX *memctx)
+{
+ return talloc_zero(memctx, struct sysdb_attrs);
+}
+
+static int sysdb_attrs_get_el_int(struct sysdb_attrs *attrs, const char *name,
+ bool alloc, struct ldb_message_element **el)
+{
+ struct ldb_message_element *e = NULL;
+ int i;
+
+ for (i = 0; i < attrs->num; i++) {
+ if (strcasecmp(name, attrs->a[i].name) == 0)
+ e = &(attrs->a[i]);
+ }
+
+ if (!e && alloc) {
+ e = talloc_realloc(attrs, attrs->a,
+ struct ldb_message_element, attrs->num+1);
+ if (!e) return ENOMEM;
+ attrs->a = e;
+
+ e[attrs->num].name = talloc_strdup(e, name);
+ if (!e[attrs->num].name) return ENOMEM;
+
+ e[attrs->num].num_values = 0;
+ e[attrs->num].values = NULL;
+ e[attrs->num].flags = 0;
+
+ e = &(attrs->a[attrs->num]);
+ attrs->num++;
+ }
+
+ if (!e) {
+ return ENOENT;
+ }
+
+ *el = e;
+
+ return EOK;
+}
+
+int sysdb_attrs_get_el(struct sysdb_attrs *attrs, const char *name,
+ struct ldb_message_element **el)
+{
+ return sysdb_attrs_get_el_int(attrs, name, true, el);
+}
+
+int sysdb_attrs_get_string(struct sysdb_attrs *attrs, const char *name,
+ const char **string)
+{
+ struct ldb_message_element *el;
+ int ret;
+
+ ret = sysdb_attrs_get_el_int(attrs, name, false, &el);
+ if (ret) {
+ return ret;
+ }
+
+ if (el->num_values != 1) {
+ return ERANGE;
+ }
+
+ *string = (const char *)el->values[0].data;
+ return EOK;
+}
+
+int sysdb_attrs_add_val(struct sysdb_attrs *attrs,
+ const char *name, const struct ldb_val *val)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *vals;
+ int ret;
+
+ ret = sysdb_attrs_get_el(attrs, name, &el);
+
+ vals = talloc_realloc(attrs->a, el->values,
+ struct ldb_val, el->num_values+1);
+ if (!vals) return ENOMEM;
+
+ vals[el->num_values] = ldb_val_dup(vals, val);
+ if (vals[el->num_values].data == NULL &&
+ vals[el->num_values].length != 0) {
+ return ENOMEM;
+ }
+
+ el->values = vals;
+ el->num_values++;
+
+ return EOK;
+}
+
+int sysdb_attrs_add_string(struct sysdb_attrs *attrs,
+ const char *name, const char *str)
+{
+ struct ldb_val v;
+
+ v.data = (uint8_t *)discard_const(str);
+ v.length = strlen(str);
+
+ return sysdb_attrs_add_val(attrs, name, &v);
+}
+
+int sysdb_attrs_steal_string(struct sysdb_attrs *attrs,
+ const char *name, char *str)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *vals;
+ int ret;
+
+ ret = sysdb_attrs_get_el(attrs, name, &el);
+
+ vals = talloc_realloc(attrs->a, el->values,
+ struct ldb_val, el->num_values+1);
+ if (!vals) return ENOMEM;
+ el->values = vals;
+
+ /* now steal and assign the string */
+ talloc_steal(el->values, str);
+
+ el->values[el->num_values].data = (uint8_t *)str;
+ el->values[el->num_values].length = strlen(str);
+ el->num_values++;
+
+ return EOK;
+}
+
+int sysdb_attrs_add_long(struct sysdb_attrs *attrs,
+ const char *name, long value)
+{
+ struct ldb_val v;
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(attrs, "%ld", value);
+ if (!str) return ENOMEM;
+
+ v.data = (uint8_t *)str;
+ v.length = strlen(str);
+
+ ret = sysdb_attrs_add_val(attrs, name, &v);
+ talloc_free(str);
+
+ return ret;
+}
+
+int sysdb_attrs_add_uint32(struct sysdb_attrs *attrs,
+ const char *name, uint32_t value)
+{
+ unsigned long val = value;
+ struct ldb_val v;
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(attrs, "%lu", val);
+ if (!str) return ENOMEM;
+
+ v.data = (uint8_t *)str;
+ v.length = strlen(str);
+
+ ret = sysdb_attrs_add_val(attrs, name, &v);
+ talloc_free(str);
+
+ return ret;
+}
+
+int sysdb_attrs_add_time_t(struct sysdb_attrs *attrs,
+ const char *name, time_t value)
+{
+ long long val = value;
+ struct ldb_val v;
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(attrs, "%lld", val);
+ if (!str) return ENOMEM;
+
+ v.data = (uint8_t *)str;
+ v.length = strlen(str);
+
+ ret = sysdb_attrs_add_val(attrs, name, &v);
+ talloc_free(str);
+
+ return ret;
+}
+
+int sysdb_attrs_users_from_str_list(struct sysdb_attrs *attrs,
+ const char *attr_name,
+ const char *domain,
+ const char **list)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *vals;
+ int i, j, num;
+ char *member;
+ int ret;
+
+ ret = sysdb_attrs_get_el(attrs, attr_name, &el);
+ if (ret) {
+ return ret;
+ }
+
+ for (num = 0; list[num]; num++) /* count */ ;
+
+ vals = talloc_realloc(attrs->a, el->values,
+ struct ldb_val, el->num_values + num);
+ if (!vals) {
+ return ENOMEM;
+ }
+ el->values = vals;
+
+ DEBUG(9, ("Adding %d members to existing %d ones\n",
+ num, el->num_values));
+
+ for (i = 0, j = el->num_values; i < num; i++) {
+
+ member = sysdb_user_strdn(el->values, domain, list[i]);
+ if (!member) {
+ DEBUG(4, ("Failed to get user dn for [%s]\n", list[i]));
+ continue;
+ }
+ el->values[j].data = (uint8_t *)member;
+ el->values[j].length = strlen(member);
+ j++;
+
+ DEBUG(7, (" member #%d: [%s]\n", i, member));
+ }
+ el->num_values = j;
+
+ return EOK;
+}
+
+int sysdb_attrs_users_from_ldb_vals(struct sysdb_attrs *attrs,
+ const char *attr_name,
+ const char *domain,
+ struct ldb_val *values,
+ int num_values)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *vals;
+ int i, j;
+ char *member;
+ int ret;
+
+ ret = sysdb_attrs_get_el(attrs, attr_name, &el);
+ if (ret) {
+ return ret;
+ }
+
+ vals = talloc_realloc(attrs->a, el->values, struct ldb_val,
+ el->num_values + num_values);
+ if (!vals) {
+ return ENOMEM;
+ }
+ el->values = vals;
+
+ DEBUG(9, ("Adding %d members to existing %d ones\n",
+ num_values, el->num_values));
+
+ for (i = 0, j = el->num_values; i < num_values; i++) {
+ member = sysdb_user_strdn(el->values, domain,
+ (char *)values[i].data);
+ if (!member) {
+ DEBUG(4, ("Failed to get user dn for [%s]\n",
+ (char *)values[i].data));
+ return ENOMEM;
+ }
+ el->values[j].data = (uint8_t *)member;
+ el->values[j].length = strlen(member);
+ j++;
+
+ DEBUG(7, (" member #%d: [%s]\n", i, member));
+ }
+ el->num_values = j;
+
+ return EOK;
+}
+
+static char *build_dom_dn_str_escape(TALLOC_CTX *memctx, const char *template,
+ const char *domain, const char *name)
+{
+ char *ret;
+ int l;
+
+ l = strcspn(name, ",=\n+<>#;\\\"");
+ if (name[l] != '\0') {
+ struct ldb_val v;
+ char *tmp;
+
+ v.data = discard_const_p(uint8_t, name);
+ v.length = strlen(name);
+
+ tmp = ldb_dn_escape_value(memctx, v);
+ if (!tmp) {
+ return NULL;
+ }
+
+ ret = talloc_asprintf(memctx, template, tmp, domain);
+ talloc_zfree(tmp);
+ if (!ret) {
+ return NULL;
+ }
+
+ return ret;
+ }
+
+ ret = talloc_asprintf(memctx, template, name, domain);
+ if (!ret) {
+ return NULL;
+ }
+
+ return ret;
+}
+
+char *sysdb_user_strdn(TALLOC_CTX *memctx,
+ const char *domain, const char *name)
+{
+ return build_dom_dn_str_escape(memctx, SYSDB_TMPL_USER, domain, name);
+}
+
+char *sysdb_group_strdn(TALLOC_CTX *memctx,
+ const char *domain, const char *name)
+{
+ return build_dom_dn_str_escape(memctx, SYSDB_TMPL_GROUP, domain, name);
+}
+
+/* TODO: make a more complete and precise mapping */
+int sysdb_error_to_errno(int ldberr)
+{
+ switch (ldberr) {
+ case LDB_SUCCESS:
+ return EOK;
+ case LDB_ERR_OPERATIONS_ERROR:
+ return EIO;
+ case LDB_ERR_NO_SUCH_OBJECT:
+ return ENOENT;
+ case LDB_ERR_BUSY:
+ return EBUSY;
+ case LDB_ERR_ENTRY_ALREADY_EXISTS:
+ return EEXIST;
+ default:
+ return EFAULT;
+ }
+}
+
+/* =Internal-Operations-Queue============================================= */
+
+static void sysdb_run_operation(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct sysdb_handle *handle = talloc_get_type(pvt, struct sysdb_handle);
+
+ tevent_req_done(handle->subreq);
+}
+
+static void sysdb_schedule_operation(struct sysdb_handle *handle)
+{
+ struct timeval tv = { 0, 0 };
+ struct tevent_timer *te;
+
+ te = tevent_add_timer(handle->ctx->ev, handle, tv,
+ sysdb_run_operation, handle);
+ if (!te) {
+ DEBUG(1, ("Failed to add critical timer to run next handle!\n"));
+ }
+}
+
+static int sysdb_handle_destructor(void *mem)
+{
+ struct sysdb_handle *handle = talloc_get_type(mem, struct sysdb_handle);
+ bool start_next = false;
+ int ret;
+
+ /* if this was the current op start next */
+ if (handle->ctx->queue == handle) {
+ start_next = true;
+ }
+
+ DLIST_REMOVE(handle->ctx->queue, handle);
+
+ if (start_next && handle->ctx->queue) {
+ /* run next */
+ sysdb_schedule_operation(handle->ctx->queue);
+ }
+
+ if (handle->transaction_active) {
+ ret = ldb_transaction_cancel(handle->ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to cancel ldb transaction! (%d)\n", ret));
+ }
+ /* FIXME: abort() ? */
+ handle->transaction_active = false;
+ }
+
+ return 0;
+}
+
+struct sysdb_get_handle_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *ctx;
+
+ struct sysdb_handle *handle;
+};
+
+struct tevent_req *sysdb_get_handle_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *ctx)
+{
+ struct tevent_req *req;
+ struct sysdb_get_handle_state *state;
+ struct sysdb_handle *handle;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_get_handle_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ handle = talloc_zero(state, struct sysdb_handle);
+ if (!handle) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ handle->ctx = ctx;
+ handle->subreq = req;
+
+ talloc_set_destructor((TALLOC_CTX *)handle, sysdb_handle_destructor);
+
+ DLIST_ADD_END(ctx->queue, handle, struct sysdb_handle *);
+
+ if (ctx->queue == handle) {
+ /* this is the first in the queue, schedule an immediate run */
+ sysdb_schedule_operation(handle);
+ }
+
+ state->handle = handle;
+
+ return req;
+}
+
+static int sysdb_get_handle_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle)
+{
+ struct sysdb_get_handle_state *state = tevent_req_data(req,
+ struct sysdb_get_handle_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *handle = talloc_steal(memctx, state->handle);
+ if (!*handle) return ENOMEM;
+
+ return EOK;
+}
+
+/* =Transactions========================================================== */
+
+struct sysdb_transaction_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *ctx;
+
+ struct sysdb_handle *handle;
+};
+
+static void sysdb_transaction_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_transaction_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_transaction_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_transaction_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ subreq = sysdb_get_handle_send(state, ev, ctx);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, sysdb_transaction_done, req);
+
+ return req;
+}
+
+static void sysdb_transaction_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_transaction_state *state = tevent_req_data(req,
+ struct sysdb_transaction_state);
+ int ret;
+
+ ret = sysdb_get_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ldb_transaction_start(state->ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to start ldb transaction! (%d)\n", ret));
+ tevent_req_error(req, sysdb_error_to_errno(ret));
+ return;
+ }
+ state->handle->transaction_active = true;
+
+ tevent_req_done(req);
+}
+
+int sysdb_transaction_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle)
+{
+ struct sysdb_transaction_state *state = tevent_req_data(req,
+ struct sysdb_transaction_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *handle = talloc_steal(memctx, state->handle);
+ if (!*handle) return ENOMEM;
+
+ return EOK;
+}
+
+struct tevent_req *sysdb_transaction_commit_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle)
+{
+ struct tevent_req *req;
+ struct sysdb_transaction_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_transaction_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = handle->ctx;
+ state->handle = handle;
+
+ ret = ldb_transaction_commit(handle->ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to commit ldb transaction! (%d)\n", ret));
+ tevent_req_error(req, sysdb_error_to_errno(ret));
+ }
+ handle->transaction_active = false;
+
+ /* the following may seem weird but it is actually fine.
+ * _done() will not actually call the callback as it will not be set
+ * until we return. But it will mark the request as done.
+ * _post() will trigger the callback as it schedules after we returned
+ * and actually set the callback */
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_transaction_commit_recv(struct tevent_req *req)
+{
+ struct sysdb_transaction_state *state = tevent_req_data(req,
+ struct sysdb_transaction_state);
+
+ /* finally free handle
+ * this will also trigger the next transaction in the queue if any */
+ talloc_zfree(state->handle);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* default transaction commit receive function.
+ * This function does not use the request state so it is safe to use
+ * from any caller */
+void sysdb_transaction_complete(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+
+/* =Operations============================================================ */
+
+struct sysdb_operation_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *ctx;
+
+ struct sysdb_handle *handle;
+};
+
+static void sysdb_operation_process(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_operation_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_operation_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_operation_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ subreq = sysdb_get_handle_send(state, ev, ctx);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, sysdb_operation_process, req);
+
+ return req;
+}
+
+static void sysdb_operation_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_operation_state *state = tevent_req_data(req,
+ struct sysdb_operation_state);
+ int ret;
+
+ ret = sysdb_get_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_operation_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle)
+{
+ struct sysdb_operation_state *state = tevent_req_data(req,
+ struct sysdb_operation_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *handle = talloc_steal(memctx, state->handle);
+ if (!*handle) return ENOMEM;
+
+ return EOK;
+}
+
+void sysdb_operation_done(struct sysdb_handle *handle)
+{
+ talloc_free(handle);
+}
+
+/* =Initialization======================================================== */
+
+static int sysdb_domain_init_internal(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domain,
+ const char *db_path,
+ bool allow_upgrade,
+ struct sysdb_ctx **_ctx);
+
+static int sysdb_get_db_file(TALLOC_CTX *mem_ctx,
+ const char *provider, const char *name,
+ const char *base_path, char **_ldb_file)
+{
+ char *ldb_file;
+
+ /* special case for the local domain */
+ if (strcasecmp(provider, "local") == 0) {
+ ldb_file = talloc_asprintf(mem_ctx, "%s/"LOCAL_SYSDB_FILE,
+ base_path);
+ } else {
+ ldb_file = talloc_asprintf(mem_ctx, "%s/"CACHE_SYSDB_FILE,
+ base_path, name);
+ }
+ if (!ldb_file) {
+ return ENOMEM;
+ }
+
+ *_ldb_file = ldb_file;
+ return EOK;
+}
+
+/* serach all groups that have a memberUid attribute.
+ * change it into a member attribute for a user of same domain.
+ * remove the memberUid attribute
+ * add the new member attribute
+ * finally stop indexing memberUid
+ * upgrade version to 0.2
+ */
+static int sysdb_upgrade_01(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const char **ver)
+{
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct ldb_dn *basedn;
+ struct ldb_dn *mem_dn;
+ struct ldb_message *msg;
+ const struct ldb_val *val;
+ const char *filter = "(&(memberUid=*)(objectclass=group))";
+ const char *attrs[] = { "memberUid", NULL };
+ const char *mdn;
+ char *domain;
+ int ret, i, j;
+
+ basedn = ldb_dn_new(mem_ctx, ldb, "cn=sysdb");
+ if (!basedn) {
+ ret = EIO;
+ goto done;
+ }
+
+ ret = ldb_search(ldb, mem_ctx, &res,
+ basedn, LDB_SCOPE_SUBTREE,
+ attrs, filter);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ el = ldb_msg_find_element(res->msgs[i], "memberUid");
+ if (!el) {
+ DEBUG(1, ("memberUid is missing from message [%s], skipping\n",
+ ldb_dn_get_linearized(res->msgs[i]->dn)));
+ continue;
+ }
+
+ /* create modification message */
+ msg = ldb_msg_new(mem_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = res->msgs[i]->dn;
+
+ ret = ldb_msg_add_empty(msg, "memberUid", LDB_FLAG_MOD_DELETE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, SYSDB_MEMBER, LDB_FLAG_MOD_ADD, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* get domain name component value */
+ val = ldb_dn_get_component_val(res->msgs[i]->dn, 2);
+ domain = talloc_strndup(mem_ctx, (const char *)val->data, val->length);
+ if (!domain) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (j = 0; j < el->num_values; j++) {
+ mem_dn = ldb_dn_new_fmt(mem_ctx, ldb, SYSDB_TMPL_USER,
+ (const char *)el->values[j].data, domain);
+ if (!mem_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ mdn = talloc_strdup(msg, ldb_dn_get_linearized(mem_dn));
+ if (!mdn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, SYSDB_MEMBER, mdn);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ talloc_zfree(mem_dn);
+ }
+
+ /* ok now we are ready to modify the entry */
+ ret = ldb_modify(ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ talloc_zfree(msg);
+ }
+
+ /* conversion done, upgrade version number */
+ msg = ldb_msg_new(mem_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(mem_ctx, ldb, "cn=sysdb");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "version", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, "version", SYSDB_VERSION_0_2);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ ldb_transaction_cancel(ldb);
+ } else {
+ ret = ldb_transaction_commit(ldb);
+ if (ret != LDB_SUCCESS) {
+ return EIO;
+ }
+
+ *ver = SYSDB_VERSION_0_2;
+ }
+
+ return ret;
+}
+
+static int sysdb_check_upgrade_02(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domains,
+ const char *db_path)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_context *ldb;
+ char *ldb_file;
+ struct sysdb_ctx *ctx;
+ struct sss_domain_info *dom;
+ struct ldb_message_element *el;
+ struct ldb_message *msg;
+ struct ldb_result *res;
+ struct ldb_dn *verdn;
+ const char *version = NULL;
+ bool do_02_upgrade = false;
+ bool ctx_trans = false;
+ int ret;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = sysdb_get_db_file(mem_ctx,
+ "local", "UPGRADE",
+ db_path, &ldb_file);
+ if (ret != EOK) {
+ goto exit;
+ }
+
+ ldb = ldb_init(tmp_ctx, ev);
+ if (!ldb) {
+ ret = EIO;
+ goto exit;
+ }
+
+ ret = ldb_set_debug(ldb, ldb_debug_messages, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto exit;
+ }
+
+#ifdef SYSDB_TEST
+ ldb_set_modules_dir(ldb, "./.libs");
+#endif
+
+ ret = ldb_connect(ldb, ldb_file, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto exit;
+ }
+
+ verdn = ldb_dn_new(tmp_ctx, ldb, "cn=sysdb");
+ if (!verdn) {
+ ret = EIO;
+ goto exit;
+ }
+
+ ret = ldb_search(ldb, tmp_ctx, &res,
+ verdn, LDB_SCOPE_BASE,
+ NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto exit;
+ }
+ if (res->count > 1) {
+ ret = EIO;
+ goto exit;
+ }
+
+ if (res->count == 1) {
+ el = ldb_msg_find_element(res->msgs[0], "version");
+ if (el) {
+ if (el->num_values != 1) {
+ ret = EINVAL;
+ goto exit;
+ }
+ version = talloc_strndup(tmp_ctx,
+ (char *)(el->values[0].data),
+ el->values[0].length);
+ if (!version) {
+ ret = ENOMEM;
+ goto exit;
+ }
+
+ if (strcmp(version, SYSDB_VERSION) == 0) {
+ /* all fine, return */
+ ret = EOK;
+ goto exit;
+ }
+
+ DEBUG(4, ("Upgrading DB from version: %s\n", version));
+
+ if (strcmp(version, SYSDB_VERSION_0_1) == 0) {
+ /* convert database */
+ ret = sysdb_upgrade_01(tmp_ctx, ldb, &version);
+ if (ret != EOK) goto exit;
+ }
+
+ if (strcmp(version, SYSDB_VERSION_0_2) == 0) {
+ /* need to convert database to split files */
+ do_02_upgrade = true;
+ }
+
+ }
+ }
+
+ if (!do_02_upgrade) {
+ /* not a v2 upgrade, return and let the normal code take over any
+ * further upgrade */
+ ret = EOK;
+ goto exit;
+ }
+
+ /* == V2->V3 UPGRADE == */
+
+ DEBUG(0, ("UPGRADING DB TO VERSION %s\n", SYSDB_VERSION_0_3));
+
+ /* ldb uses posix locks,
+ * posix is stupid and kills all locks when you close *any* file
+ * descriptor associated to the same file.
+ * Therefore we must close and reopen the ldb file here */
+
+ /* == Backup and reopen ldb == */
+
+ /* close */
+ talloc_zfree(ldb);
+
+ /* backup*/
+ ret = backup_file(ldb_file, 0);
+ if (ret != EOK) {
+ goto exit;
+ }
+
+ /* reopen */
+ ldb = ldb_init(tmp_ctx, ev);
+ if (!ldb) {
+ ret = EIO;
+ goto exit;
+ }
+
+ ret = ldb_set_debug(ldb, ldb_debug_messages, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto exit;
+ }
+
+ ret = ldb_connect(ldb, ldb_file, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto exit;
+ }
+
+ /* open a transaction */
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to start ldb transaction! (%d)\n", ret));
+ ret = EIO;
+ goto exit;
+ }
+
+ /* == Upgrade contents == */
+
+ for (dom = domains; dom; dom = dom->next) {
+ struct ldb_dn *domain_dn;
+ struct ldb_dn *users_dn;
+ struct ldb_dn *groups_dn;
+ int i;
+
+ /* skip local */
+ if (strcasecmp(dom->provider, "local") == 0) {
+ continue;
+ }
+
+ /* create new dom db */
+ ret = sysdb_domain_init_internal(tmp_ctx, ev, dom,
+ db_path, false, &ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = ldb_transaction_start(ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to start ldb transaction! (%d)\n", ret));
+ ret = EIO;
+ goto done;
+ }
+ ctx_trans = true;
+
+ /* search all entries for this domain in local,
+ * copy them all in the new database,
+ * then remove them from local */
+
+ domain_dn = ldb_dn_new_fmt(tmp_ctx, ctx->ldb,
+ SYSDB_DOM_BASE, ctx->domain->name);
+ if (!domain_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_search(ldb, tmp_ctx, &res,
+ domain_dn, LDB_SCOPE_SUBTREE,
+ NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ users_dn = ldb_dn_new_fmt(tmp_ctx, ctx->ldb,
+ SYSDB_TMPL_USER_BASE, ctx->domain->name);
+ if (!users_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ groups_dn = ldb_dn_new_fmt(tmp_ctx, ctx->ldb,
+ SYSDB_TMPL_GROUP_BASE, ctx->domain->name);
+ if (!groups_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < res->count; i++) {
+
+ struct ldb_dn *orig_dn;
+
+ msg = res->msgs[i];
+
+ /* skip pre-created congtainers */
+ if ((ldb_dn_compare(msg->dn, domain_dn) == 0) ||
+ (ldb_dn_compare(msg->dn, users_dn) == 0) ||
+ (ldb_dn_compare(msg->dn, groups_dn) == 0)) {
+ continue;
+ }
+
+ /* regenerate the DN against the new ldb as it may have different
+ * casefolding rules (example: name changing from case insensitive
+ * to case sensitive) */
+ orig_dn = msg->dn;
+ msg->dn = ldb_dn_new(msg, ctx->ldb,
+ ldb_dn_get_linearized(orig_dn));
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_add(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("WARNING: Could not add entry %s,"
+ " to new ldb file! (%d [%s])\n",
+ ldb_dn_get_linearized(msg->dn),
+ ret, ldb_errstring(ctx->ldb)));
+ }
+
+ ret = ldb_delete(ldb, orig_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("WARNING: Could not remove entry %s,"
+ " from old ldb file! (%d [%s])\n",
+ ldb_dn_get_linearized(orig_dn),
+ ret, ldb_errstring(ldb)));
+ }
+ }
+
+ /* now remove the basic containers from local */
+ /* these were optional so debug at level 9 in case
+ * of failure just for tracing */
+ ret = ldb_delete(ldb, groups_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(9, ("WARNING: Could not remove entry %s,"
+ " from old ldb file! (%d [%s])\n",
+ ldb_dn_get_linearized(groups_dn),
+ ret, ldb_errstring(ldb)));
+ }
+ ret = ldb_delete(ldb, users_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(9, ("WARNING: Could not remove entry %s,"
+ " from old ldb file! (%d [%s])\n",
+ ldb_dn_get_linearized(users_dn),
+ ret, ldb_errstring(ldb)));
+ }
+ ret = ldb_delete(ldb, domain_dn);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(9, ("WARNING: Could not remove entry %s,"
+ " from old ldb file! (%d [%s])\n",
+ ldb_dn_get_linearized(domain_dn),
+ ret, ldb_errstring(ldb)));
+ }
+
+ ret = ldb_transaction_commit(ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to commit ldb transaction! (%d)\n", ret));
+ ret = EIO;
+ goto done;
+ }
+ ctx_trans = false;
+
+ talloc_zfree(domain_dn);
+ talloc_zfree(groups_dn);
+ talloc_zfree(users_dn);
+ talloc_zfree(res);
+ }
+
+ /* conversion done, upgrade version number */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ldb, "cn=sysdb");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "version", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, "version", SYSDB_VERSION_0_3);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ ret = ldb_transaction_commit(ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to commit ldb transaction! (%d)\n", ret));
+ ret = EIO;
+ goto exit;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ if (ctx_trans) {
+ ret = ldb_transaction_cancel(ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to cancel ldb transaction! (%d)\n", ret));
+ }
+ }
+ ret = ldb_transaction_cancel(ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to cancel ldb transaction! (%d)\n", ret));
+ }
+ }
+
+exit:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int sysdb_upgrade_03(struct sysdb_ctx *ctx, const char **ver)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+ struct ldb_message *msg;
+
+ tmp_ctx = talloc_new(ctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ DEBUG(0, ("UPGRADING DB TO VERSION %s\n", SYSDB_VERSION_0_4));
+
+ ret = ldb_transaction_start(ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Make this database case-sensitive */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ctx->ldb, "@ATTRIBUTES");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "name", LDB_FLAG_MOD_DELETE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ /* conversion done, upgrade version number */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ctx->ldb, "cn=sysdb");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "version", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, "version", SYSDB_VERSION_0_4);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_zfree(tmp_ctx);
+
+ if (ret != EOK) {
+ ret = ldb_transaction_cancel(ctx->ldb);
+ } else {
+ ret = ldb_transaction_commit(ctx->ldb);
+ *ver = SYSDB_VERSION_0_4;
+ }
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ }
+
+ return ret;
+}
+
+static int sysdb_upgrade_04(struct sysdb_ctx *ctx, const char **ver)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+ struct ldb_message *msg;
+
+ tmp_ctx = talloc_new(ctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ DEBUG(0, ("UPGRADING DB TO VERSION %s\n", SYSDB_VERSION_0_5));
+
+ ret = ldb_transaction_start(ctx->ldb);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Add new index */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ctx->ldb, "@INDEXLIST");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "@IDXATTR", LDB_FLAG_MOD_ADD, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, "@IDXATTR", "originalDN");
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ /* Rebuild memberuid and memberoif attributes */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ctx->ldb, "@MEMBEROF-REBUILD");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_add(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ /* conversion done, upgrade version number */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new(tmp_ctx, ctx->ldb, "cn=sysdb");
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_msg_add_empty(msg, "version", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_string(msg, "version", SYSDB_VERSION_0_5);
+ if (ret != LDB_SUCCESS) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = ldb_modify(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_zfree(tmp_ctx);
+
+ if (ret != EOK) {
+ ret = ldb_transaction_cancel(ctx->ldb);
+ } else {
+ ret = ldb_transaction_commit(ctx->ldb);
+ *ver = SYSDB_VERSION_0_5;
+ }
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ }
+
+ return ret;
+}
+
+static int sysdb_domain_init_internal(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domain,
+ const char *db_path,
+ bool allow_upgrade,
+ struct sysdb_ctx **_ctx)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct sysdb_ctx *ctx;
+ const char *base_ldif;
+ struct ldb_ldif *ldif;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ struct ldb_result *res;
+ struct ldb_dn *verdn;
+ const char *version = NULL;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct sysdb_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->ev = ev;
+ ctx->domain = domain;
+
+ /* The local provider s the only true MPG,
+ * for the other domains, the provider actually unrolls MPGs */
+ if (strcasecmp(domain->provider, "local") == 0) {
+ ctx->mpg = true;
+ }
+
+ ret = sysdb_get_db_file(ctx, domain->provider,
+ domain->name, db_path,
+ &ctx->ldb_file);
+ if (ret != EOK) {
+ return ret;
+ }
+ DEBUG(5, ("DB File for %s: %s\n", domain->name, ctx->ldb_file));
+
+ ctx->ldb = ldb_init(ctx, ev);
+ if (!ctx->ldb) {
+ return EIO;
+ }
+
+ ret = ldb_set_debug(ctx->ldb, ldb_debug_messages, NULL);
+ if (ret != LDB_SUCCESS) {
+ return EIO;
+ }
+
+#ifdef SYSDB_TEST
+ ldb_set_modules_dir(ctx->ldb, "./.libs");
+#endif
+
+ ret = ldb_connect(ctx->ldb, ctx->ldb_file, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ return EIO;
+ }
+
+ tmp_ctx = talloc_new(ctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ verdn = ldb_dn_new(tmp_ctx, ctx->ldb, "cn=sysdb");
+ if (!verdn) {
+ ret = EIO;
+ goto done;
+ }
+
+ ret = ldb_search(ctx->ldb, tmp_ctx, &res,
+ verdn, LDB_SCOPE_BASE,
+ NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ if (res->count > 1) {
+ ret = EIO;
+ goto done;
+ }
+
+ if (res->count == 1) {
+ el = ldb_msg_find_element(res->msgs[0], "version");
+ if (el) {
+ if (el->num_values != 1) {
+ ret = EINVAL;
+ goto done;
+ }
+ version = talloc_strndup(tmp_ctx,
+ (char *)(el->values[0].data),
+ el->values[0].length);
+ if (!version) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (strcmp(version, SYSDB_VERSION) == 0) {
+ /* all fine, return */
+ ret = EOK;
+ goto done;
+ }
+
+ if (!allow_upgrade) {
+ DEBUG(0, ("Wrong DB version (got %s expected %s)\n",
+ version, SYSDB_VERSION));
+ ret = EINVAL;
+ goto done;
+ }
+
+ DEBUG(4, ("Upgrading DB [%s] from version: %s\n",
+ domain->name, version));
+
+ if (strcmp(version, SYSDB_VERSION_0_3) == 0) {
+ ret = sysdb_upgrade_03(ctx, &version);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ if (strcmp(version, SYSDB_VERSION_0_4) == 0) {
+ ret = sysdb_upgrade_04(ctx, &version);
+ goto done;
+ }
+ }
+
+ DEBUG(0,("Unknown DB version [%s], expected [%s] for domain %s!\n",
+ version?version:"not found", SYSDB_VERSION, domain->name));
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* cn=sysdb does not exists, means db is empty, populate */
+
+ base_ldif = SYSDB_BASE_LDIF;
+ while ((ldif = ldb_ldif_read_string(ctx->ldb, &base_ldif))) {
+ ret = ldb_add(ctx->ldb, ldif->msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d, [%s]) for domain %s!",
+ ret, ldb_errstring(ctx->ldb), domain->name));
+ ret = EIO;
+ goto done;
+ }
+ ldb_ldif_read_free(ctx->ldb, ldif);
+ }
+
+ /* == create base domain object == */
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new_fmt(msg, ctx->ldb, SYSDB_DOM_BASE, domain->name);
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_fmt(msg, "cn", "%s", domain->name);
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ /* do a synchronous add */
+ ret = ldb_add(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d, [%s]) for domain %s!",
+ ret, ldb_errstring(ctx->ldb), domain->name));
+ ret = EIO;
+ goto done;
+ }
+ talloc_zfree(msg);
+
+ /* == create Users tree == */
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new_fmt(msg, ctx->ldb,
+ SYSDB_TMPL_USER_BASE, domain->name);
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_fmt(msg, "cn", "Users");
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ /* do a synchronous add */
+ ret = ldb_add(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d, [%s]) for domain %s!",
+ ret, ldb_errstring(ctx->ldb), domain->name));
+ ret = EIO;
+ goto done;
+ }
+ talloc_zfree(msg);
+
+ /* == create Groups tree == */
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ret = ENOMEM;
+ goto done;
+ }
+ msg->dn = ldb_dn_new_fmt(msg, ctx->ldb,
+ SYSDB_TMPL_GROUP_BASE, domain->name);
+ if (!msg->dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = ldb_msg_add_fmt(msg, "cn", "Groups");
+ if (ret != LDB_SUCCESS) {
+ ret = EIO;
+ goto done;
+ }
+ /* do a synchronous add */
+ ret = ldb_add(ctx->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to initialize DB (%d, [%s]) for domain %s!",
+ ret, ldb_errstring(ctx->ldb), domain->name));
+ ret = EIO;
+ goto done;
+ }
+ talloc_zfree(msg);
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_ctx = ctx;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int sysdb_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb,
+ const char *alt_db_path,
+ bool allow_upgrade,
+ struct sysdb_ctx_list **_ctx_list)
+{
+ struct sysdb_ctx_list *ctx_list;
+ struct sss_domain_info *domains, *dom;
+ struct sysdb_ctx *ctx;
+ int ret;
+
+ if (!ev) return EINVAL;
+
+ ctx_list = talloc_zero(mem_ctx, struct sysdb_ctx_list);
+ if (!ctx_list) {
+ return ENOMEM;
+ }
+
+ if (alt_db_path) {
+ ctx_list->db_path = talloc_strdup(ctx_list, alt_db_path);
+ } else {
+ ctx_list->db_path = talloc_strdup(ctx_list, DB_PATH);
+ }
+ if (!ctx_list->db_path) {
+ talloc_zfree(ctx_list);
+ return ENOMEM;
+ }
+
+ /* open a db for each backend */
+ ret = confdb_get_domains(cdb, &domains);
+ if (ret != EOK) {
+ talloc_zfree(ctx_list);
+ return ret;
+ }
+
+ if (allow_upgrade) {
+ /* check if we have an old sssd.ldb to upgrade */
+ ret = sysdb_check_upgrade_02(ctx_list, ev, domains,
+ ctx_list->db_path);
+ if (ret != EOK) {
+ talloc_zfree(ctx_list);
+ return ret;
+ }
+ }
+
+ for (dom = domains; dom; dom = dom->next) {
+
+ ctx_list->dbs = talloc_realloc(ctx_list, ctx_list->dbs,
+ struct sysdb_ctx *,
+ ctx_list->num_dbs + 1);
+ if (!ctx_list->dbs) {
+ talloc_zfree(ctx_list);
+ return ENOMEM;
+ }
+
+ ret = sysdb_domain_init_internal(ctx_list, ev, dom,
+ ctx_list->db_path,
+ allow_upgrade, &ctx);
+ if (ret != EOK) {
+ talloc_zfree(ctx_list);
+ return ret;
+ }
+
+ ctx_list->dbs[ctx_list->num_dbs] = ctx;
+ ctx_list->num_dbs++;
+ }
+ if (ctx_list->num_dbs == 0) {
+ /* what? .. */
+ talloc_zfree(ctx_list);
+ return ENOENT;
+ }
+
+ *_ctx_list = ctx_list;
+
+ return EOK;
+}
+
+int sysdb_domain_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domain,
+ const char *db_path,
+ struct sysdb_ctx **_ctx)
+{
+ return sysdb_domain_init_internal(mem_ctx, ev, domain,
+ db_path, false, _ctx);
+}
+
+int sysdb_get_ctx_from_list(struct sysdb_ctx_list *ctx_list,
+ struct sss_domain_info *domain,
+ struct sysdb_ctx **ctx)
+{
+ int i;
+
+ for (i = 0; i < ctx_list->num_dbs; i++) {
+ if (ctx_list->dbs[i]->domain == domain) {
+ *ctx = ctx_list->dbs[i];
+ return EOK;
+ }
+ if (strcasecmp(ctx_list->dbs[i]->domain->name, domain->name) == 0) {
+ *ctx = ctx_list->dbs[i];
+ return EOK;
+ }
+ }
+ /* definitely not found */
+ return ENOENT;
+}
+
+
+int compare_ldb_dn_comp_num(const void *m1, const void *m2)
+{
+ struct ldb_message *msg1 = talloc_get_type(*(void **) discard_const(m1),
+ struct ldb_message);
+ struct ldb_message *msg2 = talloc_get_type(*(void **) discard_const(m2),
+ struct ldb_message);
+
+ return ldb_dn_get_comp_num(msg2->dn) - ldb_dn_get_comp_num(msg1->dn);
+}
+
+int sysdb_attrs_replace_name(struct sysdb_attrs *attrs, const char *oldname,
+ const char *newname)
+{
+ struct ldb_message_element *e = NULL;
+ int i;
+ const char *dummy;
+
+ if (attrs == NULL || oldname == NULL || newname == NULL) return EINVAL;
+
+ for (i = 0; i < attrs->num; i++) {
+ if (strcasecmp(oldname, attrs->a[i].name) == 0) {
+ e = &(attrs->a[i]);
+ }
+ if (strcasecmp(newname, attrs->a[i].name) == 0) {
+ DEBUG(3, ("New attribute name [%s] already exists.\n", newname));
+ return EEXIST;
+ }
+ }
+
+ if (e != NULL) {
+ dummy = talloc_strdup(attrs, newname);
+ if (dummy == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+
+ talloc_free(discard_const(e->name));
+ e->name = dummy;
+ }
+
+ return EOK;
+}
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
new file mode 100644
index 00000000..cf97ed62
--- /dev/null
+++ b/src/db/sysdb.h
@@ -0,0 +1,650 @@
+/*
+ SSSD
+
+ System Database Header
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SYS_DB_H__
+#define __SYS_DB_H__
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include <tevent.h>
+
+#define SYSDB_CONF_SECTION "config/sysdb"
+#define CACHE_SYSDB_FILE "cache_%s.ldb"
+#define LOCAL_SYSDB_FILE "sssd.ldb"
+
+#define SYSDB_BASE "cn=sysdb"
+#define SYSDB_DOM_BASE "cn=%s,cn=sysdb"
+#define SYSDB_USERS_CONTAINER "cn=users"
+#define SYSDB_GROUPS_CONTAINER "cn=groups"
+#define SYSDB_CUSTOM_CONTAINER "cn=custom"
+#define SYSDB_TMPL_USER_BASE SYSDB_USERS_CONTAINER",cn=%s,"SYSDB_BASE
+#define SYSDB_TMPL_GROUP_BASE SYSDB_GROUPS_CONTAINER",cn=%s,"SYSDB_BASE
+#define SYSDB_TMPL_CUSTOM_BASE SYSDB_CUSTOM_CONTAINER",cn=%s,"SYSDB_BASE
+
+#define SYSDB_USER_CLASS "user"
+#define SYSDB_GROUP_CLASS "group"
+
+#define SYSDB_NAME "name"
+
+#define SYSDB_NEXTID "nextID"
+#define SYSDB_UIDNUM "uidNumber"
+#define SYSDB_GIDNUM "gidNumber"
+#define SYSDB_CREATE_TIME "createTimestamp"
+
+#define SYSDB_PWD "userPassword"
+#define SYSDB_FULLNAME "fullName"
+#define SYSDB_HOMEDIR "homeDirectory"
+#define SYSDB_SHELL "loginShell"
+#define SYSDB_MEMBEROF "memberOf"
+#define SYSDB_DISABLED "disabled"
+
+#define SYSDB_MEMBER "member"
+#define SYSDB_MEMBERUID "memberUid"
+
+#define SYSDB_DEFAULTGROUP "defaultGroup"
+#define SYSDB_GECOS "gecos"
+#define SYSDB_LOCALE "locale"
+#define SYSDB_KEYBOARD "keyboard"
+#define SYSDB_SESSION "session"
+#define SYSDB_LAST_LOGIN "lastLogin"
+#define SYSDB_LAST_ONLINE_AUTH "lastOnlineAuth"
+#define SYSDB_USERPIC "userPicture"
+#define SYSDB_LAST_FAILED_LOGIN "lastFailedLogin"
+#define SYSDB_FAILED_LOGIN_ATTEMPTS "failedLoginAttempts"
+
+#define SYSDB_LAST_UPDATE "lastUpdate"
+#define SYSDB_CACHE_EXPIRE "dataExpireTimestamp"
+#define SYSDB_INITGR_EXPIRE "initgrExpireTimestamp"
+
+#define SYSDB_CACHEDPWD "cachedPassword"
+
+#define SYSDB_UUID "uniqueID"
+#define SYSDB_UPN "userPrincipalName"
+#define SYSDB_CCACHE_FILE "ccacheFile"
+
+#define SYSDB_ORIG_DN "originalDN"
+#define SYSDB_ORIG_MODSTAMP "originalModifyTimestamp"
+#define SYSDB_ORIG_MEMBEROF "originalMemberOf"
+
+#define SYSDB_USN "entryUSN"
+#define SYSDB_HIGH_USN "highestUSN"
+
+#define SYSDB_NEXTID_FILTER "("SYSDB_NEXTID"=*)"
+
+#define SYSDB_UC "objectclass="SYSDB_USER_CLASS
+#define SYSDB_GC "objectclass="SYSDB_GROUP_CLASS
+#define SYSDB_MPGC "|("SYSDB_UC")("SYSDB_GC")"
+
+#define SYSDB_PWNAM_FILTER "(&("SYSDB_UC")("SYSDB_NAME"=%s))"
+#define SYSDB_PWUID_FILTER "(&("SYSDB_UC")("SYSDB_UIDNUM"=%lu))"
+#define SYSDB_PWENT_FILTER "("SYSDB_UC")"
+
+#define SYSDB_GRNAM_FILTER "(&("SYSDB_GC")("SYSDB_NAME"=%s))"
+#define SYSDB_GRNA2_FILTER "(&("SYSDB_UC")("SYSDB_MEMBEROF"=%s))"
+#define SYSDB_GRGID_FILTER "(&("SYSDB_GC")("SYSDB_GIDNUM"=%lu))"
+#define SYSDB_GRENT_FILTER "("SYSDB_GC")"
+#define SYSDB_GRNAM_MPG_FILTER "(&("SYSDB_MPGC")("SYSDB_NAME"=%s))"
+#define SYSDB_GRGID_MPG_FILTER "(&("SYSDB_MPGC")("SYSDB_GIDNUM"=%lu))"
+#define SYSDB_GRENT_MPG_FILTER "("SYSDB_MPGC")"
+
+#define SYSDB_INITGR_FILTER "(&("SYSDB_GC")("SYSDB_GIDNUM"=*))"
+
+#define SYSDB_GETCACHED_FILTER "(&"SYSDB_UC")("SYSDB_LAST_LOGIN">=%lu))"
+
+#define SYSDB_DEFAULT_ATTRS SYSDB_LAST_UPDATE, \
+ SYSDB_CACHE_EXPIRE, \
+ SYSDB_INITGR_EXPIRE, \
+ "objectClass"
+
+#define SYSDB_PW_ATTRS {SYSDB_NAME, SYSDB_UIDNUM, \
+ SYSDB_GIDNUM, SYSDB_GECOS, \
+ SYSDB_HOMEDIR, SYSDB_SHELL, \
+ SYSDB_DEFAULT_ATTRS, \
+ NULL}
+#define SYSDB_GRSRC_ATTRS {SYSDB_NAME, SYSDB_GIDNUM, \
+ SYSDB_MEMBERUID, \
+ SYSDB_DEFAULT_ATTRS, \
+ NULL}
+#define SYSDB_GRPW_ATTRS {SYSDB_NAME, SYSDB_UIDNUM, \
+ SYSDB_DEFAULT_ATTRS, \
+ NULL}
+#define SYSDB_GRENT_ATTRS {SYSDB_NAME, SYSDB_UIDNUM, SYSDB_MEMBEROF, \
+ SYSDB_DEFAULT_ATTRS, \
+ NULL}
+
+#define SYSDB_INITGR_ATTR SYSDB_MEMBEROF
+#define SYSDB_INITGR_ATTRS {SYSDB_GIDNUM, \
+ SYSDB_DEFAULT_ATTRS, \
+ NULL}
+
+#define SYSDB_TMPL_USER SYSDB_NAME"=%s,"SYSDB_TMPL_USER_BASE
+#define SYSDB_TMPL_GROUP SYSDB_NAME"=%s,"SYSDB_TMPL_GROUP_BASE
+#define SYSDB_TMPL_CUSTOM_SUBTREE "cn=%s,"SYSDB_TMPL_CUSTOM_BASE
+#define SYSDB_TMPL_CUSTOM SYSDB_NAME"=%s,cn=%s,"SYSDB_TMPL_CUSTOM_BASE
+
+#define SYSDB_MOD_ADD LDB_FLAG_MOD_ADD
+#define SYSDB_MOD_DEL LDB_FLAG_MOD_DELETE
+#define SYSDB_MOD_REP LDB_FLAG_MOD_REPLACE
+
+struct confdb_ctx;
+struct sysdb_ctx_list;
+struct sysdb_ctx;
+struct sysdb_handle;
+
+struct sysdb_attrs {
+ int num;
+ struct ldb_message_element *a;
+};
+
+/* sysdb_attrs helper functions */
+struct sysdb_attrs *sysdb_new_attrs(TALLOC_CTX *memctx);
+
+/* values are copied in the structure, allocated on "attrs" */
+int sysdb_attrs_add_val(struct sysdb_attrs *attrs,
+ const char *name, const struct ldb_val *val);
+int sysdb_attrs_add_string(struct sysdb_attrs *attrs,
+ const char *name, const char *str);
+int sysdb_attrs_add_long(struct sysdb_attrs *attrs,
+ const char *name, long value);
+int sysdb_attrs_add_uint32(struct sysdb_attrs *attrs,
+ const char *name, uint32_t value);
+int sysdb_attrs_add_time_t(struct sysdb_attrs *attrs,
+ const char *name, time_t value);
+int sysdb_attrs_get_el(struct sysdb_attrs *attrs, const char *name,
+ struct ldb_message_element **el);
+int sysdb_attrs_steal_string(struct sysdb_attrs *attrs,
+ const char *name, char *str);
+int sysdb_attrs_get_string(struct sysdb_attrs *attrs, const char *name,
+ const char **string);
+
+int sysdb_attrs_replace_name(struct sysdb_attrs *attrs, const char *oldname,
+ const char *newname);
+
+int sysdb_attrs_users_from_str_list(struct sysdb_attrs *attrs,
+ const char *attr_name,
+ const char *domain,
+ const char **list);
+int sysdb_attrs_users_from_ldb_vals(struct sysdb_attrs *attrs,
+ const char *attr_name,
+ const char *domain,
+ struct ldb_val *values,
+ int num_values);
+
+/* convert an ldb error into an errno error */
+int sysdb_error_to_errno(int ldberr);
+
+/* DNs related helper functions */
+struct ldb_dn *sysdb_user_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *name);
+struct ldb_dn *sysdb_group_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *name);
+struct ldb_dn *sysdb_domain_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain);
+struct ldb_dn *sysdb_custom_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain, const char *object_name,
+ const char *subtree_name);
+struct ldb_dn *sysdb_custom_subtree_dn(struct sysdb_ctx *ctx, void *memctx,
+ const char *domain,
+ const char *subtree_name);
+
+char *sysdb_user_strdn(TALLOC_CTX *memctx,
+ const char *domain, const char *name);
+char *sysdb_group_strdn(TALLOC_CTX *memctx,
+ const char *domain, const char *name);
+
+
+struct ldb_context *sysdb_ctx_get_ldb(struct sysdb_ctx *ctx);
+struct ldb_context *sysdb_handle_get_ldb(struct sysdb_handle *handle);
+struct sysdb_ctx *sysdb_handle_get_ctx(struct sysdb_handle *handle);
+
+int compare_ldb_dn_comp_num(const void *m1, const void *m2);
+
+/* function to start and finish a transaction
+ * sysdb_transaction_send() will queue a request for a transaction
+ * when it is done it will call the tevent_req callback, which must
+ * retrieve the transaction handle using sysdb_transaction_recv()
+ *
+ * A transaction must be completed either by sending a commit:
+ * sysdb_transaction_commit_send()/sysdb_transaction_commit_recv()
+ * or by freeing the transaction handle (this will implicitly cause
+ * a transaction cancelation).
+ *
+ * Transactions are serialized, no other transaction or operation can be
+ * performed while a transaction is active. Multiple transaction request
+ * are queued internally and served in order.
+ */
+
+struct tevent_req *sysdb_transaction_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *ctx);
+int sysdb_transaction_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle);
+
+struct tevent_req *sysdb_transaction_commit_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle);
+int sysdb_transaction_commit_recv(struct tevent_req *req);
+
+
+/* default transaction commit receive function.
+ * This function does not use the request state so it is safe to use
+ * from any caller */
+void sysdb_transaction_complete(struct tevent_req *subreq);
+
+
+/* Sysdb initialization.
+ * call this function *only* once to initialize the database and get
+ * the sysdb ctx */
+int sysdb_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb,
+ const char *alt_db_path,
+ bool allow_upgrade,
+ struct sysdb_ctx_list **_ctx_list);
+/* used to initialize only one domain database.
+ * Do NOT use if sysdb_init has already been called */
+int sysdb_domain_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *domain,
+ const char *db_path,
+ struct sysdb_ctx **_ctx);
+
+int sysdb_get_ctx_from_list(struct sysdb_ctx_list *ctx_list,
+ struct sss_domain_info *domain,
+ struct sysdb_ctx **_ctx);
+
+/* FIXME: REMOVE */
+typedef void (*sysdb_callback_t)(void *, int, struct ldb_result *);
+
+/* functions to retrieve information from sysdb
+ * These functions automatically starts an operation
+ * therefore they cannot be called within a transaction */
+int sysdb_getpwnam(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_getpwuid(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ uid_t uid,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_enumpwent(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *expression,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_getgrnam(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_getgrgid(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_enumgrent(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_initgroups(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr);
+
+int sysdb_get_user_attr(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attributes,
+ sysdb_callback_t fn, void *ptr);
+
+
+/* functions that modify the databse
+ * they have to be called within a transaction
+ * See sysdb_transaction_send()/_recv() */
+
+/* Delete Entry */
+struct tevent_req *sysdb_delete_entry_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *dn,
+ bool ignore_not_found);
+int sysdb_delete_entry_recv(struct tevent_req *req);
+
+
+struct tevent_req *sysdb_delete_recursive_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *dn,
+ bool ignore_not_found);
+int sysdb_delete_recursive_recv(struct tevent_req *req);
+
+/* Search Entry */
+struct tevent_req *sysdb_search_entry_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *base_dn,
+ int scope,
+ const char *filter,
+ const char **attrs);
+int sysdb_search_entry_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *msgs_size,
+ struct ldb_message ***msgs);
+
+/* Search User (by uid or name) */
+struct tevent_req *sysdb_search_user_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attrs);
+struct tevent_req *sysdb_search_user_by_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ uid_t uid,
+ const char **attrs);
+int sysdb_search_user_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg);
+
+/* Search Group (gy gid or name) */
+struct tevent_req *sysdb_search_group_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attrs);
+struct tevent_req *sysdb_search_group_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ gid_t gid,
+ const char **attrs);
+int sysdb_search_group_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg);
+
+/* Replace entry attrs */
+struct tevent_req *sysdb_set_entry_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *entry_dn,
+ struct sysdb_attrs *attrs,
+ int mod_op);
+int sysdb_set_entry_attr_recv(struct tevent_req *req);
+
+/* Replace user attrs */
+struct tevent_req *sysdb_set_user_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ struct sysdb_attrs *attrs,
+ int mod_op);
+int sysdb_set_user_attr_recv(struct tevent_req *req);
+
+/* Replace group attrs */
+struct tevent_req *sysdb_set_group_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ struct sysdb_attrs *attrs,
+ int mod_op);
+int sysdb_set_group_attr_recv(struct tevent_req *req);
+
+/* Allocate a new id */
+struct tevent_req *sysdb_get_new_id_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain);
+int sysdb_get_new_id_recv(struct tevent_req *req, uint32_t *id);
+
+/* Add user (only basic attrs and w/o checks) */
+struct tevent_req *sysdb_add_basic_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell);
+int sysdb_add_basic_user_recv(struct tevent_req *req);
+
+/* Add user (all checks) */
+struct tevent_req *sysdb_add_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ struct sysdb_attrs *attrs,
+ int cache_timeout);
+int sysdb_add_user_recv(struct tevent_req *req);
+
+/* Add group (only basic attrs and w/o checks) */
+struct tevent_req *sysdb_add_basic_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid);
+int sysdb_add_basic_group_recv(struct tevent_req *req);
+
+/* Add group (all checks) */
+struct tevent_req *sysdb_add_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid,
+ struct sysdb_attrs *attrs,
+ int cache_timeout);
+int sysdb_add_group_recv(struct tevent_req *req);
+
+/* mod_op must be either LDB_FLAG_MOD_ADD or LDB_FLAG_MOD_DELETE */
+struct tevent_req *sysdb_mod_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *member_dn,
+ struct ldb_dn *group_dn,
+ int mod_op);
+int sysdb_mod_group_member_recv(struct tevent_req *req);
+
+int sysdb_set_group_gid(struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid,
+ sysdb_callback_t fn, void *pvt);
+
+struct tevent_req *sysdb_store_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *pwd,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ struct sysdb_attrs *attrs,
+ uint64_t cache_timeout);
+int sysdb_store_user_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_store_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ gid_t gid,
+ struct sysdb_attrs *attrs,
+ uint64_t cache_timeout);
+int sysdb_store_group_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_add_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *group,
+ const char *member);
+int sysdb_add_group_member_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_remove_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *group,
+ const char *member);
+int sysdb_remove_group_member_recv(struct tevent_req *req);
+
+/* Password caching function.
+ * If you are in a transaction ignore sysdb and pass in the handle.
+ * If you are not in a transaction pass NULL in handle and provide sysdb,
+ * in this case a transaction will be automatically started and the
+ * function will be completely wrapped in it's own sysdb transaction */
+struct tevent_req *sysdb_cache_password_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *username,
+ const char *password);
+int sysdb_cache_password_recv(struct tevent_req *req);
+
+
+errno_t check_failed_login_attempts(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
+ struct ldb_message *ldb_msg,
+ uint32_t *failed_login_attempts,
+ time_t *delayed_until);
+struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const uint8_t *authtok,
+ size_t authtok_size,
+ struct confdb_ctx *cdb);
+int sysdb_cache_auth_recv(struct tevent_req *req, time_t *expire_date,
+ time_t *delayed_until);
+
+struct tevent_req *sysdb_store_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name,
+ struct sysdb_attrs *attrs);
+int sysdb_store_custom_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_search_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *filter,
+ const char *subtree_name,
+ const char **attrs);
+struct tevent_req *sysdb_search_custom_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name,
+ const char **attrs);
+int sysdb_search_custom_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *msgs_count,
+ struct ldb_message ***msg);
+
+struct tevent_req *sysdb_delete_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name);
+int sysdb_delete_custom_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_asq_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ struct ldb_dn *base_dn,
+ const char *expression,
+ const char *asq_attribute,
+ const char **attrs);
+int sysdb_asq_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs);
+
+struct tevent_req *sysdb_search_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *sub_filter,
+ const char **attrs);
+int sysdb_search_users_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs);
+
+struct tevent_req *sysdb_delete_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, uid_t uid);
+int sysdb_delete_user_recv(struct tevent_req *req);
+
+struct tevent_req *sysdb_search_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *sub_filter,
+ const char **attrs);
+int sysdb_search_groups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs);
+
+struct tevent_req *sysdb_delete_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid);
+int sysdb_delete_group_recv(struct tevent_req *req);
+
+#endif /* __SYS_DB_H__ */
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
new file mode 100644
index 00000000..33cfd91f
--- /dev/null
+++ b/src/db/sysdb_ops.c
@@ -0,0 +1,5059 @@
+/*
+ SSSD
+
+ System Database
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "db/sysdb_private.h"
+#include "util/sha512crypt.h"
+#include <time.h>
+
+static int add_string(struct ldb_message *msg, int flags,
+ const char *attr, const char *value)
+{
+ int ret;
+
+ ret = ldb_msg_add_empty(msg, attr, flags, NULL);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_msg_add_string(msg, attr, value);
+ if (ret == LDB_SUCCESS) return EOK;
+ }
+ return ENOMEM;
+}
+
+static int add_ulong(struct ldb_message *msg, int flags,
+ const char *attr, unsigned long value)
+{
+ int ret;
+
+ ret = ldb_msg_add_empty(msg, attr, flags, NULL);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_msg_add_fmt(msg, attr, "%lu", value);
+ if (ret == LDB_SUCCESS) return EOK;
+ }
+ return ENOMEM;
+}
+
+static uint32_t get_attr_as_uint32(struct ldb_message *msg, const char *attr)
+{
+ const struct ldb_val *v = ldb_msg_find_ldb_val(msg, attr);
+ long long int l;
+
+ if (!v || !v->data) {
+ return 0;
+ }
+
+ errno = 0;
+ l = strtoll((const char *)v->data, NULL, 0);
+ if (errno) {
+ return (uint32_t)-1;
+ }
+
+ if (l < 0 || l > ((uint32_t)(-1))) {
+ return (uint32_t)-1;
+ }
+
+ return l;
+}
+
+#define ERROR_OUT(v, r, l) do { v = r; goto l; } while(0);
+
+/* =LDB-Request-(tevent_req-style)======================================== */
+
+struct sldb_request_state {
+ struct tevent_context *ev;
+ struct ldb_context *ldbctx;
+ struct ldb_request *ldbreq;
+ struct ldb_reply *ldbreply;
+};
+
+static void sldb_request_wakeup(struct tevent_req *subreq);
+static int sldb_request_callback(struct ldb_request *ldbreq,
+ struct ldb_reply *ldbreply);
+
+static struct tevent_req *sldb_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ldb_context *ldbctx,
+ struct ldb_request *ldbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct sldb_request_state *state;
+ struct timeval tv = { 0, 0 };
+
+ req = tevent_req_create(mem_ctx, &state, struct sldb_request_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ldbctx = ldbctx;
+ state->ldbreq = ldbreq;
+ state->ldbreply = NULL;
+
+ subreq = tevent_wakeup_send(state, ev, tv);
+ if (!subreq) {
+ DEBUG(1, ("Failed to add critical timer to run next ldb operation!\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sldb_request_wakeup, req);
+
+ return req;
+}
+
+static void sldb_request_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sldb_request_state *state = tevent_req_data(req,
+ struct sldb_request_state);
+ int ret;
+
+ if (!tevent_wakeup_recv(subreq)) return;
+ talloc_zfree(subreq);
+
+ state->ldbreq->callback = sldb_request_callback;
+ state->ldbreq->context = req;
+
+ ret = ldb_request(state->ldbctx, state->ldbreq);
+ if (ret != LDB_SUCCESS) {
+ int err = sysdb_error_to_errno(ret);
+ DEBUG(6, ("Error: %d (%s)\n", err, strerror(err)));
+ tevent_req_error(req, err);
+ }
+}
+
+static int sldb_request_callback(struct ldb_request *ldbreq,
+ struct ldb_reply *ldbreply)
+{
+ struct tevent_req *req = talloc_get_type(ldbreq->context,
+ struct tevent_req);
+ struct sldb_request_state *state = tevent_req_data(req,
+ struct sldb_request_state);
+ int err;
+
+ if (!ldbreply) {
+ DEBUG(6, ("Error: Missing ldbreply"));
+ ERROR_OUT(err, EIO, fail);
+ }
+
+ state->ldbreply = talloc_steal(state, ldbreply);
+
+ if (ldbreply->error != LDB_SUCCESS) {
+ DEBUG(6, ("LDB Error: %d (%s)\n",
+ ldbreply->error, ldb_errstring(state->ldbctx)));
+ ERROR_OUT(err, sysdb_error_to_errno(ldbreply->error), fail);
+ }
+
+ if (ldbreply->type == LDB_REPLY_DONE) {
+ tevent_req_done(req);
+ return EOK;
+ }
+
+ tevent_req_notify_callback(req);
+ return EOK;
+
+fail:
+ tevent_req_error(req, err);
+ return EOK;
+}
+
+static int sldb_request_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_reply **ldbreply)
+{
+ struct sldb_request_state *state = tevent_req_data(req,
+ struct sldb_request_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (state->ldbreply) {
+ *ldbreply = talloc_move(mem_ctx, &state->ldbreply);
+ }
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ switch (tstate) {
+ case TEVENT_REQ_USER_ERROR:
+ return err;
+ case TEVENT_REQ_IN_PROGRESS:
+ return EOK;
+ default:
+ return EIO;
+ }
+ }
+
+ return EOK;
+}
+
+/* =Standard-Sysdb-Operations-utility-functions=========================== */
+
+struct sysdb_op_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ bool ignore_not_found;
+
+ struct ldb_reply *ldbreply;
+ size_t msgs_count;
+ struct ldb_message **msgs;
+};
+
+static void sysdb_op_default_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_op_state *state = tevent_req_data(req,
+ struct sysdb_op_state);
+ int ret;
+
+ ret = sldb_request_recv(subreq, state, &state->ldbreply);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (state->ignore_not_found && ret == ENOENT) {
+ goto done;
+ }
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->ldbreply->type != LDB_REPLY_DONE) {
+ DEBUG(6, ("Error: %d (%s)\n", EIO, strerror(EIO)));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+done:
+ tevent_req_done(req);
+}
+
+static int sysdb_op_default_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Remove-Entry-From-Sysdb=============================================== */
+
+struct tevent_req *sysdb_delete_entry_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *dn,
+ bool ignore_not_found)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = ignore_not_found;
+ state->ldbreply = NULL;
+
+ ret = ldb_build_del_req(&ldbreq, handle->ctx->ldb, state, dn,
+ NULL, NULL, NULL, NULL);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("LDB Error: %s(%d)\nError Message: [%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_op_default_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_delete_entry_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Remove-Subentries-From-Sysdb=============================================== */
+
+struct sysdb_delete_recursive_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ bool ignore_not_found;
+
+ struct ldb_reply *ldbreply;
+ size_t msgs_count;
+ struct ldb_message **msgs;
+ size_t current_item;
+};
+
+static void sysdb_delete_search_done(struct tevent_req *subreq);
+static void sysdb_delete_recursive_prepare_op(struct tevent_req *req);
+static void sysdb_delete_recursive_op_done(struct tevent_req *req);
+
+struct tevent_req *sysdb_delete_recursive_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *dn,
+ bool ignore_not_found)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_delete_recursive_state *state;
+ int ret;
+ const char **no_attrs;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct sysdb_delete_recursive_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = ignore_not_found;
+ state->ldbreply = NULL;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+ state->current_item = 0;
+
+ no_attrs = talloc_array(state, const char *, 1);
+ if (no_attrs == NULL) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ no_attrs[0] = NULL;
+
+ subreq = sysdb_search_entry_send(state, ev, handle, dn, LDB_SCOPE_SUBTREE,
+ "(distinguishedName=*)", no_attrs);
+
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_search_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_delete_search_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_delete_recursive_state *state = tevent_req_data(req,
+ struct sysdb_delete_recursive_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state, &state->msgs_count,
+ &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (state->ignore_not_found && ret == ENOENT) {
+ tevent_req_done(req);
+ return;
+ }
+ DEBUG(6, ("Search error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ DEBUG(9, ("Found [%d] items to delete.\n", state->msgs_count));
+
+ qsort(state->msgs, state->msgs_count, sizeof(struct ldb_message *),
+ compare_ldb_dn_comp_num);
+
+ state->current_item = 0;
+ sysdb_delete_recursive_prepare_op(req);
+}
+
+static void sysdb_delete_recursive_prepare_op(struct tevent_req *req)
+{
+ struct sysdb_delete_recursive_state *state = tevent_req_data(req,
+ struct sysdb_delete_recursive_state);
+ struct tevent_req *subreq;
+ int ret;
+ struct ldb_request *ldbreq;
+
+ if (state->current_item < state->msgs_count) {
+ DEBUG(9 ,("Trying to delete [%s].\n",
+ ldb_dn_canonical_string(state,
+ state->msgs[state->current_item]->dn)));
+ ret = ldb_build_del_req(&ldbreq, state->handle->ctx->ldb, state,
+ state->msgs[state->current_item]->dn, NULL,
+ NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("LDB Error: %s(%d)\nError Message: [%s]\n",
+ ldb_strerror(ret), ret,
+ ldb_errstring(state->handle->ctx->ldb)));
+ ret = sysdb_error_to_errno(ret);
+ goto fail;
+ }
+
+ subreq = sldb_request_send(state, state->ev, state->handle->ctx->ldb,
+ ldbreq);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->current_item++;
+ tevent_req_set_callback(subreq, sysdb_delete_recursive_op_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+}
+
+static void sysdb_delete_recursive_op_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_op_default_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Delete error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sysdb_delete_recursive_prepare_op(req);
+}
+
+int sysdb_delete_recursive_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Search-Entry========================================================== */
+
+static void sysdb_search_entry_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_entry_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *base_dn,
+ int scope,
+ const char *filter,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ ret = ldb_build_search_req(&ldbreq, handle->ctx->ldb, state,
+ base_dn, scope, filter, attrs,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build search request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_entry_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_search_entry_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_op_state *state = tevent_req_data(req,
+ struct sysdb_op_state);
+ struct ldb_reply *ldbreply;
+ struct ldb_message **dummy;
+ int ret;
+
+ ret = sldb_request_recv(subreq, state, &ldbreply);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (ldbreply->type) {
+ case LDB_REPLY_ENTRY:
+ dummy = talloc_realloc(state, state->msgs,
+ struct ldb_message *,
+ state->msgs_count + 2);
+ if (dummy == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ state->msgs = dummy;
+
+ state->msgs[state->msgs_count + 1] = NULL;
+
+ state->msgs[state->msgs_count] = talloc_steal(state->msgs,
+ ldbreply->message);
+ state->msgs_count++;
+
+ talloc_zfree(ldbreply);
+ return;
+
+ case LDB_REPLY_DONE:
+ talloc_zfree(subreq);
+ talloc_zfree(ldbreply);
+ if (state->msgs_count == 0) {
+ DEBUG(6, ("Error: Entry not Found!\n"));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+ return tevent_req_done(req);
+
+ default:
+ /* unexpected stuff */
+ talloc_zfree(ldbreply);
+ DEBUG(6, ("Error: Unknown error!\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+int sysdb_search_entry_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *msgs_count,
+ struct ldb_message ***msgs)
+{
+ struct sysdb_op_state *state = tevent_req_data(req,
+ struct sysdb_op_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *msgs_count = state->msgs_count;
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+
+ return EOK;
+}
+
+
+/* =Search-User-by-[UID/NAME]============================================= */
+
+struct sysdb_search_user_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ struct ldb_dn *basedn;
+ const char **attrs;
+ const char *filter;
+ int scope;
+
+ size_t msgs_count;
+ struct ldb_message **msgs;
+};
+
+static void sysdb_search_user_cont(struct tevent_req *subreq);
+static void sysdb_search_user_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_user_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_user_state *state;
+ static const char *def_attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, NULL };
+ int ret;
+
+ if (!sysdb && !handle) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ state->attrs = attrs ? attrs : def_attrs;
+ state->filter = NULL;
+ state->scope = LDB_SCOPE_BASE;
+
+ if (!sysdb) sysdb = handle->ctx;
+
+ state->basedn = sysdb_user_dn(sysdb, state, domain->name, name);
+ if (!state->basedn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ if (!handle) {
+ subreq = sysdb_operation_send(state, state->ev, sysdb);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_user_cont, req);
+ }
+ else {
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_user_done, req);
+ }
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+struct tevent_req *sysdb_search_user_by_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ uid_t uid,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_user_state *state;
+ static const char *def_attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, NULL };
+ int ret;
+
+ if (!sysdb && !handle) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+ state->attrs = attrs ? attrs : def_attrs;
+
+ if (!sysdb) sysdb = handle->ctx;
+
+ state->basedn = ldb_dn_new_fmt(state, sysdb->ldb,
+ SYSDB_TMPL_USER_BASE, domain->name);
+ if (!state->basedn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ state->filter = talloc_asprintf(state, SYSDB_PWUID_FILTER,
+ (unsigned long)uid);
+ if (!state->filter) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ state->scope = LDB_SCOPE_ONELEVEL;
+
+ if (!handle) {
+ subreq = sysdb_operation_send(state, state->ev, sysdb);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_user_cont, req);
+ }
+ else {
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_user_done, req);
+ }
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_search_user_cont(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_user_state *state = tevent_req_data(req,
+ struct sysdb_search_user_state);
+ int ret;
+
+ ret = sysdb_operation_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_user_done, req);
+}
+
+static void sysdb_search_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_user_state *state = tevent_req_data(req,
+ struct sysdb_search_user_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state, &state->msgs_count,
+ &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_search_user_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg)
+{
+ struct sysdb_search_user_state *state = tevent_req_data(req,
+ struct sysdb_search_user_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (state->msgs_count > 1) {
+ DEBUG(1, ("More than one result found.\n"));
+ return EFAULT;
+ }
+
+ *msg = talloc_move(mem_ctx, &state->msgs[0]);
+
+ return EOK;
+}
+
+
+/* =Search-Group-by-[GID/NAME]============================================ */
+
+struct sysdb_search_group_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ struct ldb_dn *basedn;
+ const char **attrs;
+ const char *filter;
+ int scope;
+
+ size_t msgs_count;
+ struct ldb_message **msgs;
+};
+
+static void sysdb_search_group_cont(struct tevent_req *subreq);
+static void sysdb_search_group_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_group_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_group_state *state;
+ static const char *def_attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL };
+ int ret;
+
+ if (!sysdb && !handle) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ state->attrs = attrs ? attrs : def_attrs;
+ state->filter = NULL;
+ state->scope = LDB_SCOPE_BASE;
+
+ if (!sysdb) sysdb = handle->ctx;
+
+ state->basedn = sysdb_group_dn(sysdb, state, domain->name, name);
+ if (!state->basedn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ if (!handle) {
+ subreq = sysdb_operation_send(state, state->ev, sysdb);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_group_cont, req);
+ }
+ else {
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_group_done, req);
+ }
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+struct tevent_req *sysdb_search_group_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ gid_t gid,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_group_state *state;
+ static const char *def_attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL };
+ int ret;
+
+ if (!sysdb && !handle) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+ state->attrs = attrs ? attrs : def_attrs;
+
+ if (!sysdb) sysdb = handle->ctx;
+
+ state->basedn = ldb_dn_new_fmt(state, sysdb->ldb,
+ SYSDB_TMPL_GROUP_BASE, domain->name);
+ if (!state->basedn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ state->filter = talloc_asprintf(state, SYSDB_GRGID_FILTER,
+ (unsigned long)gid);
+ if (!state->filter) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ state->scope = LDB_SCOPE_ONELEVEL;
+
+ if (!handle) {
+ subreq = sysdb_operation_send(state, state->ev, sysdb);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_group_cont, req);
+ }
+ else {
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_search_group_done, req);
+ }
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_search_group_cont(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_group_state *state = tevent_req_data(req,
+ struct sysdb_search_group_state);
+ int ret;
+
+ ret = sysdb_operation_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_group_done, req);
+}
+
+static void sysdb_search_group_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_group_state *state = tevent_req_data(req,
+ struct sysdb_search_group_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state, &state->msgs_count,
+ &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_search_group_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg)
+{
+ struct sysdb_search_group_state *state = tevent_req_data(req,
+ struct sysdb_search_group_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (state->msgs_count > 1) {
+ DEBUG(1, ("More than one result found.\n"));
+ return EFAULT;
+ }
+
+ *msg = talloc_move(mem_ctx, &state->msgs[0]);
+
+ return EOK;
+}
+
+
+/* =Replace-Attributes-On-Entry=========================================== */
+
+struct tevent_req *sysdb_set_entry_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *entry_dn,
+ struct sysdb_attrs *attrs,
+ int mod_op)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ struct ldb_message *msg;
+ int i, ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ if (!entry_dn) {
+ ERROR_OUT(ret, EINVAL, fail);
+ }
+
+ if (attrs->num == 0) {
+ ERROR_OUT(ret, EINVAL, fail);
+ }
+
+ msg = ldb_msg_new(state);
+ if (!msg) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ msg->dn = entry_dn;
+
+ msg->elements = talloc_array(msg, struct ldb_message_element, attrs->num);
+ if (!msg->elements) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ for (i = 0; i < attrs->num; i++) {
+ msg->elements[i] = attrs->a[i];
+ msg->elements[i].flags = mod_op;
+ }
+
+ msg->num_elements = attrs->num;
+
+ ret = ldb_build_mod_req(&ldbreq, handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build modify request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_op_default_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_set_entry_attr_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Replace-Attributes-On-User============================================ */
+
+static void sysdb_set_user_attr_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_set_user_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ struct sysdb_attrs *attrs,
+ int mod_op)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_dn *dn;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ dn = sysdb_user_dn(handle->ctx, state, domain->name, name);
+ if (!dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ subreq = sysdb_set_entry_attr_send(state, ev, handle, dn, attrs, mod_op);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_set_user_attr_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_set_user_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_entry_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_set_user_attr_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Replace-Attributes-On-Group=========================================== */
+
+static void sysdb_set_group_attr_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_set_group_attr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ struct sysdb_attrs *attrs,
+ int mod_op)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_dn *dn;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ dn = sysdb_group_dn(handle->ctx, state, domain->name, name);
+ if (!dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ subreq = sysdb_set_entry_attr_send(state, ev, handle, dn, attrs, mod_op);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_set_group_attr_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_set_group_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_entry_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_set_group_attr_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Get-New-ID============================================================ */
+
+struct sysdb_get_new_id_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ struct ldb_dn *base_dn;
+ struct ldb_message *base;
+
+ struct ldb_message **v_msgs;
+ int v_count;
+
+ uint32_t new_id;
+};
+
+static void sysdb_get_new_id_base(struct tevent_req *subreq);
+static void sysdb_get_new_id_verify(struct tevent_req *subreq);
+static void sysdb_get_new_id_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_get_new_id_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_get_new_id_state *state;
+ static const char *attrs[] = { SYSDB_NEXTID, NULL };
+ struct ldb_request *ldbreq;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_get_new_id_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->base = NULL;
+ state->v_msgs = NULL;
+ state->v_count = 0;
+ state->new_id = 0;
+
+ state->base_dn = sysdb_domain_dn(handle->ctx, state, domain->name);
+ if (!state->base_dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ ret = ldb_build_search_req(&ldbreq, handle->ctx->ldb, state,
+ state->base_dn, LDB_SCOPE_BASE,
+ SYSDB_NEXTID_FILTER, attrs,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build search request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_get_new_id_base, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_get_new_id_base(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_get_new_id_state *state = tevent_req_data(req,
+ struct sysdb_get_new_id_state);
+ static const char *attrs[] = { SYSDB_UIDNUM, SYSDB_GIDNUM, NULL };
+ struct ldb_reply *ldbreply;
+ struct ldb_request *ldbreq;
+ char *filter;
+ int ret;
+
+ ret = sldb_request_recv(subreq, state, &ldbreply);
+ if (ret) {
+ talloc_zfree(subreq);
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (ldbreply->type) {
+ case LDB_REPLY_ENTRY:
+ if (state->base) {
+ DEBUG(1, ("More than one reply for a base search ?! "
+ "DB seems corrupted, aborting."));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ state->base = talloc_move(state, &ldbreply->message);
+ if (!state->base) {
+ DEBUG(6, ("Error: Out of memory!\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* just return, wait for a LDB_REPLY_DONE entry */
+ talloc_zfree(ldbreply);
+ return;
+
+ case LDB_REPLY_DONE:
+ break;
+
+ default:
+ /* unexpected stuff */
+ DEBUG(6, ("Error: Unknown error\n"));
+ tevent_req_error(req, EIO);
+ talloc_zfree(ldbreply);
+ return;
+ }
+
+ talloc_zfree(subreq);
+
+ if (state->base) {
+ state->new_id = get_attr_as_uint32(state->base, SYSDB_NEXTID);
+ if (state->new_id == (uint32_t)(-1)) {
+ DEBUG(1, ("Invalid Next ID in domain %s\n", state->domain->name));
+ tevent_req_error(req, ERANGE);
+ return;
+ }
+
+ if (state->new_id < state->domain->id_min) {
+ state->new_id = state->domain->id_min;
+ }
+
+ if ((state->domain->id_max != 0) &&
+ (state->new_id > state->domain->id_max)) {
+ DEBUG(0, ("Failed to allocate new id, out of range (%u/%u)\n",
+ state->new_id, state->domain->id_max));
+ tevent_req_error(req, ERANGE);
+ return;
+ }
+
+ } else {
+ /* looks like the domain is not initialized yet, use min_id */
+ state->new_id = state->domain->id_min;
+ }
+
+ /* verify the id is actually really free.
+ * search all entries with id >= new_id and < max_id */
+ if (state->domain->id_max) {
+ filter = talloc_asprintf(state,
+ "(|(&(%s>=%u)(%s<=%u))(&(%s>=%u)(%s<=%u)))",
+ SYSDB_UIDNUM, state->new_id,
+ SYSDB_UIDNUM, state->domain->id_max,
+ SYSDB_GIDNUM, state->new_id,
+ SYSDB_GIDNUM, state->domain->id_max);
+ }
+ else {
+ filter = talloc_asprintf(state,
+ "(|(%s>=%u)(%s>=%u))",
+ SYSDB_UIDNUM, state->new_id,
+ SYSDB_GIDNUM, state->new_id);
+ }
+ if (!filter) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ret = ldb_build_search_req(&ldbreq, state->handle->ctx->ldb, state,
+ state->base_dn, LDB_SCOPE_SUBTREE,
+ filter, attrs,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build search request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret,
+ ldb_errstring(state->handle->ctx->ldb)));
+ tevent_req_error(req, sysdb_error_to_errno(ret));
+ return;
+ }
+
+ subreq = sldb_request_send(state, state->ev,
+ state->handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_get_new_id_verify, req);
+
+ return;
+}
+
+static void sysdb_get_new_id_verify(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_get_new_id_state *state = tevent_req_data(req,
+ struct sysdb_get_new_id_state);
+ struct ldb_reply *ldbreply;
+ struct ldb_request *ldbreq;
+ struct ldb_message *msg;
+ int ret, i;
+
+ ret = sldb_request_recv(subreq, state, &ldbreply);
+ if (ret) {
+ talloc_zfree(subreq);
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (ldbreply->type) {
+ case LDB_REPLY_ENTRY:
+ state->v_msgs = talloc_realloc(state, state->v_msgs,
+ struct ldb_message *,
+ state->v_count + 2);
+ if (!state->v_msgs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->v_msgs[state->v_count] = talloc_move(state, &ldbreply->message);
+ if (!state->v_msgs[state->v_count]) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ state->v_count++;
+
+ /* just return, wait for a LDB_REPLY_DONE entry */
+ talloc_zfree(ldbreply);
+ return;
+
+ case LDB_REPLY_DONE:
+ break;
+
+ default:
+ /* unexpected stuff */
+ DEBUG(6, ("Error: Unknown error\n"));
+ tevent_req_error(req, EIO);
+ talloc_zfree(ldbreply);
+ return;
+ }
+
+ talloc_zfree(subreq);
+
+ /* if anything was found, find the maximum and increment past it */
+ if (state->v_count) {
+ uint32_t id;
+
+ for (i = 0; i < state->v_count; i++) {
+ id = get_attr_as_uint32(state->v_msgs[i], SYSDB_UIDNUM);
+ if (id != (uint32_t)(-1)) {
+ if (id > state->new_id) state->new_id = id;
+ }
+ id = get_attr_as_uint32(state->v_msgs[i], SYSDB_GIDNUM);
+ if (id != (uint32_t)(-1)) {
+ if (id > state->new_id) state->new_id = id;
+ }
+ }
+
+ state->new_id++;
+
+ /* check again we are not falling out of range */
+ if ((state->domain->id_max != 0) &&
+ (state->new_id > state->domain->id_max)) {
+ DEBUG(0, ("Failed to allocate new id, out of range (%u/%u)\n",
+ state->new_id, state->domain->id_max));
+ tevent_req_error(req, ERANGE);
+ return;
+ }
+
+ talloc_zfree(state->v_msgs);
+ state->v_count = 0;
+ }
+
+ /* finally store the new next id */
+ msg = ldb_msg_new(state);
+ if (!msg) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ msg->dn = state->base_dn;
+
+ ret = add_ulong(msg, LDB_FLAG_MOD_REPLACE,
+ SYSDB_NEXTID, state->new_id + 1);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ldb_build_mod_req(&ldbreq, state->handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build modify request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret,
+ ldb_errstring(state->handle->ctx->ldb)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sldb_request_send(state, state->ev,
+ state->handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_get_new_id_done, req);
+}
+
+static void sysdb_get_new_id_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_get_new_id_state *state = tevent_req_data(req,
+ struct sysdb_get_new_id_state);
+ struct ldb_reply *ldbreply;
+ int ret;
+
+ ret = sldb_request_recv(subreq, state, &ldbreply);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ldbreply->type != LDB_REPLY_DONE) {
+ DEBUG(6, ("Error: %d (%s)\n", EIO, strerror(EIO)));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_get_new_id_recv(struct tevent_req *req, uint32_t *id)
+{
+ struct sysdb_get_new_id_state *state = tevent_req_data(req,
+ struct sysdb_get_new_id_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *id = state->new_id;
+
+ return EOK;
+}
+
+
+/* =Add-Basic-User-NO-CHECKS============================================== */
+
+struct tevent_req *sysdb_add_basic_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ struct ldb_message *msg;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ msg = ldb_msg_new(state);
+ if (!msg) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ /* user dn */
+ msg->dn = sysdb_user_dn(handle->ctx, msg, domain->name, name);
+ if (!msg->dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, "objectClass", SYSDB_USER_CLASS);
+ if (ret) goto fail;
+
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_NAME, name);
+ if (ret) goto fail;
+
+ ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_UIDNUM, (unsigned long)uid);
+ if (ret) goto fail;
+
+ ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_GIDNUM, (unsigned long)gid);
+ if (ret) goto fail;
+
+ /* We set gecos to be the same as fullname on user creation,
+ * But we will not enforce coherency after that, it's up to
+ * admins to decide if they want to keep it in sync if they change
+ * one of the 2 */
+ if (gecos && *gecos) {
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_FULLNAME, gecos);
+ if (ret) goto fail;
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_GECOS, gecos);
+ if (ret) goto fail;
+ }
+
+ if (homedir && *homedir) {
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_HOMEDIR, homedir);
+ if (ret) goto fail;
+ }
+
+ if (shell && *shell) {
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_SHELL, shell);
+ if (ret) goto fail;
+ }
+
+ /* creation time */
+ ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_CREATE_TIME,
+ (unsigned long)time(NULL));
+ if (ret) goto fail;
+
+
+ ret = ldb_build_add_req(&ldbreq, handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build modify request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_op_default_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_add_basic_user_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Add-User-Function===================================================== */
+
+struct sysdb_add_user_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ uid_t uid;
+ gid_t gid;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ struct sysdb_attrs *attrs;
+
+ int cache_timeout;
+};
+
+static void sysdb_add_user_group_check(struct tevent_req *subreq);
+static void sysdb_add_user_uid_check(struct tevent_req *subreq);
+static void sysdb_add_user_basic_done(struct tevent_req *subreq);
+static void sysdb_add_user_get_id_done(struct tevent_req *subreq);
+static void sysdb_add_user_set_id_done(struct tevent_req *subreq);
+static void sysdb_add_user_set_attrs(struct tevent_req *req);
+static void sysdb_add_user_set_attrs_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_add_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ struct sysdb_attrs *attrs,
+ int cache_timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_add_user_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_add_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->uid = uid;
+ state->gid = gid;
+ state->gecos = gecos;
+ state->homedir = homedir;
+ state->shell = shell;
+ state->attrs = attrs;
+ state->cache_timeout = cache_timeout;
+
+ if (handle->ctx->mpg) {
+ if (gid != 0) {
+ DEBUG(0, ("Cannot add user with arbitrary GID in MPG domain!\n"));
+ ERROR_OUT(ret, EINVAL, fail);
+ }
+ state->gid = state->uid;
+ }
+
+ if (domain->id_max != 0 && uid != 0 &&
+ (uid < domain->id_min || uid > domain->id_max)) {
+ DEBUG(2, ("Supplied uid [%d] is not in the allowed range [%d-%d].\n",
+ uid, domain->id_min, domain->id_max));
+ ERROR_OUT(ret, ERANGE, fail);
+ }
+
+ if (domain->id_max != 0 && gid != 0 &&
+ (gid < domain->id_min || gid > domain->id_max)) {
+ DEBUG(2, ("Supplied gid [%d] is not in the allowed range [%d-%d].\n",
+ gid, domain->id_min, domain->id_max));
+ ERROR_OUT(ret, ERANGE, fail);
+ }
+
+ if (handle->ctx->mpg) {
+ /* In MPG domains you can't have groups with the same name as users,
+ * search if a group with the same name exists.
+ * Don't worry about users, if we try to add a user with the same
+ * name the operation will fail */
+
+ subreq = sysdb_search_group_by_name_send(state, ev, NULL, handle,
+ domain, name, NULL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_group_check, req);
+ return req;
+ }
+
+ /* check no other user with the same uid exist */
+ if (state->uid != 0) {
+ subreq = sysdb_search_user_by_uid_send(state, ev, NULL, handle,
+ domain, uid, NULL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_uid_check, req);
+ return req;
+ }
+
+ /* try to add the user */
+ subreq = sysdb_add_basic_user_send(state, ev, handle,
+ domain, name, uid, gid,
+ gecos, homedir, shell);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_basic_done, req);
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_add_user_group_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ struct ldb_message *msg;
+ int ret;
+
+ /* We can succeed only if we get an ENOENT error, which means no groups
+ * with the same name exist.
+ * If any other error is returned fail as well. */
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret != ENOENT) {
+ if (ret == EOK) ret = EEXIST;
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* check no other user with the same uid exist */
+ if (state->uid != 0) {
+ subreq = sysdb_search_user_by_uid_send(state, state->ev,
+ NULL, state->handle,
+ state->domain, state->uid,
+ NULL);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_uid_check, req);
+ return;
+ }
+
+ /* try to add the user */
+ subreq = sysdb_add_basic_user_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->uid, state->gid,
+ state->gecos,
+ state->homedir,
+ state->shell);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_basic_done, req);
+}
+
+static void sysdb_add_user_uid_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ struct ldb_message *msg;
+ int ret;
+
+ /* We can succeed only if we get an ENOENT error, which means no user
+ * with the same uid exist.
+ * If any other error is returned fail as well. */
+ ret = sysdb_search_user_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret != ENOENT) {
+ if (ret == EOK) ret = EEXIST;
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* try to add the user */
+ subreq = sysdb_add_basic_user_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->uid, state->gid,
+ state->gecos,
+ state->homedir,
+ state->shell);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_basic_done, req);
+}
+
+static void sysdb_add_user_basic_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ int ret;
+
+ ret = sysdb_add_basic_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->uid == 0) {
+ subreq = sysdb_get_new_id_send(state,
+ state->ev, state->handle,
+ state->domain);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_get_id_done, req);
+ return;
+ }
+
+ sysdb_add_user_set_attrs(req);
+}
+
+static void sysdb_add_user_get_id_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ struct sysdb_attrs *id_attrs;
+ uint32_t id;
+ int ret;
+
+ ret = sysdb_get_new_id_recv(subreq, &id);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->uid == 0) {
+ id_attrs = sysdb_new_attrs(state);
+ if (!id_attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_add_uint32(id_attrs, SYSDB_UIDNUM, id);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (state->handle->ctx->mpg) {
+ ret = sysdb_attrs_add_uint32(id_attrs, SYSDB_GIDNUM, id);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ id_attrs, SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_set_id_done, req);
+ return;
+ }
+
+ sysdb_add_user_set_attrs(req);
+}
+
+static void sysdb_add_user_set_id_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->attrs) {
+ }
+
+ tevent_req_done(req);
+}
+
+static void sysdb_add_user_set_attrs(struct tevent_req *req)
+{
+ struct sysdb_add_user_state *state = tevent_req_data(req,
+ struct sysdb_add_user_state);
+ struct tevent_req *subreq;
+ time_t now = time(NULL);
+ int ret;
+
+ if (!state->attrs) {
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_LAST_UPDATE, now);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_CACHE_EXPIRE,
+ ((state->cache_timeout) ?
+ (now + state->cache_timeout) : 0));
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->attrs,
+ SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_user_set_attrs_done, req);
+}
+
+static void sysdb_add_user_set_attrs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_add_user_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Add-Basic-Group-NO-CHECKS============================================= */
+
+struct tevent_req *sysdb_add_basic_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ struct ldb_message *msg;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ msg = ldb_msg_new(state);
+ if (!msg) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ /* user dn */
+ msg->dn = sysdb_group_dn(handle->ctx, msg, domain->name, name);
+ if (!msg->dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, "objectClass", SYSDB_GROUP_CLASS);
+ if (ret) goto fail;
+
+ ret = add_string(msg, LDB_FLAG_MOD_ADD, SYSDB_NAME, name);
+ if (ret) goto fail;
+
+ ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_GIDNUM, (unsigned long)gid);
+ if (ret) goto fail;
+
+ /* creation time */
+ ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_CREATE_TIME,
+ (unsigned long)time(NULL));
+ if (ret) goto fail;
+
+
+ ret = ldb_build_add_req(&ldbreq, handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build modify request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_op_default_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_add_basic_group_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Add-Group-Function==================================================== */
+
+struct sysdb_add_group_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ gid_t gid;
+ struct sysdb_attrs *attrs;
+
+ int cache_timeout;
+};
+
+static void sysdb_add_group_user_check(struct tevent_req *subreq);
+static void sysdb_add_group_gid_check(struct tevent_req *subreq);
+static void sysdb_add_group_basic_done(struct tevent_req *subreq);
+static void sysdb_add_group_get_id_done(struct tevent_req *subreq);
+static void sysdb_add_group_set_attrs(struct tevent_req *req);
+static void sysdb_add_group_set_attrs_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_add_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid,
+ struct sysdb_attrs *attrs,
+ int cache_timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_add_group_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_add_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->gid = gid;
+ state->attrs = attrs;
+ state->cache_timeout = cache_timeout;
+
+ if (domain->id_max != 0 && gid != 0 &&
+ (gid < domain->id_min || gid > domain->id_max)) {
+ DEBUG(2, ("Supplied gid [%d] is not in the allowed range [%d-%d].\n",
+ gid, domain->id_min, domain->id_max));
+ ERROR_OUT(ret, ERANGE, fail);
+ }
+
+ if (handle->ctx->mpg) {
+ /* In MPG domains you can't have groups with the same name as users,
+ * search if a group with the same name exists.
+ * Don't worry about users, if we try to add a user with the same
+ * name the operation will fail */
+
+ subreq = sysdb_search_user_by_name_send(state, ev, NULL, handle,
+ domain, name, NULL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_user_check, req);
+ return req;
+ }
+
+ /* check no other groups with the same gid exist */
+ if (state->gid != 0) {
+ subreq = sysdb_search_group_by_gid_send(state, ev, NULL, handle,
+ domain, gid, NULL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_gid_check, req);
+ return req;
+ }
+
+ /* try to add the group */
+ subreq = sysdb_add_basic_group_send(state, ev, handle,
+ domain, name, gid);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_basic_done, req);
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_add_group_user_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_group_state *state = tevent_req_data(req,
+ struct sysdb_add_group_state);
+ struct ldb_message *msg;
+ int ret;
+
+ /* We can succeed only if we get an ENOENT error, which means no users
+ * with the same name exist.
+ * If any other error is returned fail as well. */
+ ret = sysdb_search_user_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret != ENOENT) {
+ if (ret == EOK) ret = EEXIST;
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* check no other group with the same gid exist */
+ if (state->gid != 0) {
+ subreq = sysdb_search_group_by_gid_send(state, state->ev,
+ NULL, state->handle,
+ state->domain, state->gid,
+ NULL);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_gid_check, req);
+ return;
+ }
+
+ /* try to add the group */
+ subreq = sysdb_add_basic_group_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->gid);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_basic_done, req);
+}
+
+static void sysdb_add_group_gid_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_group_state *state = tevent_req_data(req,
+ struct sysdb_add_group_state);
+ struct ldb_message *msg;
+ int ret;
+
+ /* We can succeed only if we get an ENOENT error, which means no group
+ * with the same gid exist.
+ * If any other error is returned fail as well. */
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret != ENOENT) {
+ if (ret == EOK) ret = EEXIST;
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* try to add the group */
+ subreq = sysdb_add_basic_group_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->gid);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_basic_done, req);
+}
+
+static void sysdb_add_group_basic_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_group_state *state = tevent_req_data(req,
+ struct sysdb_add_group_state);
+ int ret;
+
+ ret = sysdb_add_basic_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->gid == 0) {
+ subreq = sysdb_get_new_id_send(state,
+ state->ev, state->handle,
+ state->domain);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_get_id_done, req);
+ return;
+ }
+
+ sysdb_add_group_set_attrs(req);
+}
+
+static void sysdb_add_group_get_id_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_add_group_state *state = tevent_req_data(req,
+ struct sysdb_add_group_state);
+ uint32_t id;
+ int ret;
+
+ ret = sysdb_get_new_id_recv(subreq, &id);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->gid == 0) {
+ if (!state->attrs) {
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_GIDNUM, id);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ sysdb_add_group_set_attrs(req);
+}
+
+static void sysdb_add_group_set_attrs(struct tevent_req *req)
+{
+ struct sysdb_add_group_state *state = tevent_req_data(req,
+ struct sysdb_add_group_state);
+ struct tevent_req *subreq;
+ time_t now = time(NULL);
+ int ret;
+
+ if (!state->attrs) {
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_LAST_UPDATE, now);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_CACHE_EXPIRE,
+ ((state->cache_timeout) ?
+ (now + state->cache_timeout) : 0));
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_group_attr_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->attrs,
+ SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_set_attrs_done, req);
+}
+
+static void sysdb_add_group_set_attrs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_group_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_add_group_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Add-Or-Remove-Group-Memeber=========================================== */
+
+/* mod_op must be either SYSDB_MOD_ADD or SYSDB_MOD_DEL */
+struct tevent_req *sysdb_mod_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct ldb_dn *member_dn,
+ struct ldb_dn *group_dn,
+ int mod_op)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_request *ldbreq;
+ struct ldb_message *msg;
+ const char *dn;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ msg = ldb_msg_new(state);
+ if (!msg) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ msg->dn = group_dn;
+ ret = ldb_msg_add_empty(msg, SYSDB_MEMBER, mod_op, NULL);
+ if (ret != LDB_SUCCESS) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ dn = ldb_dn_get_linearized(member_dn);
+ if (!dn) {
+ ERROR_OUT(ret, EINVAL, fail);
+ }
+
+ ret = ldb_msg_add_fmt(msg, SYSDB_MEMBER, "%s", dn);
+ if (ret != LDB_SUCCESS) {
+ ERROR_OUT(ret, EINVAL, fail);
+ }
+
+ ret = ldb_build_mod_req(&ldbreq, handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build modify request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret, ldb_errstring(handle->ctx->ldb)));
+ ERROR_OUT(ret, sysdb_error_to_errno(ret), fail);
+ }
+
+ subreq = sldb_request_send(state, ev, handle->ctx->ldb, ldbreq);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_op_default_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+int sysdb_mod_group_member_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Store-Users-(Native/Legacy)-(replaces-existing-data)================== */
+
+/* if one of the basic attributes is empty ("") as opposed to NULL,
+ * this will just remove it */
+
+struct sysdb_store_user_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ uid_t uid;
+ gid_t gid;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ struct sysdb_attrs *attrs;
+
+ uint64_t cache_timeout;
+};
+
+static void sysdb_store_user_check(struct tevent_req *subreq);
+static void sysdb_store_user_add_done(struct tevent_req *subreq);
+static void sysdb_store_user_attr_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_store_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *pwd,
+ uid_t uid, gid_t gid,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ struct sysdb_attrs *attrs,
+ uint64_t cache_timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_store_user_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_store_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->uid = uid;
+ state->gid = gid;
+ state->gecos = gecos;
+ state->homedir = homedir;
+ state->shell = shell;
+ state->attrs = attrs;
+ state->cache_timeout = cache_timeout;
+
+ if (pwd && (domain->legacy_passwords || !*pwd)) {
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_PWD, pwd);
+ if (ret) goto fail;
+ }
+
+ subreq = sysdb_search_user_by_name_send(state, ev, NULL, handle,
+ domain, name, NULL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_store_user_check, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_store_user_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_store_user_state *state = tevent_req_data(req,
+ struct sysdb_store_user_state);
+ struct ldb_message *msg;
+ time_t now = time(NULL);
+ int ret;
+
+ ret = sysdb_search_user_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ /* users doesn't exist, turn into adding a user */
+ subreq = sysdb_add_user_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->uid, state->gid,
+ state->gecos, state->homedir,
+ state->shell, state->attrs,
+ state->cache_timeout);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_user_add_done, req);
+ return;
+ }
+
+ /* the user exists, let's just replace attributes when set */
+ if (!state->attrs) {
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ if (state->uid) {
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_UIDNUM, state->uid);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ if (state->gid) {
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_GIDNUM, state->gid);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ if (state->uid && !state->gid && state->handle->ctx->mpg) {
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_GIDNUM, state->uid);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ if (state->gecos) {
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_GECOS, state->gecos);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ if (state->homedir) {
+ ret = sysdb_attrs_add_string(state->attrs,
+ SYSDB_HOMEDIR, state->homedir);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ if (state->shell) {
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_SHELL, state->shell);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_LAST_UPDATE, now);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_CACHE_EXPIRE,
+ ((state->cache_timeout) ?
+ (now + state->cache_timeout) : 0));
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->attrs,
+ SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_user_attr_done, req);
+}
+
+static void sysdb_store_user_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_add_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void sysdb_store_user_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_store_user_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+/* =Store-Group-(Native/Legacy)-(replaces-existing-data)================== */
+
+/* this function does not check that all user members are actually present */
+
+struct sysdb_store_group_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ gid_t gid;
+
+ struct sysdb_attrs *attrs;
+
+ uint64_t cache_timeout;
+};
+
+static void sysdb_store_group_check(struct tevent_req *subreq);
+static void sysdb_store_group_add_done(struct tevent_req *subreq);
+static void sysdb_store_group_attr_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_store_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name,
+ gid_t gid,
+ struct sysdb_attrs *attrs,
+ uint64_t cache_timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_store_group_state *state;
+ static const char *src_attrs[] = { SYSDB_NAME, SYSDB_GIDNUM,
+ SYSDB_ORIG_MODSTAMP, NULL };
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_store_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->gid = gid;
+ state->attrs = attrs;
+ state->cache_timeout = cache_timeout;
+
+ subreq = sysdb_search_group_by_name_send(state, ev, NULL, handle,
+ domain, name, src_attrs);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_store_group_check, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_store_group_check(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_store_group_state *state = tevent_req_data(req,
+ struct sysdb_store_group_state);
+ struct ldb_message *msg;
+ time_t now = time(NULL);
+ bool new_group = false;
+ int ret;
+
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (ret == ENOENT) {
+ new_group = true;
+ }
+
+ /* FIXME: use the remote modification timestamp to know if the
+ * group needs any update */
+
+ if (new_group) {
+ /* group doesn't exist, turn into adding a group */
+ subreq = sysdb_add_group_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->gid, state->attrs,
+ state->cache_timeout);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_group_add_done, req);
+
+ return;
+ }
+
+ /* the group exists, let's just replace attributes when set */
+
+ if (!state->attrs) {
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ if (state->gid) {
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_GIDNUM, state->gid);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_LAST_UPDATE, now);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = sysdb_attrs_add_time_t(state->attrs, SYSDB_CACHE_EXPIRE,
+ ((state->cache_timeout) ?
+ (now + state->cache_timeout) : 0));
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_group_attr_send(state, state->ev,
+ state->handle, state->domain,
+ state->name, state->attrs,
+ SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_group_attr_done, req);
+}
+
+static void sysdb_store_group_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_add_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void sysdb_store_group_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_set_group_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_store_group_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Add-User-to-Group(Native/Legacy)====================================== */
+
+static void sysdb_add_group_member_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_add_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *group,
+ const char *user)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_dn *group_dn, *user_dn;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ group_dn = sysdb_group_dn(handle->ctx, state, domain->name, group);
+ if (!group_dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ user_dn = sysdb_user_dn(handle->ctx, state, domain->name, user);
+ if (!user_dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ subreq = sysdb_mod_group_member_send(state, ev, handle,
+ user_dn, group_dn,
+ SYSDB_MOD_ADD);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_add_group_member_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_add_group_member_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_add_group_member_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Remove-member-from-Group(Native/Legacy)=============================== */
+
+static void sysdb_remove_group_member_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_remove_group_member_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *group,
+ const char *user)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_op_state *state;
+ struct ldb_dn *group_dn, *user_dn;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_op_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ignore_not_found = false;
+ state->ldbreply = NULL;
+
+ group_dn = sysdb_group_dn(handle->ctx, state, domain->name, group);
+ if (!group_dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ user_dn = sysdb_user_dn(handle->ctx, state, domain->name, user);
+ if (!user_dn) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ subreq = sysdb_mod_group_member_send(state, ev, handle,
+ user_dn, group_dn,
+ SYSDB_MOD_DEL);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_remove_group_member_done, req);
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_remove_group_member_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_remove_group_member_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Password-Caching====================================================== */
+
+struct sysdb_cache_pw_state {
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+
+ const char *username;
+ struct sysdb_attrs *attrs;
+
+ struct sysdb_handle *handle;
+ bool commit;
+};
+
+static void sysdb_cache_password_trans(struct tevent_req *subreq);
+static void sysdb_cache_password_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_cache_password_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *username,
+ const char *password)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_cache_pw_state *state;
+ char *hash = NULL;
+ char *salt;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_cache_pw_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->domain = domain;
+ state->username = username;
+
+ ret = s3crypt_gen_salt(state, &salt);
+ if (ret) {
+ DEBUG(4, ("Failed to generate random salt.\n"));
+ goto fail;
+ }
+
+ ret = s3crypt_sha512(state, password, salt, &hash);
+ if (ret) {
+ DEBUG(4, ("Failed to create password hash.\n"));
+ goto fail;
+ }
+
+ state->attrs = sysdb_new_attrs(state);
+ if (!state->attrs) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_CACHEDPWD, hash);
+ if (ret) goto fail;
+
+ /* FIXME: should we use a different attribute for chache passwords ?? */
+ ret = sysdb_attrs_add_long(state->attrs, "lastCachedPasswordChange",
+ (long)time(NULL));
+ if (ret) goto fail;
+
+ ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0U);
+ if (ret) goto fail;
+
+ state->handle = NULL;
+
+ if (handle) {
+ state->handle = handle;
+ state->commit = false;
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->username,
+ state->attrs, SYSDB_MOD_REP);
+ if (!subreq) {
+ ERROR_OUT(ret, ENOMEM, fail);
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_password_done, req);
+ } else {
+ state->commit = true;
+
+ subreq = sysdb_transaction_send(state, state->ev, sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_password_trans, req);
+ }
+
+ return req;
+
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_cache_password_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_cache_pw_state *state = tevent_req_data(req,
+ struct sysdb_cache_pw_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->username,
+ state->attrs, SYSDB_MOD_REP);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_password_done, req);
+}
+
+static void sysdb_cache_password_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_cache_pw_state *state = tevent_req_data(req,
+ struct sysdb_cache_pw_state);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->commit) {
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_cache_password_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+/* = sysdb_check_handle ================== */
+struct sysdb_check_handle_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+};
+
+static void sysdb_check_handle_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_check_handle_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sysdb_check_handle_state *state;
+
+ if (sysdb == NULL && handle == NULL) {
+ DEBUG(1, ("Sysdb context not available.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_check_handle_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ if (handle != NULL) {
+ state->handle = talloc_memdup(state, handle, sizeof(struct sysdb_handle));
+ if (state->handle == NULL) {
+ DEBUG(1, ("talloc_memdup failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ } else {
+ tevent_req_done(req);
+ }
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ state->handle = NULL;
+
+ subreq = sysdb_operation_send(state, state->ev, sysdb);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_operation_send failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sysdb_check_handle_done, req);
+
+ return req;
+}
+
+static void sysdb_check_handle_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_check_handle_state *state = tevent_req_data(req,
+ struct sysdb_check_handle_state);
+ int ret;
+
+ ret = sysdb_operation_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+int sysdb_check_handle_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle)
+{
+ struct sysdb_check_handle_state *state = tevent_req_data(req,
+ struct sysdb_check_handle_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *handle = talloc_move(memctx, &state->handle);
+
+ return EOK;
+
+}
+
+/* =Custom Search================== */
+struct sysdb_search_custom_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+
+ struct ldb_dn *basedn;
+ const char **attrs;
+ const char *filter;
+ int scope;
+ bool expect_not_more_than_one;
+
+ size_t msgs_count;
+ struct ldb_message **msgs;
+};
+
+static void sysdb_search_custom_check_handle_done(struct tevent_req *subreq);
+static void sysdb_search_custom_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *filter,
+ const char *subtree_name,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_custom_state *state;
+ int ret;
+
+ if (sysdb == NULL && handle == NULL) return NULL;
+
+ if (filter == NULL || subtree_name == NULL) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_custom_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->attrs = attrs;
+ state->filter = filter;
+ state->scope = LDB_SCOPE_SUBTREE;
+ state->expect_not_more_than_one = false;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ if (sysdb == NULL) {
+ sysdb = handle->ctx;
+ }
+ state->basedn = sysdb_custom_subtree_dn(sysdb, state, domain->name,
+ subtree_name);
+ if (state->basedn == NULL) {
+ DEBUG(1, ("sysdb_custom_subtree_dn failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (!ldb_dn_validate(state->basedn)) {
+ DEBUG(1, ("Failed to create DN.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ subreq = sysdb_check_handle_send(state, state->ev, sysdb, state->handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_custom_check_handle_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+struct tevent_req *sysdb_search_custom_by_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_custom_state *state;
+ int ret;
+
+ if (sysdb == NULL && handle == NULL) return NULL;
+
+ if (object_name == NULL || subtree_name == NULL) return NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_custom_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->attrs = attrs;
+ state->filter = NULL;
+ state->scope = LDB_SCOPE_BASE;
+ state->expect_not_more_than_one = true;
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ if (sysdb == NULL) {
+ sysdb = handle->ctx;
+ }
+ state->basedn = sysdb_custom_dn(sysdb, state, domain->name, object_name,
+ subtree_name);
+ if (state->basedn == NULL) {
+ DEBUG(1, ("sysdb_custom_dn failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (!ldb_dn_validate(state->basedn)) {
+ DEBUG(1, ("Failed to create DN.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ subreq = sysdb_check_handle_send(state, state->ev, sysdb, state->handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_custom_check_handle_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_search_custom_check_handle_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_custom_state *state = tevent_req_data(req,
+ struct sysdb_search_custom_state);
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ state->basedn, state->scope,
+ state->filter, state->attrs);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_search_entry_send failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_custom_done, req);
+ return;
+}
+
+static void sysdb_search_custom_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_custom_state *state = tevent_req_data(req,
+ struct sysdb_search_custom_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state, &state->msgs_count,
+ &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->expect_not_more_than_one && state->msgs_count > 1) {
+ DEBUG(1, ("More than one result found.\n"));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_search_custom_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *msgs_count,
+ struct ldb_message ***msgs)
+{
+ struct sysdb_search_custom_state *state = tevent_req_data(req,
+ struct sysdb_search_custom_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *msgs_count = state->msgs_count;
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+
+ return EOK;
+}
+
+
+/* =Custom Store (replaces-existing-data)================== */
+
+struct sysdb_store_custom_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *object_name;
+ const char *subtree_name;
+ struct ldb_dn *dn;
+ struct sysdb_attrs *attrs;
+ struct ldb_message *msg;
+};
+
+static void sysdb_store_custom_check_done(struct tevent_req *subreq);
+static void sysdb_store_custom_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_store_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name,
+ struct sysdb_attrs *attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_store_custom_state *state;
+ int ret;
+ const char **search_attrs;
+
+ if (object_name == NULL || subtree_name == NULL) return NULL;
+
+ if (handle == NULL) {
+ DEBUG(1, ("Sysdb context not available.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_store_custom_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->object_name = object_name;
+ state->subtree_name = subtree_name;
+ state->attrs = attrs;
+ state->msg = NULL;
+ state->dn = sysdb_custom_dn(handle->ctx, state, domain->name, object_name,
+ subtree_name);
+ if (state->dn == NULL) {
+ DEBUG(1, ("sysdb_custom_dn failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ search_attrs = talloc_array(state, const char *, 2);
+ if (search_attrs == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ search_attrs[0] = "*";
+ search_attrs[1] = NULL;
+
+ subreq = sysdb_search_custom_by_name_send(state, state->ev, NULL,
+ state->handle,
+ state->domain,
+ state->object_name,
+ state->subtree_name,
+ search_attrs);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_search_custom_by_name_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_custom_check_done, req);
+
+ return req;
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_store_custom_check_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_store_custom_state *state = tevent_req_data(req,
+ struct sysdb_store_custom_state);
+ int ret;
+ int i;
+ size_t resp_count = 0;
+ struct ldb_message **resp;
+ struct ldb_message *msg;
+ struct ldb_request *ldbreq;
+ struct ldb_message_element *el;
+ bool add_object = false;
+
+ ret = sysdb_search_custom_recv(subreq, state, &resp_count, &resp);
+ talloc_zfree(subreq);
+ if (ret != EOK && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ add_object = true;
+ }
+
+ msg = ldb_msg_new(state);
+ if (msg == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ msg->dn = state->dn;
+
+ msg->elements = talloc_array(msg, struct ldb_message_element,
+ state->attrs->num);
+ if (!msg->elements) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ for (i = 0; i < state->attrs->num; i++) {
+ msg->elements[i] = state->attrs->a[i];
+ if (add_object) {
+ msg->elements[i].flags = LDB_FLAG_MOD_ADD;
+ } else {
+ el = ldb_msg_find_element(resp[0], state->attrs->a[i].name);
+ if (el == NULL) {
+ msg->elements[i].flags = LDB_FLAG_MOD_ADD;
+ } else {
+ msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ }
+ }
+ }
+ msg->num_elements = state->attrs->num;
+
+ if (add_object) {
+ ret = ldb_build_add_req(&ldbreq, state->handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ } else {
+ ret = ldb_build_mod_req(&ldbreq, state->handle->ctx->ldb, state, msg,
+ NULL, NULL, NULL, NULL);
+ }
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("Failed to build request: %s(%d)[%s]\n",
+ ldb_strerror(ret), ret,
+ ldb_errstring(state->handle->ctx->ldb)));
+ tevent_req_error(req, sysdb_error_to_errno(ret));
+ return;
+ }
+
+ subreq = sldb_request_send(state, state->ev, state->handle->ctx->ldb,
+ ldbreq);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_store_custom_done, req);
+ return;
+}
+
+static void sysdb_store_custom_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_op_default_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+int sysdb_store_custom_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* = Custom Delete======================================= */
+
+struct sysdb_delete_custom_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ const char *object_name;
+ const char *subtree_name;
+ struct ldb_dn *dn;
+};
+static void sysdb_delete_custom_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_delete_custom_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *object_name,
+ const char *subtree_name)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_delete_custom_state *state;
+ int ret;
+
+ if (object_name == NULL || subtree_name == NULL) return NULL;
+
+ if (handle == NULL) {
+ DEBUG(1, ("Sysdb context not available.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_store_custom_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->object_name = object_name;
+ state->subtree_name = subtree_name;
+ state->dn = sysdb_custom_dn(handle->ctx, state, domain->name, object_name,
+ subtree_name);
+ if (state->dn == NULL) {
+ DEBUG(1, ("sysdb_custom_dn failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle,
+ state->dn, true);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_delete_entry_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_custom_done, req);
+
+ return req;
+fail:
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sysdb_delete_custom_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+int sysdb_delete_custom_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* = ASQ search request ======================================== */
+struct sysdb_asq_search_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+ struct ldb_dn *base_dn;
+ const char *asq_attribute;
+ const char **attrs;
+ const char *expression;
+
+ int msgs_count;
+ struct ldb_message **msgs;
+};
+
+void sysdb_asq_search_check_handle_done(struct tevent_req *subreq);
+static void sysdb_asq_search_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_asq_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ struct ldb_dn *base_dn,
+ const char *expression,
+ const char *asq_attribute,
+ const char **attrs)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sysdb_asq_search_state *state;
+ int ret;
+
+ if (sysdb == NULL && handle == NULL) {
+ DEBUG(1, ("Sysdb context not available.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_asq_search_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sysdb = (sysdb == NULL) ? handle->ctx : sysdb;
+ state->handle = handle;
+ state->domain = domain;
+ state->base_dn = base_dn;
+ state->expression = expression;
+ state->asq_attribute = asq_attribute;
+ state->attrs = attrs;
+
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ subreq = sysdb_check_handle_send(state, state->ev, state->sysdb,
+ state->handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_asq_search_check_handle_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+void sysdb_asq_search_check_handle_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_asq_search_state *state = tevent_req_data(req,
+ struct sysdb_asq_search_state);
+ struct ldb_request *ldb_req;
+ struct ldb_control **ctrl;
+ struct ldb_asq_control *asq_control;
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ctrl = talloc_array(state, struct ldb_control *, 2);
+ if (ctrl == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ctrl[0] = talloc(ctrl, struct ldb_control);
+ if (ctrl[0] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ctrl[1] = NULL;
+
+ ctrl[0]->oid = LDB_CONTROL_ASQ_OID;
+ ctrl[0]->critical = 1;
+
+ asq_control = talloc(ctrl[0], struct ldb_asq_control);
+ if (asq_control == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ asq_control->request = 1;
+ asq_control->source_attribute = talloc_strdup(asq_control,
+ state->asq_attribute);
+ if (asq_control->source_attribute == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ asq_control->src_attr_len = strlen(asq_control->source_attribute);
+ ctrl[0]->data = asq_control;
+
+ ret = ldb_build_search_req(&ldb_req, state->handle->ctx->ldb, state,
+ state->base_dn, LDB_SCOPE_BASE,
+ state->expression, state->attrs, ctrl,
+ NULL, NULL, NULL);
+ if (ret != LDB_SUCCESS) {
+ ret = sysdb_error_to_errno(ret);
+ goto fail;
+ }
+
+ subreq = sldb_request_send(state, state->ev, state->handle->ctx->ldb,
+ ldb_req);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, sysdb_asq_search_done, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void sysdb_asq_search_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_asq_search_state *state = tevent_req_data(req,
+ struct sysdb_asq_search_state);
+ struct ldb_reply *ldbreply;
+ int ret;
+
+ ret = sldb_request_recv(subreq, state, &ldbreply);
+ /* DO NOT free the subreq here, the subrequest search is not
+ * finished until we get an ldbreply of type LDB_REPLY_DONE */
+ if (ret != EOK) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (ldbreply->type) {
+ case LDB_REPLY_ENTRY:
+ state->msgs = talloc_realloc(state, state->msgs,
+ struct ldb_message *,
+ state->msgs_count + 2);
+ if (state->msgs == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->msgs[state->msgs_count + 1] = NULL;
+
+ state->msgs[state->msgs_count] = talloc_steal(state->msgs,
+ ldbreply->message);
+ state->msgs_count++;
+
+ talloc_zfree(ldbreply);
+ return;
+
+ case LDB_REPLY_DONE:
+ /* now it is safe to free the subrequest, the search is complete */
+ talloc_zfree(subreq);
+ break;
+
+ default:
+ DEBUG(1, ("Unknown ldb reply type [%d].\n", ldbreply->type));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_asq_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs)
+{
+ struct sysdb_asq_search_state *state = tevent_req_data(req,
+ struct sysdb_asq_search_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *msgs_count = state->msgs_count;
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+
+ return EOK;
+}
+
+/* =Search-Users-with-Custom-Filter====================================== */
+
+struct sysdb_search_users_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+ const char *sub_filter;
+ const char **attrs;
+
+ struct ldb_message **msgs;
+ size_t msgs_count;
+};
+
+void sysdb_search_users_check_handle(struct tevent_req *subreq);
+static void sysdb_search_users_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *sub_filter,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_users_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_users_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->sub_filter = sub_filter;
+ state->attrs = attrs;
+
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ subreq = sysdb_check_handle_send(state, ev, sysdb, handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_users_check_handle, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+void sysdb_search_users_check_handle(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_users_state *state = tevent_req_data(req,
+ struct sysdb_search_users_state);
+ struct ldb_dn *basedn;
+ char *filter;
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ basedn = ldb_dn_new_fmt(state, state->handle->ctx->ldb,
+ SYSDB_TMPL_USER_BASE, state->domain->name);
+ if (!basedn) {
+ DEBUG(2, ("Failed to build base dn\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ filter = talloc_asprintf(state, "(&(%s)%s)",
+ SYSDB_UC, state->sub_filter);
+ if (!filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ DEBUG(6, ("Search users with filter: %s\n", filter));
+
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ basedn, LDB_SCOPE_SUBTREE,
+ filter, state->attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_users_done, req);
+}
+
+static void sysdb_search_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_users_state *state = tevent_req_data(req,
+ struct sysdb_search_users_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state,
+ &state->msgs_count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_search_users_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs)
+{
+ struct sysdb_search_users_state *state = tevent_req_data(req,
+ struct sysdb_search_users_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *msgs_count = state->msgs_count;
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+
+ return EOK;
+}
+
+/* =Delete-User-by-Name-OR-uid============================================ */
+
+struct sysdb_delete_user_state {
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ uid_t uid;
+
+ struct sysdb_handle *handle;
+};
+
+void sysdb_delete_user_check_handle(struct tevent_req *subreq);
+static void sysdb_delete_user_found(struct tevent_req *subreq);
+static void sysdb_delete_user_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_delete_user_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, uid_t uid)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_delete_user_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_delete_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->uid = uid;
+
+ subreq = sysdb_check_handle_send(state, ev, sysdb, handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_user_check_handle, req);
+
+ return req;
+}
+
+void sysdb_delete_user_check_handle(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_delete_user_state *state = tevent_req_data(req,
+ struct sysdb_delete_user_state);
+ static const char *attrs[] = { SYSDB_NAME, SYSDB_UIDNUM, NULL };
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->name) {
+ subreq = sysdb_search_user_by_name_send(state, state->ev, NULL,
+ state->handle, state->domain,
+ state->name, attrs);
+ } else {
+ subreq = sysdb_search_user_by_uid_send(state, state->ev, NULL,
+ state->handle, state->domain,
+ state->uid, NULL);
+ }
+
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_user_found, req);
+}
+
+static void sysdb_delete_user_found(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_delete_user_state *state = tevent_req_data(req,
+ struct sysdb_delete_user_state);
+ struct ldb_message *msg;
+ int ret;
+
+ ret = sysdb_search_user_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->name && state->uid) {
+ /* verify name/gid match */
+ const char *name;
+ uint64_t uid;
+
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+ if (name == NULL || uid == 0) {
+ DEBUG(2, ("Attribute is missing but this should never happen!\n"));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ if (strcmp(state->name, name) || state->uid != uid) {
+ /* this is not the entry we are looking for */
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev,
+ state->handle, msg->dn, false);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_user_done, req);
+}
+
+static void sysdb_delete_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_delete_user_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+
+/* =Search-Groups-with-Custom-Filter===================================== */
+
+struct sysdb_search_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+ const char *sub_filter;
+ const char **attrs;
+
+ struct ldb_message **msgs;
+ size_t msgs_count;
+};
+
+void sysdb_search_groups_check_handle(struct tevent_req *subreq);
+static void sysdb_search_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_search_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *sub_filter,
+ const char **attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_search_groups_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_search_groups_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->sub_filter = sub_filter;
+ state->attrs = attrs;
+
+ state->msgs_count = 0;
+ state->msgs = NULL;
+
+ subreq = sysdb_check_handle_send(state, ev, sysdb, handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_groups_check_handle, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+void sysdb_search_groups_check_handle(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_groups_state *state = tevent_req_data(req,
+ struct sysdb_search_groups_state);
+ struct ldb_dn *basedn;
+ char *filter;
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ basedn = ldb_dn_new_fmt(state, state->handle->ctx->ldb,
+ SYSDB_TMPL_GROUP_BASE, state->domain->name);
+ if (!basedn) {
+ DEBUG(2, ("Failed to build base dn\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ filter = talloc_asprintf(state, "(&(%s)%s)",
+ SYSDB_GC, state->sub_filter);
+ if (!filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ DEBUG(6, ("Search groups with filter: %s\n", filter));
+
+ subreq = sysdb_search_entry_send(state, state->ev, state->handle,
+ basedn, LDB_SCOPE_SUBTREE,
+ filter, state->attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_search_groups_done, req);
+}
+
+static void sysdb_search_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_search_groups_state *state = tevent_req_data(req,
+ struct sysdb_search_groups_state);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, state,
+ &state->msgs_count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_search_groups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *msgs_count, struct ldb_message ***msgs)
+{
+ struct sysdb_search_groups_state *state = tevent_req_data(req,
+ struct sysdb_search_groups_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *msgs_count = state->msgs_count;
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+
+ return EOK;
+}
+
+/* =Delete-Group-by-Name-OR-gid=========================================== */
+
+struct sysdb_delete_group_state {
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ gid_t gid;
+
+ struct sysdb_handle *handle;
+};
+
+void sysdb_delete_group_check_handle(struct tevent_req *subreq);
+static void sysdb_delete_group_found(struct tevent_req *subreq);
+static void sysdb_delete_group_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_delete_group_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name, gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct sysdb_delete_group_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_delete_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->domain = domain;
+ state->name = name;
+ state->gid = gid;
+
+ subreq = sysdb_check_handle_send(state, ev, sysdb, handle);
+ if (!subreq) {
+ DEBUG(1, ("sysdb_check_handle_send failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_group_check_handle, req);
+
+ return req;
+}
+
+void sysdb_delete_group_check_handle(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_delete_group_state *state = tevent_req_data(req,
+ struct sysdb_delete_group_state);
+ static const char *attrs[] = { SYSDB_NAME, SYSDB_GIDNUM, NULL };
+ int ret;
+
+ ret = sysdb_check_handle_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->name) {
+ subreq = sysdb_search_group_by_name_send(state, state->ev, NULL,
+ state->handle, state->domain,
+ state->name, attrs);
+ } else {
+ subreq = sysdb_search_group_by_gid_send(state, state->ev, NULL,
+ state->handle, state->domain,
+ state->gid, NULL);
+ }
+
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_group_found, req);
+}
+
+static void sysdb_delete_group_found(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sysdb_delete_group_state *state = tevent_req_data(req,
+ struct sysdb_delete_group_state);
+ struct ldb_message *msg;
+ int ret;
+
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->name && state->gid) {
+ /* verify name/gid match */
+ const char *name;
+ uint64_t gid;
+
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+ if (name == NULL || gid == 0) {
+ DEBUG(2, ("Attribute is missing but this should never happen!\n"));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ if (strcmp(state->name, name) || state->gid != gid) {
+ /* this is not the entry we are looking for */
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev,
+ state->handle, msg->dn, false);
+ if (!subreq) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_delete_group_done, req);
+}
+
+static void sysdb_delete_group_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sysdb_delete_group_recv(struct tevent_req *req)
+{
+ return sysdb_op_default_recv(req);
+}
+
+/* ========= Authentication against cached password ============ */
+
+struct sysdb_cache_auth_state {
+ struct tevent_context *ev;
+ const char *name;
+ const uint8_t *authtok;
+ size_t authtok_size;
+ struct sss_domain_info *domain;
+ struct sysdb_ctx *sysdb;
+ struct confdb_ctx *cdb;
+ struct sysdb_attrs *update_attrs;
+ bool authentication_successful;
+ struct sysdb_handle *handle;
+ time_t expire_date;
+ time_t delayed_until;
+};
+
+errno_t check_failed_login_attempts(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
+ struct ldb_message *ldb_msg,
+ uint32_t *failed_login_attempts,
+ time_t *delayed_until)
+{
+ int ret;
+ int allowed_failed_login_attempts;
+ int failed_login_delay;
+ time_t last_failed_login;
+ time_t end;
+
+ *delayed_until = -1;
+ *failed_login_attempts = ldb_msg_find_attr_as_uint(ldb_msg,
+ SYSDB_FAILED_LOGIN_ATTEMPTS, 0);
+ last_failed_login = (time_t) ldb_msg_find_attr_as_int64(ldb_msg,
+ SYSDB_LAST_FAILED_LOGIN, 0);
+ ret = confdb_get_int(cdb, mem_ctx, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_FAILED_LOGIN_ATTEMPTS,
+ CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS,
+ &allowed_failed_login_attempts);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to read the number of allowed failed login "
+ "attempts.\n"));
+ return EIO;
+ }
+ ret = confdb_get_int(cdb, mem_ctx, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_FAILED_LOGIN_DELAY,
+ CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY,
+ &failed_login_delay);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to read the failed login delay.\n"));
+ return EIO;
+ }
+ DEBUG(9, ("Failed login attempts [%d], allowed failed login attempts [%d], "
+ "failed login delay [%d].\n", *failed_login_attempts,
+ allowed_failed_login_attempts, failed_login_delay));
+
+ if (allowed_failed_login_attempts) {
+ if (*failed_login_attempts >= allowed_failed_login_attempts) {
+ if (failed_login_delay) {
+ end = last_failed_login + (failed_login_delay * 60);
+ if (end < time(NULL)) {
+ DEBUG(7, ("failed_login_delay has passed, "
+ "resetting failed_login_attempts.\n"));
+ *failed_login_attempts = 0;
+ } else {
+ DEBUG(7, ("login delayed until %lld.\n", (long long) end));
+ *delayed_until = end;
+ return EACCES;
+ }
+ } else {
+ DEBUG(4, ("Too many failed logins.\n"));
+ return EACCES;
+ }
+ }
+ }
+
+ return EOK;
+}
+
+static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq);
+static void sysdb_cache_auth_transaction_start_done(struct tevent_req *subreq);
+static void sysdb_cache_auth_attr_update_done(struct tevent_req *subreq);
+static void sysdb_cache_auth_done(struct tevent_req *subreq);
+
+struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const uint8_t *authtok,
+ size_t authtok_size,
+ struct confdb_ctx *cdb)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sysdb_cache_auth_state *state;
+
+ if (name == NULL || *name == '\0') {
+ DEBUG(1, ("Missing user name.\n"));
+ return NULL;
+ }
+
+ if (cdb == NULL) {
+ DEBUG(1, ("Missing config db context.\n"));
+ return NULL;
+ }
+
+ if (sysdb == NULL) {
+ DEBUG(1, ("Missing sysdb db context.\n"));
+ return NULL;
+ }
+
+ if (!domain->cache_credentials) {
+ DEBUG(3, ("Cached credentials not available.\n"));
+ return NULL;
+ }
+
+ static const char *attrs[] = {SYSDB_NAME,
+ SYSDB_CACHEDPWD,
+ SYSDB_DISABLED,
+ SYSDB_LAST_LOGIN,
+ SYSDB_LAST_ONLINE_AUTH,
+ "lastCachedPasswordChange",
+ "accountExpires",
+ SYSDB_FAILED_LOGIN_ATTEMPTS,
+ SYSDB_LAST_FAILED_LOGIN,
+ NULL};
+
+ req = tevent_req_create(mem_ctx, &state, struct sysdb_cache_auth_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->name = name;
+ state->authtok = authtok;
+ state->authtok_size = authtok_size;
+ state->domain = domain;
+ state->sysdb = sysdb;
+ state->cdb = cdb;
+ state->update_attrs = NULL;
+ state->authentication_successful = false;
+ state->handle = NULL;
+ state->expire_date = -1;
+ state->delayed_until = -1;
+
+ subreq = sysdb_search_user_by_name_send(state, ev, sysdb, NULL, domain,
+ name, attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_user_by_name_send failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_auth_get_attrs_done, req);
+
+ return req;
+}
+
+static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq)
+{
+ struct ldb_message *ldb_msg;
+ const char *userhash;
+ char *comphash;
+ char *password = NULL;
+ int i;
+ int ret;
+ uint64_t lastLogin = 0;
+ int cred_expiration;
+ uint32_t failed_login_attempts = 0;
+
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ struct sysdb_cache_auth_state *state = tevent_req_data(req,
+ struct sysdb_cache_auth_state);
+
+ ret = sysdb_search_user_recv(subreq, state, &ldb_msg);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_search_user_by_name_send failed [%d][%s].\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ /* Check offline_auth_cache_timeout */
+ lastLogin = ldb_msg_find_attr_as_uint64(ldb_msg,
+ SYSDB_LAST_ONLINE_AUTH,
+ 0);
+
+ ret = confdb_get_int(state->cdb, state, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_CRED_TIMEOUT, 0, &cred_expiration);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to read expiration time of offline credentials.\n"));
+ ret = EACCES;
+ goto done;
+ }
+ DEBUG(9, ("Offline credentials expiration is [%d] days.\n",
+ cred_expiration));
+
+ if (cred_expiration) {
+ state->expire_date = lastLogin + (cred_expiration * 86400);
+ if (state->expire_date < time(NULL)) {
+ DEBUG(4, ("Cached user entry is too old.\n"));
+ state->expire_date = 0;
+ ret = EACCES;
+ goto done;
+ }
+ } else {
+ state->expire_date = 0;
+ }
+
+ ret = check_failed_login_attempts(state, state->cdb, ldb_msg,
+ &failed_login_attempts,
+ &state->delayed_until);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* TODO: verify user account (disabled, expired ...) */
+
+ password = talloc_strndup(state, (const char *) state->authtok,
+ state->authtok_size);
+ if (password == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ userhash = ldb_msg_find_attr_as_string(ldb_msg, SYSDB_CACHEDPWD, NULL);
+ if (userhash == NULL || *userhash == '\0') {
+ DEBUG(4, ("Cached credentials not available.\n"));
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = s3crypt_sha512(state, password, userhash, &comphash);
+ if (ret) {
+ DEBUG(4, ("Failed to create password hash.\n"));
+ ret = EFAULT;
+ goto done;
+ }
+
+ state->update_attrs = sysdb_new_attrs(state);
+ if (state->update_attrs == NULL) {
+ DEBUG(1, ("sysdb_new_attrs failed.\n"));
+ goto done;
+ }
+
+ if (strcmp(userhash, comphash) == 0) {
+ /* TODO: probable good point for audit logging */
+ DEBUG(4, ("Hashes do match!\n"));
+ state->authentication_successful = true;
+
+ ret = sysdb_attrs_add_time_t(state->update_attrs, SYSDB_LAST_LOGIN,
+ time(NULL));
+ if (ret != EOK) {
+ DEBUG(3, ("sysdb_attrs_add_time_t failed, "
+ "but authentication is successful.\n"));
+ ret = EOK;
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_uint32(state->update_attrs,
+ SYSDB_FAILED_LOGIN_ATTEMPTS, 0U);
+ if (ret != EOK) {
+ DEBUG(3, ("sysdb_attrs_add_uint32 failed, "
+ "but authentication is successful.\n"));
+ ret = EOK;
+ goto done;
+ }
+
+
+ } else {
+ DEBUG(4, ("Authentication failed.\n"));
+ state->authentication_successful = false;
+
+ ret = sysdb_attrs_add_time_t(state->update_attrs,
+ SYSDB_LAST_FAILED_LOGIN,
+ time(NULL));
+ if (ret != EOK) {
+ DEBUG(3, ("sysdb_attrs_add_time_t failed\n."));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_uint32(state->update_attrs,
+ SYSDB_FAILED_LOGIN_ATTEMPTS,
+ ++failed_login_attempts);
+ if (ret != EOK) {
+ DEBUG(3, ("sysdb_attrs_add_uint32 failed.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_send failed.\n"));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_auth_transaction_start_done,
+ req);
+ return;
+
+done:
+ if (password) for (i = 0; password[i]; i++) password[i] = 0;
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ return;
+}
+
+static void sysdb_cache_auth_transaction_start_done(struct tevent_req *subreq)
+{
+ int ret;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ struct sysdb_cache_auth_state *state = tevent_req_data(req,
+ struct sysdb_cache_auth_state);
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_transaction_send failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->update_attrs,
+ LDB_FLAG_MOD_REPLACE);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_set_user_attr_send failed.\n"));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_auth_attr_update_done,
+ req);
+ return;
+
+done:
+ if (state->authentication_successful) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, EINVAL);
+ }
+ return;
+}
+
+static void sysdb_cache_auth_attr_update_done(struct tevent_req *subreq)
+{
+ int ret;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ struct sysdb_cache_auth_state *state = tevent_req_data(req,
+ struct sysdb_cache_auth_state);
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_set_user_attr request failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+ goto done;
+ }
+ tevent_req_set_callback(subreq, sysdb_cache_auth_done, req);
+ return;
+
+done:
+ if (state->authentication_successful) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, EINVAL);
+ }
+ return;
+}
+
+static void sysdb_cache_auth_done(struct tevent_req *subreq)
+{
+ int ret;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ struct sysdb_cache_auth_state *state = tevent_req_data(req,
+ struct sysdb_cache_auth_state);
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed [%d][%s].\n",
+ ret, strerror(ret)));
+ }
+
+ if (state->authentication_successful) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, EINVAL);
+ }
+ return;
+}
+
+int sysdb_cache_auth_recv(struct tevent_req *req, time_t *expire_date,
+ time_t *delayed_until) {
+ struct sysdb_cache_auth_state *state = tevent_req_data(req,
+ struct sysdb_cache_auth_state);
+ *expire_date = state->expire_date;
+ *delayed_until = state->delayed_until;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return (state->authentication_successful ? EOK : EINVAL);
+}
diff --git a/src/db/sysdb_private.h b/src/db/sysdb_private.h
new file mode 100644
index 00000000..270cf360
--- /dev/null
+++ b/src/db/sysdb_private.h
@@ -0,0 +1,107 @@
+
+/*
+ SSSD
+
+ Private System Database Header
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __INT_SYS_DB_H__
+#define __INT_SYS_DB_H__
+
+#define SYSDB_VERSION_0_5 "0.5"
+#define SYSDB_VERSION_0_4 "0.4"
+#define SYSDB_VERSION_0_3 "0.3"
+#define SYSDB_VERSION_0_2 "0.2"
+#define SYSDB_VERSION_0_1 "0.1"
+
+#define SYSDB_VERSION SYSDB_VERSION_0_5
+
+#define SYSDB_BASE_LDIF \
+ "dn: @ATTRIBUTES\n" \
+ "userPrincipalName: CASE_INSENSITIVE\n" \
+ "cn: CASE_INSENSITIVE\n" \
+ "dc: CASE_INSENSITIVE\n" \
+ "dn: CASE_INSENSITIVE\n" \
+ "objectclass: CASE_INSENSITIVE\n" \
+ "\n" \
+ "dn: @INDEXLIST\n" \
+ "@IDXATTR: cn\n" \
+ "@IDXATTR: objectclass\n" \
+ "@IDXATTR: member\n" \
+ "@IDXATTR: memberof\n" \
+ "@IDXATTR: name\n" \
+ "@IDXATTR: uidNumber\n" \
+ "@IDXATTR: gidNumber\n" \
+ "@IDXATTR: lastUpdate\n" \
+ "@IDXATTR: originalDN\n" \
+ "\n" \
+ "dn: @MODULES\n" \
+ "@LIST: asq,memberof\n" \
+ "\n" \
+ "dn: cn=sysdb\n" \
+ "cn: sysdb\n" \
+ "version: " SYSDB_VERSION "\n" \
+ "description: base object\n" \
+ "\n"
+
+#include "db/sysdb.h"
+
+struct sysdb_handle {
+ struct sysdb_handle *prev;
+ struct sysdb_handle *next;
+
+ struct sysdb_ctx *ctx;
+ struct tevent_req *subreq;
+
+ bool transaction_active;
+};
+
+struct sysdb_ctx {
+ struct tevent_context *ev;
+
+ struct sss_domain_info *domain;
+ bool mpg;
+
+ struct ldb_context *ldb;
+ char *ldb_file;
+
+ struct sysdb_handle *queue;
+};
+
+struct sysdb_ctx_list {
+ struct sysdb_ctx **dbs;
+ size_t num_dbs;
+
+ char *db_path;
+};
+
+/* An operation blocks the transaction queue as well, but does not
+ * start a transaction, normally useful only for search type calls.
+ * do *NOT* call within a transaction you'll deadlock sysdb.
+ * Also make sure to free the handle as soon as the operation is
+ * finished to avoid stalling or potentially deadlocking sysdb */
+
+struct tevent_req *sysdb_operation_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *ctx);
+int sysdb_operation_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct sysdb_handle **handle);
+
+void sysdb_operation_done(struct sysdb_handle *handle);
+
+#endif /* __INT_SYS_DB_H__ */
diff --git a/src/db/sysdb_search.c b/src/db/sysdb_search.c
new file mode 100644
index 00000000..4b9470ba
--- /dev/null
+++ b/src/db/sysdb_search.c
@@ -0,0 +1,691 @@
+/*
+ SSSD
+
+ System Database
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "db/sysdb_private.h"
+#include "confdb/confdb.h"
+#include <time.h>
+
+struct sysdb_search_ctx;
+
+typedef void (*gen_callback)(struct sysdb_search_ctx *);
+
+struct sysdb_search_ctx {
+ struct tevent_context *ev;
+ struct sysdb_ctx *ctx;
+ struct sysdb_handle *handle;
+
+ struct sss_domain_info *domain;
+
+ const char *expression;
+
+ sysdb_callback_t callback;
+ void *ptr;
+
+ gen_callback gen_aux_fn;
+ bool gen_conv_mpg_users;
+
+ struct ldb_result *res;
+
+ const char **attrs;
+
+ int error;
+};
+
+static struct sysdb_search_ctx *init_src_ctx(TALLOC_CTX *mem_ctx,
+ struct sss_domain_info *domain,
+ struct sysdb_ctx *ctx,
+ sysdb_callback_t fn,
+ void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+
+ sctx = talloc_zero(mem_ctx, struct sysdb_search_ctx);
+ if (!sctx) {
+ return NULL;
+ }
+ sctx->ctx = ctx;
+ sctx->ev = ctx->ev;
+ sctx->callback = fn;
+ sctx->ptr = ptr;
+ sctx->res = talloc_zero(sctx, struct ldb_result);
+ if (!sctx->res) {
+ talloc_free(sctx);
+ return NULL;
+ }
+ sctx->domain = domain;
+
+ return sctx;
+}
+
+static void request_ldberror(struct sysdb_search_ctx *sctx, int error)
+{
+ sysdb_operation_done(sctx->handle);
+ sctx->callback(sctx->ptr, sysdb_error_to_errno(error), NULL);
+}
+
+static void request_error(struct sysdb_search_ctx *sctx, int error)
+{
+ sysdb_operation_done(sctx->handle);
+ sctx->callback(sctx->ptr, error, NULL);
+}
+
+static void request_done(struct sysdb_search_ctx *sctx)
+{
+ sysdb_operation_done(sctx->handle);
+ sctx->callback(sctx->ptr, EOK, sctx->res);
+}
+
+static int mpg_convert(struct ldb_message *msg);
+
+static int get_gen_callback(struct ldb_request *req,
+ struct ldb_reply *rep)
+{
+ struct sysdb_search_ctx *sctx;
+ struct ldb_result *res;
+ int n, ret;
+
+ sctx = talloc_get_type(req->context, struct sysdb_search_ctx);
+ res = sctx->res;
+
+ if (!rep) {
+ request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (rep->error != LDB_SUCCESS) {
+ request_ldberror(sctx, rep->error);
+ return rep->error;
+ }
+
+ switch (rep->type) {
+ case LDB_REPLY_ENTRY:
+
+ if (sctx->gen_conv_mpg_users) {
+ ret = mpg_convert(rep->message);
+ if (ret != EOK) {
+ request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ res->msgs = talloc_realloc(res, res->msgs,
+ struct ldb_message *,
+ res->count + 2);
+ if (!res->msgs) {
+ request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ res->msgs[res->count + 1] = NULL;
+
+ res->msgs[res->count] = talloc_steal(res->msgs, rep->message);
+ res->count++;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ if (res->refs) {
+ for (n = 0; res->refs[n]; n++) /*noop*/ ;
+ } else {
+ n = 0;
+ }
+
+ res->refs = talloc_realloc(res, res->refs, char *, n + 2);
+ if (! res->refs) {
+ request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ res->refs[n] = talloc_steal(res->refs, rep->referral);
+ res->refs[n + 1] = NULL;
+ break;
+
+ case LDB_REPLY_DONE:
+ res->controls = talloc_steal(res, rep->controls);
+
+ /* check if we need to call any aux function */
+ if (sctx->gen_aux_fn) {
+ sctx->gen_aux_fn(sctx);
+ } else {
+ /* no aux functions, this means the request is done */
+ request_done(sctx);
+ }
+ return LDB_SUCCESS;
+ }
+
+ talloc_free(rep);
+ return LDB_SUCCESS;
+}
+
+/* users */
+
+static void user_search(struct tevent_req *treq)
+{
+ struct sysdb_search_ctx *sctx;
+ struct ldb_request *req;
+ struct ldb_dn *base_dn;
+ int ret;
+
+ sctx = tevent_req_callback_data(treq, struct sysdb_search_ctx);
+
+ ret = sysdb_operation_recv(treq, sctx, &sctx->handle);
+ if (ret) {
+ return request_error(sctx, ret);
+ }
+
+ base_dn = ldb_dn_new_fmt(sctx, sctx->ctx->ldb,
+ SYSDB_TMPL_USER_BASE, sctx->domain->name);
+ if (!base_dn) {
+ return request_error(sctx, ENOMEM);
+ }
+
+ ret = ldb_build_search_req(&req, sctx->ctx->ldb, sctx,
+ base_dn, LDB_SCOPE_SUBTREE,
+ sctx->expression, sctx->attrs, NULL,
+ sctx, get_gen_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+
+ ret = ldb_request(sctx->ctx->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+}
+
+int sysdb_getpwnam(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr)
+{
+ static const char *attrs[] = SYSDB_PW_ATTRS;
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ sctx->expression = talloc_asprintf(sctx, SYSDB_PWNAM_FILTER, name);
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ sctx->attrs = attrs;
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, user_search, sctx);
+
+ return EOK;
+}
+
+int sysdb_getpwuid(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ uid_t uid,
+ sysdb_callback_t fn, void *ptr)
+{
+ static const char *attrs[] = SYSDB_PW_ATTRS;
+ struct sysdb_search_ctx *sctx;
+ unsigned long int filter_uid = uid;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ sctx->expression = talloc_asprintf(sctx, SYSDB_PWUID_FILTER, filter_uid);
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ sctx->attrs = attrs;
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, user_search, sctx);
+
+ return EOK;
+}
+
+int sysdb_enumpwent(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *expression,
+ sysdb_callback_t fn, void *ptr)
+{
+ static const char *attrs[] = SYSDB_PW_ATTRS;
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ if (expression)
+ sctx->expression = expression;
+ else
+ sctx->expression = SYSDB_PWENT_FILTER;
+
+ sctx->attrs = attrs;
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, user_search, sctx);
+
+ return EOK;
+}
+
+/* groups */
+
+static int mpg_convert(struct ldb_message *msg)
+{
+ struct ldb_message_element *el;
+ struct ldb_val *val;
+ int i;
+
+ el = ldb_msg_find_element(msg, "objectClass");
+ if (!el) return EINVAL;
+
+ /* see if this is a user to convert to a group */
+ for (i = 0; i < el->num_values; i++) {
+ val = &(el->values[i]);
+ if (strncasecmp(SYSDB_USER_CLASS,
+ (char *)val->data, val->length) == 0) {
+ break;
+ }
+ }
+ /* no, leave as is */
+ if (i == el->num_values) return EOK;
+
+ /* yes, convert */
+ val->data = (uint8_t *)talloc_strdup(msg, SYSDB_GROUP_CLASS);
+ if (val->data == NULL) return ENOMEM;
+ val->length = strlen(SYSDB_GROUP_CLASS);
+
+ return EOK;
+}
+
+static void grp_search(struct tevent_req *treq)
+{
+ struct sysdb_search_ctx *sctx;
+ static const char *attrs[] = SYSDB_GRSRC_ATTRS;
+ struct ldb_request *req;
+ struct ldb_dn *base_dn;
+ int ret;
+
+ sctx = tevent_req_callback_data(treq, struct sysdb_search_ctx);
+
+ ret = sysdb_operation_recv(treq, sctx, &sctx->handle);
+ if (ret) {
+ return request_error(sctx, ret);
+ }
+
+ if (sctx->gen_conv_mpg_users) {
+ base_dn = ldb_dn_new_fmt(sctx, sctx->ctx->ldb,
+ SYSDB_DOM_BASE, sctx->domain->name);
+ } else {
+ base_dn = ldb_dn_new_fmt(sctx, sctx->ctx->ldb,
+ SYSDB_TMPL_GROUP_BASE, sctx->domain->name);
+ }
+ if (!base_dn) {
+ return request_error(sctx, ENOMEM);
+ }
+
+ ret = ldb_build_search_req(&req, sctx->ctx->ldb, sctx,
+ base_dn, LDB_SCOPE_SUBTREE,
+ sctx->expression, attrs, NULL,
+ sctx, get_gen_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+
+ ret = ldb_request(sctx->ctx->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+}
+
+int sysdb_getgrnam(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ if (ctx->mpg) {
+ sctx->gen_conv_mpg_users = true;
+ sctx->expression = talloc_asprintf(sctx, SYSDB_GRNAM_MPG_FILTER, name);
+ } else {
+ sctx->expression = talloc_asprintf(sctx, SYSDB_GRNAM_FILTER, name);
+ }
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, grp_search, sctx);
+
+ return EOK;
+}
+
+int sysdb_getgrgid(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid,
+ sysdb_callback_t fn, void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ if (ctx->mpg) {
+ sctx->gen_conv_mpg_users = true;
+ sctx->expression = talloc_asprintf(sctx,
+ SYSDB_GRGID_MPG_FILTER,
+ (unsigned long int)gid);
+ } else {
+ sctx->expression = talloc_asprintf(sctx,
+ SYSDB_GRGID_FILTER,
+ (unsigned long int)gid);
+ }
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, grp_search, sctx);
+
+ return EOK;
+}
+
+int sysdb_enumgrent(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ sysdb_callback_t fn, void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ if (ctx->mpg) {
+ sctx->gen_conv_mpg_users = true;
+ sctx->expression = SYSDB_GRENT_MPG_FILTER;
+ } else {
+ sctx->expression = SYSDB_GRENT_FILTER;
+ }
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, grp_search, sctx);
+
+ return EOK;
+}
+
+static void initgr_mem_search(struct sysdb_search_ctx *sctx)
+{
+ struct sysdb_ctx *ctx = sctx->ctx;
+ struct ldb_result *res = sctx->res;
+ struct ldb_request *req;
+ struct ldb_control **ctrl;
+ struct ldb_asq_control *control;
+ static const char *attrs[] = SYSDB_INITGR_ATTRS;
+ int ret;
+
+ if (res->count == 0) {
+ return request_done(sctx);
+ }
+ if (res->count > 1) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* make sure we don't loop with get_gen_callback() */
+ sctx->gen_aux_fn = NULL;
+
+ sctx->expression = talloc_asprintf(sctx, SYSDB_INITGR_FILTER);
+ if (!sctx->expression) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ctrl = talloc_array(sctx, struct ldb_control *, 2);
+ if (!ctrl) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+ ctrl[1] = NULL;
+ ctrl[0] = talloc(ctrl, struct ldb_control);
+ if (!ctrl[0]) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+ ctrl[0]->oid = LDB_CONTROL_ASQ_OID;
+ ctrl[0]->critical = 1;
+ control = talloc(ctrl[0], struct ldb_asq_control);
+ if (!control) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+ control->request = 1;
+ control->source_attribute = talloc_strdup(control, SYSDB_INITGR_ATTR);
+ if (!control->source_attribute) {
+ return request_ldberror(sctx, LDB_ERR_OPERATIONS_ERROR);
+ }
+ control->src_attr_len = strlen(control->source_attribute);
+ ctrl[0]->data = control;
+
+ ret = ldb_build_search_req(&req, ctx->ldb, sctx,
+ res->msgs[0]->dn,
+ LDB_SCOPE_BASE,
+ sctx->expression, attrs, ctrl,
+ sctx, get_gen_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+
+ ret = ldb_request(ctx->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+}
+
+static void initgr_search(struct tevent_req *treq)
+{
+ struct sysdb_search_ctx *sctx;
+ static const char *attrs[] = SYSDB_PW_ATTRS;
+ struct ldb_request *req;
+ struct ldb_dn *base_dn;
+ int ret;
+
+ sctx = tevent_req_callback_data(treq, struct sysdb_search_ctx);
+
+ ret = sysdb_operation_recv(treq, sctx, &sctx->handle);
+ if (ret) {
+ return request_error(sctx, ret);
+ }
+
+ sctx->gen_aux_fn = initgr_mem_search;
+
+ base_dn = ldb_dn_new_fmt(sctx, sctx->ctx->ldb,
+ SYSDB_TMPL_USER_BASE, sctx->domain->name);
+ if (!base_dn) {
+ return request_error(sctx, ENOMEM);
+ }
+
+ ret = ldb_build_search_req(&req, sctx->ctx->ldb, sctx,
+ base_dn, LDB_SCOPE_SUBTREE,
+ sctx->expression, attrs, NULL,
+ sctx, get_gen_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+
+ ret = ldb_request(sctx->ctx->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ return request_ldberror(sctx, ret);
+ }
+}
+
+int sysdb_initgroups(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ sysdb_callback_t fn, void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ sctx->expression = talloc_asprintf(sctx, SYSDB_PWNAM_FILTER, name);
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, initgr_search, sctx);
+
+ return EOK;
+}
+
+int sysdb_get_user_attr(TALLOC_CTX *mem_ctx,
+ struct sysdb_ctx *ctx,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char **attributes,
+ sysdb_callback_t fn, void *ptr)
+{
+ struct sysdb_search_ctx *sctx;
+ struct tevent_req *req;
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ sctx = init_src_ctx(mem_ctx, domain, ctx, fn, ptr);
+ if (!sctx) {
+ return ENOMEM;
+ }
+
+ sctx->expression = talloc_asprintf(sctx, SYSDB_PWNAM_FILTER, name);
+ if (!sctx->expression) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ sctx->attrs = attributes;
+
+ req = sysdb_operation_send(mem_ctx, ctx->ev, ctx);
+ if (!req) {
+ talloc_free(sctx);
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(req, user_search, sctx);
+
+ return EOK;
+}
diff --git a/src/doxy.config.in b/src/doxy.config.in
new file mode 100644
index 00000000..6c147496
--- /dev/null
+++ b/src/doxy.config.in
@@ -0,0 +1,1538 @@
+# Doxyfile 1.6.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = @srcdir@/confdb
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.cpp \
+ *.cc \
+ *.c \
+ *.h \
+ *.hh \
+ *.hpp \
+ *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = */.git/* \
+ */.svn/* \
+ */cmake/* \
+ */build/*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# If the HTML_FOOTER_DESCRIPTION tag is set to YES, Doxygen will
+# add generated date, project name and doxygen version to HTML footer.
+
+HTML_FOOTER_DESCRIPTION= NO
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NONE
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
+# there is already a search function so this one should typically
+# be disabled.
+
+SEARCHENGINE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED = DOXYGEN
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/src/examples/sssd.conf b/src/examples/sssd.conf
new file mode 100644
index 00000000..82c6d6b0
--- /dev/null
+++ b/src/examples/sssd.conf
@@ -0,0 +1,81 @@
+[sssd]
+config_file_version = 2
+# Number of times services should attempt to reconnect in the
+# event of a crash or restart before they give up
+reconnection_retries = 3
+# if a backend is particularly slow you can raise this timeout here
+sbus_timeout = 30
+services = nss, pam
+; domains = LOCAL,LDAP
+# SSSD will not start if you don't configure any domain.
+# Add new domains condifgurations as [domain/<NAME>] sections.
+# Then add the list of domains (in the order you want them to be
+# queried in the 'domains" attribute above and uncomment it
+
+
+[nss]
+# the following prevents sssd for searching for the root user/group in
+# all domains (you can add here a comma separated list of system accounts are
+# always going to be /etc/passwd users, or that you want to filter out)
+filter_groups = root
+filter_users = root
+reconnection_retries = 3
+
+# The EntryCacheTimeout indicates the number of seconds to retain before
+# an entry in cache is considered stale and must block to refresh.
+# The EntryCacheNoWaitRefreshTimeout indicates the number of seconds to
+# wait before updating the cache out-of-band. (NSS requests will still
+# be returned from cache until the full EntryCacheTimeout). Setting this
+# value to 0 turns this feature off (default)
+; entry_cache_timeout = 600
+; entry_cache_nowait_timeout = 300
+
+[pam]
+reconnection_retries = 3
+
+# Example LOCAL domain that stores all users natively in the SSSD internal
+# directory. These local users and groups are not visibile in /etc/passwd, it
+# now contains only root and system accounts.
+; [domain/LOCAL]
+; description = LOCAL Users domain
+; id_provider = local
+; enumerate = true
+; min_id = 500
+; max_id = 999
+
+# Example native LDAP domain
+; [domain/LDAP]
+; id_provider = ldap
+; auth_provider = ldap
+; ldap_uri = ldap://ldap.mydomain.org
+; ldap_user_search_base = dc=mydomain,dc=org
+; ldap_tls_reqcert = demand
+; cache_credentials = true
+; enumerate = true
+
+# Example LDAP domain where the LDAP server is an Active Directory server.
+
+; [domain/AD]
+; description = LDAP domain with AD server
+; enumerate = false
+; min_id = 1000
+;
+; id_provider = ldap
+; auth_provider = ldap
+; ldap_uri = ldap://your.ad.server.com
+; ldap_schema = rfc2307bis
+; ldap_user_search_base = cn=users,dc=example,dc=com
+; ldap_group_search_base = cn=users,dc=example,dc=com
+; ldap_default_bind_dn = cn=Administrator,cn=Users,dc=example,dc=com
+; ldap_default_authtok_type = password
+; ldap_default_authtok = YOUR_PASSWORD
+; ldap_user_object_class = person
+; ldap_user_name = msSFU30Name
+; ldap_user_uid_number = msSFU30UidNumber
+; ldap_user_gid_number = msSFU30GidNumber
+; ldap_user_home_directory = msSFU30HomeDirectory
+; ldap_user_shell = msSFU30LoginShell
+; ldap_user_principal = userPrincipalName
+; ldap_group_object_class = group
+; ldap_group_name = msSFU30Name
+; ldap_group_gid_number = msSFU30GidNumber
diff --git a/src/examples/sssdproxytest b/src/examples/sssdproxytest
new file mode 100644
index 00000000..14217969
--- /dev/null
+++ b/src/examples/sssdproxytest
@@ -0,0 +1,5 @@
+#%PAM-1.0
+auth irequired pam_ldap.so
+
+account required pam_ldap.so
+
diff --git a/src/examples/sudo b/src/examples/sudo
new file mode 100644
index 00000000..4af91ba6
--- /dev/null
+++ b/src/examples/sudo
@@ -0,0 +1,6 @@
+#%PAM-1.0
+auth required pam_sss.so
+account required pam_sss.so
+password required pam_sss.so
+session optional pam_keyinit.so revoke
+session required pam_limits.so
diff --git a/src/external/crypto.m4 b/src/external/crypto.m4
new file mode 100644
index 00000000..d1bcf40a
--- /dev/null
+++ b/src/external/crypto.m4
@@ -0,0 +1,13 @@
+AC_ARG_ENABLE(crypto,
+ [ --enable-crypto Use OpenSSL crypto instead of NSS],
+ [CRYPTO="$enableval"],
+ [CRYPTO="no"]
+)
+
+if test x$CRYPTO != xyes; then
+ PKG_CHECK_MODULES([NSS],[nss],[have_nss=1],[have_nss=])
+else
+ PKG_CHECK_MODULES([CRYPTO],[libcrypto],[have_crypto=1],[have_crypto=])
+fi
+AM_CONDITIONAL([HAVE_NSS], [test x$have_nss != x])
+AM_CONDITIONAL([HAVE_CRYPTO], [test x$have_crypto != x])
diff --git a/src/external/docbook.m4 b/src/external/docbook.m4
new file mode 100644
index 00000000..cae89feb
--- /dev/null
+++ b/src/external/docbook.m4
@@ -0,0 +1,35 @@
+dnl Checks for tools needed to generate manual pages
+AC_DEFUN([CHECK_XML_TOOLS],
+[
+ AC_PATH_PROG([XSLTPROC], [xsltproc])
+ if test ! -x "$XSLTPROC"; then
+ AC_MSG_ERROR([Could not find xsltproc])
+ fi
+
+ AC_PATH_PROG([XMLLINT], [xmllint])
+ if test ! -x "$XMLLINT"; then
+ AC_MSG_ERROR([Could not find xmllint])
+ fi
+
+ AC_PATH_PROG([XMLCATALOG], [xmlcatalog])
+ if test ! -x "$XMLCATALOG"; then
+ AC_MSG_ERROR([Could not find xmlcatalog])
+ fi
+])
+
+dnl Usage:
+dnl CHECK_STYLESHEET_URI(FILE, URI, [FRIENDLY-NAME])
+dnl Checks if the XML catalog given by FILE exists and
+dnl if a particular URI appears in the XML catalog
+AC_DEFUN([CHECK_STYLESHEET],
+[
+ AC_CHECK_FILE($1, [], [AC_MSG_ERROR([could not find XML catalog])])
+
+ AC_MSG_CHECKING([for ifelse([$3],,[$2],[$3]) in XML catalog])
+ if AC_RUN_LOG([$XMLCATALOG --noout "$1" "$2" >&2]); then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_ERROR([could not find ifelse([$3],,[$2],[$3]) in XML catalog])
+ fi
+])
+
diff --git a/src/external/krb5.m4 b/src/external/krb5.m4
new file mode 100644
index 00000000..84bb4417
--- /dev/null
+++ b/src/external/krb5.m4
@@ -0,0 +1,62 @@
+AC_SUBST(KRB5_CFLAGS)
+AC_SUBST(KRB5_LIBS)
+
+if test x$KRB5_LIBS != x; then
+ KRB5_PASSED_LIBS=$KRB5_LIBS
+fi
+
+if test x$KRB5_CFLAGS != x; then
+ KRB5_PASSED_CFLAGS=$KRB5_CFLAGS
+fi
+
+AC_PATH_PROG(KRB5_CONFIG, krb5-config)
+AC_MSG_CHECKING(for working krb5-config)
+if test -x "$KRB5_CONFIG"; then
+ KRB5_CFLAGS="`$KRB5_CONFIG --cflags`"
+ KRB5_LIBS="`$KRB5_CONFIG --libs`"
+ AC_MSG_RESULT(yes)
+else
+ if test x$KRB5_PASSED_LIBS = x; then
+ AC_MSG_ERROR(no. Please install MIT kerberos devel package)
+ fi
+fi
+
+if test x$KRB5_PASSED_LIBS != x; then
+ KRB5_LIBS=$KRB5_PASSED_LIBS
+fi
+
+if test x$KRB5_PASSED_CFLAGS != x; then
+ KRB5_CFLAGS=$KRB5_PASSED_CFLAGS
+fi
+
+AC_ARG_VAR([KRB5_CFLAGS], [C compiler flags for kerberos, overriding krb5-config])dnl
+AC_ARG_VAR([KRB5_LIBS], [linker flags for kerberos, overriding krb5-config])dnl
+
+SAVE_CFLAGS=$CFLAGS
+SAVE_LIBS=$LIBS
+CFLAGS="$CFLAGS $KRB5_CFLAGS"
+LIBS="$LIBS $KRB5_LIBS"
+AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc krb5_get_error_message \
+ krb5_free_unparsed_name])
+CFLAGS=$SAVE_CFLAGS
+LIBS=$SAVE_LIBS
+
+if test x$ac_cv_header_krb5_h != xyes -a x$ac_cv_header_krb5_krb5_h != xyes
+then
+ AC_MSG_ERROR(you must have Kerberos 5 header files to build sssd)
+fi
+
+AC_ARG_ENABLE([krb5-locator-plugin],
+ [AS_HELP_STRING([--disable-krb5-locator-plugin],
+ [do not build Kerberos locator plugin])],
+ [build_locator=$enableval],
+ [build_locator=yes])
+
+AC_CHECK_HEADER([krb5/locate_plugin.h],
+ [have_locate_plugin=yes],
+ [have_locate_plugin=no]
+ [AC_MSG_NOTICE([Kerberos locator plugin cannot be build])])
+AM_CONDITIONAL([BUILD_KRB5_LOCATOR_PLUGIN],
+ [test x$have_locate_plugin == xyes -a x$build_locator == xyes])
+
diff --git a/src/external/ldap.m4 b/src/external/ldap.m4
new file mode 100644
index 00000000..87d95fa8
--- /dev/null
+++ b/src/external/ldap.m4
@@ -0,0 +1,63 @@
+dnl AC_SUBST(LDAP_LIBS)
+dnl
+dnl AC_CHECK_HEADERS(lber.h ldap.h, , AC_MSG_ERROR("could not locate ldap header files please install devel package"))
+dnl
+dnl AC_CHECK_LIB(lber, main, LDAP_LIBS="-llber $LDAP_LIBS")
+dnl AC_CHECK_LIB(ldap, main, LDAP_LIBS="-lldap $LDAP_LIBS")
+dnl
+dnl ---------------------------------------------------------------------------
+dnl - Check for Mozilla LDAP or OpenLDAP SDK
+dnl ---------------------------------------------------------------------------
+
+AC_CHECK_LIB(ldap, ldap_search, with_ldap=yes)
+dnl Check for other libraries we need to link with to get the main routines.
+test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes], , -llber) }
+test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes], , -llber -lkrb) }
+test "$with_ldap" != "yes" && { AC_CHECK_LIB(ldap, ldap_open, [with_ldap=yes with_ldap_lber=yes with_ldap_krb=yes with_ldap_des=yes], , -llber -lkrb -ldes) }
+dnl Recently, we need -lber even though the main routines are elsewhere,
+dnl because otherwise be get link errors w.r.t. ber_pvt_opt_on. So just
+dnl check for that (it's a variable not a fun but that doesn't seem to
+dnl matter in these checks) and stick in -lber if so. Can't hurt (even to
+dnl stick it in always shouldn't hurt, I don't think) ... #### Someone who
+dnl #### understands LDAP needs to fix this properly.
+test "$with_ldap_lber" != "yes" && { AC_CHECK_LIB(lber, ber_pvt_opt_on, with_ldap_lber=yes) }
+
+if test "$with_ldap" = "yes"; then
+ if test "$with_ldap_des" = "yes" ; then
+ OPENLDAP_LIBS="${OPENLDAP_LIBS} -ldes"
+ fi
+ if test "$with_ldap_krb" = "yes" ; then
+ OPENLDAP_LIBS="${OPENLDAP_LIBS} -lkrb"
+ fi
+ if test "$with_ldap_lber" = "yes" ; then
+ OPENLDAP_LIBS="${OPENLDAP_LIBS} -llber"
+ fi
+ OPENLDAP_LIBS="${OPENLDAP_LIBS} -lldap"
+else
+ AC_MSG_ERROR([OpenLDAP not found])
+fi
+
+AC_SUBST(OPENLDAP_LIBS)
+
+SAVE_CFLAGS=$CFLAGS
+SAVE_LIBS=$LIBS
+CFLAGS="$CFLAGS $OPENLDAP_CFLAGS"
+LIBS="$LIBS $OPENLDAP_LIBS"
+AC_CHECK_FUNCS([ldap_control_create])
+AC_CHECK_MEMBERS([struct ldap_conncb.lc_arg],
+ [AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[ #include <ldap.h> ]],
+ [[
+ struct ldap_conncb cb;
+ return ldap_set_option(NULL, LDAP_OPT_CONNECT_CB, &cb);
+ ]] )],
+ [AC_DEFINE([HAVE_LDAP_CONNCB], [1],
+ [Define if LDAP connection callbacks are available])],
+ [AC_MSG_WARN([Found broken callback implementation])],
+ [])],
+ [], [[#include <ldap.h>]])
+
+CFLAGS=$SAVE_CFLAGS
+LIBS=$SAVE_LIBS
+
diff --git a/src/external/libcares.m4 b/src/external/libcares.m4
new file mode 100644
index 00000000..657deac5
--- /dev/null
+++ b/src/external/libcares.m4
@@ -0,0 +1,20 @@
+AC_SUBST(CARES_OBJ)
+AC_SUBST(CARES_LIBS)
+AC_SUBST(CARES_CFLAGS)
+
+AC_CHECK_HEADERS(ares.h,
+ [AC_CHECK_LIB([cares], [ares_init], [ CARES_LIBS="-lcares" ], [AC_MSG_ERROR([No usable c-ares library found])])],
+ [AC_MSG_ERROR([c-ares header files are not installed])]
+)
+
+dnl Check if this particular version of c-ares supports the generic ares_free_data function
+AC_CHECK_LIB([cares],
+ [ares_free_data],
+ [AC_DEFINE([HAVE_ARES_DATA], 1, [Does c-ares have ares_free_data()?])
+ ],
+ [
+ ares_data=1
+ ]
+)
+
+AM_CONDITIONAL(BUILD_ARES_DATA, test x$ares_data = x1)
diff --git a/src/external/libcollection.m4 b/src/external/libcollection.m4
new file mode 100644
index 00000000..8fc37e8c
--- /dev/null
+++ b/src/external/libcollection.m4
@@ -0,0 +1,12 @@
+AC_SUBST(SYSTEM_COLLECTION_OBJ)
+AC_SUBST(SYSTEM_COLLECTION_CFLAGS)
+AC_SUBST(SYSTEM_COLLECTION_LIBS)
+
+PKG_CHECK_MODULES(SYSTEM_COLLECTION, collection >= 0.4.0,
+ have_system_collection=true,
+ have_system_collection=false
+ )
+# This is future-compatible. Right now, we'll force the use of our
+# in-tree copy. When collection is split off as its own source package, we'll
+# fix this test
+AM_CONDITIONAL(HAVE_SYSTEM_COLLECTION, test x$have_system_collection = xtrue_FORCE_IN_TREE)
diff --git a/src/external/libdhash.m4 b/src/external/libdhash.m4
new file mode 100644
index 00000000..e3afdac3
--- /dev/null
+++ b/src/external/libdhash.m4
@@ -0,0 +1,12 @@
+AC_SUBST(SYSTEM_DHASH_OBJ)
+AC_SUBST(SYSTEM_DHASH_CFLAGS)
+AC_SUBST(SYSTEM_DHASH_LIBS)
+
+PKG_CHECK_MODULES(SYSTEM_DHASH, dhash >= 0.4.0,
+ have_system_dhash=true,
+ have_system_dhash=false
+ )
+# This is future-compatible. Right now, we'll force the use of our
+# in-tree copy. When dhash is split off as its own source package, we'll
+# fix this test
+AM_CONDITIONAL(HAVE_SYSTEM_DHASH, test x$have_system_dhash = xtrue_FORCE_IN_TREE)
diff --git a/src/external/libini_config.m4 b/src/external/libini_config.m4
new file mode 100644
index 00000000..20291efa
--- /dev/null
+++ b/src/external/libini_config.m4
@@ -0,0 +1,12 @@
+AC_SUBST(SYSTEM_INI_CONFIG_OBJ)
+AC_SUBST(SYSTEM_INI_CONFIG_CFLAGS)
+AC_SUBST(SYSTEM_INI_CONFIG_LIBS)
+
+PKG_CHECK_MODULES(SYSTEM_INI_CONFIG, ini_config >= 0.4.0,
+ have_system_ini_config=true,
+ have_system_ini_config=false
+ )
+# This is future-compatible. Right now, we'll force the use of our
+# in-tree copy. When ini_config is split off as its own source package, we'll
+# fix this test
+AM_CONDITIONAL(HAVE_SYSTEM_INI_CONFIG, test x$have_system_ini_config = xtrue_FORCE_IN_TREE)
diff --git a/src/external/libldb.m4 b/src/external/libldb.m4
new file mode 100644
index 00000000..0679e1d1
--- /dev/null
+++ b/src/external/libldb.m4
@@ -0,0 +1,28 @@
+AC_SUBST(LDB_OBJ)
+AC_SUBST(LDB_CFLAGS)
+AC_SUBST(LDB_LIBS)
+
+PKG_CHECK_MODULES(LDB, ldb >= 0.9.2)
+
+AC_CHECK_HEADERS(ldb.h ldb_module.h,
+ [AC_CHECK_LIB(ldb, ldb_init, [LDB_LIBS="-lldb"], , -ltevent) ],
+ [AC_MSG_ERROR([LDB header files are not installed])]
+)
+
+AC_ARG_WITH([ldb-lib-dir],
+ [AC_HELP_STRING([--with-ldb-lib-dir=PATH],
+ [Path to store ldb modules [${libdir}/ldb]]
+ )
+ ]
+ )
+
+if test x"$with_ldb_lib_dir" != x; then
+ ldblibdir=$with_ldb_lib_dir
+else
+ ldblibdir="`$PKG_CONFIG --variable=modulesdir ldb`"
+ if ! test -d $ldblibdir; then
+ ldblibdir="${libdir}/ldb"
+ fi
+fi
+AC_MSG_NOTICE([ldb lib directory: $ldblibdir])
+AC_SUBST(ldblibdir)
diff --git a/src/external/libpcre.m4 b/src/external/libpcre.m4
new file mode 100644
index 00000000..5917c8cf
--- /dev/null
+++ b/src/external/libpcre.m4
@@ -0,0 +1,15 @@
+PCRE_OBJ=""
+AC_SUBST(PCRE_OBJ)
+AC_SUBST(PCRE_LIBS)
+AC_SUBST(PCRE_CFLAGS)
+
+PKG_CHECK_MODULES(PCRE, libpcre)
+PKG_CHECK_EXISTS(libpcre >= 7,
+ [AC_MSG_NOTICE([PCRE version is 7 or higher])],
+ [AC_MSG_NOTICE([PCRE version is below 7])
+ AC_DEFINE([HAVE_LIBPCRE_LESSER_THAN_7],
+ 1,
+ [Define if libpcre version is less than 7])])
+
+AC_CHECK_HEADERS(pcre.h)
+AC_CHECK_LIB(pcre, pcre_compile, [ PCRE_LIBS="-lpcre" ], [AC_MSG_ERROR([PCRE is required])])
diff --git a/src/external/libpopt.m4 b/src/external/libpopt.m4
new file mode 100644
index 00000000..e59b2610
--- /dev/null
+++ b/src/external/libpopt.m4
@@ -0,0 +1,9 @@
+POPT_OBJ=""
+AC_SUBST(POPT_OBJ)
+AC_SUBST(POPT_LIBS)
+AC_SUBST(POPT_CFLAGS)
+
+AC_CHECK_HEADERS([popt.h],
+ [AC_CHECK_LIB(popt, poptGetContext, [ POPT_LIBS="-lpopt" ], [AC_MSG_ERROR([POPT must support poptGetContext])])],
+ [AC_MSG_ERROR([POPT development libraries not installed])]
+)
diff --git a/src/external/libtalloc.m4 b/src/external/libtalloc.m4
new file mode 100644
index 00000000..a4c5b8a9
--- /dev/null
+++ b/src/external/libtalloc.m4
@@ -0,0 +1,7 @@
+AC_SUBST(TALLOC_OBJ)
+AC_SUBST(TALLOC_CFLAGS)
+AC_SUBST(TALLOC_LIBS)
+
+AC_CHECK_HEADER(talloc.h,
+ [AC_CHECK_LIB(talloc, talloc_init, [TALLOC_LIBS="-ltalloc"]) ],
+ [PKG_CHECK_MODULES(TALLOC, talloc)])
diff --git a/src/external/libtdb.m4 b/src/external/libtdb.m4
new file mode 100644
index 00000000..196bc5cc
--- /dev/null
+++ b/src/external/libtdb.m4
@@ -0,0 +1,8 @@
+AC_SUBST(TDB_OBJ)
+AC_SUBST(TDB_CFLAGS)
+AC_SUBST(TDB_LIBS)
+
+AC_CHECK_HEADERS([tdb.h],
+ [AC_CHECK_LIB(tdb, tdb_repack, [TDB_LIBS="-ltdb"], [AC_MSG_ERROR([TDB must support tdb_repack])]) ],
+ [PKG_CHECK_MODULES(TDB, tdb >= 1.1.3)]
+)
diff --git a/src/external/libtevent.m4 b/src/external/libtevent.m4
new file mode 100644
index 00000000..6a0e36af
--- /dev/null
+++ b/src/external/libtevent.m4
@@ -0,0 +1,7 @@
+AC_SUBST(TEVENT_OBJ)
+AC_SUBST(TEVENT_CFLAGS)
+AC_SUBST(TEVENT_LIBS)
+
+AC_CHECK_HEADER(tevent.h,
+ [AC_CHECK_LIB(tevent, tevent_context_init, [TEVENT_LIBS="-ltevent"]) ],
+ [PKG_CHECK_MODULES(TEVENT, tevent)])
diff --git a/src/external/pam.m4 b/src/external/pam.m4
new file mode 100644
index 00000000..40c8fd0d
--- /dev/null
+++ b/src/external/pam.m4
@@ -0,0 +1,6 @@
+AC_SUBST(PAM_LIBS)
+
+AC_CHECK_HEADERS([security/pam_appl.h security/pam_misc.h security/pam_modules.h],
+ [AC_CHECK_LIB(pam, pam_get_item, [ PAM_LIBS="-lpam" ], [AC_MSG_ERROR([PAM must support pam_get_item])])],
+ [AC_MSG_ERROR([PAM development libraries not installed])]
+)
diff --git a/src/external/pkg.m4 b/src/external/pkg.m4
new file mode 100644
index 00000000..a8b3d06c
--- /dev/null
+++ b/src/external/pkg.m4
@@ -0,0 +1,156 @@
+# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
+#
+# Copyright © 2004 Scott James Remnant <scott@netsplit.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/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# PKG_PROG_PKG_CONFIG([MIN-VERSION])
+# ----------------------------------
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+ AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+ _pkg_min_version=m4_default([$1], [0.9.0])
+ AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+ if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ PKG_CONFIG=""
+ fi
+
+fi[]dnl
+])# PKG_PROG_PKG_CONFIG
+
+# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# Check to see whether a particular set of modules exists. Similar
+# to PKG_CHECK_MODULES(), but does not set variables or print errors.
+#
+#
+# Similar to PKG_CHECK_MODULES, make sure that the first instance of
+# this or PKG_CHECK_MODULES is called, or make sure to call
+# PKG_CHECK_EXISTS manually
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+ m4_ifval([$2], [$2], [:])
+m4_ifvaln([$3], [else
+ $3])dnl
+fi])
+
+
+# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+# ---------------------------------------------
+m4_define([_PKG_CONFIG],
+[if test -n "$PKG_CONFIG"; then
+ if test -n "$$1"; then
+ pkg_cv_[]$1="$$1"
+ else
+ PKG_CHECK_EXISTS([$3],
+ [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
+ [pkg_failed=yes])
+ fi
+else
+ pkg_failed=untried
+fi[]dnl
+])# _PKG_CONFIG
+
+# _PKG_SHORT_ERRORS_SUPPORTED
+# -----------------------------
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi[]dnl
+])# _PKG_SHORT_ERRORS_SUPPORTED
+
+
+# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+# [ACTION-IF-NOT-FOUND])
+#
+#
+# Note that if there is a possibility the first call to
+# PKG_CHECK_MODULES might not happen, you should be sure to include an
+# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+#
+#
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $1])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+ _PKG_SHORT_ERRORS_SUPPORTED
+ if test $_pkg_short_errors_supported = yes; then
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
+ else
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+ ifelse([$4], , [AC_MSG_ERROR(dnl
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT
+])],
+ [AC_MSG_RESULT([no])
+ $4])
+elif test $pkg_failed = untried; then
+ ifelse([$4], , [AC_MSG_FAILURE(dnl
+[The pkg-config script could not be found or is too old. Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://www.freedesktop.org/software/pkgconfig>.])],
+ [$4])
+else
+ $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+ $1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+ AC_MSG_RESULT([yes])
+ ifelse([$3], , :, [$3])
+fi[]dnl
+])# PKG_CHECK_MODULES
diff --git a/src/external/platform.m4 b/src/external/platform.m4
new file mode 100644
index 00000000..71b4f2c8
--- /dev/null
+++ b/src/external/platform.m4
@@ -0,0 +1,29 @@
+AC_ARG_WITH([os],
+ [AC_HELP_STRING([--with-os=OS_TYPE], [Type of your operation system (fedora|redhat|suse)])]
+ )
+osname=""
+if test x"$with_os" != x ; then
+ if test x"$with_os" == xfedora -o \
+ x"$with_os" == xredhat -o \
+ x"$with_os" == xsuse ; then
+ osname=$with_os
+ else
+ AC_MSG_ERROR([Illegal value -$with_os- for option --with-os])
+ fi
+fi
+
+if test x"$osname" = x ; then
+ if test -f /etc/fedora-release ; then
+ osname="fedora"
+ elif test -f /etc/redhat-release ; then
+ osname="redhat"
+ elif test -f /etc/SuSE-release ; then
+ osname="suse"
+ fi
+
+ AC_MSG_NOTICE([Detected operation system type: $osname])
+fi
+
+AM_CONDITIONAL([HAVE_FEDORA], [test x"$osname" == xfedora])
+AM_CONDITIONAL([HAVE_REDHAT], [test x"$osname" == xredhat])
+AM_CONDITIONAL([HAVE_SUSE], [test x"$osname" == xsuse])
diff --git a/src/external/python.m4 b/src/external/python.m4
new file mode 100644
index 00000000..db598638
--- /dev/null
+++ b/src/external/python.m4
@@ -0,0 +1,58 @@
+dnl Check for python-config and substitute needed CFLAGS and LDFLAGS
+dnl Usage:
+dnl AM_PYTHON_CONFIG
+
+AC_DEFUN([AM_PYTHON_CONFIG],
+[ AC_SUBST(PYTHON_CFLAGS)
+ AC_SUBST(PYTHON_LIBS)
+
+dnl We need to check for python build flags using distutils.sysconfig
+dnl We cannot use python-config, as it was not available on older
+dnl versions of python
+ AC_PATH_PROG(PYTHON, python)
+ AC_MSG_CHECKING([for working python])
+ if test -x "$PYTHON"; then
+ PYTHON_CFLAGS="`$PYTHON -c \"from distutils import sysconfig; \
+ print '-I' + sysconfig.get_python_inc() + \
+ ' -I' + sysconfig.get_python_inc(plat_specific=True) + ' ' + \
+ sysconfig.get_config_var('BASECFLAGS')\"`"
+ PYTHON_LIBS="`$PYTHON -c \"from distutils import sysconfig; \
+ print \\\" \\\".join(sysconfig.get_config_var('LIBS').split() + \
+ sysconfig.get_config_var('SYSLIBS').split()) + \
+ ' -lpython' + sysconfig.get_config_var('VERSION')\"`"
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_ERROR([no. Please install python devel package])
+ fi
+])
+
+dnl Taken from GNOME sources
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+ AC_MSG_CHECKING(for headers required to compile python extensions)
+
+ dnl deduce PYTHON_INCLUDES
+ py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+ py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+ PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+ if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+ fi
+
+ AC_SUBST(PYTHON_INCLUDES)
+
+ dnl check if the headers exist:
+ save_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+ AC_TRY_CPP([#include <Python.h>],dnl
+ [AC_MSG_RESULT([found])
+ $1],dnl
+ [AC_MSG_RESULT([not found])
+ $2])
+ CPPFLAGS="$save_CPPFLAGS"
+])
+
+
diff --git a/src/external/selinux.m4 b/src/external/selinux.m4
new file mode 100644
index 00000000..0c5d5294
--- /dev/null
+++ b/src/external/selinux.m4
@@ -0,0 +1,13 @@
+dnl A macro to check the availability of SELinux
+AC_DEFUN([AM_CHECK_SELINUX],
+[
+ AC_CHECK_HEADERS(selinux/selinux.h,
+ [AC_CHECK_LIB(selinux, is_selinux_enabled,
+ [SELINUX_LIBS="-lselinux"],
+ [AC_MSG_ERROR([SELinux library is missing])]
+ )
+ ],
+ [AC_MSG_ERROR([SELinux headers are missing])])
+ AC_SUBST(SELINUX_LIBS)
+])
+
diff --git a/src/external/sizes.m4 b/src/external/sizes.m4
new file mode 100644
index 00000000..53df61de
--- /dev/null
+++ b/src/external/sizes.m4
@@ -0,0 +1,44 @@
+# Solaris needs HAVE_LONG_LONG defined
+AC_CHECK_TYPES(long long)
+
+AC_CHECK_SIZEOF(int)
+AC_CHECK_SIZEOF(char)
+AC_CHECK_SIZEOF(short)
+AC_CHECK_SIZEOF(long)
+AC_CHECK_SIZEOF(long long)
+
+if test $ac_cv_sizeof_long_long -lt 8 ; then
+AC_MSG_ERROR([SSSD requires long long of 64-bits])
+fi
+
+AC_CHECK_TYPE(uint_t, unsigned int)
+AC_CHECK_TYPE(int8_t, char)
+AC_CHECK_TYPE(uint8_t, unsigned char)
+AC_CHECK_TYPE(int16_t, short)
+AC_CHECK_TYPE(uint16_t, unsigned short)
+
+if test $ac_cv_sizeof_int -eq 4 ; then
+AC_CHECK_TYPE(int32_t, int)
+AC_CHECK_TYPE(uint32_t, unsigned int)
+elif test $ac_cv_size_long -eq 4 ; then
+AC_CHECK_TYPE(int32_t, long)
+AC_CHECK_TYPE(uint32_t, unsigned long)
+else
+AC_MSG_ERROR([LIBREPLACE no 32-bit type found])
+fi
+
+AC_CHECK_TYPE(int64_t, long long)
+AC_CHECK_TYPE(uint64_t, unsigned long long)
+
+AC_CHECK_TYPE(size_t, unsigned int)
+AC_CHECK_TYPE(ssize_t, int)
+
+AC_CHECK_SIZEOF(off_t)
+AC_CHECK_SIZEOF(size_t)
+AC_CHECK_SIZEOF(ssize_t)
+
+AC_CHECK_TYPE(intptr_t, long long)
+AC_CHECK_TYPE(uintptr_t, unsigned long long)
+AC_CHECK_TYPE(ptrdiff_t, unsigned long long)
+
+
diff --git a/src/krb5_plugin/sssd_krb5_locator_plugin.c b/src/krb5_plugin/sssd_krb5_locator_plugin.c
new file mode 100644
index 00000000..5e797333
--- /dev/null
+++ b/src/krb5_plugin/sssd_krb5_locator_plugin.c
@@ -0,0 +1,289 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 _GNU_SOURCE
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <krb5/locate_plugin.h>
+
+#include "providers/krb5/krb5_common.h"
+
+#define BUFSIZE 512
+#define SSSD_KRB5_LOCATOR_DEBUG "SSSD_KRB5_LOCATOR_DEBUG"
+#define DEBUG_KEY "[sssd_krb5_locator] "
+#define PLUGIN_DEBUG(body) do { \
+ if (ctx->debug) { \
+ debug_fn body; \
+ } \
+} while(0);
+
+struct sssd_ctx {
+ char *sssd_realm;
+ struct addrinfo *sssd_kdc_addrinfo;
+ bool debug;
+};
+
+void debug_fn(const char *format, ...)
+{
+ va_list ap;
+ char *s = NULL;
+ int ret;
+
+ va_start(ap, format);
+
+ ret = vasprintf(&s, format, ap);
+ if (ret < 0) {
+ /* ENOMEM */
+ return;
+ }
+
+ va_end(ap);
+
+ fprintf(stderr, DEBUG_KEY "%s", s);
+ free(s);
+}
+
+static int get_kdcinfo(const char *realm, struct sssd_ctx *ctx)
+{
+ int ret;
+ char *kdcinfo_name = NULL;
+ size_t len;
+ uint8_t buf[BUFSIZE + 1];
+ uint8_t *p;
+ int fd = -1;
+
+ len = strlen(realm) + strlen(KDCINFO_TMPL);
+
+ kdcinfo_name = calloc(1, len + 1);
+ if (kdcinfo_name == NULL) {
+ PLUGIN_DEBUG(("malloc failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = snprintf(kdcinfo_name, len, KDCINFO_TMPL, realm);
+ if (ret < 0) {
+ PLUGIN_DEBUG(("snprintf failed"));
+ ret = EINVAL;
+ }
+ kdcinfo_name[len] = '\0';
+
+ fd = open(kdcinfo_name, O_RDONLY);
+ if (fd == -1) {
+ PLUGIN_DEBUG(("open failed [%d][%s].\n", errno, strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+
+ len = BUFSIZE;
+ p = buf;
+ memset(buf, 0, BUFSIZE+1);
+ while (len != 0 && (ret = read(fd, p, len)) != 0) {
+ if (ret == -1) {
+ if (errno == EINTR) continue;
+ PLUGIN_DEBUG(("read failed [%d][%s].\n", errno, strerror(errno)));
+ close(fd);
+ goto done;
+ }
+
+ len -= ret;
+ p += ret;
+ }
+ close(fd);
+
+ if (len == 0) {
+ PLUGIN_DEBUG(("Content of kdcinfo file [%s] is [%d] or larger.\n",
+ kdcinfo_name, BUFSIZE));
+ }
+ PLUGIN_DEBUG(("Found kdcinfo [%s].\n", buf));
+
+ ret = getaddrinfo((char *) buf, "kerberos", NULL, &ctx->sssd_kdc_addrinfo);
+ if (ret != 0) {
+ PLUGIN_DEBUG(("getaddrinfo failed [%d][%s].\n", ret,
+ gai_strerror(ret)));
+ if (ret == EAI_SYSTEM) {
+ PLUGIN_DEBUG(("getaddrinfo failed [%d][%s].\n", errno,
+ strerror(errno)));
+ }
+ goto done;
+ }
+
+ ctx->sssd_realm = strdup(realm);
+ if (ctx->sssd_realm == NULL) {
+ PLUGIN_DEBUG(("strdup failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+
+
+done:
+ free(kdcinfo_name);
+ return ret;
+}
+
+krb5_error_code sssd_krb5_locator_init(krb5_context context,
+ void **private_data)
+{
+ struct sssd_ctx *ctx;
+ const char *dummy;
+
+ ctx = calloc(1,sizeof(struct sssd_ctx));
+ if (ctx == NULL) return ENOMEM;
+
+ dummy = getenv(SSSD_KRB5_LOCATOR_DEBUG);
+ if (dummy == NULL) {
+ ctx->debug = false;
+ } else {
+ ctx->debug = true;
+ PLUGIN_DEBUG(("sssd_krb5_locator_init called\n"));
+ }
+
+ *private_data = ctx;
+
+ return 0;
+}
+
+void sssd_krb5_locator_close(void *private_data)
+{
+ struct sssd_ctx *ctx;
+
+ if (private_data == NULL) return;
+
+ ctx = (struct sssd_ctx *) private_data;
+ PLUGIN_DEBUG(("sssd_krb5_locator_close called\n"));
+
+ freeaddrinfo(ctx->sssd_kdc_addrinfo);
+ free(ctx->sssd_realm);
+ free(ctx);
+ private_data = NULL;
+
+ return;
+}
+
+krb5_error_code sssd_krb5_locator_lookup(void *private_data,
+ enum locate_service_type svc,
+ const char *realm,
+ int socktype,
+ int family,
+ int (*cbfunc)(void *, int, struct sockaddr *),
+ void *cbdata)
+{
+ int ret;
+ struct addrinfo *ai;
+ struct sssd_ctx *ctx;
+ char hostip[NI_MAXHOST];
+
+ if (private_data == NULL) return KRB5_PLUGIN_NO_HANDLE;
+ ctx = (struct sssd_ctx *) private_data;
+
+ if (ctx->sssd_realm == NULL || strcmp(ctx->sssd_realm, realm) != 0) {
+ freeaddrinfo(ctx->sssd_kdc_addrinfo);
+ ctx->sssd_kdc_addrinfo = NULL;
+ free(ctx->sssd_realm);
+ ctx->sssd_realm = NULL;
+ ret = get_kdcinfo(realm, ctx);
+ if (ret != EOK) {
+ PLUGIN_DEBUG(("get_kdcinfo failed.\n"));
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ }
+
+ PLUGIN_DEBUG(("sssd_realm[%s] requested realm[%s] family[%d] socktype[%d] "
+ "locate_service[%d]\n", ctx->sssd_realm, realm, family,
+ socktype, svc));
+
+ switch (svc) {
+ case locate_service_kdc:
+ case locate_service_master_kdc:
+ case locate_service_kadmin:
+ break;
+ case locate_service_krb524:
+ case locate_service_kpasswd:
+ return KRB5_PLUGIN_NO_HANDLE;
+ default:
+ return EINVAL;
+ }
+
+ switch (family) {
+ case AF_UNSPEC:
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ switch (socktype) {
+ case SOCK_STREAM:
+ case SOCK_DGRAM:
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (strcmp(realm, ctx->sssd_realm) != 0)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ for (ai = ctx->sssd_kdc_addrinfo; ai != NULL; ai = ai->ai_next) {
+ ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, hostip, NI_MAXHOST,
+ NULL, 0, NI_NUMERICHOST);
+ if (ret != 0) {
+ PLUGIN_DEBUG(("getnameinfo failed [%d][%s].\n", ret,
+ gai_strerror(ret)));
+ if (ret == EAI_SYSTEM) {
+ PLUGIN_DEBUG(("getnameinfo failed [%d][%s].\n", errno,
+ strerror(errno)));
+ }
+ }
+ PLUGIN_DEBUG(("addr[%s] family[%d] socktype[%d] - ", hostip,
+ ai->ai_family, ai->ai_socktype));
+
+ if ((family == AF_UNSPEC || ai->ai_family == family) &&
+ ai->ai_socktype == socktype) {
+
+ ret = cbfunc(cbdata, socktype, ai->ai_addr);
+ if (ret != 0) {
+ PLUGIN_DEBUG(("\ncbfunc failed\n"));
+ } else {
+ PLUGIN_DEBUG(("used\n"));
+ }
+ } else {
+ PLUGIN_DEBUG((" NOT used\n"));
+ }
+ }
+
+ return 0;
+}
+
+const krb5plugin_service_locate_ftable service_locator = {
+ 0, /* version */
+ sssd_krb5_locator_init,
+ sssd_krb5_locator_close,
+ sssd_krb5_locator_lookup,
+};
diff --git a/src/ldb_modules/memberof.c b/src/ldb_modules/memberof.c
new file mode 100644
index 00000000..e0a241a5
--- /dev/null
+++ b/src/ldb_modules/memberof.c
@@ -0,0 +1,3612 @@
+/*
+ SSSD memberof module
+
+ Copyright (C) Simo Sorce <idra@samba.org> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include "ldb_module.h"
+#include "util/util.h"
+#include "dhash.h"
+
+#define DB_MEMBER "member"
+#define DB_MEMBEROF "memberof"
+#define DB_MEMBERUID "memberuid"
+#define DB_NAME "name"
+#define DB_USER_CLASS "user"
+#define DB_OC "objectClass"
+
+#ifndef talloc_zfree
+#define talloc_zfree(ptr) do { talloc_free(ptr); ptr = NULL; } while(0)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+struct mbof_dn_array {
+ struct ldb_dn **dns;
+ int num;
+};
+
+struct mbof_dn {
+ struct mbof_dn *next;
+ struct ldb_dn *dn;
+};
+
+struct mbof_ctx {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct ldb_control **ret_ctrls;
+ struct ldb_extended *ret_resp;
+};
+
+struct mbof_add_operation {
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_add_operation *next;
+
+ struct mbof_dn_array *parents;
+ struct ldb_dn *entry_dn;
+
+ struct ldb_message *entry;
+};
+
+struct mbof_memberuid_op {
+ struct ldb_dn *dn;
+ struct ldb_message_element *el;
+};
+
+struct mbof_add_ctx {
+ struct mbof_ctx *ctx;
+
+ struct mbof_add_operation *add_list;
+ struct mbof_add_operation *current_op;
+
+ struct ldb_message *msg;
+ struct ldb_dn *msg_dn;
+ bool terminate;
+
+ struct mbof_dn *missing;
+
+ struct mbof_memberuid_op *muops;
+ int num_muops;
+ int cur_muop;
+};
+
+struct mbof_del_ancestors_ctx {
+ struct mbof_dn_array *new_list;
+ int num_direct;
+ int cur;
+
+ struct ldb_message *entry;
+};
+
+struct mbof_del_operation {
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_del_operation *parent;
+ struct mbof_del_operation **children;
+ int num_children;
+ int next_child;
+
+ struct ldb_dn *entry_dn;
+
+ struct ldb_message *entry;
+ struct ldb_message **parents;
+ int num_parents;
+ int cur_parent;
+
+ struct mbof_del_ancestors_ctx *anc_ctx;
+};
+
+struct mbof_mod_ctx;
+
+struct mbof_del_ctx {
+ struct mbof_ctx *ctx;
+
+ struct mbof_del_operation *first;
+ struct mbof_dn *history;
+
+ struct ldb_message **mus;
+ int num_mus;
+
+ struct mbof_memberuid_op *muops;
+ int num_muops;
+ int cur_muop;
+
+ struct mbof_mod_ctx *follow_mod;
+ bool is_mod;
+};
+
+struct mbof_mod_ctx {
+ struct mbof_ctx *ctx;
+
+ const struct ldb_message_element *membel;
+ struct ldb_message *entry;
+
+ struct mbof_dn_array *to_add;
+
+ struct ldb_message *msg;
+ bool terminate;
+};
+
+static struct mbof_ctx *mbof_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct mbof_ctx *ctx;
+
+ ctx = talloc_zero(req, struct mbof_ctx);
+ if (!ctx) {
+ return NULL;
+ }
+
+ ctx->module = module;
+ ctx->req = req;
+
+ return ctx;
+}
+
+static int entry_is_user_object(struct ldb_message *entry)
+{
+ struct ldb_message_element *el;
+ struct ldb_val *val;
+ int i;
+
+ el = ldb_msg_find_element(entry, DB_OC);
+ if (!el) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* see if this is a user */
+ for (i = 0; i < el->num_values; i++) {
+ val = &(el->values[i]);
+ if (strncasecmp(DB_USER_CLASS, (char *)val->data, val->length) == 0) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+}
+
+static int mbof_append_muop(TALLOC_CTX *memctx,
+ struct mbof_memberuid_op **_muops,
+ int *_num_muops,
+ int flags,
+ struct ldb_dn *parent,
+ const char *name)
+{
+ struct mbof_memberuid_op *muops = *_muops;
+ int num_muops = *_num_muops;
+ struct mbof_memberuid_op *op;
+ struct ldb_val *val;
+ int i;
+
+ op = NULL;
+ if (muops) {
+ for (i = 0; i < num_muops; i++) {
+ if (ldb_dn_compare(parent, muops[i].dn) == 0) {
+ op = &muops[i];
+ break;
+ }
+ }
+ }
+ if (!op) {
+ muops = talloc_realloc(memctx, muops,
+ struct mbof_memberuid_op,
+ num_muops + 1);
+ if (!muops) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ op = &muops[num_muops];
+ num_muops++;
+ *_muops = muops;
+ *_num_muops = num_muops;
+
+ op->dn = parent;
+ op->el = NULL;
+ }
+
+ if (!op->el) {
+ op->el = talloc_zero(muops, struct ldb_message_element);
+ if (!op->el) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ op->el->name = talloc_strdup(op->el, DB_MEMBERUID);
+ if (!op->el->name) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ op->el->flags = flags;
+ }
+
+ for (i = 0; i < op->el->num_values; i++) {
+ if (strcmp((char *)op->el->values[i].data, name) == 0) {
+ /* we already have this value, get out*/
+ return LDB_SUCCESS;
+ }
+ }
+
+ val = talloc_realloc(op->el, op->el->values,
+ struct ldb_val, op->el->num_values + 1);
+ if (!val) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ val[op->el->num_values].data = (uint8_t *)talloc_strdup(val, name);
+ if (!val[op->el->num_values].data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ val[op->el->num_values].length = strlen(name);
+
+ op->el->values = val;
+ op->el->num_values++;
+
+ return LDB_SUCCESS;
+}
+
+
+/* add operation */
+
+/* An add operation is quite simple.
+ * First of all a new object cannot yet have parents, so the only memberof
+ * attribute that can be added to any member contains just one object DN.
+ *
+ * The real add operation is done first, to assure nothing else fails.
+ * Then we list all members of the object just created, and for each member
+ * we create an "add operation" and we pass it a parent list of one member
+ * (the object we just added again).
+ *
+ * For each add operation we lookup the object we want to operate on.
+ * We take the list of memberof attributes and sort out which parents are
+ * still missing from the parent list we have provided.
+ * We modify the object memberof attributes to reflect the new memberships.
+ * Then we list all members of this object, and for each once again we create
+ * an "add operation" as we did in the initial object.
+ *
+ * Processing stops when the target object does not have members or when it
+ * already has all the parents (can happen if nested groups create loops).
+ *
+ * Group cache unrolling:
+ * Every time we add a memberof attribute to an actual user object,
+ * we proceed to store the user name.
+ *
+ * At the end we will add a memberuid attribute to our new object that
+ * includes all direct and indeirect user members names.
+ */
+
+static int mbof_append_addop(struct mbof_add_ctx *add_ctx,
+ struct mbof_dn_array *parents,
+ struct ldb_dn *entry_dn)
+{
+ struct mbof_add_operation *lastop = NULL;
+ struct mbof_add_operation *addop;
+
+ /* test if this is a duplicate */
+ /* FIXME: this is not efficient */
+ if (add_ctx->add_list) {
+ do {
+ if (lastop) {
+ lastop = lastop->next;
+ } else {
+ lastop = add_ctx->add_list;
+ }
+
+ /* FIXME: check if this is right, might have to compare parents */
+ if (ldb_dn_compare(lastop->entry_dn, entry_dn) == 0) {
+ /* duplicate found */
+ return LDB_SUCCESS;
+ }
+ } while (lastop->next);
+ }
+
+ addop = talloc_zero(add_ctx, struct mbof_add_operation);
+ if (!addop) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ addop->add_ctx = add_ctx;
+ addop->parents = parents;
+ addop->entry_dn = entry_dn;
+
+ if (add_ctx->add_list) {
+ lastop->next = addop;
+ } else {
+ add_ctx->add_list = addop;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int memberof_recompute_task(struct ldb_module *module,
+ struct ldb_request *req);
+
+static int mbof_add_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_next_add(struct mbof_add_operation *addop);
+static int mbof_next_add_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_add_operation(struct mbof_add_operation *addop);
+static int mbof_add_missing(struct mbof_add_ctx *add_ctx, struct ldb_dn *dn);
+static int mbof_add_cleanup(struct mbof_add_ctx *add_ctx);
+static int mbof_add_cleanup_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_add_muop(struct mbof_add_ctx *add_ctx);
+static int mbof_add_muop_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+
+static int memberof_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_request *add_req;
+ struct ldb_message_element *el;
+ struct mbof_dn_array *parents;
+ struct ldb_dn *valdn;
+ int i, ret;
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+
+ if (strcmp("@MEMBEROF-REBUILD",
+ ldb_dn_get_linearized(req->op.add.message->dn)) == 0) {
+ return memberof_recompute_task(module, req);
+ }
+
+ /* do not manipulate other control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* check if memberof is specified */
+ el = ldb_msg_find_element(req->op.add.message, DB_MEMBEROF);
+ if (el) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Error: the memberof attribute is readonly.");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* check if memberuid is specified */
+ el = ldb_msg_find_element(req->op.add.message, DB_MEMBERUID);
+ if (el) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Error: the memberuid attribute is readonly.");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ctx = mbof_init(module, req);
+ if (!ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ add_ctx = talloc_zero(ctx, struct mbof_add_ctx);
+ if (!add_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ add_ctx->ctx = ctx;
+
+ add_ctx->msg = ldb_msg_copy(add_ctx, req->op.add.message);
+ if (!add_ctx->msg) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ add_ctx->msg_dn = add_ctx->msg->dn;
+
+ /* continue with normal ops if there are no members */
+ el = ldb_msg_find_element(add_ctx->msg, DB_MEMBER);
+ if (!el) {
+ add_ctx->terminate = true;
+ goto done;
+ }
+
+ parents = talloc_zero(add_ctx, struct mbof_dn_array);
+ if (!parents) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ parents->dns = talloc_array(parents, struct ldb_dn *, 1);
+ if (!parents->dns) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ parents->dns[0] = add_ctx->msg_dn;
+ parents->num = 1;
+
+ /* process new members */
+ /* check we are not adding ourselves as member as well */
+ for (i = 0; i < el->num_values; i++) {
+ valdn = ldb_dn_from_ldb_val(add_ctx, ldb, &el->values[i]);
+ if (!valdn || !ldb_dn_validate(valdn)) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR, "Invalid dn value: [%s]",
+ (const char *)el->values[i].data);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ if (ldb_dn_compare(valdn, req->op.add.message->dn) == 0) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Adding self as member is not permitted! Skipping");
+ continue;
+ }
+ ret = mbof_append_addop(add_ctx, parents, valdn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+done:
+ /* add original object */
+ ret = ldb_build_add_req(&add_req, ldb, add_ctx,
+ add_ctx->msg, req->controls,
+ add_ctx, mbof_add_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, add_req);
+}
+
+static int mbof_add_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
+ ctx = add_ctx->ctx;
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (add_ctx->terminate) {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+
+ if (add_ctx->current_op == NULL) {
+ /* first operation */
+ ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
+ ctx->ret_resp = talloc_steal(ctx, ares->response);
+ ret = mbof_next_add(add_ctx->add_list);
+ }
+ else if (add_ctx->current_op->next) {
+ /* next operation */
+ ret = mbof_next_add(add_ctx->current_op->next);
+ }
+ else {
+ /* no more operations */
+ if (add_ctx->missing) {
+ ret = mbof_add_cleanup(add_ctx);
+ }
+ else if (add_ctx->muops) {
+ ret = mbof_add_muop(add_ctx);
+ }
+ else {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_next_add(struct mbof_add_operation *addop)
+{
+ static const char *attrs[] = { DB_OC, DB_NAME,
+ DB_MEMBER, DB_MEMBEROF, NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *req;
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ add_ctx = addop->add_ctx;
+ ctx = add_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ /* mark the operation as being handled */
+ add_ctx->current_op = addop;
+
+ ret = ldb_build_search_req(&req, ldb, ctx,
+ addop->entry_dn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL,
+ addop, mbof_next_add_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_request(ldb, req);
+}
+
+static int mbof_next_add_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_add_operation *addop;
+ struct mbof_add_ctx *add_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ addop = talloc_get_type(req->context, struct mbof_add_operation);
+ add_ctx = addop->add_ctx;
+ ctx = add_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (addop->entry != NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Found multiple entries for (%s)",
+ ldb_dn_get_linearized(addop->entry_dn));
+ /* more than one entry per dn ?? db corrupted ? */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ addop->entry = talloc_steal(addop, ares->message);
+ if (addop->entry == NULL) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_zfree(ares);
+ if (addop->entry == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Entry not found (%s)",
+ ldb_dn_get_linearized(addop->entry_dn));
+
+ /* this target does not exists, save as missing */
+ ret = mbof_add_missing(add_ctx, addop->entry_dn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ /* now try the next operation */
+ if (add_ctx->current_op->next) {
+ ret = mbof_next_add(add_ctx->current_op->next);
+ }
+ else {
+ /* no more operations */
+ if (add_ctx->missing) {
+ ret = mbof_add_cleanup(add_ctx);
+ }
+ else if (add_ctx->muops) {
+ ret = mbof_add_muop(add_ctx);
+ }
+ else {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+ else {
+ ret = mbof_add_operation(addop);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+ return LDB_SUCCESS;
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+/* if it is a group, add all members for cascade effect
+ * add memberof attribute to this entry
+ */
+static int mbof_add_operation(struct mbof_add_operation *addop)
+{
+
+ TALLOC_CTX *tmp_ctx;
+ struct mbof_ctx *ctx;
+ struct mbof_add_ctx *add_ctx;
+ struct ldb_context *ldb;
+ struct ldb_message_element *el;
+ struct ldb_request *mod_req;
+ struct ldb_message *msg;
+ struct ldb_dn *elval_dn;
+ struct ldb_dn *valdn;
+ struct mbof_dn_array *parents;
+ int i, j, ret;
+ const char *val;
+ const char *name;
+
+ add_ctx = addop->add_ctx;
+ ctx = add_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ parents = talloc_zero(add_ctx, struct mbof_dn_array);
+ if (!parents) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* can't be more than the immediate parent */
+ parents->dns = talloc_array(parents, struct ldb_dn *,
+ addop->parents->num);
+ if (!parents->dns) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* create new parent set for this entry */
+ for (i = 0; i < addop->parents->num; i++) {
+ /* never add yourself as memberof */
+ if (ldb_dn_compare(addop->parents->dns[i], addop->entry_dn) == 0) {
+ continue;
+ }
+ parents->dns[parents->num] = addop->parents->dns[i];
+ parents->num++;
+ }
+
+ /* remove entries that are already there */
+ el = ldb_msg_find_element(addop->entry, DB_MEMBEROF);
+ if (el) {
+
+ tmp_ctx = talloc_new(addop);
+ if (!tmp_ctx) return LDB_ERR_OPERATIONS_ERROR;
+
+ for (i = 0; i < el->num_values; i++) {
+ elval_dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &el->values[i]);
+ if (!elval_dn) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid DN in memberof [%s]",
+ (const char *)el->values[i].data);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (j = 0; j < parents->num; j++) {
+ if (ldb_dn_compare(parents->dns[j], elval_dn) == 0) {
+ /* duplicate found */
+ break;
+ }
+ }
+ if (j < parents->num) {
+ /* remove duplicate */
+ for (;j+1 < parents->num; j++) {
+ parents->dns[j] = parents->dns[j+1];
+ }
+ parents->num--;
+ }
+ }
+
+ if (parents->num == 0) {
+ /* already contains all parents as memberof, skip to next */
+ talloc_free(tmp_ctx);
+ talloc_free(addop->entry);
+ addop->entry = NULL;
+
+ if (addop->next) {
+ return mbof_next_add(addop->next);
+ }
+ else if (add_ctx->muops) {
+ return mbof_add_muop(add_ctx);
+ }
+ else {
+ /* that was the last entry, get out */
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+ }
+ talloc_free(tmp_ctx);
+ }
+
+ /* if it is a group add all members */
+ el = ldb_msg_find_element(addop->entry, DB_MEMBER);
+ if (el) {
+ for (i = 0; i < el->num_values; i++) {
+ valdn = ldb_dn_from_ldb_val(add_ctx, ldb, &el->values[i]);
+ if (!valdn) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid DN in member [%s]",
+ (const char *)el->values[i].data);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (!ldb_dn_validate(valdn)) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Invalid DN syntax for member [%s]",
+ (const char *)el->values[i].data);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ ret = mbof_append_addop(add_ctx, parents, valdn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* check if we need to store memberuid ops for this entry */
+ ret = entry_is_user_object(addop->entry);
+ switch (ret) {
+ case LDB_SUCCESS:
+ /* it's a user object */
+ name = ldb_msg_find_attr_as_string(addop->entry, DB_NAME, NULL);
+ if (!name) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < parents->num; i++) {
+ ret = mbof_append_muop(add_ctx, &add_ctx->muops,
+ &add_ctx->num_muops,
+ LDB_FLAG_MOD_ADD,
+ parents->dns[i], name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ break;
+
+ case LDB_ERR_NO_SUCH_ATTRIBUTE:
+ /* it is not a user object, continue */
+ break;
+
+ default:
+ /* an error occured, return */
+ return ret;
+ }
+
+ /* we are done with the entry now */
+ talloc_free(addop->entry);
+ addop->entry = NULL;
+
+ /* add memberof to entry */
+ msg = ldb_msg_new(addop);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = addop->entry_dn;
+
+ ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->values = talloc_array(msg, struct ldb_val, parents->num);
+ if (!el->values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i = 0, j = 0; i < parents->num; i++) {
+ if (ldb_dn_compare(parents->dns[i], msg->dn) == 0) continue;
+ val = ldb_dn_get_linearized(parents->dns[i]);
+ el->values[j].length = strlen(val);
+ el->values[j].data = (uint8_t *)talloc_strdup(el->values, val);
+ if (!el->values[j].data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ j++;
+ }
+ el->num_values = j;
+
+ ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
+ msg, NULL,
+ add_ctx, mbof_add_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(mod_req, msg);
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_add_missing(struct mbof_add_ctx *add_ctx, struct ldb_dn *dn)
+{
+ struct mbof_dn *mdn;
+
+ mdn = talloc(add_ctx, struct mbof_dn);
+ if (!mdn) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ mdn->dn = talloc_steal(mdn, dn);
+
+ /* add to the list */
+ mdn->next = add_ctx->missing;
+ add_ctx->missing = mdn;
+
+ return LDB_SUCCESS;
+}
+
+/* remove unexisting members and add memberuid attribute */
+static int mbof_add_cleanup(struct mbof_add_ctx *add_ctx)
+{
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ struct ldb_request *mod_req;
+ struct ldb_message_element *el;
+ struct mbof_ctx *ctx;
+ struct mbof_dn *iter;
+ const char *val;
+ int ret, i, num;
+
+ ctx = add_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ num = 0;
+ for (iter = add_ctx->missing; iter; iter = iter->next) {
+ num++;
+ }
+ if (num == 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg = ldb_msg_new(add_ctx);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = add_ctx->msg_dn;
+
+ ret = ldb_msg_add_empty(msg, DB_MEMBER, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->values = talloc_array(msg, struct ldb_val, num);
+ if (!el->values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el->num_values = num;
+ for (i = 0, iter = add_ctx->missing; iter; iter = iter->next, i++) {
+ val = ldb_dn_get_linearized(iter->dn);
+ el->values[i].length = strlen(val);
+ el->values[i].data = (uint8_t *)talloc_strdup(el->values, val);
+ if (!el->values[i].data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
+ msg, NULL,
+ add_ctx, mbof_add_cleanup_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_add_cleanup_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
+ ctx = add_ctx->ctx;
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (add_ctx->muops) {
+ ret = mbof_add_muop(add_ctx);
+ }
+ else {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+/* add memberuid attributes to parent groups */
+static int mbof_add_muop(struct mbof_add_ctx *add_ctx)
+{
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ struct ldb_request *mod_req;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ ctx = add_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ msg = ldb_msg_new(add_ctx);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = add_ctx->muops[add_ctx->cur_muop].dn;
+ msg->elements = add_ctx->muops[add_ctx->cur_muop].el;
+ msg->num_elements = 1;
+
+ ret = ldb_build_mod_req(&mod_req, ldb, add_ctx,
+ msg, NULL,
+ add_ctx, mbof_add_muop_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_add_muop_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_add_ctx *add_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ add_ctx = talloc_get_type(req->context, struct mbof_add_ctx);
+ ctx = add_ctx->ctx;
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ add_ctx->cur_muop++;
+ if (add_ctx->cur_muop < add_ctx->num_muops) {
+ ret = mbof_add_muop(add_ctx);
+ }
+ else {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+
+
+
+/* delete operations */
+
+/* The implementation of delete operations is a bit more complex than an add
+ * operation. This is because we need to recompute memberships of potentially
+ * quite far descendants and we also have to account for loops and how to
+ * break them without ending in an endless loop ourselves.
+ * The difficulty is in the fact that while the member -> memberof link is
+ * direct, memberof -> member is not as membership is transitive.
+ *
+ * Ok, first of all, contrary to the add operation, a delete operation
+ * involves an existing object that may have existing parents. So, first, we
+ * search the object itself to get the original membership lists (member and
+ * memberof) for this object, and we also search for any object that has it as
+ * one of its members.
+ * Once we have the results, we store object and parents and proceed with the
+ * original operation to make sure it is valid.
+ *
+ * Once the original op returns we proceed fixing parents (parents being each
+ * object that has the delete operation target object as member), if any.
+ *
+ * For each parent we retrieved we proceed to delete the member attribute that
+ * points to the object we just deleted. Once done for all parents (or if no
+ * parents exists), we proceed with the children and descendants.
+ *
+ * To handle the children we create a first ancestor operation that reflects
+ * the delete we just made. We set as parents of this object the parents just
+ * retrieved with the first search. Then we create a remove list.
+ *
+ * The remove list contains all objects in the original memberof list and the
+ * object dn itself of the original delete operation target object (the first
+ * ancestor).
+ *
+ * An operation is identified by an object that contains a tree of
+ * descendants:
+ * The remove list for the children, the immediate parent, and the dn and
+ * entry of the object this operation is about.
+ *
+ * We now proceed with adding a new operation for each original member of the
+ * first ancestor.
+ *
+ * In each operation we must first lookup the target object and each immediate
+ * parent (all the objects in the tree that have target as a "member").
+ *
+ * Then we proceed to calculate the new memberof list that we are going to set
+ * on the target object.
+ * The new memberof list starts with including all the objects that have the
+ * target as their direct member.
+ * Finally for each entry in this provisional new memberof list we add all its
+ * memberof elements to the new memberof list (taking care of excluding
+ * duplicates). This way we are certain all direct and indirect membership are
+ * accounted for.
+ *
+ * At this point we have the final new memberof list for this operation and we
+ * can proceed to modify the entry.
+ *
+ * Once the entry has been modified we proceed again to check if there are any
+ * children of this entry (the entry has "member"s).
+ * We create a new remove list that is the difference between the original
+ * entry memberof list and the new memberof list we just stored back in the
+ * object.
+ * Then for each member we create a new operation.
+ *
+ * We continue to process operations until no new operations need to be
+ * performed.
+ *
+ * Ordering is important here, se the mbof_del_get_next() function to
+ * understand how we proceed to select which new operation to process.
+ *
+ * As a final operation remove any memberuid corresponding to a removal of
+ * a memberof field from a user entry
+ */
+
+static int mbof_del_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_orig_del(struct mbof_del_ctx *ctx);
+static int mbof_orig_del_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_del_cleanup_parents(struct mbof_del_ctx *del_ctx);
+static int mbof_del_clean_par_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_del_cleanup_children(struct mbof_del_ctx *del_ctx);
+static int mbof_append_delop(struct mbof_del_operation *parent,
+ struct ldb_dn *entry_dn);
+static int mbof_del_execute_op(struct mbof_del_operation *delop);
+static int mbof_del_exop_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_del_execute_cont(struct mbof_del_operation *delop);
+static int mbof_del_ancestors(struct mbof_del_operation *delop);
+static int mbof_del_anc_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_del_mod_entry(struct mbof_del_operation *delop);
+static int mbof_del_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_del_progeny(struct mbof_del_operation *delop);
+static int mbof_del_get_next(struct mbof_del_operation *delop,
+ struct mbof_del_operation **nextop);
+static int mbof_del_fill_muop(struct mbof_del_ctx *del_ctx,
+ struct ldb_message *entry);
+static int mbof_del_muop(struct mbof_del_ctx *ctx);
+static int mbof_del_muop_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+
+
+static int memberof_del(struct ldb_module *module, struct ldb_request *req)
+{
+ static const char *attrs[] = { DB_OC, DB_NAME,
+ DB_MEMBER, DB_MEMBEROF, NULL };
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct mbof_del_operation *first;
+ struct ldb_request *search;
+ char *expression;
+ const char *dn;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ctx = mbof_init(module, req);
+ if (!ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ del_ctx = talloc_zero(ctx, struct mbof_del_ctx);
+ if (!del_ctx) {
+ talloc_free(ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ del_ctx->ctx = ctx;
+
+ /* create first entry */
+ /* the first entry is the parent of all entries and the one where we remove
+ * member from, it does not get the same treatment as others */
+ first = talloc_zero(del_ctx, struct mbof_del_operation);
+ if (!first) {
+ talloc_free(ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ del_ctx->first = first;
+
+ first->del_ctx = del_ctx;
+ first->entry_dn = req->op.del.dn;
+
+ dn = ldb_dn_get_linearized(req->op.del.dn);
+ if (!dn) {
+ talloc_free(ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ expression = talloc_asprintf(del_ctx,
+ "(|(distinguishedName=%s)(%s=%s))",
+ dn, DB_MEMBER, dn);
+ if (!expression) {
+ talloc_free(ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&search, ldb, del_ctx,
+ NULL, LDB_SCOPE_SUBTREE,
+ expression, attrs, NULL,
+ first, mbof_del_search_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ctx);
+ return ret;
+ }
+
+ return ldb_request(ldb, search);
+}
+
+static int mbof_del_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_operation *first;
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ first = talloc_get_type(req->context, struct mbof_del_operation);
+ del_ctx = first->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ msg = ares->message;
+
+ if (ldb_dn_compare(msg->dn, ctx->req->op.del.dn) == 0) {
+
+ if (first->entry != NULL) {
+ /* more than one entry per dn ?? db corrupted ? */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ first->entry = talloc_steal(first, msg);
+ if (first->entry == NULL) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ } else {
+ first->parents = talloc_realloc(first, first->parents,
+ struct ldb_message *,
+ first->num_parents + 1);
+ if (!first->parents) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ msg = talloc_steal(first->parents, ares->message);
+ if (!msg) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ first->parents[first->num_parents] = msg;
+ first->num_parents++;
+ }
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (first->entry == NULL) {
+ /* this target does not exists, too bad! */
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Target entry (%s) not found",
+ ldb_dn_get_linearized(first->entry_dn));
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+
+ /* now perform the requested delete, before proceeding further */
+ ret = mbof_orig_del(del_ctx);
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_orig_del(struct mbof_del_ctx *del_ctx)
+{
+ struct ldb_request *del_req;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ ctx = del_ctx->ctx;
+
+ ret = ldb_build_del_req(&del_req, ldb_module_get_ctx(ctx->module),
+ ctx->req, ctx->req->op.del.dn, NULL,
+ del_ctx, mbof_orig_del_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, del_req);
+}
+
+static int mbof_orig_del_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_zfree(ares);
+ ldb_set_errstring(ldb, "Invalid reply type!");
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* save real call stuff */
+ ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
+ ctx->ret_resp = talloc_steal(ctx, ares->response);
+
+ /* prep following clean ops */
+ if (del_ctx->first->num_parents) {
+
+ /* if there are parents there may be memberuids to remove */
+ ret = mbof_del_fill_muop(del_ctx, del_ctx->first->entry);
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+
+ /* if there are any parents, fire a removal sequence */
+ ret = mbof_del_cleanup_parents(del_ctx);
+ }
+ else if (ldb_msg_find_element(del_ctx->first->entry, DB_MEMBER)) {
+ /* if there are any children, fire a removal sequence */
+ ret = mbof_del_cleanup_children(del_ctx);
+ }
+ /* see if there are memberuid operations to perform */
+ else if (del_ctx->muops) {
+ return mbof_del_muop(del_ctx);
+ }
+ else {
+ /* no parents nor children, end ops */
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ LDB_SUCCESS);
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_cleanup_parents(struct mbof_del_ctx *del_ctx)
+{
+ struct mbof_del_operation *first;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct ldb_request *mod_req;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ const char *val;
+ int ret;
+
+ first = del_ctx->first;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ msg = ldb_msg_new(first->parents);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = first->parents[first->cur_parent]->dn;
+ first->cur_parent++;
+
+ ret = ldb_msg_add_empty(msg, DB_MEMBER, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->values = talloc_array(msg, struct ldb_val, 1);
+ if (!el->values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ val = ldb_dn_get_linearized(first->entry_dn);
+ el->values[0].length = strlen(val);
+ el->values[0].data = (uint8_t *)talloc_strdup(el->values, val);
+ if (!el->values[0].data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el->num_values = 1;
+
+ ret = ldb_build_mod_req(&mod_req, ldb, first->parents,
+ msg, NULL,
+ del_ctx, mbof_del_clean_par_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_del_clean_par_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_operation *first;
+ struct ldb_context *ldb;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
+ first = del_ctx->first;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_zfree(ares);
+ ldb_set_errstring(ldb, "Invalid reply type!");
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (first->num_parents > first->cur_parent) {
+ /* still parents to cleanup, go on */
+ ret = mbof_del_cleanup_parents(del_ctx);
+ }
+ else {
+ /* continue */
+ if (ldb_msg_find_element(first->entry, DB_MEMBER)) {
+ /* if there are any children, fire a removal sequence */
+ ret = mbof_del_cleanup_children(del_ctx);
+ }
+ /* see if there are memberuid operations to perform */
+ else if (del_ctx->muops) {
+ return mbof_del_muop(del_ctx);
+ }
+ else {
+ /* no children, end ops */
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_cleanup_children(struct mbof_del_ctx *del_ctx)
+{
+ struct mbof_del_operation *first;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ const struct ldb_message_element *el;
+ struct ldb_dn *valdn;
+ int i, ret;
+
+ first = del_ctx->first;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ el = ldb_msg_find_element(first->entry, DB_MEMBER);
+
+ /* prepare del sets */
+ for (i = 0; i < el->num_values; i++) {
+ valdn = ldb_dn_from_ldb_val(first, ldb, &el->values[i]);
+ if (!valdn || !ldb_dn_validate(valdn)) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Invalid dn syntax for member [%s]",
+ (const char *)el->values[i].data);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ ret = mbof_append_delop(first, valdn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* now that sets are built, start processing */
+ return mbof_del_execute_op(first->children[0]);
+}
+
+static int mbof_append_delop(struct mbof_del_operation *parent,
+ struct ldb_dn *entry_dn)
+{
+ struct mbof_del_operation *delop;
+
+ delop = talloc_zero(parent, struct mbof_del_operation);
+ if (!delop) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ delop->del_ctx = parent->del_ctx;
+ delop->parent = parent;
+ delop->entry_dn = entry_dn;
+
+ parent->children = talloc_realloc(parent, parent->children,
+ struct mbof_del_operation *,
+ parent->num_children +1);
+ if (!parent->children) {
+ talloc_free(delop);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ parent->children[parent->num_children] = delop;
+ parent->num_children++;
+
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_execute_op(struct mbof_del_operation *delop)
+{
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct ldb_request *search;
+ char *expression;
+ const char *dn;
+ static const char *attrs[] = { DB_OC, DB_NAME,
+ DB_MEMBER, DB_MEMBEROF, NULL };
+ int ret;
+
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ /* load entry */
+ dn = ldb_dn_get_linearized(delop->entry_dn);
+ if (!dn) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ expression = talloc_asprintf(del_ctx,
+ "(|(distinguishedName=%s)(%s=%s))",
+ dn, DB_MEMBER, dn);
+ if (!expression) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&search, ldb, delop,
+ NULL, LDB_SCOPE_SUBTREE,
+ expression, attrs, NULL,
+ delop, mbof_del_exop_search_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ctx);
+ return ret;
+ }
+
+ return ldb_request(ldb, search);
+}
+
+static int mbof_del_exop_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_operation *delop;
+ struct mbof_del_ctx *del_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ struct ldb_message *msg;
+ int ret;
+
+ delop = talloc_get_type(req->context, struct mbof_del_operation);
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ msg = ares->message;
+
+ if (ldb_dn_compare(msg->dn, delop->entry_dn) == 0) {
+
+ if (delop->entry != NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Found multiple entries for (%s)",
+ ldb_dn_get_linearized(delop->entry_dn));
+ /* more than one entry per dn ?? db corrupted ? */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ delop->entry = talloc_steal(delop, msg);
+ if (delop->entry == NULL) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ } else {
+ delop->parents = talloc_realloc(delop, delop->parents,
+ struct ldb_message *,
+ delop->num_parents + 1);
+ if (!delop->parents) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ msg = talloc_steal(delop->parents, msg);
+ if (!msg) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ delop->parents[delop->num_parents] = msg;
+ delop->num_parents++;
+ }
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (delop->entry == NULL) {
+ /* no target, no party! */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* ok process the entry */
+ ret = mbof_del_execute_cont(delop);
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_execute_cont(struct mbof_del_operation *delop)
+{
+ struct mbof_del_ancestors_ctx *anc_ctx;
+ struct mbof_del_operation *parent;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct mbof_dn_array *new_list;
+ int i;
+
+ del_ctx = delop->del_ctx;
+ parent = delop->parent;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ anc_ctx = talloc_zero(delop, struct mbof_del_ancestors_ctx);
+ if (!anc_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ delop->anc_ctx = anc_ctx;
+
+ new_list = talloc_zero(anc_ctx, struct mbof_dn_array);
+ if (!new_list) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* at the very least we have a number of memberof elements
+ * equal to the number of objects that have this entry as
+ * direct member */
+ new_list->num = delop->num_parents;
+
+ /* attach the list to the operation */
+ delop->anc_ctx->new_list = new_list;
+ delop->anc_ctx->num_direct = new_list->num;
+
+ /* do we have any direct parent at all ? */
+ if (new_list->num == 0) {
+ /* no entries at all, entry ended up being orphaned */
+ /* skip to directly set the new memberof list for this entry */
+
+ return mbof_del_mod_entry(delop);
+ }
+
+ /* fill in the list if we have parents */
+ new_list->dns = talloc_zero_array(new_list,
+ struct ldb_dn *,
+ new_list->num);
+ if (!new_list->dns) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i = 0; i < delop->num_parents; i++) {
+ new_list->dns[i] = delop->parents[i]->dn;
+ }
+
+ /* before proceeding we also need to fetch the ancestors (anew as some may
+ * have changed by preceeding operations) */
+ return mbof_del_ancestors(delop);
+}
+
+static int mbof_del_ancestors(struct mbof_del_operation *delop)
+{
+ struct mbof_del_ancestors_ctx *anc_ctx;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct mbof_dn_array *new_list;
+ static const char *attrs[] = { DB_MEMBEROF, NULL };
+ struct ldb_request *search;
+ int ret;
+
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+ anc_ctx = delop->anc_ctx;
+ new_list = anc_ctx->new_list;
+
+ ret = ldb_build_search_req(&search, ldb, anc_ctx,
+ new_list->dns[anc_ctx->cur],
+ LDB_SCOPE_BASE, NULL, attrs, NULL,
+ delop, mbof_del_anc_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_request(ldb, search);
+}
+
+static int mbof_del_anc_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_ancestors_ctx *anc_ctx;
+ struct mbof_del_operation *delop;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ const struct ldb_message_element *el;
+ struct mbof_dn_array *new_list;
+ struct ldb_dn *valdn;
+ int i, j, ret;
+
+ delop = talloc_get_type(req->context, struct mbof_del_operation);
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+ anc_ctx = delop->anc_ctx;
+ new_list = anc_ctx->new_list;
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ msg = ares->message;
+
+ if (anc_ctx->entry != NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Found multiple entries for (%s)",
+ ldb_dn_get_linearized(anc_ctx->entry->dn));
+ /* more than one entry per dn ?? db corrupted ? */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ anc_ctx->entry = talloc_steal(anc_ctx, msg);
+ if (anc_ctx->entry == NULL) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (anc_ctx->entry == NULL) {
+ /* no target, no party! */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* check entry */
+ el = ldb_msg_find_element(anc_ctx->entry, DB_MEMBEROF);
+ if (el) {
+ for (i = 0; i < el->num_values; i++) {
+ valdn = ldb_dn_from_ldb_val(new_list, ldb, &el->values[i]);
+ if (!valdn) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Invalid dn for memberof: (%s)",
+ (const char *)el->values[i].data);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ for (j = 0; j < new_list->num; j++) {
+ if (ldb_dn_compare(valdn, new_list->dns[j]) == 0)
+ break;
+ }
+ if (j < new_list->num) {
+ talloc_free(valdn);
+ continue;
+ }
+ new_list->dns = talloc_realloc(new_list,
+ new_list->dns,
+ struct ldb_dn *,
+ new_list->num + 1);
+ if (!new_list->dns) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ new_list->dns[new_list->num] = valdn;
+ new_list->num++;
+ }
+ }
+
+ /* done with this one */
+ talloc_free(anc_ctx->entry);
+ anc_ctx->entry = NULL;
+ anc_ctx->cur++;
+
+ /* check if we need to process any more */
+ if (anc_ctx->cur < anc_ctx->num_direct) {
+ /* ok process the next one */
+ ret = mbof_del_ancestors(delop);
+ } else {
+ /* ok, end of the story, proceed to modify the entry */
+ ret = mbof_del_mod_entry(delop);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_mod_entry(struct mbof_del_operation *delop)
+{
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct ldb_context *ldb;
+ struct mbof_dn_array *new_list;
+ struct ldb_request *mod_req;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ struct ldb_dn **diff = NULL;
+ const char *name;
+ const char *val;
+ int i, j, k;
+ bool is_user;
+ int ret;
+
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+ new_list = delop->anc_ctx->new_list;
+
+ /* if this is a user we need to find out which entries have been
+ * removed so that we can later schedule removal of memberuid
+ * attributes from these entries */
+ ret = entry_is_user_object(delop->entry);
+ switch (ret) {
+ case LDB_SUCCESS:
+ /* it's a user object */
+ is_user = true;
+ break;
+ case LDB_ERR_NO_SUCH_ATTRIBUTE:
+ /* it is not a user object, continue */
+ is_user = false;
+ break;
+ default:
+ /* an error occured, return */
+ return ret;
+ }
+
+ if (is_user) {
+ /* prepare memberuid delete list */
+ /* copy all original memberof entries, and then later remove
+ * the ones that will survive in the entry */
+ el = ldb_msg_find_element(delop->entry, DB_MEMBEROF);
+ if (!el || !el->num_values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ diff = talloc_array(del_ctx->muops, struct ldb_dn *,
+ el->num_values + 1);
+ if (!diff) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i = 0, j = 0; i < el->num_values; i++) {
+ diff[j] = ldb_dn_from_ldb_val(diff, ldb, &el->values[i]);
+ if (!diff[j]) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* skip the deleted entry if this is a delete op */
+ if (!del_ctx->is_mod) {
+ if (ldb_dn_compare(del_ctx->first->entry_dn, diff[j]) == 0) {
+ continue;
+ }
+ }
+ j++;
+ }
+ /* zero terminate array */
+ diff[j] = NULL;
+ }
+
+ /* change memberof on entry */
+ msg = ldb_msg_new(delop);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = delop->entry_dn;
+
+ if (new_list->num) {
+ ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ el->values = talloc_array(el, struct ldb_val, new_list->num);
+ if (!el->values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i = 0, j = 0; i < new_list->num; i++) {
+ if (ldb_dn_compare(new_list->dns[i], msg->dn) == 0)
+ continue;
+ val = ldb_dn_get_linearized(new_list->dns[i]);
+ if (!val) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el->values[j].length = strlen(val);
+ el->values[j].data = (uint8_t *)talloc_strdup(el->values, val);
+ if (!el->values[j].data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ j++;
+
+ if (is_user) {
+ /* compare the entry's original memberof list with the new
+ * one and for each missing entry add a memberuid removal
+ * operation */
+ for (k = 0; diff[k]; k++) {
+ if (ldb_dn_compare(new_list->dns[i], diff[k]) == 0) {
+ break;
+ }
+ }
+ if (diff[k]) {
+ talloc_zfree(diff[k]);
+ for (; diff[k + 1]; k++) {
+ diff[k] = diff[k + 1];
+ }
+ diff[k] = NULL;
+ }
+ }
+ }
+ el->num_values = j;
+
+ }
+ else {
+ ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (is_user && diff[0]) {
+ /* file memberuid removal operations */
+ name = ldb_msg_find_attr_as_string(delop->entry, DB_NAME, NULL);
+ if (!name) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; diff[i]; i++) {
+ ret = mbof_append_muop(del_ctx, &del_ctx->muops,
+ &del_ctx->num_muops,
+ LDB_FLAG_MOD_DELETE,
+ diff[i], name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, delop,
+ msg, NULL,
+ delop, mbof_del_mod_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(mod_req, msg);
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_del_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_operation *delop;
+ struct mbof_del_ctx *del_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ delop = talloc_get_type(req->context, struct mbof_del_operation);
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Got an entry on a non search op ?!");
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_zfree(ares);
+ break;
+
+ case LDB_REPLY_DONE:
+
+ ret = mbof_del_progeny(delop);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx,
+ struct mbof_dn_array *ael);
+
+static int mbof_del_progeny(struct mbof_del_operation *delop)
+{
+ struct mbof_ctx *ctx;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_del_operation *nextop;
+ const struct ldb_message_element *el;
+ struct ldb_context *ldb;
+ struct ldb_dn *valdn;
+ int i, ret;
+
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ /* now verify if this entry is a group and members need to be processed as
+ * well */
+
+ el = ldb_msg_find_element(delop->entry, DB_MEMBER);
+ if (el) {
+ for (i = 0; i < el->num_values; i++) {
+ valdn = ldb_dn_from_ldb_val(delop, ldb, &el->values[i]);
+ if (!valdn || !ldb_dn_validate(valdn)) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Invalid DN for member: (%s)",
+ (const char *)el->values[i].data);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ ret = mbof_append_delop(delop, valdn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* finally find the next entry to handle */
+ ret = mbof_del_get_next(delop, &nextop);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (nextop) {
+ return mbof_del_execute_op(nextop);
+ }
+
+ /* see if there are memberuid operations to perform */
+ if (del_ctx->muops) {
+ return mbof_del_muop(del_ctx);
+ }
+ /* see if there are follow functions to run */
+ if (del_ctx->follow_mod) {
+ return mbof_mod_add(del_ctx->follow_mod,
+ del_ctx->follow_mod->to_add);
+ }
+
+ /* ok, no more ops, this means our job is done */
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+}
+
+static int mbof_del_get_next(struct mbof_del_operation *delop,
+ struct mbof_del_operation **nextop)
+{
+ struct mbof_del_operation *top, *cop;
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ struct mbof_dn *save, *tmp;
+
+ del_ctx = delop->del_ctx;
+ ctx = del_ctx->ctx;
+
+ /* first of all, save the current delop in the history */
+ save = talloc_zero(del_ctx, struct mbof_dn);
+ if (!save) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ save->dn = delop->entry_dn;
+
+ if (del_ctx->history) {
+ tmp = del_ctx->history;
+ while (tmp->next) tmp = tmp->next;
+ tmp->next = save;
+ } else {
+ del_ctx->history = save;
+ }
+
+ /* Find next one */
+ for (top = delop; top; top = top->parent) {
+ if (top->num_children == 0 || top->next_child >= top->num_children) {
+ /* no children, go for next one */
+ continue;
+ }
+
+ while (top->next_child < top->num_children) {
+ cop = top->children[top->next_child];
+ top->next_child++;
+
+ /* verify this operation has not already been performed */
+ for (tmp = del_ctx->history; tmp; tmp = tmp->next) {
+ if (ldb_dn_compare(tmp->dn, cop->entry_dn) == 0) {
+ break;
+ }
+ }
+ if (tmp == NULL) {
+ /* and return the current one */
+ *nextop = cop;
+ return LDB_SUCCESS;
+ }
+ }
+ }
+
+ /* we have no more ops */
+ *nextop = NULL;
+ return LDB_SUCCESS;
+}
+
+static int mbof_del_fill_muop(struct mbof_del_ctx *del_ctx,
+ struct ldb_message *entry)
+{
+ struct ldb_message_element *el;
+ char *name;
+ int ret;
+ int i;
+
+ el = ldb_msg_find_element(entry, DB_MEMBEROF);
+ if (!el || el->num_values == 0) {
+ /* no memberof attributes ... */
+ return LDB_SUCCESS;
+ }
+
+ ret = entry_is_user_object(entry);
+ switch (ret) {
+ case LDB_SUCCESS:
+ /* it's a user object, continue */
+ break;
+
+ case LDB_ERR_NO_SUCH_ATTRIBUTE:
+ /* it is not a user object, just return */
+ return LDB_SUCCESS;
+
+ default:
+ /* an error occured, return */
+ return ret;
+ }
+
+ name = talloc_strdup(del_ctx,
+ ldb_msg_find_attr_as_string(entry, DB_NAME, NULL));
+ if (!name) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ struct ldb_dn *valdn;
+
+ valdn = ldb_dn_from_ldb_val(del_ctx->muops,
+ ldb_module_get_ctx(del_ctx->ctx->module),
+ &el->values[i]);
+ if (!valdn || !ldb_dn_validate(valdn)) {
+ ldb_debug(ldb_module_get_ctx(del_ctx->ctx->module),
+ LDB_DEBUG_ERROR,
+ "Invalid dn value: [%s]",
+ (const char *)el->values[i].data);
+ }
+
+ ret = mbof_append_muop(del_ctx, &del_ctx->muops,
+ &del_ctx->num_muops,
+ LDB_FLAG_MOD_DELETE,
+ valdn, name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* del memberuid attributes to parent groups */
+static int mbof_del_muop(struct mbof_del_ctx *del_ctx)
+{
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ struct ldb_request *mod_req;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ ctx = del_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ msg = ldb_msg_new(del_ctx);
+ if (!msg) return LDB_ERR_OPERATIONS_ERROR;
+
+ msg->dn = del_ctx->muops[del_ctx->cur_muop].dn;
+ msg->elements = del_ctx->muops[del_ctx->cur_muop].el;
+ msg->num_elements = 1;
+
+ ret = ldb_build_mod_req(&mod_req, ldb, del_ctx,
+ msg, NULL,
+ del_ctx, mbof_del_muop_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_del_muop_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_del_ctx *del_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ del_ctx = talloc_get_type(req->context, struct mbof_del_ctx);
+ ctx = del_ctx->ctx;
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ del_ctx->cur_muop++;
+ if (del_ctx->cur_muop < del_ctx->num_muops) {
+ ret = mbof_del_muop(del_ctx);
+ }
+ /* see if there are follow functions to run */
+ else if (del_ctx->follow_mod) {
+ return mbof_mod_add(del_ctx->follow_mod,
+ del_ctx->follow_mod->to_add);
+ }
+ else {
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+
+
+/* mod operation */
+
+/* A modify operation just implements either an add operation, or a delete
+ * operation or both (replace) in turn.
+ * The only difference between a modify and a pure add or a pure delete is that
+ * the object is not created a new or not completely removed, but the setup just
+ * treats it in the same way children objects are treated in a pure add or delete
+ * operation. A list of appropriate parents and objects to modify is built, then
+ * we jump directly in the add or delete code.
+ * If both add and delete are necessary, delete operations are performed first
+ * and then a followup add operation is concatenated */
+
+static int mbof_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_orig_mod(struct mbof_mod_ctx *mod_ctx);
+static int mbof_orig_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done);
+static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx,
+ struct mbof_dn_array *del);
+static int mbof_fill_dn_array(TALLOC_CTX *memctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *el,
+ struct mbof_dn_array **dn_array);
+
+static int memberof_mod(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_message_element *el;
+ struct mbof_mod_ctx *mod_ctx;
+ struct mbof_ctx *ctx;
+ static const char *attrs[] = {DB_MEMBER, DB_MEMBEROF, NULL};
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *search;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* check if memberof is specified */
+ el = ldb_msg_find_element(req->op.mod.message, DB_MEMBEROF);
+ if (el) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Error: the memberof attribute is readonly.");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* check if memberuid is specified */
+ el = ldb_msg_find_element(req->op.mod.message, DB_MEMBERUID);
+ if (el) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Error: the memberuid attribute is readonly.");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ctx = mbof_init(module, req);
+ if (!ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ mod_ctx = talloc_zero(ctx, struct mbof_mod_ctx);
+ if (!mod_ctx) {
+ talloc_free(ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ mod_ctx->ctx = ctx;
+
+ mod_ctx->msg = ldb_msg_copy(mod_ctx, req->op.mod.message);
+ if (!mod_ctx->msg) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* continue with normal ops if there are no members */
+ el = ldb_msg_find_element(mod_ctx->msg, DB_MEMBER);
+ if (!el) {
+ mod_ctx->terminate = true;
+ return mbof_orig_mod(mod_ctx);
+ }
+
+ mod_ctx->membel = el;
+
+ /* can't do anything,
+ * must check first what's on the entry */
+ ret = ldb_build_search_req(&search, ldb, mod_ctx,
+ mod_ctx->msg->dn, LDB_SCOPE_BASE,
+ NULL, attrs, NULL,
+ mod_ctx, mbof_mod_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ctx);
+ return ret;
+ }
+
+ return ldb_request(ldb, search);
+}
+
+
+static int mbof_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_mod_ctx *mod_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ mod_ctx = talloc_get_type(req->context, struct mbof_mod_ctx);
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (mod_ctx->entry != NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "Found multiple entries for (%s)",
+ ldb_dn_get_linearized(mod_ctx->msg->dn));
+ /* more than one entry per dn ?? db corrupted ? */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ mod_ctx->entry = talloc_steal(mod_ctx, ares->message);
+ if (mod_ctx->entry == NULL) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ if (mod_ctx->entry == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Entry not found (%s)",
+ ldb_dn_get_linearized(mod_ctx->msg->dn));
+ /* this target does not exists, too bad! */
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+
+ ret = mbof_orig_mod(mod_ctx);
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_orig_mod(struct mbof_mod_ctx *mod_ctx)
+{
+ struct ldb_request *mod_req;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ ret = ldb_build_mod_req(&mod_req, ldb, ctx->req,
+ mod_ctx->msg, ctx->req->controls,
+ mod_ctx, mbof_orig_mod_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ctx->module, mod_req);
+}
+
+static int mbof_orig_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct mbof_mod_ctx *mod_ctx;
+ struct mbof_ctx *ctx;
+ int ret;
+
+ mod_ctx = talloc_get_type(req->context, struct mbof_mod_ctx);
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_zfree(ares);
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid reply type!");
+ ldb_set_errstring(ldb, "Invalid reply type!");
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* save real call stuff */
+ ctx->ret_ctrls = talloc_steal(ctx, ares->controls);
+ ctx->ret_resp = talloc_steal(ctx, ares->response);
+
+ if (!mod_ctx->terminate) {
+ /* next step */
+ ret = mbof_mod_process(mod_ctx, &mod_ctx->terminate);
+ if (ret != LDB_SUCCESS) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+ }
+ }
+
+ if (mod_ctx->terminate) {
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req,
+ ctx->ret_ctrls,
+ ctx->ret_resp,
+ LDB_SUCCESS);
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_mod_process(struct mbof_mod_ctx *mod_ctx, bool *done)
+{
+ const struct ldb_message_element *el;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ struct mbof_dn_array *removed;
+ struct mbof_dn_array *added;
+ int i, j, ret;
+
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ switch (mod_ctx->membel->flags) {
+ case LDB_FLAG_MOD_ADD:
+
+ ret = mbof_fill_dn_array(mod_ctx, ldb, mod_ctx->membel, &added);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return mbof_mod_add(mod_ctx, added);
+
+ case LDB_FLAG_MOD_DELETE:
+
+ if (mod_ctx->membel->num_values == 0) {
+ el = ldb_msg_find_element(mod_ctx->entry, DB_MEMBER);
+ } else {
+ el = mod_ctx->membel;
+ }
+
+ if (!el) {
+ /* nothing to do really */
+ *done = true;
+ return LDB_SUCCESS;
+ }
+
+ ret = mbof_fill_dn_array(mod_ctx, ldb, el, &removed);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return mbof_mod_delete(mod_ctx, removed);
+
+ case LDB_FLAG_MOD_REPLACE:
+
+ removed = NULL;
+ el = ldb_msg_find_element(mod_ctx->entry, DB_MEMBER);
+ if (el) {
+ ret = mbof_fill_dn_array(mod_ctx, ldb, el, &removed);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ added = NULL;
+ el = mod_ctx->membel;
+ if (el) {
+ ret = mbof_fill_dn_array(mod_ctx, ldb, el, &added);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* remove from arrays values that ended up unchanged */
+ if (removed && removed->num && added && added->num) {
+ for (i = 0; i < added->num; i++) {
+ for (j = 0; j < removed->num; j++) {
+ if (ldb_dn_compare(added->dns[i], removed->dns[j]) == 0) {
+ break;
+ }
+ }
+ if (j < removed->num) {
+ /* preexisting one, not removed, nor added */
+ for (; j+1 < removed->num; j++) {
+ removed->dns[j] = removed->dns[j+1];
+ }
+ removed->num--;
+ for (j = i; j+1 < added->num; j++) {
+ added->dns[j] = added->dns[j+1];
+ }
+ added->num--;
+ }
+ }
+ }
+
+ /* if we need to add something put it away so that it
+ * can be done after all delete operations are over */
+ if (added && added->num) {
+ mod_ctx->to_add = added;
+ }
+
+ /* if we have something to remove do it first */
+ if (removed && removed->num) {
+ return mbof_mod_delete(mod_ctx, removed);
+ }
+
+ /* if there is nothing to remove and we have stuff to add
+ * do it right away */
+ if (mod_ctx->to_add) {
+ return mbof_mod_add(mod_ctx, added);
+ }
+
+ /* the replacement function resulted in a null op,
+ * nothing to do, return happily */
+ *done = true;
+ return LDB_SUCCESS;
+ }
+
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int mbof_mod_add(struct mbof_mod_ctx *mod_ctx,
+ struct mbof_dn_array *ael)
+{
+ const struct ldb_message_element *el;
+ struct mbof_dn_array *parents;
+ struct mbof_add_ctx *add_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int i, ret;
+
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ el = ldb_msg_find_element(mod_ctx->entry, DB_MEMBEROF);
+
+ /* all the parents + itself */
+ ret = mbof_fill_dn_array(mod_ctx, ldb, el, &parents);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ parents->dns = talloc_realloc(parents, parents->dns,
+ struct ldb_dn *, parents->num + 1);
+ if (!parents->dns) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ parents->dns[parents->num] = mod_ctx->entry->dn;
+ parents->num++;
+
+ add_ctx = talloc_zero(mod_ctx, struct mbof_add_ctx);
+ if (!add_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ add_ctx->ctx = ctx;
+ add_ctx->msg_dn = mod_ctx->msg->dn;
+
+ for (i = 0; i < ael->num; i++) {
+ ret = mbof_append_addop(add_ctx, parents, ael->dns[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return mbof_next_add(add_ctx->add_list);
+}
+
+static int mbof_mod_delete(struct mbof_mod_ctx *mod_ctx,
+ struct mbof_dn_array *del)
+{
+ struct mbof_del_operation *first;
+ struct mbof_del_ctx *del_ctx;
+ struct ldb_context *ldb;
+ struct mbof_ctx *ctx;
+ int i, ret;
+
+ ctx = mod_ctx->ctx;
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ del_ctx = talloc_zero(mod_ctx, struct mbof_del_ctx);
+ if (!del_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ del_ctx->ctx = ctx;
+ del_ctx->is_mod = true;
+
+ /* create first entry */
+ /* the first entry is the parent of all entries and the one where we
+ * remove member from, it does not get the same treatment as others */
+ first = talloc_zero(del_ctx, struct mbof_del_operation);
+ if (!first) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ del_ctx->first = first;
+
+ first->del_ctx = del_ctx;
+ first->entry = mod_ctx->entry;
+ first->entry_dn = mod_ctx->entry->dn;
+
+ /* prepare del sets */
+ for (i = 0; i < del->num; i++) {
+ ret = mbof_append_delop(first, del->dns[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* add followup function if we also have stuff to add */
+ if (mod_ctx->to_add) {
+ del_ctx->follow_mod = mod_ctx;
+ }
+
+ /* now that sets are built, start processing */
+ return mbof_del_execute_op(first->children[0]);
+}
+
+static int mbof_fill_dn_array(TALLOC_CTX *memctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *el,
+ struct mbof_dn_array **dn_array)
+{
+ struct mbof_dn_array *ar;
+ struct ldb_dn *valdn;
+ int i;
+
+ ar = talloc_zero(memctx, struct mbof_dn_array);
+ if (!ar) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *dn_array = ar;
+
+ if (!el || el->num_values == 0) {
+ return LDB_SUCCESS;
+ }
+
+ ar->dns = talloc_array(ar, struct ldb_dn *, el->num_values);
+ if (!ar->dns) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ar->num = el->num_values;
+
+ for (i = 0; i < ar->num; i++) {
+ valdn = ldb_dn_from_ldb_val(ar, ldb, &el->values[i]);
+ if (!valdn || !ldb_dn_validate(valdn)) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Invalid dn value: [%s]",
+ (const char *)el->values[i].data);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ ar->dns[i] = valdn;
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*************************
+ * Cleanup task routines *
+ *************************/
+
+struct mbof_member {
+ struct mbof_member *prev;
+ struct mbof_member *next;
+
+ struct ldb_dn *dn;
+ const char *name;
+ bool orig_has_memberof;
+ bool orig_has_memberuid;
+ struct ldb_message_element *orig_members;
+
+ struct mbof_member **members;
+
+ hash_table_t *memberofs;
+
+ struct ldb_message_element *memuids;
+
+ enum { MBOF_GROUP_TO_DO = 0,
+ MBOF_GROUP_DONE,
+ MBOF_USER,
+ MBOF_ITER_ERROR } status;
+};
+
+struct mbof_rcmp_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct mbof_member *user_list;
+ hash_table_t *user_table;
+
+ struct mbof_member *group_list;
+ hash_table_t *group_table;
+};
+
+static void *hash_alloc(const size_t size, void *pvt)
+{
+ return talloc_size(pvt, size);
+}
+
+static void hash_free(void *ptr, void *pvt)
+{
+ talloc_free(ptr);
+}
+
+static int mbof_steal_msg_el(TALLOC_CTX *memctx,
+ const char *name,
+ struct ldb_message *msg,
+ struct ldb_message_element **_dest)
+{
+ struct ldb_message_element *src;
+ struct ldb_message_element *dest;
+
+ src = ldb_msg_find_element(msg, name);
+ if (!src) {
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ dest = talloc_zero(memctx, struct ldb_message_element);
+ if (!dest) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *dest = *src;
+ talloc_steal(dest, dest->name);
+ talloc_steal(dest, dest->values);
+
+ *_dest = dest;
+ return LDB_SUCCESS;
+}
+
+static int mbof_rcmp_usr_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_rcmp_search_groups(struct mbof_rcmp_context *ctx);
+static int mbof_rcmp_grp_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int mbof_member_update(struct mbof_rcmp_context *ctx,
+ struct mbof_member *parent,
+ struct mbof_member *mem);
+static bool mbof_member_iter(hash_entry_t *item, void *user_data);
+static int mbof_add_memuid(struct mbof_member *grp, const char *user);
+static int mbof_rcmp_update(struct mbof_rcmp_context *ctx);
+static int mbof_rcmp_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+
+static int memberof_recompute_task(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ static const char *attrs[] = { DB_NAME, DB_MEMBEROF, NULL };
+ static const char *filter = "(objectclass=user)";
+ struct mbof_rcmp_context *ctx;
+ struct ldb_request *src_req;
+ int ret;
+
+ ctx = talloc_zero(req, struct mbof_rcmp_context);
+ if (!ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ctx->module = module;
+ ctx->req = req;
+
+ ret = hash_create_ex(1024, &ctx->user_table, 0, 0, 0, 0,
+ hash_alloc, hash_free, ctx, NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&src_req, ldb, ctx,
+ NULL, LDB_SCOPE_SUBTREE,
+ filter, attrs, NULL,
+ ctx, mbof_rcmp_usr_callback, ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_request(ldb, src_req);
+}
+
+static int mbof_rcmp_usr_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct mbof_rcmp_context *ctx;
+ struct mbof_member *usr;
+ hash_value_t value;
+ hash_key_t key;
+ const char *name;
+ int ret;
+
+ ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ usr = talloc_zero(ctx, struct mbof_member);
+ if (!usr) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ usr->status = MBOF_USER;
+ usr->dn = talloc_steal(usr, ares->message->dn);
+ name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
+ if (name) {
+ usr->name = talloc_steal(usr, name);
+ }
+
+ if (ldb_msg_find_element(ares->message, DB_MEMBEROF)) {
+ usr->orig_has_memberof = true;
+ }
+
+ DLIST_ADD(ctx->user_list, usr);
+
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const(ldb_dn_get_linearized(usr->dn));
+ value.type = HASH_VALUE_PTR;
+ value.ptr = usr;
+
+ ret = hash_enter(ctx->user_table, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_zfree(ares);
+
+ /* and now search groups */
+ return mbof_rcmp_search_groups(ctx);
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_rcmp_search_groups(struct mbof_rcmp_context *ctx)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ctx->module);
+ static const char *attrs[] = { DB_MEMBEROF, DB_MEMBERUID,
+ DB_NAME, DB_MEMBER, NULL };
+ static const char *filter = "(objectclass=group)";
+ struct ldb_request *req;
+ int ret;
+
+ ret = hash_create_ex(1024, &ctx->group_table, 0, 0, 0, 0,
+ hash_alloc, hash_free, ctx, NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ldb_build_search_req(&req, ldb, ctx,
+ NULL, LDB_SCOPE_SUBTREE,
+ filter, attrs, NULL,
+ ctx, mbof_rcmp_grp_callback, ctx->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_request(ldb, req);
+}
+
+static int mbof_rcmp_grp_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct mbof_rcmp_context *ctx;
+ struct ldb_message_element *el;
+ struct mbof_member *iter;
+ struct mbof_member *grp;
+ hash_value_t value;
+ hash_key_t key;
+ const char *name;
+ int i, j;
+ int ret;
+
+ ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ grp = talloc_zero(ctx, struct mbof_member);
+ if (!grp) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ grp->status = MBOF_GROUP_TO_DO;
+ grp->dn = talloc_steal(grp, ares->message->dn);
+ grp->name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
+ name = ldb_msg_find_attr_as_string(ares->message, DB_NAME, NULL);
+ if (name) {
+ grp->name = talloc_steal(grp, name);
+ }
+
+ if (ldb_msg_find_element(ares->message, DB_MEMBEROF)) {
+ grp->orig_has_memberof = true;
+ }
+
+ if (ldb_msg_find_element(ares->message, DB_MEMBERUID)) {
+ grp->orig_has_memberuid = true;
+ }
+
+ ret = mbof_steal_msg_el(grp, DB_MEMBER,
+ ares->message, &grp->orig_members);
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ DLIST_ADD(ctx->group_list, grp);
+
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const(ldb_dn_get_linearized(grp->dn));
+ value.type = HASH_VALUE_PTR;
+ value.ptr = grp;
+
+ ret = hash_enter(ctx->group_table, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_zfree(ares);
+
+ if (!ctx->group_list) {
+ /* no groups ? */
+ return ldb_module_done(ctx->req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ /* for each group compute the members list */
+ for (iter = ctx->group_list; iter; iter = iter->next) {
+
+ el = iter->orig_members;
+ if (!el || el->num_values == 0) {
+ /* no members */
+ continue;
+ }
+
+ /* we have at most num_values group members */
+ iter->members = talloc_array(iter, struct mbof_member *,
+ el->num_values +1);
+ if (!iter->members) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ for (i = 0, j = 0; i < el->num_values; i++) {
+ key.type = HASH_KEY_STRING;
+ key.str = (char *)el->values[i].data;
+
+ ret = hash_lookup(ctx->user_table, &key, &value);
+ switch (ret) {
+ case HASH_SUCCESS:
+ iter->members[j] = (struct mbof_member *)value.ptr;
+ j++;
+ break;
+
+ case HASH_ERROR_KEY_NOT_FOUND:
+ /* not a user, see if it is a group */
+
+ ret = hash_lookup(ctx->group_table, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ if (ret != HASH_ERROR_KEY_NOT_FOUND) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ if (ret == HASH_ERROR_KEY_NOT_FOUND) {
+ /* not a known user, nor a known group ?
+ give a warning an continue */
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "member attribute [%s] has no corresponding"
+ " entry!", key.str);
+ break;
+ }
+
+ iter->members[j] = (struct mbof_member *)value.ptr;
+ j++;
+ break;
+
+ default:
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ /* terminate */
+ iter->members[j] = NULL;
+
+ talloc_zfree(iter->orig_members);
+ }
+
+ /* now generate correct memberof tables */
+ while (ctx->group_list->status == MBOF_GROUP_TO_DO) {
+
+ grp = ctx->group_list;
+
+ /* move to end of list and mark as done.
+ * NOTE: this is not efficient, but will do for now */
+ DLIST_DEMOTE(ctx->group_list, grp, struct mbof_member *);
+ grp->status = MBOF_GROUP_DONE;
+
+ /* verify if members need updating */
+ if (!grp->members) {
+ continue;
+ }
+ for (i = 0; grp->members[i]; i++) {
+ ret = mbof_member_update(ctx, grp, grp->members[i]);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ }
+
+ /* ok all done, now go on and modify the tree */
+ return mbof_rcmp_update(ctx);
+ }
+
+ talloc_zfree(ares);
+ return LDB_SUCCESS;
+}
+
+static int mbof_member_update(struct mbof_rcmp_context *ctx,
+ struct mbof_member *parent,
+ struct mbof_member *mem)
+{
+ hash_value_t value;
+ hash_key_t key;
+ int ret;
+
+ /* ignore loops */
+ if (parent == mem) return LDB_SUCCESS;
+
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const(ldb_dn_get_linearized(parent->dn));
+
+ if (!mem->memberofs) {
+ ret = hash_create_ex(32, &mem->memberofs, 0, 0, 0, 0,
+ hash_alloc, hash_free, mem, NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = HASH_ERROR_KEY_NOT_FOUND;
+
+ } else {
+
+ ret = hash_lookup(mem->memberofs, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ if (ret != HASH_ERROR_KEY_NOT_FOUND) {
+ /* fatal error */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ }
+
+ if (ret == HASH_ERROR_KEY_NOT_FOUND) {
+
+ /* it's missing, update member */
+ value.type = HASH_VALUE_PTR;
+ value.ptr = parent;
+
+ ret = hash_enter(mem->memberofs, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (mem->status == MBOF_USER) {
+ /* add corresponding memuid to the group */
+ ret = mbof_add_memuid(parent, mem->name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* if we updated a group, mark it as TO DO again */
+ if (mem->status == MBOF_GROUP_DONE) {
+ mem->status = MBOF_GROUP_TO_DO;
+ }
+ }
+
+ /* now see if the parent has memberofs to pass down */
+ if (parent->memberofs) {
+ ret = hash_iterate(parent->memberofs, mbof_member_iter, mem);
+ if (ret != HASH_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (mem->status == MBOF_ITER_ERROR) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* finally, if it was made TO DO move it to the head */
+ if (mem->status == MBOF_GROUP_TO_DO) {
+ DLIST_PROMOTE(ctx->group_list, mem);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static bool mbof_member_iter(hash_entry_t *item, void *user_data)
+{
+ struct mbof_member *parent;
+ struct mbof_member *mem;
+ hash_value_t value;
+ int ret;
+
+ mem = talloc_get_type(user_data, struct mbof_member);
+
+ /* exclude self */
+ if (strcmp(item->key.str, ldb_dn_get_linearized(mem->dn)) == 0) {
+ return true;
+ }
+
+ /* check if we already have it */
+ ret = hash_lookup(mem->memberofs, &item->key, &value);
+ if (ret != HASH_SUCCESS) {
+ if (ret != HASH_ERROR_KEY_NOT_FOUND) {
+ /* fatal error */
+ mem->status = MBOF_ITER_ERROR;
+ return false;
+ }
+
+ /* was not already here, add it and mark group as TO DO */
+ ret = hash_enter(mem->memberofs, &item->key, &item->value);
+ if (ret != HASH_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (mem->status == MBOF_GROUP_DONE) {
+ mem->status = MBOF_GROUP_TO_DO;
+ }
+
+ if (mem->status == MBOF_USER) {
+ /* add corresponding memuid to the group */
+ parent = (struct mbof_member *)item->value.ptr;
+ ret = mbof_add_memuid(parent, mem->name);
+ if (ret != LDB_SUCCESS) {
+ mem->status = MBOF_ITER_ERROR;
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static int mbof_add_memuid(struct mbof_member *grp, const char *user)
+{
+ struct ldb_val *vals;
+ int n;
+
+ if (!grp->memuids) {
+ grp->memuids = talloc_zero(grp, struct ldb_message_element);
+ if (!grp->memuids) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ grp->memuids->name = talloc_strdup(grp->memuids, DB_MEMBERUID);
+ if (!grp->memuids->name) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ n = grp->memuids->num_values;
+ vals = talloc_realloc(grp->memuids,
+ grp->memuids->values,
+ struct ldb_val, n + 1);
+ if (!vals) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ vals[n].data = (uint8_t *)talloc_strdup(vals, user);
+ vals[n].length = strlen(user);
+
+ grp->memuids->values = vals;
+ grp->memuids->num_values = n + 1;
+
+ return LDB_SUCCESS;
+}
+
+static int mbof_rcmp_update(struct mbof_rcmp_context *ctx)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ctx->module);
+ struct ldb_message_element *el;
+ struct ldb_message *msg = NULL;
+ struct ldb_request *req;
+ struct mbof_member *x = NULL;
+ hash_key_t *keys;
+ unsigned long count;
+ int flags;
+ int ret, i;
+
+ /* we process all users first and then all groups */
+ if (ctx->user_list) {
+ /* take the next entry and remove it from the list */
+ x = ctx->user_list;
+ DLIST_REMOVE(ctx->user_list, x);
+ }
+ else if (ctx->group_list) {
+ /* take the next entry and remove it from the list */
+ x = ctx->group_list;
+ DLIST_REMOVE(ctx->group_list, x);
+ }
+ else {
+ /* processing terminated, return */
+ ret = LDB_SUCCESS;
+ goto done;
+ }
+
+ msg = ldb_msg_new(ctx);
+ if (!msg) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ msg->dn = x->dn;
+
+ /* process memberof */
+ if (x->memberofs) {
+ ret = hash_keys(x->memberofs, &count, &keys);
+ if (ret != HASH_SUCCESS) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if (x->orig_has_memberof) {
+ flags = LDB_FLAG_MOD_REPLACE;
+ } else {
+ flags = LDB_FLAG_MOD_ADD;
+ }
+
+ ret = ldb_msg_add_empty(msg, DB_MEMBEROF, flags, &el);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+
+ el->values = talloc_array(el, struct ldb_val, count);
+ if (!el->values) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ el->num_values = count;
+
+ for (i = 0; i < count; i++) {
+ el->values[i].data = (uint8_t *)keys[i].str;
+ el->values[i].length = strlen(keys[i].str);
+ }
+ } else if (x->orig_has_memberof) {
+ ret = ldb_msg_add_empty(msg, DB_MEMBEROF, LDB_FLAG_MOD_DELETE, NULL);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ }
+
+ /* process memberuid */
+ if (x->memuids) {
+ if (x->orig_has_memberuid) {
+ flags = LDB_FLAG_MOD_REPLACE;
+ } else {
+ flags = LDB_FLAG_MOD_ADD;
+ }
+
+ ret = ldb_msg_add(msg, x->memuids, flags);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ }
+ else if (x->orig_has_memberuid) {
+ ret = ldb_msg_add_empty(msg, DB_MEMBERUID, LDB_FLAG_MOD_DELETE, NULL);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ }
+
+ ret = ldb_build_mod_req(&req, ldb, ctx, msg, NULL,
+ ctx, mbof_rcmp_mod_callback,
+ ctx->req);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ talloc_steal(req, msg);
+
+ /* fire next call */
+ return ldb_next_request(ctx->module, req);
+
+done:
+ /* all users and groups have been processed */
+ return ldb_module_done(ctx->req, NULL, NULL, ret);
+}
+
+static int mbof_rcmp_mod_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct mbof_rcmp_context *ctx;
+
+ ctx = talloc_get_type(req->context, struct mbof_rcmp_context);
+ ldb = ldb_module_get_ctx(ctx->module);
+
+ if (!ares) {
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ctx->req,
+ ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "Got an entry on a non search op ?!");
+ /* shouldn't happen */
+ talloc_zfree(ares);
+ return ldb_module_done(ctx->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_zfree(ares);
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_zfree(ares);
+
+ /* update the next one */
+ return mbof_rcmp_update(ctx);
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+
+/* module init code */
+
+static int memberof_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ /* set syntaxes for member and memberof so that comparisons in filters and
+ * such are done right */
+ ret = ldb_schema_attribute_add(ldb, DB_MEMBER, 0, LDB_SYNTAX_DN);
+ ret = ldb_schema_attribute_add(ldb, DB_MEMBEROF, 0, LDB_SYNTAX_DN);
+
+ return ldb_next_init(module);
+}
+
+const struct ldb_module_ops ldb_memberof_module_ops = {
+ .name = "memberof",
+ .init_context = memberof_init,
+ .add = memberof_add,
+ .modify = memberof_mod,
+ .del = memberof_del,
+};
diff --git a/src/m4/.dir b/src/m4/.dir
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/m4/.dir
diff --git a/src/man/include/failover.xml b/src/man/include/failover.xml
new file mode 100644
index 00000000..efe3ee42
--- /dev/null
+++ b/src/man/include/failover.xml
@@ -0,0 +1,42 @@
+<refsect1 id='failover'>
+ <title>FAILOVER</title>
+ <para>
+ The failover feature allows back ends to automatically switch to
+ a different server if the primary server fails.
+ </para>
+ <refsect2 id='failover_syntax'>
+ <title>Failover Syntax</title>
+ <para>
+ The list of servers is given as a comma-separated list; any
+ number of spaces is allowed around the comma. The servers are
+ listed in order of preference. The list can contain any number
+ of servers.
+ </para>
+ </refsect2>
+ <refsect2 id='failover_mechanism'>
+ <title>The Failover Mechanism</title>
+ <para>
+ The failover mechanism distinguishes between a machine and a
+ service. The back end first tries to resolve the hostname of a
+ given machine; if this resolution attempt fails, the machine is
+ considered offline. No further attempts are made to connect
+ to this machine for any other service. If the resolution
+ attempt succeeds, the back end tries to connect to a service
+ on this machine. If the service connection attempt fails,
+ then only this particular service is considered offline and
+ the back end automatically switches over to the next service.
+ The machine is still considered online and might still be tried
+ for another service.
+ </para>
+ <para>
+ Further connection attempts are made to machines or services
+ marked as offline after a specified period of time; this is
+ currently hard coded to 30 seconds.
+ </para>
+ <para>
+ If there are no more machines to try, the back end as a whole
+ switches to offline mode, and then attempts to reconnect
+ every 30 seconds.
+ </para>
+ </refsect2>
+</refsect1>
diff --git a/src/man/include/param_help.xml b/src/man/include/param_help.xml
new file mode 100644
index 00000000..a2478bf2
--- /dev/null
+++ b/src/man/include/param_help.xml
@@ -0,0 +1,10 @@
+<varlistentry>
+ <term>
+ <option>-h</option>,<option>--help</option>
+ </term>
+ <listitem>
+ <para>
+ Display help message and exit.
+ </para>
+ </listitem>
+</varlistentry>
diff --git a/src/man/include/upstream.xml b/src/man/include/upstream.xml
new file mode 100644
index 00000000..b6f633cc
--- /dev/null
+++ b/src/man/include/upstream.xml
@@ -0,0 +1,4 @@
+<refentryinfo>
+ <productname>SSSD</productname>
+ <orgname>The SSSD upstream - http://fedorahosted.org/sssd</orgname>
+</refentryinfo>
diff --git a/src/man/sss_groupadd.8.xml b/src/man/sss_groupadd.8.xml
new file mode 100644
index 00000000..15b7ea5a
--- /dev/null
+++ b/src/man/sss_groupadd.8.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_groupadd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_groupadd</refname>
+ <refpurpose>create a new group</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_groupadd</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>GROUP</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_groupadd</command> creates a new group. These groups are compatible
+ with POSIX groups, with the additional feature that they can contain other groups
+ as members.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-g</option>,<option>--gid</option>
+ <replaceable>GID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Set the GID of the group to the value of <replaceable>GID</replaceable>.
+ If not given, it is chosen automatically.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_groupdel.8.xml b/src/man/sss_groupdel.8.xml
new file mode 100644
index 00000000..22f4fca0
--- /dev/null
+++ b/src/man/sss_groupdel.8.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_groupdel</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_groupdel</refname>
+ <refpurpose>create a new group</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_groupdel</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>GROUP</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_groupdel</command> deletes a group
+ identified by its name <replaceable>GROUP</replaceable>
+ from the system.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_groupmod.8.xml b/src/man/sss_groupmod.8.xml
new file mode 100644
index 00000000..b2226e2e
--- /dev/null
+++ b/src/man/sss_groupmod.8.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_groupmod</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_groupmod</refname>
+ <refpurpose>modify a group</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_groupmod</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>GROUP</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_groupmod</command> modifies the
+ group to reflect the changes that are specified on
+ the command line.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-a</option>,<option>--append-group</option>
+ <replaceable>GROUPS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Append this group to groups specified by the
+ <replaceable>GROUPS</replaceable> parameter.
+ The <replaceable>GROUPS</replaceable> parameter
+ is a comma separated list of group names.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-r</option>,<option>--remove-group</option>
+ <replaceable>GROUPS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Remove this group from groups specified by the
+ <replaceable>GROUPS</replaceable> parameter.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_groupshow.8.xml b/src/man/sss_groupshow.8.xml
new file mode 100644
index 00000000..13e2dfcb
--- /dev/null
+++ b/src/man/sss_groupshow.8.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_groupshow</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_groupshow</refname>
+ <refpurpose>print properties of a group</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_groupshow</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>GROUP</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_groupshow</command> displays information about a group
+ identified by its name <replaceable>GROUP</replaceable>. The information
+ includes the group ID number, members of the group and the parent group.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-R</option>,<option>--recursive</option>
+ </term>
+ <listitem>
+ <para>
+ Also print indirect group members in a tree-like hierarchy.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_useradd.8.xml b/src/man/sss_useradd.8.xml
new file mode 100644
index 00000000..7620ffda
--- /dev/null
+++ b/src/man/sss_useradd.8.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_useradd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_useradd</refname>
+ <refpurpose>create a new user</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_useradd</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>LOGIN</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_useradd</command> creates a new user account using
+ the values specified on the command line plus the default values from
+ the system.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-u</option>,<option>--uid</option>
+ <replaceable>UID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Set the UID of the user to the value of <replaceable>UID</replaceable>.
+ If not given, it is chosen automatically.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-g</option>,<option>--gid</option>
+ <replaceable>GID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Set the GID or group membership of the user to the value
+ of <replaceable>GID</replaceable>. If not given, it is
+ chosen automatically.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-c</option>,<option>--gecos</option>
+ <replaceable>COMMENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Any text string describing the user. Often used as
+ the field for the user's full name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-h</option>,<option>--home</option>
+ <replaceable>HOME_DIR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The home directory of the user account.
+ The default is to append the <replaceable>LOGIN</replaceable> name
+ to <filename>/home</filename> and use that as the home directory.
+ The base that is prepended before <replaceable>LOGIN</replaceable> is tunable
+ with <quote>user_defaults/baseDirectory</quote> setting in sssd.conf.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-s</option>,<option>--shell</option>
+ <replaceable>SHELL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The user's login shell. The default is currently <filename>/bin/bash</filename>.
+ The default can be changed with
+ <quote>user_defaults/defaultShell</quote> setting
+ in sssd.conf.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-G</option>,<option>--groups</option>
+ <replaceable>GROUPS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ A list of existing groups this user is also a member of.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-m</option>,<option>--create-home</option>
+ </term>
+ <listitem>
+ <para>
+ Create the user's home directory if it does not
+ exist. The files and directories contained in the
+ skeleton directory (which can be defined with the
+ -k option or in the config file) will be copied
+ to the home directory.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-M</option>,<option>--no-create-home</option>
+ </term>
+ <listitem>
+ <para>
+ Do not create the user's home directory. Overrides
+ configuration settings.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-k</option>,<option>--skel</option>
+ <replaceable>SKELDIR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The skeleton directory, which contains files
+ and directories to be copied in the user's home
+ directory, when the home directory is
+ created by <command>sss_useradd</command>.
+ </para>
+ <para>
+ This option is only valid if the <option>-m</option>
+ (or <option>--create-home</option>) option is
+ specified, or creation of home directories is set to TRUE
+ in the configuration.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_userdel.8.xml b/src/man/sss_userdel.8.xml
new file mode 100644
index 00000000..0c495297
--- /dev/null
+++ b/src/man/sss_userdel.8.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_userdel</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_userdel</refname>
+ <refpurpose>delete a user account</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_userdel</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>LOGIN</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_userdel</command> deletes a user
+ identified by login name <replaceable>LOGIN</replaceable>
+ from the system.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ <varlistentry>
+ <term>
+ <option>-r</option>,<option>--remove</option>
+ </term>
+ <listitem>
+ <para>
+ Files in the user's home directory will be
+ removed along with the home directory itself and
+ the user's mail spool. Overrides the configuration.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-R</option>,<option>--no-remove</option>
+ </term>
+ <listitem>
+ <para>
+ Files in the user's home directory will NOT be
+ removed along with the home directory itself and
+ the user's mail spool. Overrides the configuration.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-f</option>,<option>--force</option>
+ </term>
+ <listitem>
+ <para>
+ This option forces <command>sss_userdel</command>
+ to remove the user's home directory and mail spool,
+ even if they are not owned by the specified user.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sss_usermod.8.xml b/src/man/sss_usermod.8.xml
new file mode 100644
index 00000000..b94fc738
--- /dev/null
+++ b/src/man/sss_usermod.8.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sss_usermod</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sss_usermod</refname>
+ <refpurpose>modify a user account</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sss_usermod</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ <arg choice='plain'><replaceable>LOGIN</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>sss_usermod</command> modifies the
+ account specified by <replaceable>LOGIN</replaceable>
+ to reflect the changes that are specified on the command line.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-c</option>,<option>--gecos</option>
+ <replaceable>COMMENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Any text string describing the user. Often used as
+ the field for the user's full name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-h</option>,<option>--home</option>
+ <replaceable>HOME_DIR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The home directory of the user account.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-s</option>,<option>--shell</option>
+ <replaceable>SHELL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The user's login shell.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-a</option>,<option>--append-group</option>
+ <replaceable>GROUPS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Append this user to groups specified by the
+ <replaceable>GROUPS</replaceable> parameter.
+ The <replaceable>GROUPS</replaceable> parameter
+ is a comma separated list of group names.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-r</option>,<option>--remove-group</option>
+ <replaceable>GROUPS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Remove this user from groups specified by the
+ <replaceable>GROUPS</replaceable> parameter.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-l</option>,<option>--lock</option>
+ </term>
+ <listitem>
+ <para>
+ Lock the user account. The user won't be able
+ to log in.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-u</option>,<option>--unlock</option>
+ </term>
+ <listitem>
+ <para>
+ Unlock the user account.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupshow</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml
new file mode 100644
index 00000000..d1ba1c52
--- /dev/null
+++ b/src/man/sssd-ipa.5.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd-ipa</refentrytitle>
+ <manvolnum>5</manvolnum>
+ <refmiscinfo class="manual">File Formats and Conventions</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd-ipa</refname>
+ <refpurpose>the configuration file for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ This manual page describes the configuration of the IPA provider
+ for
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>.
+ For a detailed syntax reference, refer to the <quote>FILE FORMAT</quote> section of the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page.
+ </para>
+ <para>
+ The IPA provider is a back end used to connect to an IPA server.
+ (Refer to the freeipa.org web site for information about IPA servers.)
+ This provider requires that the machine be joined to the IPA domain;
+ configuration is almost entirely self-discovered and obtained
+ directly from the server.
+ </para>
+ <para>
+ The IPA provider accepts the same options used by the
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> identity provider and the
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> authentication provider.
+ However, it is neither necessary nor recommended to set these options.
+ </para>
+ </refsect1>
+
+ <refsect1 id='file-format'>
+ <title>CONFIGURATION OPTIONS</title>
+ <para>Refer to the section <quote>DOMAIN SECTIONS</quote> of the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page for details on the configuration of an SSSD domain.
+ <variablelist>
+ <varlistentry>
+ <term>ipa_domain (string)</term>
+ <listitem>
+ <para>
+ Specifies the name of the IPA domain.
+ This is optional. If not provided, the configuration
+ domain name is used.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ipa_server (string)</term>
+ <listitem>
+ <para>
+ The list of IP addresses or hostnames of the
+ IPA servers to which SSSD should connect in
+ the order of preference. For more information
+ on failover and server redundancy, see the
+ <quote>FAILOVER</quote> section.
+ This is optional if autodiscovery is enabled.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ipa_hostname (string)</term>
+ <listitem>
+ <para>
+ Optional. May be set on machines where the
+ hostname(5) does not reflect the fully qualified
+ name used in the IPA domain to identify this host.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_validate (boolean)</term>
+ <listitem>
+ <para>
+ Verify with the help of krb5_keytab that the TGT
+ obtained has not been spoofed.
+ </para>
+ <para>
+ Default: true
+ </para>
+ <para>
+ Note that this default differs from the
+ traditional Kerberos provider back end.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+ </refsect1>
+
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/failover.xml" />
+
+ <refsect1 id='example'>
+ <title>EXAMPLE</title>
+ <para>
+ The following example assumes that SSSD is correctly
+ configured and example.com is one of the domains in the
+ <replaceable>[sssd]</replaceable> section. This examples shows only
+ the ipa provider-specific options.
+ </para>
+ <para>
+<programlisting>
+ [domain/example.com]
+ id_provider = ipa
+ ipa_server = ipaserver.example.com
+ ipa_hostname = myhost.example.com
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd-krb5.5.xml b/src/man/sssd-krb5.5.xml
new file mode 100644
index 00000000..32b6c293
--- /dev/null
+++ b/src/man/sssd-krb5.5.xml
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd-krb5</refentrytitle>
+ <manvolnum>5</manvolnum>
+ <refmiscinfo class="manual">File Formats and Conventions</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd-krb5</refname>
+ <refpurpose>the configuration file for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ This manual page describes the configuration of the Kerberos
+ 5 authentication backend for
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>.
+ For a detailed syntax reference, please refer to the <quote>FILE FORMAT</quote> section of the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page
+ </para>
+ <para>
+ The Kerberos 5 authentication backend does not contain an identity
+ provider and must be paired with one in order to function properly (for
+ example, id_provider = ldap). Some information required by the Kerberos
+ 5 authentication backend must be provided by the identity provider, such
+ as the user's Kerberos Principal Name (UPN). The configuration of the
+ identity provider should have an entry to specify the UPN. Please refer
+ to the man page for the applicable identity provider for details on how
+ to configure this.
+ </para>
+ <para>
+ In the case where the UPN is not available in the identity backend
+ <command>sssd</command> will construct a UPN using the format
+ <replaceable>username</replaceable>@<replaceable>krb5_realm</replaceable>.
+ </para>
+
+ </refsect1>
+
+ <refsect1 id='file-format'>
+ <title>CONFIGURATION OPTIONS</title>
+ <para>
+ If the auth-module krb5 is used in a SSSD domain, the following
+ options must be used. See the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page, section <quote>DOMAIN SECTIONS</quote>
+ for details on the configuration of a SSSD domain.
+ <variablelist>
+ <varlistentry>
+ <term>krb5_kdcip (string)</term>
+ <listitem>
+ <para>
+ Specifies the list of IP addresses or hostnames
+ of the Kerberos servers to which SSSD should
+ connect in the order of preference. For more
+ information on failover and server redundancy,
+ see the <quote>FAILOVER</quote> section.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_realm (string)</term>
+ <listitem>
+ <para>
+ The name of the Kerberos realm.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_changepw_principal (string)</term>
+ <listitem>
+ <para>
+ The priciple of the change password service.
+ If only the 'identifier/instance' part of the
+ principal are given the realm part is added
+ automatically.
+ </para>
+ <para>
+ Default: kadmin/changepw
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_ccachedir (string)</term>
+ <listitem>
+ <para>
+ Directory to store credential caches.
+ </para>
+ <para>
+ Default: /tmp
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_ccname_template (string)</term>
+ <listitem>
+ <para>
+ Location of the user's credential cache. Currently
+ only file based credential caches are supported. In
+ the template the following sequences are
+ substituted:
+ <variablelist>
+ <varlistentry>
+ <term>%u</term>
+ <listitem><para>login name</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%U</term>
+ <listitem><para>login UID</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%p</term>
+ <listitem><para>principal name</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%r</term>
+ <listitem><para>realm name</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%h</term>
+ <listitem><para>home directory</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%d</term>
+ <listitem><para>value of krb5ccache_dir
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%P</term>
+ <listitem><para>the process ID of the sssd
+ client</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>%%</term>
+ <listitem><para>a literal '%'</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ If the template ends with 'XXXXXX' mkstemp(3) is
+ used to create a unique filename in a safe way.
+ </para>
+ <para>
+ Default: FILE:%d/krb5cc_%U_XXXXXX
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_auth_timeout (integer)</term>
+ <listitem>
+ <para>
+ Timeout in seconds after an online authentication or
+ change password request is aborted. If possible the
+ authentication request is continued offline.
+ </para>
+ <para>
+ Default: 15
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_validate (boolean)</term>
+ <listitem>
+ <para>
+ Verify with the help of krb5_keytab that the TGT obtained has not been spoofed.
+ </para>
+ <para>
+ Default: false
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_keytab (string)</term>
+ <listitem>
+ <para>
+ The location of the keytab to use when validating
+ credentials obtained from KDCs.
+ </para>
+ <para>
+ Default: /etc/krb5.keytab
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+ </refsect1>
+
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/failover.xml" />
+
+ <refsect1 id='example'>
+ <title>EXAMPLE</title>
+ <para>
+ The following example assumes that SSSD is correctly
+ configured and FOO is one of the domains in the
+ <replaceable>[sssd]</replaceable> section. This example shows
+ only configuration of Kerberos authentication, it does not include
+ any identity provider.
+ </para>
+ <para>
+<programlisting>
+ [domain/FOO]
+ auth_provider = krb5
+ krb5_kdcip = 192.168.1.1
+ krb5_realm = EXAMPLE.COM
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
new file mode 100644
index 00000000..b79cbbc9
--- /dev/null
+++ b/src/man/sssd-ldap.5.xml
@@ -0,0 +1,688 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd-ldap</refentrytitle>
+ <manvolnum>5</manvolnum>
+ <refmiscinfo class="manual">File Formats and Conventions</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd-ldap</refname>
+ <refpurpose>the configuration file for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ This manual page describes the configuration of LDAP
+ domains for
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>.
+ Refer to the <quote>FILE FORMAT</quote> section of the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page for detailed syntax information.</para>
+ <para>
+ You can configure SSSD to use more than one LDAP domain.
+ </para>
+ <para>
+ If you want to authenticate against an LDAP server then TLS/SSL is
+ required. <command>sssd</command> <emphasis>does not</emphasis>
+ support authentication over an unencrypted channel. If the LDAP
+ server is used only as an identify provider, an encrypted channel
+ is not needed.
+ </para>
+ </refsect1>
+
+ <refsect1 id='file-format'>
+ <title>CONFIGURATION OPTIONS</title>
+ <para>
+ All of the common configuration options that apply to SSSD domains also apply
+ to LDAP domains. Refer to the <quote>DOMAIN SECTIONS</quote> section of the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page for full details.
+
+ <variablelist>
+ <varlistentry>
+ <term>ldap_uri (string)</term>
+ <listitem>
+ <para>
+ Specifies the list of URIs of the LDAP servers to which
+ SSSD should connect in the order of preference. Refer to the
+ <quote>FAILOVER</quote> section for more information on failover and server redundancy.
+ </para>
+ <para>
+ Default: ldap://localhost
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_search_base (string)</term>
+ <listitem>
+ <para>
+ The default base DN to use for
+ performing LDAP user operations.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_schema (string)</term>
+ <listitem>
+ <para>
+ Specifies the Schema Type in use on the target LDAP
+ server.
+ Depending on the selected schema, the default
+ attribute names retrieved from the servers may vary.
+ The way that some attributes are handled may also differ.
+
+ Two schema types are currently supported:
+ rfc2307
+ rfc2307bis
+
+ The main difference between these two schema types is
+ how group memberships are recorded in the server.
+ With rfc2307, group members are listed by name in the
+ <emphasis>memberUid</emphasis> attribute.
+ With rfc2307bis, group members are listed by DN and
+ stored in the <emphasis>member</emphasis> attribute.
+
+ </para>
+ <para>
+ Default: rfc2307
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_default_bind_dn (string)</term>
+ <listitem>
+ <para>
+ The default bind DN to use for
+ performing LDAP operations.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_default_authtok_type (string)</term>
+ <listitem>
+ <para>
+ The type of the authentication token of the
+ default bind DN. The only currently supported value is "password".
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_default_authtok (string)</term>
+ <listitem>
+ <para>
+ The authentication token of the default bind DN.
+ Only clear text passwords are currently supported.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_search_base (string)</term>
+ <listitem>
+ <para>
+ An optional base DN to restrict user searches
+ to a specific subtree.
+ </para>
+ <para>
+ Default: the value of
+ <emphasis>ldap_search_base</emphasis>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_object_class (string)</term>
+ <listitem>
+ <para>
+ The object class of a user entry in LDAP.
+ </para>
+ <para>
+ Default: posixAccount
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_name (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ user's login name.
+ </para>
+ <para>
+ Default: uid
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_uid_number (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ user's id.
+ </para>
+ <para>
+ Default: uidNumber
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_gid_number (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ user's primary group id.
+ </para>
+ <para>
+ Default: gidNumber
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_gecos (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ user's gecos field.
+ </para>
+ <para>
+ Default: gecos
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_home_directory (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the name of the user's
+ home directory.
+ </para>
+ <para>
+ Default: homeDirectory
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_shell (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the path to the
+ user's default shell.
+ </para>
+ <para>
+ Default: loginShell
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_uuid (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the UUID/GUID of
+ an LDAP user object.
+ </para>
+ <para>
+ Default: nsUniqueId
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_principal (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the user's Kerberos
+ User Principle Name (UPN).
+ </para>
+ <para>
+ Default: krbPrincipalName
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_force_upper_case_realm (boolean)</term>
+ <listitem>
+ <para>
+ Some directory servers, for example Active Directory,
+ might deliver the realm part of the UPN in lower case,
+ which might cause the authentication to fail. Set this
+ option to a non-zero value if you want to use an
+ upper-case realm.
+ </para>
+ <para>
+ Default: false
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_fullname (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ user's full name.
+ </para>
+ <para>
+ Default: cn
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_user_member_of (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that lists the user's
+ group memberships.
+ </para>
+ <para>
+ Default: memberOf
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_search_base (string)</term>
+ <listitem>
+ <para>
+ An optional base DN to restrict group searches
+ to a specific subtree.
+ </para>
+ <para>
+ Default: the value of
+ <emphasis>ldap_search_base</emphasis>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_object_class (string)</term>
+ <listitem>
+ <para>
+ The object class of a group entry in LDAP.
+ </para>
+ <para>
+ Default: posixGroup
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_name (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to
+ the group name.
+ </para>
+ <para>
+ Default: cn
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_gid_number (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that corresponds to the
+ group's id.
+ </para>
+ <para>
+ Default: gidNumber
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_member (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the names of
+ the group's members.
+ </para>
+ <para>
+ Default: memberuid (rfc2307) / member (rfc2307bis)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_group_uuid (string)</term>
+ <listitem>
+ <para>
+ The LDAP attribute that contains the UUID/GUID of
+ an LDAP group object.
+ </para>
+ <para>
+ Default: nsUniqueId
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_network_timeout (integer)</term>
+ <listitem>
+ <para>
+ Specifies the timeout (in seconds) after which
+ the
+ <citerefentry>
+ <refentrytitle>poll</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>/<citerefentry>
+ <refentrytitle>select</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>
+ following a
+ <citerefentry>
+ <refentrytitle>connect</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>
+ returns in case of no activity.
+ </para>
+ <para>
+ Default: 5
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_opt_timeout (integer)</term>
+ <listitem>
+ <para>
+ Specifies a timeout (in seconds) after which
+ calls to synchronous LDAP APIs will abort if no
+ response is received. Also controls the timeout
+ when communicating with the KDC in case of SASL bind.
+ </para>
+ <para>
+ Default: 5
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_tls_reqcert (string)</term>
+ <listitem>
+ <para>
+ Specifies what checks to perform on server
+ certificates in a TLS session, if any. It
+ can be specified as one of the following
+ values:
+ </para>
+ <para>
+ <emphasis>never</emphasis> = The client will
+ not request or check any server certificate.
+ </para>
+ <para>
+ <emphasis>allow</emphasis> = The server
+ certificate is requested. If no certificate is
+ provided, the session proceeds normally. If a
+ bad certificate is provided, it will be ignored
+ and the session proceeds normally.
+ </para>
+ <para>
+ <emphasis>try</emphasis> = The server certificate
+ is requested. If no certificate is provided, the
+ session proceeds normally. If a bad certificate
+ is provided, the session is immediately terminated.
+ </para>
+ <para>
+ <emphasis>demand</emphasis> = The server
+ certificate is requested. If no certificate
+ is provided, or a bad certificate is provided,
+ the session is immediately terminated.
+ </para>
+ <para>
+ <emphasis>hard</emphasis> = Same as
+ <quote>demand</quote>
+ </para>
+ <para>
+ Default: hard
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_tls_cacert (string)</term>
+ <listitem>
+ <para>
+ Specifies the file that contains certificates for
+ all of the Certificate Authorities that
+ <command>sssd</command> will recognize.
+ </para>
+ <para>
+ Default: use OpenLDAP defaults, typically in
+ <filename>/etc/openldap/ldap.conf</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_tls_cacertdir (string)</term>
+ <listitem>
+ <para>
+ Specifies the path of a directory that contains
+ Certificate Authority certificates in separate
+ individual files. Typically the file names need to
+ be the hash of the certificate followed by '.0'.
+ If available, <command>cacertdir_rehash</command>
+ can be used to create the correct names.
+ </para>
+ <para>
+ Default: use OpenLDAP defaults, typically in
+ <filename>/etc/openldap/ldap.conf</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_id_use_start_tls (boolean)</term>
+ <listitem>
+ <para>
+ Specifies that the id_provider connection must also
+ use <systemitem class="protocol">tls</systemitem> to protect the channel.
+ </para>
+ <para>
+ Default: false
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_sasl_mech (string)</term>
+ <listitem>
+ <para>
+ Specify the SASL mechanism to use.
+ Currently only GSSAPI is tested and supported.
+ </para>
+ <para>
+ Default: none
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_sasl_authid (string)</term>
+ <listitem>
+ <para>
+ Specify the SASL authorization id to use.
+ When GSSAPI is used, this represents the Kerberos
+ principal used for authentication to the directory.
+ </para>
+ <para>
+ Default: host/machine.fqdn@REALM
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_krb5_keytab (string)</term>
+ <listitem>
+ <para>
+ Specify the keytab to use when using SASL/GSSAPI.
+ </para>
+ <para>
+ Default: System keytab, normally <filename>/etc/krb5.keytab</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_krb5_init_creds (boolean)</term>
+ <listitem>
+ <para>
+ Specifies that the id_provider should init
+ Kerberos credentials (TGT).
+ This action is performed only if SASL is used and
+ the mechanism selected is GSSAPI.
+ </para>
+ <para>
+ Default: true
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>krb5_realm (string)</term>
+ <listitem>
+ <para>
+ Specify the Kerberos REALM (for SASL/GSSAPI auth).
+ </para>
+ <para>
+ Default: System defaults, see <filename>/etc/krb5.conf</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_pwd_policy (string)</term>
+ <listitem>
+ <para>
+ Select the policy to evaluate the password
+ expiration on the client side. The following values
+ are allowed:
+ </para>
+ <para>
+ <emphasis>none</emphasis> - No evaluation on the
+ client side. This option cannot disable server-side
+ password policies.
+ </para>
+ <para>
+ <emphasis>shadow</emphasis> - Use
+ <citerefentry><refentrytitle>shadow</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry> style
+ attributes to evaluate if the password has expired.
+ Note that the current version of sssd cannot
+ update this attribute during a password change.
+ </para>
+ <para>
+ <emphasis>mit_kerberos</emphasis> - Use the attributes
+ used by MIT Kerberos to determine if the password has
+ expired. Use chpass_provider=krb5 to update these
+ attributes when the password is changed.
+ </para>
+ <para>
+ Default: none
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ldap_referrals (boolean)</term>
+ <listitem>
+ <para>
+ Specifies whether automatic referral chasing should
+ be enabled.
+ </para>
+ <para>
+ Please note that sssd only supports referral chasing
+ when it is compiled with OpenLDAP version 2.4.13 or
+ higher.
+ </para>
+ <para>
+ Default: true
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+ </refsect1>
+
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/failover.xml" />
+
+ <refsect1 id='example'>
+ <title>EXAMPLE</title>
+ <para>
+ The following example assumes that SSSD is correctly
+ configured and LDAP is set to one of the domains in the
+ <replaceable>[domains]</replaceable> section.
+ </para>
+ <para>
+<programlisting>
+ [domain/LDAP]
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = ldap://ldap.mydomain.org
+ ldap_search_base = dc=mydomain,dc=org
+ ldap_tls_reqcert = demand
+ cache_credentials = true
+ enumerate = true
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1 id='notes'>
+ <title>NOTES</title>
+ <para>
+ The descriptions of some of the configuration options in this manual
+ page are based on the <citerefentry>
+ <refentrytitle>ldap.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> manual page from the OpenLDAP 2.4 distribution.
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd.8.xml b/src/man/sssd.8.xml
new file mode 100644
index 00000000..5e45a336
--- /dev/null
+++ b/src/man/sssd.8.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd</refname>
+ <refpurpose>System Security Services Daemon</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>sssd</command>
+ <arg choice='opt'>
+ <replaceable>options</replaceable>
+ </arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>SSSD</command> provides a set of daemons to manage access to remote
+ directories and authentication mechanisms. It provides an NSS and
+ PAM interface toward the system and a pluggable backend system to
+ connect to multiple different account sources as well as D-Bus
+ interface. It is also the basis to provide client auditing and
+ policy services for projects like FreeIPA. It provides a more robust database
+ to store local users as well as extended user data.
+ </para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>-d</option>,<option>--debug-level</option>
+ <replaceable>LEVEL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Debug level to run the daemon with. 0 is the default as well
+ as the lowest allowed value, 10 is the most verbose mode. This setting
+ overrides the settings from config file. This parameter implies <option>-i</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>debug_timestamps (bool)</term>
+ <listitem>
+ <para>
+ Add a timestamp to the debug messages
+ </para>
+ <para>
+ Default: false
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-f</option>,<option>--debug-to-files</option>
+ </term>
+ <listitem>
+ <para>
+ Send the debug output to files instead of stderr. By default, the
+ log files are stored in <filename>/var/log/sssd</filename> and
+ there are separate log files for every SSSD service and domain.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-D</option>,<option>--daemon</option>
+ </term>
+ <listitem>
+ <para>
+ Become a daemon after starting up.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-i</option>,<option>--interactive</option>
+ </term>
+ <listitem>
+ <para>
+ Run in the foreground, don't become a daemon.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-c</option>,<option>--config</option>
+ </term>
+ <listitem>
+ <para>
+ Specify a non-default config file. The default is
+ <filename>/etc/sssd/sssd.conf</filename>. For reference
+ on the config file syntax and options, consult the
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ manual page.
+ </para>
+ </listitem>
+ </varlistentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
new file mode 100644
index 00000000..7b240c8f
--- /dev/null
+++ b/src/man/sssd.conf.5.xml
@@ -0,0 +1,808 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ <refmiscinfo class="manual">File Formats and Conventions</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd.conf</refname>
+ <refpurpose>the configuration file for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsect1 id='file-format'>
+ <title>FILE FORMAT</title>
+
+ <para>
+ The file has an ini-style syntax and consists of sections and
+ parameters. A section begins with the name of the section in
+ square brackets and continues until the next section begins. An
+ example of section with single and multi-valued parameters:
+ <programlisting>
+ <replaceable>[section]</replaceable>
+ <replaceable>key</replaceable> = <replaceable>value</replaceable>
+ <replaceable>key2</replaceable> = <replaceable>value2,value3</replaceable>
+ </programlisting>
+ </para>
+
+ <para>
+ The data types used are string (no quotes needed), integer
+ and bool (with values of <quote>TRUE/FALSE</quote>).
+ </para>
+
+ <para>
+ A line comment starts with a hash sign (<quote>#</quote>) or a
+ semicolon (<quote>;</quote>)
+ </para>
+
+ <para>
+ All sections can have an optional
+ <replaceable>description</replaceable> parameter. Its function
+ is only as a label for the section.
+ </para>
+
+ <para>
+ <filename>sssd.conf</filename> must be a regular file, owned by
+ root and only root may read from or write to the file.
+ </para>
+ </refsect1>
+
+ <refsect1 id='special-sections'>
+ <title>SPECIAL SECTIONS</title>
+
+ <refsect2 id='services'>
+ <title>The [sssd] section</title>
+ <para>
+ Individual pieces of SSSD functionality are provided by special
+ SSSD services that are started and stopped together with SSSD.
+ The services are managed by a special service frequently called
+ <quote>monitor</quote>. The <quote>[sssd]</quote> section is used
+ to configure the monitor as well as some other important options
+ like the identity domains.
+ <variablelist>
+ <title>Section parameters</title>
+ <varlistentry>
+ <term>config_file_version (integer)</term>
+ <listitem>
+ <para>
+ Indicates what is the syntax of the config
+ file. SSSD 0.6.0 and later use version 2.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>services</term>
+ <listitem>
+ <para>
+ Comma separated list of services that are
+ started when sssd itself starts.
+ </para>
+ <para>
+ Supported services: nss, pam
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>reconnection_retries (integer)</term>
+ <listitem>
+ <para>
+ Number of times services should attempt to
+ reconnect in the event of a Data Provider
+ crash or restart before they give up
+ </para>
+ <para>
+ Default: 3
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>domains</term>
+ <listitem>
+ <para>
+ A domain is a database containing user
+ information. SSSD can use more domains
+ at the same time, but at least one
+ must be configured or SSSD won't start.
+ This parameter described the list of domains
+ in the order you want them to be queried.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>re_expression (string)</term>
+ <listitem>
+ <para>
+ Regular expression that describes how to parse the string
+ containing user name and domain into these components.
+ </para>
+ <para>
+ Default: <quote>(?P&lt;name&gt;[^@]+)@?(?P&lt;domain&gt;[^@]*$)</quote>
+ which translates to "the name is everything up to the
+ <quote>@</quote> sign, the domain everything after that"
+ </para>
+ <para>
+ PLEASE NOTE: the support for non-unique named
+ subpatterns is not available on all plattforms
+ (e.g. RHEL5 and SLES10). Only plattforms with
+ libpcre version 7 or higher can support non-unique
+ named subpatterns.
+ </para>
+ <para>
+ PLEASE NOTE ALSO: older version of libpcre only
+ support the Python syntax (?P&lt;name&gt;) to label
+ subpatterns.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>full_name_format (string)</term>
+ <listitem>
+ <para>
+ A <citerefentry>
+ <refentrytitle>printf</refentrytitle>
+ <manvolnum>3</manvolnum>
+ </citerefentry>-compatible format that describes how to
+ translate a (name, domain) tuple into a fully qualified
+ name.
+ </para>
+ <para>
+ Default: <quote>%1$s@%2$s</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </refsect2>
+
+ </refsect1>
+
+ <refsect1 id='services-sections'>
+ <title>SERVICES SECTIONS</title>
+ <para>
+ Settings that can be used to configure different services
+ are described in this section. They should reside in the
+ [<replaceable>$NAME</replaceable>] section, for example,
+ for NSS service, the section would be <quote>[nss]</quote>
+ </para>
+
+ <refsect2 id='general'>
+ <title>General service configuration options</title>
+ <para>
+ These options can be used to configure any service.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>debug_level (integer)</term>
+ <listitem>
+ <para>
+ Sets the debug level for the service. The
+ value can be in range from 0 (only critical
+ messages) to 10 (very verbose).
+ </para>
+ <para>
+ Default: 0
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>debug_timestamps (bool)</term>
+ <listitem>
+ <para>
+ Add a timestamp to the debug messages
+ </para>
+ <para>
+ Default: true
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>reconnection_retries (integer)</term>
+ <listitem>
+ <para>
+ Number of times services should attempt to
+ reconnect in the event of a Data Provider
+ crash or restart before they give up
+ </para>
+ <para>
+ Default: 3
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>command (string)</term>
+ <listitem>
+ <para>
+ By default, the executable
+ representing this service is called
+ <command>sssd_${service_name}</command>.
+ This directive allows to change the executable
+ name for the service. In the vast majority of
+ configurations, the default values should suffice.
+ </para>
+ <para>
+ Default: <command>sssd_${service_name}</command>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id='NSS'>
+ <title>NSS configuration options</title>
+ <para>
+ These options can be used to configure the
+ Name Service Switch (NSS) service.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>enum_cache_timeout (integer)</term>
+ <listitem>
+ <para>
+ How many seconds should nss_sss cache enumerations
+ (requests for info about all users)
+ </para>
+ <para>
+ Default: 120
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>entry_cache_nowait_percentage (integer)</term>
+ <listitem>
+ <para>
+ The entry cache can be set to automatically update
+ entries in the background if they are requested
+ beyond a percentage of the entry_cache_timeout
+ value for the domain.
+ </para>
+ <para>
+ For example, if the domain's entry_cache_timeout
+ is set to 30s and entry_cache_nowait_percentage is
+ set to 50 (percent), entries that come in after 15
+ seconds past the last cache update will be
+ returned immediately, but the SSSD will go and
+ update the cache on its own, so that future
+ requests will not need to block waiting for a
+ cache update.
+ </para>
+ <para>
+ Valid values for this option are 0-99 and
+ represent a percentage of the entry_cache_timeout
+ for each domain. For performance reasons, this
+ percentage will never reduce the nowait timeout to
+ less than 10 seconds.
+ (0 disables this feature)
+ </para>
+ <para>
+ Default: 0
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>entry_negative_timeout (integer)</term>
+ <listitem>
+ <para>
+ Specifies for how long nss_sss should cache
+ negative cache hits (that is, queries for
+ invalid database entries, like nonexistent ones)
+ before asking the back end again.
+ </para>
+ <para>
+ Default: 15
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>filter_users, filter_groups (string)</term>
+ <listitem>
+ <para>
+ Exclude certain users from being fetched from the sss
+ NSS database. This is particulary useful for system
+ accounts.
+ </para>
+ <para>
+ Default: root
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>filter_users_in_groups (bool)</term>
+ <listitem>
+ <para>
+ If you want filtered user still be group members
+ set this option to false.
+ </para>
+ <para>
+ Default: true
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2 id='PAM'>
+ <title>PAM configuration options</title>
+ <para>
+ These options can be used to configure the
+ Pluggable Authentication Module (PAM) service.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>offline_credentials_expiration (integer)</term>
+ <listitem>
+ <para>
+ If the authentication provider is offline, how
+ long should we allow cached logins (in days since
+ the last successful online login).
+ </para>
+ <para>
+ Default: 0 (No limit)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>offline_failed_login_attempts (integer)</term>
+ <listitem>
+ <para>
+ If the authentication provider is offline, how
+ many failed login attempts are allowed.
+ </para>
+ <para>
+ Default: 0 (No limit)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>offline_failed_login_delay (integer)</term>
+ <listitem>
+ <para>
+ The time in minutes which has to pass after
+ offline_failed_login_attempts has been reached
+ before a new login attempt is possible.
+ </para>
+ <para>
+ If set to 0 the user cannot authenticate offline if
+ offline_failed_login_attempts has been reached. Only
+ a successful online authentication can enable
+ enable offline authentication again.
+ </para>
+ <para>
+ Default: 5
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1 id='domain-sections'>
+ <title>DOMAIN SECTIONS</title>
+ <para>
+ These configuration options can be present in a domain
+ configuration section, that is, in a section called
+ <quote>[domain/<replaceable>NAME</replaceable>]</quote>
+ <variablelist>
+ <varlistentry>
+ <term>min_id,max_id (integer)</term>
+ <listitem>
+ <para>
+ UID limits for the domain. If a domain contains
+ entry that is outside these limits, it is ignored
+ </para>
+ <para>
+ Default: 1000 for min_id, 0 (no limit) for max_id
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>timeout (integer)</term>
+ <listitem>
+ <para>
+ Timeout in seconds between heartbeats for this domain.
+ This is used to ensure that the backend process is
+ alive and capable of answering requests.
+ </para>
+ <para>
+ Default: 10
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>enumerate (bool)</term>
+ <listitem>
+ <para>
+ Determines if a domain can be enumerated. This
+ parameter can have one of the following values:
+ </para>
+ <para>
+ TRUE = Users and groups are enumerated
+ </para>
+ <para>
+ FALSE = No enumerations for this domain
+ </para>
+ <para>
+ Default: TRUE
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>entry_cache_timeout (integer)</term>
+ <listitem>
+ <para>
+ How many seconds should nss_sss consider
+ entries valid before asking the backend again
+ </para>
+ <para>
+ Default: 600
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>cache_credentials (bool)</term>
+ <listitem>
+ <para>
+ Determines if user credentials are also cached
+ in the local LDB cache
+ </para>
+ <para>
+ Default: FALSE
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>id_provider (string)</term>
+ <listitem>
+ <para>
+ The Data Provider identity backend to use for this
+ domain.
+ </para>
+ <para>
+ Supported backends:
+ </para>
+ <para>
+ proxy: Support a legacy NSS provider
+ </para>
+ <para>
+ local: SSSD internal local provider
+ </para>
+ <para>
+ ldap: LDAP provider
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>use_fully_qualified_names (bool)</term>
+ <listitem>
+ <para>
+ If set to TRUE, all requests to this domain
+ must use fully qualified names. For example,
+ if used in LOCAL domain that contains a "test"
+ user, <command>getent passwd test</command>
+ wouldn't find the user while <command>getent
+ passwd test@LOCAL</command> would.
+ </para>
+ <para>
+ Default: FALSE
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>auth_provider (string)</term>
+ <listitem>
+ <para>
+ The authentication provider used for the domain.
+ Supported auth providers are:
+ </para>
+ <para>
+ <quote>ldap</quote> for native LDAP authentication. See
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for more information on configuring LDAP.
+ </para>
+ <para>
+ <quote>krb5</quote> for Kerberos authentication. See
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for more information on configuring Kerberos.
+ </para>
+ <para>
+ <quote>proxy</quote> for relaying authentication to some other PAM target.
+ </para>
+ <para>
+ <quote>none</quote> disables authentication explicitly.
+ </para>
+ <para>
+ Default: <quote>id_provider</quote> is used if it
+ is set and can handle authentication requests.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>access_provider (string)</term>
+ <listitem>
+ <para>
+ The access control provider used for the domain.
+ There are two built-in access providers (in
+ addition to any included in installed backends)
+ Internal special providers are:
+ </para>
+ <para>
+ <quote>permit</quote> always allow access.
+ </para>
+ <para>
+ <quote>deny</quote> always deny access.
+ </para>
+ <para>
+ Default: <quote>permit</quote>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>chpass_provider (string)</term>
+ <listitem>
+ <para>
+ The provider which should handle change password
+ operations for the domain.
+ Supported change password providers are:
+ </para>
+ <para>
+ <quote>ldap</quote> to change a password stored
+ in a LDAP server. See
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for more information on configuring LDAP.
+ </para>
+ <para>
+ <quote>krb5</quote> to change the Kerberos
+ password. See
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for more information on configuring Kerberos.
+ </para>
+ <para>
+ <quote>proxy</quote> for relaying password changes
+ to some other PAM target.
+ </para>
+ <para>
+ <quote>none</quote> disallows password changes explicitly.
+ </para>
+ <para>
+ Default: <quote>auth_provider</quote> is used if it
+ is set and can handle change password requests.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ Options valid for proxy domains.
+
+ <variablelist>
+ <varlistentry>
+ <term>proxy_pam_target (string)</term>
+ <listitem>
+ <para>
+ The proxy target PAM proxies to.
+ </para>
+ <para>
+ Default: not set by default, you have to take an
+ existing pam configuration or create a new one and
+ add the service name here.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>proxy_lib_name (string)</term>
+ <listitem>
+ <para>
+ The name of the NSS library to use in proxy
+ domains. The NSS functions searched for in the
+ library are in the form of
+ _nss_$(libName)_$(function), for example
+ _nss_files_getpwent.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <refsect2 id='local_domain'>
+ <title>The local domain section</title>
+ <para>
+ This section contains settings for domain that stores users and
+ groups in SSSD native database, that is, a domain that uses
+ <replaceable>id_provider=local</replaceable>.
+ </para>
+ <variablelist>
+ <title>Section parameters</title>
+ <varlistentry>
+ <term>default_shell (string)</term>
+ <listitem>
+ <para>
+ The default shell for users created
+ with SSSD userspace tools.
+ </para>
+ <para>
+ Default: <filename>/bin/bash</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>base_directory (string)</term>
+ <listitem>
+ <para>
+ The tools append the login name to
+ <replaceable>base_directory</replaceable> and
+ use that as the home directory.
+ </para>
+ <para>
+ Default: <filename>/home</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>create_homedir (bool)</term>
+ <listitem>
+ <para>
+ Indicate if a home directory should be created by default for new users.
+ Can be overriden on command line.
+ </para>
+ <para>
+ Default: TRUE
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>remove_homedir (bool)</term>
+ <listitem>
+ <para>
+ Indicate if a home directory should be removed by default for deleted users.
+ Can be overriden on command line.
+ </para>
+ <para>
+ Default: TRUE
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>homedir_umask (integer)</term>
+ <listitem>
+ <para>
+ Used by
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry> to specify the default permissions on a newly created
+ home directory.
+ </para>
+ <para>
+ Default: 077
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>skel_dir (string)</term>
+ <listitem>
+ <para>
+ The skeleton directory, which contains files
+ and directories to be copied in the user's
+ home directory, when the home directory is
+ created by
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ <para>
+ Default: <filename>/etc/skel</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>mail_dir (string)</term>
+ <listitem>
+ <para>
+ The mail spool directory. This is needed to
+ manipulate the mailbox when its corresponding
+ user account is modified or deleted.
+ If not specified, a default
+ value is used.
+ </para>
+ <para>
+ Default: <filename>/var/mail</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ </refsect1>
+
+ <refsect1 id='example'>
+ <title>EXAMPLE</title>
+ <para>
+ The following example shows a typical SSSD config. It does
+ not describe configuration of the domains themselves - refer to
+ documentation on configuring domains for more details.
+<programlisting>
+[sssd]
+domains = LDAP
+services = nss, pam
+config_file_version = 2
+
+[nss]
+filter_groups = root
+filter_users = root
+
+[pam]
+
+[domain/LDAP]
+id_provider = ldap
+ldap_uri = ldap://ldap.example.com
+ldap_search_base = dc=example,dc=com
+
+auth_provider = krb5
+krb5_kdcip = kerberos.example.com
+krb5_realm = EXAMPLE.COM
+cache_credentials = true
+
+min_id = 10000
+max_id = 20000
+enumerate = true
+</programlisting>
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd-ldap</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupadd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_groupmod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_useradd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_userdel</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sss_usermod</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam_sss</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/man/sssd_krb5_locator_plugin.8.xml b/src/man/sssd_krb5_locator_plugin.8.xml
new file mode 100644
index 00000000..6c60431f
--- /dev/null
+++ b/src/man/sssd_krb5_locator_plugin.8.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" />
+
+ <refmeta>
+ <refentrytitle>sssd_krb5_locator_plugin</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>sssd_krb5_locator_plugin</refname>
+ <refpurpose>the configuration file for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para>
+ The Kerberos locator plugin
+ <command>sssd_krb5_locator_plugin</command> is used by the Kerberos
+ provider of
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>
+ to tell the Kerberos libraries what Realm and which KDC to use.
+ Typically this is done in
+ <citerefentry>
+ <refentrytitle>krb5.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ which is always read by the Kerberos libraries. To simplyfy the
+ configuration the Realm and the KDC can be defined in
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ as described in
+ <citerefentry>
+ <refentrytitle>sssd-krb5.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ </para>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>
+ puts the Realm and the name or IP address of the KDC into the
+ enviroment variables SSSD_KRB5_REALM and SSSD_KRB5_KDC respectively.
+ When <command>sssd_krb5_locator_plugin</command> is called by the
+ kerberos libraries it reads and evaluates these variable and returns
+ them to the libraries.
+ </para>
+ </refsect1>
+
+ <refsect1 id='notes'>
+ <title>NOTES</title>
+ <para>
+ Not all Kerberos implementations support the use of plugins. If
+ <command>sssd_krb5_locator_plugin</command> is not available on
+ your system you have to edit /etc/krb5.conf to reflect your
+ Kerberos setup.
+ </para>
+ <para>
+ If the enviroment variable SSSD_KRB5_LOCATOR_DEBUG is set to any
+ value debug messages will be sent to stderr.
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd-krb5</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sssd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c
new file mode 100644
index 00000000..ddbfb6a7
--- /dev/null
+++ b/src/monitor/monitor.c
@@ -0,0 +1,2631 @@
+/*
+ SSSD
+
+ Service monitor
+
+ Copyright (C) Simo Sorce 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <time.h>
+#include <string.h>
+#include "config.h"
+#ifdef HAVE_SYS_INOTIFY_H
+#include <sys/inotify.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/* Needed for res_init() */
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+#include "util/util.h"
+#include "popt.h"
+#include "tevent.h"
+#include "confdb/confdb.h"
+#include "confdb/confdb_setup.h"
+#include "collection.h"
+#include "ini_config.h"
+#include "db/sysdb.h"
+#include "monitor/monitor.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "monitor/monitor_interfaces.h"
+
+/* ping time cannot be less then once every few seconds or the
+ * monitor will get crazy hammering children with messages */
+#define MONITOR_DEF_PING_TIME 10
+
+struct svc_spy;
+
+struct mt_svc {
+ struct mt_svc *prev;
+ struct mt_svc *next;
+ struct sbus_connection *conn;
+ struct svc_spy *conn_spy;
+
+ struct mt_ctx *mt_ctx;
+
+ char *provider;
+ char *command;
+ char *name;
+ char *identity;
+ pid_t pid;
+
+ int ping_time;
+
+ bool svc_started;
+
+ int restarts;
+ time_t last_restart;
+ time_t last_ping;
+ int failed_pongs;
+
+ int debug_level;
+
+ struct tevent_timer *ping_ev;
+};
+
+struct config_file_callback {
+ int wd;
+ int retries;
+ monitor_reconf_fn fn;
+ char *filename;
+ time_t modified;
+ struct config_file_callback *next;
+ struct config_file_callback *prev;
+};
+
+struct config_file_ctx {
+ TALLOC_CTX *parent_ctx;
+ struct tevent_timer *timer;
+ bool needs_update;
+ struct mt_ctx *mt_ctx;
+ struct config_file_callback *callbacks;
+};
+
+struct mt_ctx {
+ struct tevent_context *ev;
+ struct confdb_ctx *cdb;
+ TALLOC_CTX *domain_ctx; /* Memory context for domain list */
+ struct sss_domain_info *domains;
+ TALLOC_CTX *service_ctx; /* Memory context for services */
+ char **services;
+ struct mt_svc *svc_list;
+ struct sbus_connection *sbus_srv;
+ struct config_file_ctx *file_ctx;
+ int inotify_fd;
+ int service_id_timeout;
+ bool check_children;
+ bool services_started;
+};
+
+static int start_service(struct mt_svc *mt_svc);
+
+static int monitor_service_init(struct sbus_connection *conn, void *data);
+
+static int service_send_ping(struct mt_svc *svc);
+static void ping_check(DBusPendingCall *pending, void *data);
+
+static int service_check_alive(struct mt_svc *svc);
+
+static void set_tasks_checker(struct mt_svc *srv);
+static void set_global_checker(struct mt_ctx *ctx);
+static int monitor_kill_service (struct mt_svc *svc);
+
+static int get_service_config(struct mt_ctx *ctx, const char *name,
+ struct mt_svc **svc_cfg);
+static int get_provider_config(struct mt_ctx *ctx, const char *name,
+ struct mt_svc **svc_cfg);
+static int add_new_service(struct mt_ctx *ctx, const char *name);
+static int add_new_provider(struct mt_ctx *ctx, const char *name);
+
+static int mark_service_as_started(struct mt_svc *svc);
+
+#if 0
+static int monitor_signal_reconf(struct config_file_ctx *file_ctx,
+ const char *filename);
+#endif
+
+static int update_monitor_config(struct mt_ctx *ctx);
+static int monitor_cleanup(void);
+
+/* dbus_get_monitor_version
+ * Return the monitor version over D-BUS */
+static int get_monitor_version(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ dbus_uint16_t version = MONITOR_VERSION;
+ DBusMessage *reply;
+ dbus_bool_t ret;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+ ret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ dbus_message_unref(reply);
+ return EIO;
+ }
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ return EOK;
+}
+
+struct mon_init_conn {
+ struct mt_ctx *ctx;
+ struct sbus_connection *conn;
+ struct tevent_timer *timeout;
+};
+
+static int add_svc_conn_spy(struct mt_svc *svc);
+
+/* registers a new client.
+ * if operation is successful also sends back the Monitor version */
+static int client_registration(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ dbus_uint16_t version = MONITOR_VERSION;
+ struct mon_init_conn *mini;
+ struct mt_svc *svc;
+ void *data;
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_uint16_t svc_ver;
+ char *svc_name;
+ dbus_bool_t dbret;
+ int ret;
+
+ data = sbus_conn_get_private_data(conn);
+ mini = talloc_get_type(data, struct mon_init_conn);
+ if (!mini) {
+ DEBUG(0, ("Connection holds no valid init data\n"));
+ return EINVAL;
+ }
+
+ /* First thing, cancel the timeout */
+ talloc_zfree(mini->timeout);
+
+ dbus_error_init(&dbus_error);
+
+ dbret = dbus_message_get_args(message, &dbus_error,
+ DBUS_TYPE_STRING, &svc_name,
+ DBUS_TYPE_UINT16, &svc_ver,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to parse message, killing connection\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ sbus_disconnect(conn);
+ /* FIXME: should we just talloc_zfree(conn) ? */
+ goto done;
+ }
+
+ DEBUG(4, ("Received ID registration: (%s,%d)\n", svc_name, svc_ver));
+
+ /* search this service in the list */
+ svc = mini->ctx->svc_list;
+ while (svc) {
+ ret = strcasecmp(svc->identity, svc_name);
+ if (ret == 0) {
+ break;
+ }
+ svc = svc->next;
+ }
+ if (!svc) {
+ DEBUG(0, ("Unable to find peer [%s] in list of services,"
+ " killing connection!\n", svc_name));
+ sbus_disconnect(conn);
+ /* FIXME: should we just talloc_zfree(conn) ? */
+ goto done;
+ }
+
+ /* Fill in svc structure with connection data */
+ svc->conn = mini->conn;
+
+ ret = mark_service_as_started(svc);
+ if (ret) {
+ DEBUG(1, ("Failed to mark service [%s]!\n", svc_name));
+ goto done;
+ }
+
+ /* reply that all is ok */
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ dbus_message_unref(reply);
+ return EIO;
+ }
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+done:
+ /* init complete, get rid of temp init context */
+ talloc_zfree(mini);
+
+ return EOK;
+}
+
+struct svc_spy {
+ struct mt_svc *svc;
+};
+
+static int svc_destructor(void *mem)
+{
+ struct mt_svc *svc = talloc_get_type(mem, struct mt_svc);
+ if (!svc) {
+ /* ?!?!? */
+ return 0;
+ }
+
+ /* always try to delist service */
+ DLIST_REMOVE(svc->mt_ctx->svc_list, svc);
+
+ /* svc is beeing freed, neutralize the spy */
+ if (svc->conn_spy) {
+ talloc_set_destructor((TALLOC_CTX *)svc->conn_spy, NULL);
+ talloc_zfree(svc->conn_spy);
+ }
+ return 0;
+}
+
+static int svc_spy_destructor(void *mem)
+{
+ struct svc_spy *spy = talloc_get_type(mem, struct svc_spy);
+ if (!spy) {
+ /* ?!?!? */
+ return 0;
+ }
+
+ /* svc->conn has been freed, NULL the pointer in svc */
+ spy->svc->conn = NULL;
+ return 0;
+}
+
+static int add_svc_conn_spy(struct mt_svc *svc)
+{
+ struct svc_spy *spy;
+
+ spy = talloc(svc->conn, struct svc_spy);
+ if (!spy) return ENOMEM;
+
+ spy->svc = svc;
+ talloc_set_destructor((TALLOC_CTX *)spy, svc_spy_destructor);
+ svc->conn_spy = spy;
+
+ return EOK;
+}
+
+static int mark_service_as_started(struct mt_svc *svc)
+{
+ struct mt_ctx *ctx = svc->mt_ctx;
+ struct mt_svc *iter;
+ int ret;
+ int i;
+
+ DEBUG(5, ("Marking %s as started.\n", svc->name));
+ svc->svc_started = true;
+
+ /* we need to attach a spy to the connection structure so that if some code
+ * frees it we can zero it out in the service structure. Otherwise we may
+ * try to access or even free, freed memory. */
+ ret = add_svc_conn_spy(svc);
+ if (ret) {
+ DEBUG(0, ("Failed to attch spy\n"));
+ goto done;
+ }
+
+ if (!ctx->services_started) {
+
+ /* check if all providers are up */
+ for (iter = ctx->svc_list; iter; iter = iter->next) {
+ if (iter->provider && !iter->svc_started) {
+ DEBUG(5, ("Still waiting on %s provider.", iter->name));
+ break;
+ }
+ }
+
+ if (iter) {
+ /* there are still unstarted providers */
+ goto done;
+ }
+
+ ctx->services_started = true;
+
+ DEBUG(4, ("Now starting services!\n"));
+ /* then start all services */
+ for (i = 0; ctx->services[i]; i++) {
+ add_new_service(ctx, ctx->services[i]);
+ }
+ }
+
+done:
+ return ret;
+}
+
+static void services_startup_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct mt_ctx *ctx = talloc_get_type(ptr, struct mt_ctx);
+ int i;
+
+ DEBUG(6, ("Handling timeout\n"));
+
+ if (!ctx->services_started) {
+
+ DEBUG(1, ("Providers did not start in time, "
+ "forcing services startup!\n"));
+
+ ctx->services_started = true;
+
+ DEBUG(4, ("Now starting services!\n"));
+ /* then start all services */
+ for (i = 0; ctx->services[i]; i++) {
+ add_new_service(ctx, ctx->services[i]);
+ }
+ }
+}
+
+static int add_services_startup_timeout(struct mt_ctx *ctx)
+{
+ struct tevent_timer *to;
+ struct timeval tv;
+
+ /* 5 seconds should be plenty */
+ tv = tevent_timeval_current_ofs(5, 0);
+ to = tevent_add_timer(ctx->ev, ctx, tv, services_startup_timeout, ctx);
+ if (!to) {
+ DEBUG(0,("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+struct sbus_method monitor_methods[] = {
+ { MON_SRV_METHOD_VERSION, get_monitor_version },
+ { MON_SRV_METHOD_REGISTER, client_registration },
+ { NULL, NULL }
+};
+
+struct sbus_interface monitor_server_interface = {
+ MON_SRV_INTERFACE,
+ MON_SRV_PATH,
+ SBUS_DEFAULT_VTABLE,
+ monitor_methods,
+ NULL
+};
+
+/* monitor_dbus_init
+ * Set up the monitor service as a D-BUS Server */
+static int monitor_dbus_init(struct mt_ctx *ctx)
+{
+ char *monitor_address;
+ int ret;
+
+ ret = monitor_get_sbus_address(ctx, &monitor_address);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = sbus_new_server(ctx, ctx->ev,
+ monitor_address, &monitor_server_interface,
+ &ctx->sbus_srv, monitor_service_init, ctx);
+
+ talloc_free(monitor_address);
+
+ return ret;
+}
+
+static void svc_try_restart(struct mt_svc *svc, time_t now)
+{
+ int ret;
+
+ DLIST_REMOVE(svc->mt_ctx->svc_list, svc);
+ if (svc->last_restart != 0) {
+ if ((now - svc->last_restart) > 30) { /* TODO: get val from config */
+ /* it was long ago reset restart threshold */
+ svc->restarts = 0;
+ }
+ }
+
+ /* restart the process */
+ if (svc->restarts > 3) { /* TODO: get val from config */
+ DEBUG(0, ("Process [%s], definitely stopped!\n", svc->name));
+ talloc_free(svc);
+ return;
+ }
+
+ /* Shut down the current ping timer so it will restart
+ * cleanly in start_service()
+ */
+ talloc_free(svc->ping_ev);
+
+ ret = start_service(svc);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to restart service '%s'\n", svc->name));
+ talloc_free(svc);
+ return;
+ }
+
+ svc->restarts++;
+ svc->last_restart = now;
+ return;
+}
+
+static void tasks_check_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct mt_svc *svc = talloc_get_type(ptr, struct mt_svc);
+ time_t now = time(NULL);
+ bool process_alive = true;
+ int ret;
+
+ ret = service_check_alive(svc);
+ switch (ret) {
+ case EOK:
+ /* all fine */
+ break;
+
+ case ECHILD:
+ DEBUG(1,("Process (%s) is stopped!\n", svc->name));
+ process_alive = false;
+ break;
+
+ default:
+ /* TODO: should we tear down it ? */
+ DEBUG(1,("Checking for service %s(%d) failed!!\n",
+ svc->name, svc->pid));
+ break;
+ }
+
+ if (process_alive) {
+ ret = service_send_ping(svc);
+ switch (ret) {
+ case EOK:
+ /* all fine */
+ break;
+
+ case ENXIO:
+ DEBUG(1,("Child (%s) not responding! (yet)\n", svc->name));
+ break;
+
+ default:
+ /* TODO: should we tear it down ? */
+ DEBUG(1,("Sending a message to service (%s) failed!!\n", svc->name));
+ break;
+ }
+
+ if (svc->last_ping != 0) {
+ if ((now - svc->last_ping) > (svc->ping_time)) {
+ svc->failed_pongs++;
+ } else {
+ svc->failed_pongs = 0;
+ }
+ if (svc->failed_pongs > 3) {
+ /* too long since we last heard of this process */
+ DEBUG(1, ("Killing service [%s], not responding to pings!\n",
+ svc->name));
+ monitor_kill_service(svc);
+ process_alive = false;
+ }
+ }
+
+ svc->last_ping = now;
+ }
+
+ if (!process_alive) {
+ svc_try_restart(svc, now);
+ return;
+ }
+
+ /* all fine, set up the task checker again */
+ set_tasks_checker(svc);
+}
+
+static void set_tasks_checker(struct mt_svc *svc)
+{
+ struct tevent_timer *te = NULL;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += svc->ping_time;
+ tv.tv_usec = 0;
+ te = tevent_add_timer(svc->mt_ctx->ev, svc, tv, tasks_check_handler, svc);
+ if (te == NULL) {
+ DEBUG(0, ("failed to add event, monitor offline for [%s]!\n",
+ svc->name));
+ /* FIXME: shutdown ? */
+ }
+ svc->ping_ev = te;
+}
+
+static void global_checks_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct mt_ctx *ctx = talloc_get_type(ptr, struct mt_ctx);
+ struct mt_svc *svc;
+ int status;
+ pid_t pid;
+
+ if (!ctx->check_children) {
+ goto done;
+ }
+
+ errno = 0;
+ pid = waitpid(0, &status, WNOHANG);
+ if (pid == 0) {
+ goto done;
+ }
+
+ if (pid == -1) {
+ DEBUG(0, ("waitpid returned -1 (errno:%d[%s])\n",
+ errno, strerror(errno)));
+ goto done;
+ }
+
+ /* let's see if it is a known service, and try to restart it */
+ for (svc = ctx->svc_list; svc; svc = svc->next) {
+ if (svc->pid == pid) {
+ time_t now = time(NULL);
+ DEBUG(1, ("Service [%s] did exit\n", svc->name));
+ svc_try_restart(svc, now);
+ goto done;
+ }
+ }
+ if (svc == NULL) {
+ DEBUG(0, ("Unknown child (%d) did exit\n", pid));
+ }
+
+done:
+ set_global_checker(ctx);
+}
+
+static void set_global_checker(struct mt_ctx *ctx)
+{
+ struct tevent_timer *te = NULL;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += 1; /* once a second */
+ tv.tv_usec = 0;
+ te = tevent_add_timer(ctx->ev, ctx, tv, global_checks_handler, ctx);
+ if (te == NULL) {
+ DEBUG(0, ("failed to add global checker event! PANIC TIME!\n"));
+ /* FIXME: is this right ? shoulkd we try to clean up first ?*/
+ exit(-1);
+ }
+}
+
+static int monitor_kill_service (struct mt_svc *svc)
+{
+ int ret;
+ ret = kill(svc->pid, SIGTERM);
+ if (ret != EOK) {
+ DEBUG(0,("Sending signal to child (%s:%d) failed! "
+ "Ignore and pretend child is dead.\n",
+ svc->name, svc->pid));
+ }
+
+ return ret;
+}
+
+static void shutdown_reply(DBusPendingCall *pending, void *data)
+{
+ DBusMessage *reply;
+ int type;
+ struct mt_svc *svc = talloc_get_type(data, struct mt_svc);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("A reply callback was called but no reply was received"
+ " and no timeout occurred\n"));
+
+ /* Destroy this connection */
+ monitor_kill_service(svc);
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch(type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ /* Ok, we received a confirmation of shutdown */
+ break;
+
+ default:
+ /* Something went wrong on the client side
+ * Time to forcibly kill the service
+ */
+ DEBUG(0, ("Received an error shutting down service.\n"));
+ monitor_kill_service(svc);
+ }
+
+ /* No matter what happened here, we need to free the service */
+ talloc_free(svc);
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+/* monitor_shutdown_service
+ * Orders a monitored service to shut down cleanly
+ * This function will free the memory for svc once it
+ * completes.
+ */
+static int monitor_shutdown_service(struct mt_svc *svc)
+{
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ DBusPendingCall *pending_reply;
+ dbus_bool_t dbret;
+
+ /* Stop the service checker */
+
+ dbus_conn = sbus_get_connection(svc->conn);
+
+ /* Construct a shutdown message */
+ msg = dbus_message_new_method_call(NULL,
+ MONITOR_PATH,
+ MONITOR_INTERFACE,
+ MON_CLI_METHOD_SHUTDOWN);
+ if (!msg) {
+ DEBUG(0,("Out of memory?!\n"));
+ monitor_kill_service(svc);
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ svc->mt_ctx->service_id_timeout);
+ if (!dbret || pending_reply == NULL) {
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ monitor_kill_service(svc);
+ talloc_free(svc);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, shutdown_reply, svc, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
+static void reload_reply(DBusPendingCall *pending, void *data)
+{
+ DBusMessage *reply;
+ struct mt_svc *svc = talloc_get_type(data, struct mt_svc);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("A reply callback was called but no reply was received"
+ " and no timeout occurred\n"));
+
+ /* Destroy this connection */
+ sbus_disconnect(svc->conn);
+ goto done;
+ }
+
+ /* TODO: Handle cases where the call has timed out or returned
+ * with an error.
+ */
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+#if 0
+This function should be re-enabled once live configuration updates
+are working properly.
+
+static int monitor_signal_reconf(struct config_file_ctx *file_ctx,
+ const char *filename)
+{
+ int ret;
+ DEBUG(1, ("Configuration has changed. Reloading.\n"));
+
+ /* Update the confdb configuration */
+ ret = confdb_init_db(filename, file_ctx->mt_ctx->cdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not reload configuration!"));
+ kill(getpid(), SIGTERM);
+ return ret;
+ }
+
+ /* Update the monitor's configuration and signal children */
+ return update_monitor_config(file_ctx->mt_ctx);
+}
+#endif
+
+static int service_signal_dns_reload(struct mt_svc *svc);
+static int monitor_update_resolv(struct config_file_ctx *file_ctx,
+ const char *filename)
+{
+ int ret;
+ struct mt_svc *cur_svc;
+ DEBUG(2, ("Resolv.conf has been updated. Reloading.\n"));
+
+ ret = res_init();
+ if(ret != 0) {
+ return EIO;
+ }
+
+ /* Signal all services to reload their DNS configuration */
+ for(cur_svc = file_ctx->mt_ctx->svc_list; cur_svc; cur_svc = cur_svc->next) {
+ service_signal_dns_reload(cur_svc);
+ }
+ return EOK;
+}
+
+static int service_signal(struct mt_svc *svc, const char *svc_signal)
+{
+ DBusMessage *msg;
+ dbus_bool_t dbret;
+ DBusConnection *dbus_conn;
+ DBusPendingCall *pending_reply;
+
+ if (svc->provider && strcasecmp(svc->provider, "local") == 0) {
+ /* The local provider requires no signaling */
+ return EOK;
+ }
+
+ if (!svc->conn) {
+ /* Avoid a race condition where we are trying to
+ * order a service to reload that hasn't started
+ * yet.
+ */
+ DEBUG(1,("Could not signal service [%s].\n", svc->name));
+ return EIO;
+ }
+
+ dbus_conn = sbus_get_connection(svc->conn);
+ msg = dbus_message_new_method_call(NULL,
+ MONITOR_PATH,
+ MONITOR_INTERFACE,
+ svc_signal);
+ if (!msg) {
+ DEBUG(0,("Out of memory?!\n"));
+ monitor_kill_service(svc);
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ svc->mt_ctx->service_id_timeout);
+ if (!dbret || pending_reply == NULL) {
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ monitor_kill_service(svc);
+ talloc_free(svc);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, reload_reply, svc, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
+static int service_signal_reload(struct mt_svc *svc)
+{
+ return service_signal(svc, MON_CLI_METHOD_RELOAD);
+}
+static int service_signal_dns_reload(struct mt_svc *svc)
+{
+ return service_signal(svc, MON_CLI_METHOD_RES_INIT);
+}
+static int service_signal_offline(struct mt_svc *svc)
+{
+ return service_signal(svc, MON_CLI_METHOD_OFFLINE);
+}
+
+static int check_domain_ranges(struct sss_domain_info *domains)
+{
+ struct sss_domain_info *dom = domains, *other = NULL;
+ uint32_t id_min, id_max;
+
+ while (dom) {
+ other = dom->next;
+ if (dom->id_max && dom->id_min > dom->id_max) {
+ DEBUG(1, ("Domain '%s' does not have a valid ID range\n",
+ dom->name));
+ return EINVAL;
+ }
+
+ while (other) {
+ id_min = MAX(dom->id_min, other->id_min);
+ id_max = MIN((dom->id_max ? dom->id_max : UINT32_MAX),
+ (other->id_max ? other->id_max : UINT32_MAX));
+ if (id_min <= id_max) {
+ DEBUG(1, ("Domains '%s' and '%s' overlap in range %u - %u\n",
+ dom->name, other->name, id_min, id_max));
+ }
+ other = other->next;
+ }
+ dom = dom->next;
+ }
+
+ return EOK;
+}
+
+static int check_local_domain_unique(struct sss_domain_info *domains)
+{
+ uint8_t count = 0;
+
+ struct sss_domain_info *dom = domains;
+
+ while (dom) {
+ if (strcasecmp(dom->provider, "local") == 0) {
+ count++;
+ }
+
+ if (count > 1) {
+ break;
+ }
+
+ dom = dom->next;
+ }
+
+ if (count > 1) {
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+static char *check_services(char **services)
+{
+ const char *known_services[] = { "nss", "pam", NULL };
+ int i;
+ int ii;
+
+ /* Check if services we are about to start are in the list if known */
+ for (i = 0; services[i]; i++) {
+ for (ii=0; known_services[ii]; ii++) {
+ if (strcasecmp(services[i], known_services[ii]) == 0) {
+ break;
+ }
+ }
+
+ if (known_services[ii] == NULL) {
+ return services[i];
+ }
+ }
+
+ return NULL;
+}
+
+int get_monitor_config(struct mt_ctx *ctx)
+{
+ int ret;
+ int timeout_seconds;
+ char *badsrv = NULL;
+
+ ret = confdb_get_int(ctx->cdb, ctx,
+ CONFDB_MONITOR_CONF_ENTRY,
+ CONFDB_MONITOR_SBUS_TIMEOUT,
+ 10, &timeout_seconds);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ctx->service_id_timeout = timeout_seconds * 1000; /* service_id_timeout is in ms */
+
+ ctx->service_ctx = talloc_new(ctx);
+ if(!ctx->service_ctx) {
+ return ENOMEM;
+ }
+ ret = confdb_get_string_as_list(ctx->cdb, ctx->service_ctx,
+ CONFDB_MONITOR_CONF_ENTRY,
+ CONFDB_MONITOR_ACTIVE_SERVICES,
+ &ctx->services);
+ if (ret != EOK) {
+ DEBUG(0, ("No services configured!\n"));
+ return EINVAL;
+ }
+
+ badsrv = check_services(ctx->services);
+ if (badsrv != NULL) {
+ DEBUG(0, ("Invalid service %s\n", badsrv));
+ return EINVAL;
+ }
+
+ ctx->domain_ctx = talloc_new(ctx);
+ if(!ctx->domain_ctx) {
+ return ENOMEM;
+ }
+ ret = confdb_get_domains(ctx->cdb, &ctx->domains);
+ if (ret != EOK) {
+ DEBUG(0, ("No domains configured.\n"));
+ return ret;
+ }
+
+ ret = check_local_domain_unique(ctx->domains);
+ if (ret != EOK) {
+ DEBUG(0, ("More than one local domain configured.\n"));
+ return ret;
+ }
+
+ /* Check UID/GID overlaps */
+ ret = check_domain_ranges(ctx->domains);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int get_service_config(struct mt_ctx *ctx, const char *name,
+ struct mt_svc **svc_cfg)
+{
+ int ret;
+ char *path;
+ struct mt_svc *svc;
+
+ *svc_cfg = NULL;
+
+ svc = talloc_zero(ctx, struct mt_svc);
+ if (!svc) {
+ return ENOMEM;
+ }
+ svc->mt_ctx = ctx;
+
+ svc->name = talloc_strdup(svc, name);
+ if (!svc->name) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ svc->identity = talloc_strdup(svc, name);
+ if (!svc->identity) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ path = talloc_asprintf(svc, CONFDB_SERVICE_PATH_TMPL, svc->name);
+ if (!path) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(ctx->cdb, svc, path,
+ CONFDB_SERVICE_COMMAND,
+ NULL, &svc->command);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to start service '%s'\n", svc->name));
+ talloc_free(svc);
+ return ret;
+ }
+
+ if (!svc->command) {
+ svc->command = talloc_asprintf(svc, "%s/sssd_%s -d %d%s%s",
+ SSSD_LIBEXEC_PATH,
+ svc->name, debug_level,
+ (debug_timestamps?
+ "": " --debug-timestamps=0"),
+ (debug_to_file ?
+ " --debug-to-files":""));
+ if (!svc->command) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+ }
+
+ ret = confdb_get_int(ctx->cdb, svc, path,
+ CONFDB_SERVICE_TIMEOUT,
+ MONITOR_DEF_PING_TIME, &svc->ping_time);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to start service '%s'\n", svc->name));
+ talloc_free(svc);
+ return ret;
+ }
+
+ /* 'timeout = 0' should be translated to the default */
+ if (svc->ping_time == 0) {
+ svc->ping_time = MONITOR_DEF_PING_TIME;
+ }
+
+ *svc_cfg = svc;
+ talloc_free(path);
+
+ return EOK;
+}
+
+static int add_new_service(struct mt_ctx *ctx, const char *name)
+{
+ int ret;
+ struct mt_svc *svc;
+
+ ret = get_service_config(ctx, name, &svc);
+
+ ret = start_service(svc);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to start service '%s'\n", svc->name));
+ talloc_free(svc);
+ }
+
+ return ret;
+}
+
+static int get_provider_config(struct mt_ctx *ctx, const char *name,
+ struct mt_svc **svc_cfg)
+{
+ int ret;
+ char *path;
+ struct mt_svc *svc;
+
+ *svc_cfg = NULL;
+
+ svc = talloc_zero(ctx, struct mt_svc);
+ if (!svc) {
+ return ENOMEM;
+ }
+ svc->mt_ctx = ctx;
+
+ talloc_set_destructor((TALLOC_CTX *)svc, svc_destructor);
+
+ svc->name = talloc_strdup(svc, name);
+ if (!svc->name) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ svc->identity = talloc_asprintf(svc, "%%BE_%s", svc->name);
+ if (!svc->identity) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ path = talloc_asprintf(svc, CONFDB_DOMAIN_PATH_TMPL, name);
+ if (!path) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(ctx->cdb, svc, path,
+ CONFDB_DOMAIN_ID_PROVIDER,
+ NULL, &svc->provider);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to find ID provider from [%s] configuration\n", name));
+ talloc_free(svc);
+ return ret;
+ }
+
+ ret = confdb_get_string(ctx->cdb, svc, path,
+ CONFDB_DOMAIN_COMMAND,
+ NULL, &svc->command);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to find command from [%s] configuration\n", name));
+ talloc_free(svc);
+ return ret;
+ }
+
+ ret = confdb_get_int(ctx->cdb, svc, path,
+ CONFDB_DOMAIN_TIMEOUT,
+ MONITOR_DEF_PING_TIME, &svc->ping_time);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to start service '%s'\n", svc->name));
+ talloc_free(svc);
+ return ret;
+ }
+
+ /* 'timeout = 0' should be translated to the default */
+ if (svc->ping_time == 0) {
+ svc->ping_time = MONITOR_DEF_PING_TIME;
+ }
+
+ talloc_free(path);
+
+ /* if no provider is present do not run the domain */
+ if (!svc->provider) {
+ talloc_free(svc);
+ return EIO;
+ }
+
+ /* if there are no custom commands, build a default one */
+ if (!svc->command) {
+ svc->command = talloc_asprintf(svc,
+ "%s/sssd_be -d %d%s%s --domain %s",
+ SSSD_LIBEXEC_PATH, debug_level,
+ (debug_timestamps?"": " --debug-timestamps=0"),
+ (debug_to_file?" --debug-to-files":""),
+ svc->name);
+ if (!svc->command) {
+ talloc_free(svc);
+ return ENOMEM;
+ }
+ }
+
+ *svc_cfg = svc;
+ return EOK;
+}
+
+static int add_new_provider(struct mt_ctx *ctx, const char *name)
+{
+ int ret;
+ struct mt_svc *svc;
+
+ ret = get_provider_config(ctx, name, &svc);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not get provider configuration for [%s]\n",
+ name));
+ return ret;
+ }
+
+ if (strcasecmp(svc->provider, "local") == 0) {
+ /* The LOCAL provider requires no back-end currently
+ * We'll add it to the service list, but we don't need
+ * to poll it.
+ */
+ svc->svc_started = true;
+ DLIST_ADD(ctx->svc_list, svc);
+ return ENOENT;
+ }
+
+ ret = start_service(svc);
+ if (ret != EOK) {
+ DEBUG(0,("Failed to start service '%s'\n", svc->name));
+ talloc_free(svc);
+ }
+
+ return ret;
+}
+
+static void remove_service(struct mt_ctx *ctx, const char *name)
+{
+ int ret;
+ struct mt_svc *cur_svc;
+
+ /* Locate the service object in the list */
+ cur_svc = ctx->svc_list;
+ while (cur_svc != NULL) {
+ if (strcasecmp(name, cur_svc->name) == 0)
+ break;
+ cur_svc = cur_svc->next;
+ }
+ if (cur_svc != NULL) {
+ /* Remove the service from the list */
+ DLIST_REMOVE(ctx->svc_list, cur_svc);
+
+ /* Shut it down */
+ ret = monitor_shutdown_service(cur_svc);
+ if (ret != EOK) {
+ DEBUG(0, ("Unable to shut down service [%s]!",
+ name));
+ /* TODO: Handle this better */
+ }
+ }
+}
+
+static int update_monitor_config(struct mt_ctx *ctx)
+{
+ int ret, i, j;
+ struct mt_svc *cur_svc;
+ struct mt_svc *new_svc;
+ struct sss_domain_info *dom, *new_dom;
+ struct mt_ctx *new_config;
+
+ new_config = talloc_zero(NULL, struct mt_ctx);
+ if(!new_config) {
+ return ENOMEM;
+ }
+
+ new_config->ev = ctx->ev;
+ new_config->cdb = ctx->cdb;
+ ret = get_monitor_config(new_config);
+
+ ctx->service_id_timeout = new_config->service_id_timeout;
+
+ /* Compare the old and new active services */
+ /* Have any services been shut down? */
+ for (i = 0; ctx->services[i]; i++) {
+ /* Search for this service in the new config */
+ for (j = 0; new_config->services[j]; j++) {
+ if (strcasecmp(ctx->services[i], new_config->services[j]) == 0)
+ break;
+ }
+ if (new_config->services[j] == NULL) {
+ /* This service is no longer configured.
+ * Shut it down.
+ */
+ remove_service(ctx, ctx->services[i]);
+ }
+ }
+
+ /* Have any services been added or changed? */
+ for (i = 0; new_config->services[i]; i++) {
+ /* Search for this service in the old config */
+ for (j = 0; ctx->services[j]; j++) {
+ if (strcasecmp(new_config->services[i], ctx->services[j]) == 0)
+ break;
+ }
+
+ if (ctx->services[j] == NULL) {
+ /* New service added */
+ add_new_service(ctx, new_config->services[i]);
+ }
+ else {
+ /* Service already enabled, check for changes */
+ /* Locate the service object in the list */
+ cur_svc = ctx->svc_list;
+ for (cur_svc = ctx->svc_list; cur_svc; cur_svc = cur_svc->next) {
+ if (strcasecmp(ctx->services[i], cur_svc->name) == 0)
+ break;
+ }
+ if (cur_svc == NULL) {
+ DEBUG(0, ("Service entry missing data\n"));
+ /* This shouldn't be possible, but if it happens
+ * we'll throw an error
+ */
+ talloc_free(new_config);
+ return EIO;
+ }
+
+ /* Read in the new configuration and compare it with the
+ * old one.
+ */
+ ret = get_service_config(ctx, new_config->services[i], &new_svc);
+ if (ret != EOK) {
+ DEBUG(0, ("Unable to determine if service has changed.\n"));
+ DEBUG(0, ("Disabling service [%s].\n",
+ new_config->services[i]));
+ /* Not much we can do here, no way to know whether the
+ * current configuration is safe, and restarting the
+ * service won't work because the new startup requires
+ * this function to work. The only safe thing to do
+ * is stop the service.
+ */
+ remove_service(ctx, new_config->services[i]);
+ continue;
+ }
+
+ if (strcmp(cur_svc->command, new_svc->command) != 0) {
+ /* The executable path has changed. We need to
+ * restart the binary completely. If we send a
+ * shutdown command, the monitor will automatically
+ * reload the process with the new command.
+ */
+ talloc_free(cur_svc->command);
+ talloc_steal(cur_svc, new_svc->command);
+ cur_svc->command = new_svc->command;
+
+ /* TODO: be more graceful about this */
+ monitor_kill_service(cur_svc);
+ }
+
+ cur_svc->ping_time = new_svc->ping_time;
+
+ talloc_free(new_svc);
+ }
+ }
+
+ /* Replace the old service list with the new one */
+ talloc_free(ctx->service_ctx);
+ ctx->service_ctx = talloc_steal(ctx, new_config->service_ctx);
+ ctx->services = new_config->services;
+
+ /* Compare data providers */
+ /* Have any providers been disabled? */
+ for (dom = ctx->domains; dom; dom = dom->next) {
+ for (new_dom = new_config->domains; new_dom; new_dom = new_dom->next) {
+ if (strcasecmp(dom->name, new_dom->name) == 0) break;
+ }
+ if (new_dom == NULL) {
+ /* This provider is no longer configured
+ * Shut it down
+ */
+ remove_service(ctx, dom->name);
+ }
+ }
+
+ /* Have we added or changed any providers? */
+ for (new_dom = new_config->domains; new_dom; new_dom = new_dom->next) {
+ /* Search for this service in the old config */
+ for (dom = ctx->domains; dom; dom = dom->next) {
+ if (strcasecmp(dom->name, new_dom->name) == 0) break;
+ }
+
+ if (dom == NULL) {
+ /* New provider added */
+ add_new_provider(ctx, new_dom->name);
+ }
+ else {
+ /* Provider is already in the list.
+ * Check for changes.
+ */
+ /* Locate the service object in the list */
+ cur_svc = ctx->svc_list;
+ while (cur_svc != NULL) {
+ if (strcasecmp(new_dom->name, cur_svc->name) == 0)
+ break;
+ cur_svc = cur_svc->next;
+ }
+ if (cur_svc == NULL) {
+ DEBUG(0, ("Service entry missing data for [%s]\n", new_dom->name));
+ /* This shouldn't be possible
+ */
+ talloc_free(new_config);
+ return EIO;
+ }
+
+ /* Read in the new configuration and compare it with
+ * the old one.
+ */
+ ret = get_provider_config(ctx, new_dom->name, &new_svc);
+ if (ret != EOK) {
+ DEBUG(0, ("Unable to determine if service has changed.\n"));
+ DEBUG(0, ("Disabling service [%s].\n",
+ new_config->services[i]));
+ /* Not much we can do here, no way to know whether the
+ * current configuration is safe, and restarting the
+ * service won't work because the new startup requires
+ * this function to work. The only safe thing to do
+ * is stop the service.
+ */
+ remove_service(ctx, dom->name);
+ continue;
+ }
+
+ if ((strcmp(cur_svc->command, new_svc->command) != 0) ||
+ (strcmp(cur_svc->provider, new_svc->provider) != 0)) {
+ /* The executable path or the provider has changed.
+ * We need to restart the binary completely. If we
+ * send a shutdown command, the monitor will
+ * automatically reload the process with the new
+ * command.
+ */
+ talloc_free(cur_svc->command);
+ talloc_steal(cur_svc, new_svc->command);
+ cur_svc->command = new_svc->command;
+
+ /* TODO: be more graceful about this */
+ monitor_kill_service(cur_svc);
+ }
+
+ cur_svc->ping_time = new_svc->ping_time;
+ }
+
+ }
+
+ /* Replace the old domain list with the new one */
+ talloc_free(ctx->domain_ctx);
+ ctx->domain_ctx = talloc_steal(ctx, new_config->domain_ctx);
+ ctx->domains = new_config->domains;
+
+ /* Signal all services to reload their configuration */
+ for(cur_svc = ctx->svc_list; cur_svc; cur_svc = cur_svc->next) {
+ service_signal_reload(cur_svc);
+ }
+
+ talloc_free(new_config);
+ return EOK;
+}
+
+static void monitor_hup(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct mt_ctx *ctx = talloc_get_type(private_data, struct mt_ctx);
+
+ DEBUG(1, ("Received SIGHUP. Rereading configuration.\n"));
+ update_monitor_config(ctx);
+}
+
+static int monitor_cleanup(void)
+{
+ char *file;
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ file = talloc_asprintf(tmp_ctx, "%s/%s.pid", PID_PATH, "sssd");
+ if (file == NULL) {
+ return ENOMEM;
+ }
+
+ errno = 0;
+ ret = unlink(file);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(0, ("Error removing pidfile! (%d [%s])\n",
+ ret, strerror(ret)));
+ talloc_free(file);
+ return errno;
+ }
+
+ talloc_free(file);
+ return EOK;
+}
+
+static void monitor_quit(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ DEBUG(8, ("Received shutdown command\n"));
+ monitor_cleanup();
+
+#if HAVE_GETPGRP
+ if (getpgrp() == getpid()) {
+ DEBUG(0,("%s: killing children\n", strsignal(signum)));
+ kill(-getpgrp(), SIGTERM);
+ }
+#endif
+
+ exit(0);
+}
+
+static void signal_offline(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct mt_ctx *monitor;
+ struct mt_svc *cur_svc;
+
+ monitor = talloc_get_type(private_data, struct mt_ctx);
+
+ DEBUG(8, ("Signaling providers to go offline immediately.\n"));
+
+ /* Signal all providers to immediately go offline */
+ for(cur_svc = monitor->svc_list; cur_svc; cur_svc = cur_svc->next) {
+ /* Don't signal services, only providers */
+ if (cur_svc->provider) {
+ service_signal_offline(cur_svc);
+ }
+ }
+}
+
+int read_config_file(const char *config_file)
+{
+ int ret;
+ struct collection_item *sssd_config = NULL;
+ struct collection_item *error_list = NULL;
+
+ /* Read the configuration into a collection */
+ ret = config_from_file("sssd", config_file, &sssd_config,
+ INI_STOP_ON_ANY, &error_list);
+ if (ret != EOK) {
+ DEBUG(0, ("Parse error reading configuration file [%s]\n",
+ config_file));
+ print_file_parsing_errors(stderr, error_list);
+ }
+
+ free_ini_config_errors(error_list);
+ free_ini_config(sssd_config);
+ return ret;
+}
+
+static errno_t load_configuration(TALLOC_CTX *mem_ctx,
+ const char *config_file,
+ struct mt_ctx **monitor)
+{
+ errno_t ret;
+ struct mt_ctx *ctx;
+ char *cdb_file = NULL;
+
+ ctx = talloc_zero(mem_ctx, struct mt_ctx);
+ if(!ctx) {
+ return ENOMEM;
+ }
+
+ cdb_file = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (cdb_file == NULL) {
+ DEBUG(0,("Out of memory, aborting!\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = confdb_init(ctx, &ctx->cdb, cdb_file);
+ if (ret != EOK) {
+ DEBUG(0,("The confdb initialization failed\n"));
+ goto done;
+ }
+
+ /* Initialize the CDB from the configuration file */
+ ret = confdb_test(ctx->cdb);
+ if (ret == ENOENT) {
+ /* First-time setup */
+
+ /* Purge any existing confdb in case an old
+ * misconfiguration gets in the way
+ */
+ talloc_zfree(ctx->cdb);
+ unlink(cdb_file);
+
+ ret = confdb_init(ctx, &ctx->cdb, cdb_file);
+ if (ret != EOK) {
+ DEBUG(0,("The confdb initialization failed\n"));
+ goto done;
+ }
+
+ /* Load special entries */
+ ret = confdb_create_base(ctx->cdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Unable to load special entries into confdb\n"));
+ goto done;
+ }
+ } else if (ret != EOK) {
+ DEBUG(0, ("Fatal error initializing confdb\n"));
+ goto done;
+ }
+ talloc_zfree(cdb_file);
+
+ ret = confdb_init_db(config_file, ctx->cdb);
+ if (ret != EOK) {
+ DEBUG(0, ("ConfDB initialization has failed [%s]\n",
+ strerror(ret)));
+ goto done;
+ }
+
+ /* Validate the configuration in the database */
+ /* Read in the monitor's configuration */
+ ret = get_monitor_config(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ *monitor = ctx;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+#ifdef HAVE_SYS_INOTIFY_H
+static void process_config_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr);
+
+static void config_file_changed(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *data)
+{
+ struct tevent_timer *te = NULL;
+ struct timeval tv;
+ struct config_file_ctx *file_ctx;
+
+ file_ctx = talloc_get_type(data, struct config_file_ctx);
+ if (file_ctx->needs_update) {
+ /* Skip updating. It's already queued for update.
+ */
+ return;
+ }
+
+ /* We will queue the file for update in one second.
+ * This way, if there is a script writing to the file
+ * repeatedly, we won't be attempting to update multiple
+ * times.
+ */
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += 1;
+
+ te = tevent_add_timer(ev, ev, tv, process_config_file, file_ctx);
+ if (!te) {
+ DEBUG(0, ("Unable to queue config file update! Exiting."));
+ kill(getpid(), SIGTERM);
+ return;
+ }
+ file_ctx->needs_update = 1;
+}
+
+struct rewatch_ctx {
+ struct config_file_callback *cb;
+ struct config_file_ctx *file_ctx;
+};
+static void rewatch_config_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr);
+static void process_config_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct inotify_event *in_event;
+ char *buf;
+ char *name;
+ ssize_t len, total_len;
+ ssize_t event_size;
+ struct config_file_ctx *file_ctx;
+ struct config_file_callback *cb;
+ struct rewatch_ctx *rw_ctx;
+
+ event_size = sizeof(struct inotify_event);
+ file_ctx = talloc_get_type(ptr, struct config_file_ctx);
+
+ DEBUG(1, ("Processing config file changes\n"));
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return;
+
+ buf = talloc_size(tmp_ctx, event_size);
+ if (!buf) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ total_len = 0;
+ while (total_len < event_size) {
+ len = read(file_ctx->mt_ctx->inotify_fd, buf+total_len,
+ event_size-total_len);
+ if (len == -1 && errno != EINTR) {
+ DEBUG(0, ("Critical error reading inotify file descriptor.\n"));
+ talloc_free(tmp_ctx);
+ return;
+ }
+ total_len += len;
+ }
+
+ in_event = (struct inotify_event *)buf;
+
+ if (in_event->len > 0) {
+ /* Read in the name, even though we don't use it,
+ * so that read ptr is in the right place
+ */
+ name = talloc_size(tmp_ctx, len);
+ if (!name) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+ total_len = 0;
+ while (total_len < in_event->len) {
+ len = read(file_ctx->mt_ctx->inotify_fd, &name, in_event->len);
+ if (len == -1 && errno != EINTR) {
+ DEBUG(0, ("Critical error reading inotify file descriptor.\n"));
+ talloc_free(tmp_ctx);
+ return;
+ }
+ total_len += len;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+
+ for (cb = file_ctx->callbacks; cb; cb = cb->next) {
+ if (cb->wd == in_event->wd) {
+ break;
+ }
+ }
+ if (!cb) {
+ DEBUG(0, ("Unknown watch descriptor\n"));
+ return;
+ }
+
+ if (in_event->mask & IN_IGNORED) {
+ /* Some text editors will move a new file on top of the
+ * existing one instead of modifying it. In this case,
+ * the kernel will send us an IN_IGNORE signal.
+ * We will try to open a new watch descriptor on the
+ * new file.
+ */
+ struct timeval tv;
+ struct tevent_timer *tev;
+ tv.tv_sec = t.tv_sec+5;
+ tv.tv_usec = t.tv_usec;
+
+ cb->retries = 0;
+ rw_ctx = talloc(file_ctx, struct rewatch_ctx);
+ if(!rw_ctx) {
+ DEBUG(0, ("Could not restore inotify watch. Quitting!\n"));
+ close(file_ctx->mt_ctx->inotify_fd);
+ kill(getpid(), SIGTERM);
+ return;
+ }
+ rw_ctx->cb = cb;
+ rw_ctx->file_ctx = file_ctx;
+
+ tev = tevent_add_timer(ev, rw_ctx, tv, rewatch_config_file, rw_ctx);
+ if (te == NULL) {
+ DEBUG(0, ("Could not restore inotify watch. Quitting!\n"));
+ close(file_ctx->mt_ctx->inotify_fd);
+ kill(getpid(), SIGTERM);
+ }
+ return;
+ }
+
+ /* Tell the monitor to signal the children */
+ cb->fn(file_ctx, cb->filename);
+ file_ctx->needs_update = 0;
+}
+
+static void rewatch_config_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ int err;
+ struct tevent_timer *tev = NULL;
+ struct timeval tv;
+ struct config_file_callback *cb;
+
+ struct rewatch_ctx *rw_ctx;
+ struct config_file_ctx *file_ctx;
+
+ rw_ctx = talloc_get_type(ptr, struct rewatch_ctx);
+
+ cb = rw_ctx->cb;
+ file_ctx = rw_ctx->file_ctx;
+
+ /* Retry six times at five-second intervals before giving up */
+ cb->retries++;
+ if (cb->retries > 6) {
+ DEBUG(0, ("Could not restore inotify watch. Quitting!\n"));
+ close(file_ctx->mt_ctx->inotify_fd);
+ kill(getpid(), SIGTERM);
+ }
+
+ cb->wd = inotify_add_watch(file_ctx->mt_ctx->inotify_fd,
+ cb->filename, IN_MODIFY);
+ if (cb->wd < 0) {
+ err = errno;
+
+ tv.tv_sec = t.tv_sec+5;
+ tv.tv_usec = t.tv_usec;
+
+ DEBUG(1, ("Could not add inotify watch for file [%s]. Error [%d:%s]\n",
+ cb->filename, err, strerror(err)));
+
+ tev = tevent_add_timer(ev, ev, tv, rewatch_config_file, rw_ctx);
+ if (te == NULL) {
+ DEBUG(0, ("Could not restore inotify watch. Quitting!\n"));
+ close(file_ctx->mt_ctx->inotify_fd);
+ kill(getpid(), SIGTERM);
+ }
+
+ return;
+ }
+ cb->retries = 0;
+
+ /* Tell the monitor to signal the children */
+ cb->fn(file_ctx, cb->filename);
+
+ talloc_free(rw_ctx);
+ file_ctx->needs_update = 0;
+}
+#endif
+
+static void poll_config_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ int ret, err;
+ struct stat file_stat;
+ struct timeval tv;
+ struct config_file_ctx *file_ctx;
+ struct config_file_callback *cb;
+
+ file_ctx = talloc_get_type(ptr,struct config_file_ctx);
+
+ for (cb = file_ctx->callbacks; cb; cb = cb->next) {
+ ret = stat(cb->filename, &file_stat);
+ if (ret < 0) {
+ err = errno;
+ DEBUG(0, ("Could not stat file [%s]. Error [%d:%s]\n",
+ cb->filename, err, strerror(err)));
+ /* TODO: If the config file is missing, should we shut down? */
+ return;
+ }
+
+ if (file_stat.st_mtime != cb->modified) {
+ /* Parse the configuration file and signal the children */
+ /* Note: this will fire if the modification time changes into the past
+ * as well as the future.
+ */
+ DEBUG(1, ("Config file changed\n"));
+ cb->modified = file_stat.st_mtime;
+
+ /* Tell the monitor to signal the children */
+ cb->fn(file_ctx, cb->filename);
+ }
+ }
+
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += CONFIG_FILE_POLL_INTERVAL;
+ tv.tv_usec = 0;
+ file_ctx->timer = tevent_add_timer(ev, file_ctx->parent_ctx, tv,
+ poll_config_file, file_ctx);
+ if (!file_ctx->timer) {
+ DEBUG(0, ("Error: Config file no longer monitored for changes!"));
+ }
+}
+
+static int try_inotify(struct config_file_ctx *file_ctx, const char *filename,
+ monitor_reconf_fn fn)
+{
+#ifdef HAVE_SYS_INOTIFY_H
+ int err, fd_args, ret;
+ struct tevent_fd *tfd;
+ struct config_file_callback *cb;
+
+ /* Monitoring the file descriptor should be global */
+ if (!file_ctx->mt_ctx->inotify_fd) {
+ /* Set up inotify to monitor the config file for changes */
+ file_ctx->mt_ctx->inotify_fd = inotify_init();
+ if (file_ctx->mt_ctx->inotify_fd < 0) {
+ err = errno;
+ DEBUG(0, ("Could not initialize inotify, error [%d:%s]\n",
+ err, strerror(err)));
+ return err;
+ }
+
+ fd_args = fcntl(file_ctx->mt_ctx->inotify_fd, F_GETFL, NULL);
+ if (fd_args < 0) {
+ /* Could not set nonblocking */
+ close(file_ctx->mt_ctx->inotify_fd);
+ return EINVAL;
+ }
+
+ fd_args |= O_NONBLOCK;
+ ret = fcntl(file_ctx->mt_ctx->inotify_fd, F_SETFL, fd_args);
+ if (ret < 0) {
+ /* Could not set nonblocking */
+ close(file_ctx->mt_ctx->inotify_fd);
+ return EINVAL;
+ }
+
+ /* Add the inotify file descriptor to the TEvent context */
+ tfd = tevent_add_fd(file_ctx->mt_ctx->ev, file_ctx,
+ file_ctx->mt_ctx->inotify_fd,
+ TEVENT_FD_READ, config_file_changed,
+ file_ctx);
+ if (!tfd) {
+ close(file_ctx->mt_ctx->inotify_fd);
+ return EIO;
+ }
+ }
+
+ cb = talloc_zero(file_ctx, struct config_file_callback);
+ if(!cb) {
+ close(file_ctx->mt_ctx->inotify_fd);
+ return EIO;
+ }
+
+ cb->filename = talloc_strdup(cb, filename);
+ if (!cb->filename) {
+ close(file_ctx->mt_ctx->inotify_fd);
+ return ENOMEM;
+ }
+ cb->wd = inotify_add_watch(file_ctx->mt_ctx->inotify_fd,
+ cb->filename, IN_MODIFY);
+ if (cb->wd < 0) {
+ err = errno;
+ DEBUG(0, ("Could not add inotify watch for file [%s]. Error [%d:%s]\n",
+ cb->filename, err, strerror(err)));
+ close(file_ctx->mt_ctx->inotify_fd);
+ return err;
+ }
+ cb->fn = fn;
+
+ DLIST_ADD(file_ctx->callbacks, cb);
+
+ return EOK;
+#else
+ return EINVAL;
+#endif
+}
+
+static int monitor_config_file(TALLOC_CTX *mem_ctx,
+ struct mt_ctx *ctx,
+ const char *file,
+ monitor_reconf_fn fn)
+{
+ int ret, err;
+ struct timeval tv;
+ struct stat file_stat;
+ struct config_file_callback *cb = NULL;
+
+ ret = stat(file, &file_stat);
+ if (ret < 0) {
+ err = errno;
+ DEBUG(0, ("Could not stat file [%s]. Error [%d:%s]\n",
+ file, err, strerror(err)));
+ return err;
+ }
+ if (!ctx->file_ctx) {
+ ctx->file_ctx = talloc_zero(mem_ctx, struct config_file_ctx);
+ if (!ctx->file_ctx) return ENOMEM;
+
+ ctx->file_ctx->parent_ctx = mem_ctx;
+ ctx->file_ctx->mt_ctx = ctx;
+ }
+ ret = try_inotify(ctx->file_ctx, file, fn);
+ if (ret != EOK) {
+ /* Could not monitor file with inotify, fall back to polling */
+ cb = talloc_zero(ctx->file_ctx, struct config_file_callback);
+ if (!cb) {
+ talloc_free(ctx->file_ctx);
+ return ENOMEM;
+ }
+ cb->filename = talloc_strdup(cb, file);
+ if (!cb->filename) {
+ talloc_free(ctx->file_ctx);
+ return ENOMEM;
+ }
+ cb->fn = fn;
+ cb->modified = file_stat.st_mtime;
+
+ DLIST_ADD(ctx->file_ctx->callbacks, cb);
+
+ if(!ctx->file_ctx->timer) {
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += CONFIG_FILE_POLL_INTERVAL;
+ tv.tv_usec = 0;
+ ctx->file_ctx->timer = tevent_add_timer(ctx->ev, mem_ctx, tv,
+ poll_config_file, ctx->file_ctx);
+ if (!ctx->file_ctx->timer) {
+ talloc_free(ctx->file_ctx);
+ return EIO;
+ }
+ }
+ }
+
+ return EOK;
+}
+
+int monitor_process_init(struct mt_ctx *ctx,
+ const char *config_file)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct sysdb_ctx_list *db_list;
+ struct tevent_signal *tes;
+ struct sss_domain_info *dom;
+ int num_providers;
+ int ret;
+
+#if 0
+ This feature is incomplete and can leave the SSSD in a bad state if the
+ config file is changed while the SSSD is running.
+
+ Uncomment this once the backends are honoring reloadConfig()
+
+ /* Watch for changes to the confdb config file */
+ ret = monitor_config_file(ctx, ctx, config_file, monitor_signal_reconf);
+ if (ret != EOK) {
+ return ret;
+ }
+#endif
+ /* Watch for changes to the DNS resolv.conf */
+ ret = monitor_config_file(ctx, ctx, RESOLV_CONF_PATH,
+ monitor_update_resolv);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ /* Avoid a startup race condition between process.
+ * We need to handle DB upgrades or DB creation only
+ * in one process before all other start.
+ */
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+ ret = sysdb_init(tmp_ctx, ctx->ev, ctx->cdb, NULL, true, &db_list);
+ if (ret != EOK) {
+ return ret;
+ }
+ talloc_zfree(tmp_ctx);
+
+ /* Initialize D-BUS Server
+ * The monitor will act as a D-BUS server for all
+ * SSSD processes */
+ ret = monitor_dbus_init(ctx);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ /* start providers */
+ num_providers = 0;
+ for (dom = ctx->domains; dom; dom = dom->next) {
+ ret = add_new_provider(ctx, dom->name);
+ if (ret != EOK && ret != ENOENT) {
+ return ret;
+ }
+ if (ret != ENOENT) {
+ num_providers++;
+ }
+ }
+
+ if (num_providers > 0) {
+ /* now set the services stratup timeout *
+ * (responders will be started automatically when all
+ * providers are up and running or when the tomeout
+ * expires) */
+ ret = add_services_startup_timeout(ctx);
+ if (ret != EOK) {
+ return ret;
+ }
+ } else {
+ int i;
+
+ ctx->services_started = true;
+
+ /* No providers start services immediately
+ * Normally this means only LOCAL is configured */
+ for (i = 0; ctx->services[i]; i++) {
+ add_new_service(ctx, ctx->services[i]);
+ }
+ }
+
+ /* now start checking for global events */
+ set_global_checker(ctx);
+
+ /* Set up an event handler for a SIGHUP */
+ tes = tevent_add_signal(ctx->ev, ctx, SIGHUP, 0,
+ monitor_hup, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ /* Set up an event handler for a SIGINT */
+ tes = tevent_add_signal(ctx->ev, ctx, SIGINT, 0,
+ monitor_quit, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ /* Set up an event handler for a SIGTERM */
+ tes = tevent_add_signal(ctx->ev, ctx, SIGTERM, 0,
+ monitor_quit, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ /* Handle SIGUSR1 (tell all providers to go offline) */
+ BlockSignals(false, SIGUSR1);
+ tes = tevent_add_signal(ctx->ev, ctx, SIGUSR1, 0,
+ signal_offline, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static void init_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct mon_init_conn *mini;
+
+ DEBUG(2, ("Client timed out before Identification!\n"));
+
+ mini = talloc_get_type(ptr, struct mon_init_conn);
+
+ sbus_disconnect(mini->conn);
+ talloc_zfree(mini);
+}
+
+/*
+ * monitor_service_init
+ * Set up a timeout function and temporary connection structure.
+ * If the client does not identify before the timeout kicks in,
+ * the client is forcibly disconnected.
+ */
+static int monitor_service_init(struct sbus_connection *conn, void *data)
+{
+ struct mt_ctx *ctx;
+ struct mon_init_conn *mini;
+ struct timeval tv;
+
+ DEBUG(3, ("Initializing D-BUS Service\n"));
+
+ ctx = talloc_get_type(data, struct mt_ctx);
+
+ mini = talloc(conn, struct mon_init_conn);
+ if (!mini) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+ mini->ctx = ctx;
+ mini->conn = conn;
+
+ /* 5 seconds should be plenty */
+ tv = tevent_timeval_current_ofs(10, 0);
+
+ mini->timeout = tevent_add_timer(ctx->ev, mini, tv, init_timeout, mini);
+ if (!mini->timeout) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+
+ sbus_conn_set_private_data(conn, mini);
+
+ return EOK;
+}
+
+/* service_send_ping
+ * this function send a dbus ping to a service.
+ * It returns EOK if all is fine or ENXIO if the connection is
+ * not available (either not yet set up or teared down).
+ * Returns e generic error in other cases.
+ */
+static int service_send_ping(struct mt_svc *svc)
+{
+ DBusMessage *msg;
+ DBusPendingCall *pending_reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+
+ if (!svc->conn) {
+ DEBUG(8, ("Service not yet initialized\n"));
+ return ENXIO;
+ }
+
+ DEBUG(4,("Pinging %s\n", svc->name));
+
+ dbus_conn = sbus_get_connection(svc->conn);
+
+ /*
+ * Set up identity request
+ * This should be a well-known path and method
+ * for all services
+ */
+ msg = dbus_message_new_method_call(NULL,
+ MONITOR_PATH,
+ MONITOR_INTERFACE,
+ MON_CLI_METHOD_PING);
+ if (!msg) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(svc->conn);
+ return ENOMEM;
+ }
+
+ dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ svc->mt_ctx->service_id_timeout);
+ if (!dbret || pending_reply == NULL) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ talloc_zfree(svc->conn);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, ping_check, svc, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
+static void ping_check(DBusPendingCall *pending, void *data)
+{
+ struct mt_svc *svc;
+ DBusMessage *reply;
+ const char *dbus_error_name;
+ int type;
+
+ svc = talloc_get_type(data, struct mt_svc);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("A reply callback was called but no reply was received"
+ " and no timeout occurred\n"));
+
+ /* Destroy this connection */
+ sbus_disconnect(svc->conn);
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ /* ok peer replied,
+ * make sure we reset the failure counter in the service structure */
+
+ DEBUG(4,("Service %s replied to ping\n", svc->name));
+
+ svc->failed_pongs = 0;
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+
+ dbus_error_name = dbus_message_get_error_name(reply);
+
+ /* timeouts are handled in the main service check function */
+ if (strcmp(dbus_error_name, DBUS_ERROR_TIMEOUT) == 0)
+ break;
+
+ DEBUG(0,("A service PING returned an error [%s], closing connection.\n",
+ dbus_error_name));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+ sbus_disconnect(svc->conn);
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+
+
+/* service_check_alive
+ * This function checks if the service child is still alive
+ */
+static int service_check_alive(struct mt_svc *svc)
+{
+ int status;
+ pid_t pid;
+
+ DEBUG(4,("Checking service %s(%d) is still alive\n", svc->name, svc->pid));
+
+ pid = waitpid(svc->pid, &status, WNOHANG);
+ if (pid == 0) {
+ return EOK;
+ }
+
+ if (pid != svc->pid) {
+ DEBUG(1, ("bad return (%d) from waitpid() waiting for %d\n",
+ pid, svc->pid));
+ /* TODO: what do we do now ? */
+ return EINVAL;
+ }
+
+ if (WIFEXITED(status)) { /* children exited on it's own */
+ /* TODO: check configuration to see if it was removed
+ * from the list of process to run */
+ DEBUG(0,("Process [%s] exited\n", svc->name));
+ }
+
+ return ECHILD;
+}
+
+static void free_args(char **args)
+{
+ int i;
+
+ if (args) {
+ for (i = 0; args[i]; i++) free(args[i]);
+ free(args);
+ }
+}
+
+
+/* parse a string into arguments.
+ * arguments are separated by a space
+ * '\' is an escape character and can be used only to escape
+ * itself or the white space.
+ */
+static char **parse_args(const char *str)
+{
+ const char *p;
+ char **ret, **r;
+ char *tmp;
+ int num;
+ int i, e;
+
+ tmp = malloc(strlen(str) + 1);
+ if (!tmp) return NULL;
+
+ ret = NULL;
+ num = 0;
+ e = 0;
+ i = 0;
+ p = str;
+ while (*p) {
+ switch (*p) {
+ case '\\':
+ if (e) {
+ tmp[i] = '\\';
+ i++;
+ e = 0;
+ } else {
+ e = 1;
+ }
+ break;
+ case ' ':
+ if (e) {
+ tmp[i] = ' ';
+ i++;
+ e = 0;
+ } else {
+ tmp[i] = '\0';
+ i++;
+ }
+ break;
+ default:
+ if (e) {
+ tmp[i] = '\\';
+ i++;
+ e = 0;
+ }
+ tmp[i] = *p;
+ i++;
+ break;
+ }
+
+ p++;
+
+ /* check if this was the last char */
+ if (*p == '\0') {
+ if (e) {
+ tmp[i] = '\\';
+ i++;
+ e = 0;
+ }
+ tmp[i] = '\0';
+ i++;
+ }
+ if (tmp[i-1] != '\0' || strlen(tmp) == 0) {
+ /* check next char and skip multiple spaces */
+ continue;
+ }
+
+ r = realloc(ret, (num + 2) * sizeof(char *));
+ if (!r) goto fail;
+ ret = r;
+ ret[num+1] = NULL;
+ ret[num] = strdup(tmp);
+ if (!ret[num]) goto fail;
+ num++;
+ i = 0;
+ }
+
+ free(tmp);
+ return ret;
+
+fail:
+ free(tmp);
+ free_args(ret);
+ return NULL;
+}
+
+static void service_startup_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr);
+
+static int start_service(struct mt_svc *svc)
+{
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ DEBUG(4,("Queueing service %s for startup\n", svc->name));
+
+ /* at startup we need to start the data providers before the responders
+ * to avoid races where a service starts before sbus pipes are ready
+ * to accept connections. So if startup is true delay by 2 seconds any
+ * process that is not a data provider */
+
+ tv = tevent_timeval_current();
+
+ /* Add a timed event to start up the service.
+ * We have to do this in order to avoid a race
+ * condition where the service being started forks
+ * and attempts to connect to the SBUS before
+ * the monitor is serving it.
+ */
+ te = tevent_add_timer(svc->mt_ctx->ev, svc, tv,
+ service_startup_handler, svc);
+ if (te == NULL) {
+ DEBUG(0, ("Unable to queue service %s for startup\n", svc->name));
+ return ENOMEM;
+ }
+ return EOK;
+}
+
+static void service_startup_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct mt_svc *mt_svc;
+ char **args;
+
+ mt_svc = talloc_get_type(ptr, struct mt_svc);
+ if (mt_svc == NULL) {
+ return;
+ }
+
+ mt_svc->pid = fork();
+ if (mt_svc->pid != 0) {
+ if (mt_svc->pid == -1) {
+ DEBUG(0, ("Could not fork child to start service [%s]. Continuing.\n", mt_svc->name))
+ return;
+ }
+
+ /* Parent */
+ mt_svc->mt_ctx->check_children = true;
+ mt_svc->failed_pongs = 0;
+ DLIST_ADD(mt_svc->mt_ctx->svc_list, mt_svc);
+ set_tasks_checker(mt_svc);
+
+ return;
+ }
+
+ /* child */
+
+ args = parse_args(mt_svc->command);
+ execvp(args[0], args);
+
+ /* If we are here, exec() has failed
+ * Print errno and abort quickly */
+ DEBUG(0,("Could not exec %s, reason: %s\n", mt_svc->command, strerror(errno)));
+
+ /* We have to call _exit() instead of exit() here
+ * because a bug in D-BUS will cause the server to
+ * close its socket at exit() */
+ _exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int opt_daemon = 0;
+ int opt_interactive = 0;
+ char *opt_config_file = NULL;
+ char *config_file = NULL;
+ int flags = 0;
+ struct main_context *main_ctx;
+ TALLOC_CTX *tmp_ctx;
+ struct mt_ctx *monitor;
+ int ret;
+ uid_t uid;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ {"daemon", 'D', POPT_ARG_NONE, &opt_daemon, 0, \
+ _("Become a daemon (default)"), NULL }, \
+ {"interactive", 'i', POPT_ARG_NONE, &opt_interactive, 0, \
+ _("Run interactive (not a daemon)"), NULL}, \
+ {"config", 'c', POPT_ARG_STRING, &opt_config_file, 0, \
+ _("Specify a non-default config file"), NULL}, \
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+
+ if (opt_daemon && opt_interactive) {
+ fprintf(stderr, "Option -i|--interactive is not allowed together with -D|--daemon\n");
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+
+ poptFreeContext(pc);
+
+ uid = getuid();
+ if (uid != 0) {
+ DEBUG(1, ("Running under %d, must be root\n", uid));
+ ERROR("sssd must be run as root\n");
+ return 8;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return 7;
+ }
+
+ if (opt_daemon) flags |= FLAGS_DAEMON;
+ if (opt_interactive) flags |= FLAGS_INTERACTIVE;
+
+ if (opt_config_file)
+ config_file = talloc_strdup(tmp_ctx, opt_config_file);
+ else
+ config_file = talloc_strdup(tmp_ctx, CONFDB_DEFAULT_CONFIG_FILE);
+ if(!config_file)
+ return 6;
+
+ /* we want a pid file check */
+ flags |= FLAGS_PID_FILE;
+
+ /* Open before server_setup() does to have logging
+ * during configuration checking */
+ if (debug_to_file) {
+ ret = open_debug_file();
+ if (ret) {
+ return 7;
+ }
+ }
+
+ /* Parse config file, fail if cannot be done */
+ ret = load_configuration(tmp_ctx, config_file, &monitor);
+ if (ret != EOK) {
+ if (ret == EIO) {
+ DEBUG(1, ("Cannot read configuration file %s\n", config_file));
+ ERROR("Cannot read config file %s, please check if permissions "
+ "are 0600 and the file is owned by root.root\n", config_file);
+ }
+ return 4;
+ }
+
+ /* set up things like debug , signals, daemonization, etc... */
+ ret = server_setup("sssd", flags, CONFDB_MONITOR_CONF_ENTRY, &main_ctx);
+ if (ret != EOK) return 2;
+
+ monitor->ev = main_ctx->event_ctx;
+ talloc_steal(main_ctx, monitor);
+
+ ret = monitor_process_init(monitor,
+ config_file);
+ if (ret != EOK) return 3;
+ talloc_free(tmp_ctx);
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ ret = monitor_cleanup();
+ if (ret != EOK) return 5;
+
+ return 0;
+}
diff --git a/src/monitor/monitor.h b/src/monitor/monitor.h
new file mode 100644
index 00000000..78e10ef1
--- /dev/null
+++ b/src/monitor/monitor.h
@@ -0,0 +1,36 @@
+/*
+ SSSD
+
+ Service monitor
+
+ Copyright (C) Simo Sorce 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MONITOR_H_
+#define _MONITOR_H_
+
+#define RESOLV_CONF_PATH "/etc/resolv.conf"
+#define CONFIG_FILE_POLL_INTERVAL 5 /* seconds */
+
+typedef int (*monitor_reconf_fn) (struct config_file_ctx *file_ctx,
+ const char *filename);
+
+struct mt_ctx;
+
+int monitor_process_init(struct mt_ctx *ctx,
+ const char *config_file);
+
+#endif /* _MONITOR_H */
diff --git a/src/monitor/monitor_interfaces.h b/src/monitor/monitor_interfaces.h
new file mode 100644
index 00000000..c6361fa2
--- /dev/null
+++ b/src/monitor/monitor_interfaces.h
@@ -0,0 +1,55 @@
+/*
+ SSSD
+
+ Sbus Interfaces
+
+ Copyright (C) Simo Sorce 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*** Monitor ***/
+
+#define MONITOR_VERSION 0x0001
+
+/*** Monitor SRV Interface ***/
+#define MON_SRV_PATH "/org/freedesktop/sssd/monitor"
+#define MON_SRV_INTERFACE "org.freedesktop.sssd.monitor"
+
+/* Monitor SRV Methods */
+#define MON_SRV_METHOD_VERSION "getVersion"
+#define MON_SRV_METHOD_REGISTER "RegisterService"
+
+/*** Monitor CLI Interface ***/
+#define MONITOR_PATH "/org/freedesktop/sssd/service"
+#define MONITOR_INTERFACE "org.freedesktop.sssd.service"
+
+/* Monitor CLI Methods */
+#define MON_CLI_METHOD_IDENTITY "getIdentity"
+#define MON_CLI_METHOD_PING "ping"
+#define MON_CLI_METHOD_RELOAD "reloadConfig"
+#define MON_CLI_METHOD_SHUTDOWN "shutDown"
+#define MON_CLI_METHOD_RES_INIT "resInit"
+#define MON_CLI_METHOD_OFFLINE "goOffline" /* Applicable only to providers */
+
+#define SSSD_SERVICE_PIPE "private/sbus-monitor"
+
+int monitor_get_sbus_address(TALLOC_CTX *mem_ctx, char **address);
+int monitor_common_send_id(struct sbus_connection *conn,
+ const char *name, uint16_t version);
+int monitor_common_pong(DBusMessage *message,
+ struct sbus_connection *conn);
+int monitor_common_res_init(DBusMessage *message,
+ struct sbus_connection *conn);
+
diff --git a/src/monitor/monitor_sbus.c b/src/monitor/monitor_sbus.c
new file mode 100644
index 00000000..3f73e84f
--- /dev/null
+++ b/src/monitor/monitor_sbus.c
@@ -0,0 +1,195 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Needed for res_init() */
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "sbus/sssd_dbus.h"
+#include "monitor/monitor_interfaces.h"
+
+int monitor_get_sbus_address(TALLOC_CTX *mem_ctx, char **address)
+{
+ char *default_address;
+
+ *address = NULL;
+ default_address = talloc_asprintf(mem_ctx, "unix:path=%s/%s",
+ PIPE_PATH, SSSD_SERVICE_PIPE);
+ if (default_address == NULL) {
+ return ENOMEM;
+ }
+
+ *address = default_address;
+ return EOK;
+}
+
+static void id_callback(DBusPendingCall *pending, void *ptr)
+{
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t ret;
+ dbus_uint16_t mon_ver;
+ int type;
+
+ dbus_error_init(&dbus_error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("Severe error. A reply callback was called but no"
+ " reply was received and no timeout occurred\n"));
+
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dbus_message_get_args(reply, &dbus_error,
+ DBUS_TYPE_UINT16, &mon_ver,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to parse message\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ DEBUG(4, ("Got id ack and version (%d) from Monitor\n", mon_ver));
+
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+ DEBUG(0,("The Monitor returned an error [%s]\n",
+ dbus_message_get_error_name(reply)));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+
+ /* FIXME: Destroy this connection ? */
+ break;
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+int monitor_common_send_id(struct sbus_connection *conn,
+ const char *name, uint16_t version)
+{
+ DBusPendingCall *pending_reply;
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ dbus_bool_t ret;
+
+ dbus_conn = sbus_get_connection(conn);
+
+ /* create the message */
+ msg = dbus_message_new_method_call(NULL,
+ MON_SRV_PATH,
+ MON_SRV_INTERFACE,
+ MON_SRV_METHOD_REGISTER);
+ if (msg == NULL) {
+ DEBUG(0, ("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ DEBUG(4, ("Sending ID: (%s,%d)\n", name, version));
+
+ ret = dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to build message\n"));
+ return EIO;
+ }
+
+ ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ 30000 /* TODO: set timeout */);
+ if (!ret || !pending_reply) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, id_callback, NULL, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
+int monitor_common_pong(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ DBusMessage *reply;
+ dbus_bool_t ret;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ ret = dbus_message_append_args(reply, DBUS_TYPE_INVALID);
+ if (!ret) {
+ dbus_message_unref(reply);
+ return EIO;
+ }
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ return EOK;
+}
+
+int monitor_common_res_init(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ int ret;
+
+ ret = res_init();
+ if(ret != 0) {
+ return EIO;
+ }
+
+ /* Send an empty reply to acknowledge receipt */
+ return monitor_common_pong(message, conn);
+}
+
diff --git a/src/po/LINGUAS b/src/po/LINGUAS
new file mode 100644
index 00000000..f2c4e4c4
--- /dev/null
+++ b/src/po/LINGUAS
@@ -0,0 +1,10 @@
+es
+pl
+ja
+nl
+pt
+de
+sv
+it
+fr
+
diff --git a/src/po/Makevars b/src/po/Makevars
new file mode 100644
index 00000000..07fc8143
--- /dev/null
+++ b/src/po/Makevars
@@ -0,0 +1,41 @@
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --keyword=ERROR --keyword=PRINT
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
+# package. (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.) Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright. The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Red Hat, Inc.
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+# in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+# understood.
+# - Strings which make invalid assumptions about notation of date, time or
+# money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = sssd-devel@lists.fedorahosted.org
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used. It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
diff --git a/src/po/POTFILES.in b/src/po/POTFILES.in
new file mode 100644
index 00000000..e317161f
--- /dev/null
+++ b/src/po/POTFILES.in
@@ -0,0 +1,16 @@
+# List of source files which contain translatable strings.
+confdb/confdb_setup.c
+config/SSSDConfig.py
+tools/sss_groupdel.c
+tools/sss_groupmod.c
+tools/sss_userdel.c
+tools/tools_util.c
+tools/tools_util.h
+tools/sss_useradd.c
+tools/sss_groupadd.c
+tools/sss_usermod.c
+sss_client/common.c
+sss_client/group.c
+sss_client/pam_sss.c
+sss_client/pam_test_client.c
+sss_client/passwd.c
diff --git a/src/po/de.po b/src/po/de.po
new file mode 100644
index 00000000..3ec1a9af
--- /dev/null
+++ b/src/po/de.po
@@ -0,0 +1,692 @@
+# German translation of sssd.
+# Copyright (C) 2009 Red Hat, Inc.
+# This file is distributed under the same license as the sssd package.
+#
+# Fabian Affolter <fab@fedoraproject.org>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: SSS\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-12-09 11:13+0100\n"
+"Last-Translator: Fabian Affolter <fab@fedoraproject.org>\n"
+"Language-Team: German <fedora-trans-de@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: German\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr ""
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr ""
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr ""
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr ""
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr ""
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr ""
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr ""
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr ""
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr ""
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "IPA-Domain"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "IPA-Serveradresse"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr "IPA-Client-Rechnername"
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Kerberos-Serveradresse"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr "Kerberos Realm"
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr ""
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr ""
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr ""
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr ""
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr ""
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Benutzername-Attribut"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "UID-Attribut"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "GECOS-Attribut"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr "Shell-Attribut"
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "UUID-Attribut"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr "Vollständiger Name"
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr ""
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr ""
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr ""
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr ""
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr ""
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr ""
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr ""
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr ""
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr ""
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Bneutzerverzeichnis"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr "Anmelde-Shell"
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Gruppen"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr ""
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr ""
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr ""
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr ""
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr ""
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "Das Konto sperren"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "Das Konto entsperren"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr ""
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr ""
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr ""
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+msgid "Password change failed. "
+msgstr ""
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr ""
diff --git a/src/po/es.po b/src/po/es.po
new file mode 100644
index 00000000..36a6de06
--- /dev/null
+++ b/src/po/es.po
@@ -0,0 +1,720 @@
+# English translations for sss_daemon package.
+# Copyright (C) 2009 Red Hat, Inc.
+# This file is distributed under the same license as the sss_daemon package.
+# Automatically generated, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: sss_daemon 0.4.0\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-12-11 12:00-0300\n"
+"Last-Translator: Domingo Becker <domingobecker@gmail.com>\n"
+"Language-Team: Transifex Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: Spanish\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr "Establece el nivel de detalle del registro de depuración"
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr "Incluir la marca de tiempo en los registros de depuración"
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr "Escribir los mensajes de depuración a archivos log"
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr "Tiempo máximo de ping antes de reiniciar el servicio"
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr "Comando para iniciar el servicio"
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+"Número de veces que debe intentar la conexión con los Proveedores de Datos"
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr "Servicios SSSD a iniciar"
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr "Dominios SSSD a iniciar"
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr "Tiempo máximo para los mensajes enviados a través de SBUS"
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+"Expresión regular para analizar sintácticamente el nombre de usuario y "
+"dominio"
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+"Formato compatible con printf para mostrar nombres completamente calificados"
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr "Tiempo máximo (segundos) del caché de enumeración"
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+"Tiempo máximo (segundos) de la entrada de caché a actualizar en segundo plano"
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr "Tiempo máximo negativo del cache (segundos)"
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr "Usuarios que deben ser explícitamente ignorados por SSSD"
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr "Grupos que deben ser explícitamente ignorados por SSSD"
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr "Deben aparecer los usuarios filtrados en los grupos"
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr "El valor del campo contraseña que el proveedor NSS debe devolver"
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+"Por cuánto tiempo permitir ingresos cacheados entre ingresos en línea (días)"
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr "Proveedor de identidad"
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr "Proveedor de Autenticación"
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr "Proveedor de control de acceso"
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr "Proveedor de cambio de contraseña"
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr "ID mínimo de usuario"
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr "ID máximo de usuario"
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr "Tiempo máximo de ping antes de reinciar el dominio"
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr "Habilitar la enumeración de todos los usuarios/grupos"
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr "Hacer caché de las credenciales para ingresos fuera de línea"
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr "Guardar los hashes de la contraseña"
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr "Mostrar los usuarios/grupos en un formato completamente calificado"
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr "Tiempo máximo de una entrada del caché (segundos)"
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "Dominio IPA"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "Dirección del servidor IPA"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr "Nombre de equipo del cliente IPA"
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Dirección del servidor Kerberos"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr "Reinado Kerberos"
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr "Expiración de la autenticación"
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr "Directorio donde almacenar las credenciales cacheadas"
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr "Ubicación del caché de credenciales del usuario"
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr "Ubicación de la tabla de claves para validar las credenciales"
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr "Habilitar la validación de credenciales"
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr "El principal del servicio de cambio de contraseña"
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr "ldap_uri, El URI del servidor LDAP"
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr "DN base predeterminado"
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr "El Tipo de Esquema a usar en el servidor LDAP, rfc2307"
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr "El DN Bind predeterminado"
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr "El tipo del token de autenticación del DN bind predeterminado"
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr "El token de autenticación del DN bind predeterminado"
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr "Tiempo durante el que se intentará la conexión"
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr "Tiempo durante el que se intentará operaciones LDAP sincrónicas"
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr "Tiempo entre intentos de reconexión cuando esté fuera de línea"
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr "archivo que contiene los certificados CA"
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr "Requiere la verificación de certificado TLS"
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr "Especificar el mecanismo sasl a usar"
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr "Especifique el id de autorización sasl a usar"
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr "Tabla de clave del servicio Kerberos"
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr "Usar auth Kerberos para la conexión LDAP"
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr "Tiempo máximo a esperar un pedido de búsqueda"
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr "Tiempo en segundos entre las actualizaciones de enumeración"
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr "Requiere TLS para búsquedas de ID, falso"
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr "DN base para búsquedas de usuario"
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr "Ambito de las búsquedas del usuario"
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr "Filtro para las búsquedas del usuario"
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr "Objectclass para los usuarios"
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Atributo Username"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "Atributo UID"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr "Atributo GID primario"
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "Atributo GECOS"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr "Atributo Directorio de inicio"
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr "Atributo shell"
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "Atributo UUID"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr "Atributo principal del usuario (para Kerberos) "
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr "Nombre completo"
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr "Atributo memberOf"
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr "Atributo hora de modificación"
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr "Política para evaluar el vencimiento de la contraseña"
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr "Shell predeterminado, /bin/bash"
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr "Base de los directorios de inicio"
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr "Nombre de la biblioteca NSS a usar"
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr "Pila PAM a usar"
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr "Nivel de depuración en que se debe ejecutar"
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr "Error al poner la región\n"
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr "Especifique el grupo a borrar\n"
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+#, fuzzy
+msgid "Error initializing the tools - no local domain\n"
+msgstr "Error al inicializar las herramientas\n"
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr "Error al inicializar las herramientas\n"
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr "Dominio inválido especificado en FQDN\n"
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr "El grupo %s está fuera del rango de ID definido para los dominios\n"
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+"No existe tal grupo en el dominio local. Eliminando los grupos que sólo se "
+"permiten en el dominio local.\n"
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr "Error interno. No se pudo eliminar el grupo.\n"
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr "Grupos a los que se debe agregar este grupo"
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr "Grupos desde los que se debe eliminar este grupo"
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr "El GID del grupo"
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr "Especifique el grupo a modificar\n"
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+"No se pudo encontrar el grupo en el dominio local, la modificación de grupos "
+"se permite sólo en el dominio local\n"
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr "Error interno al analizar sintácticamente los parámetros.\n"
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+"Los grupos miembro deben estar en el mismo dominio que el grupo padre\n"
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+"No se pudo encontrar el grupo %s en el dominio local, solo se permiten los "
+"grupos del dominio local\n"
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr "El GID elegido está fuera del rango permitido\n"
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+"No se pudo modificar el grupo - verifique si los nombre de grupo miembro son "
+"los correctos\n"
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+"No se pudo modificar el grupo - verifique si el nombre de grupo es correcto\n"
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr "Error de transacción. No se pudo modificar el grupo.\n"
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr "Eliminar el directorio de inicio y el receptor de correo"
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr "No eliminar el directorio de inicio y el receptor de correo"
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr "Forzar la eliminación de los archivos que no pertenecen al usuario"
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr "Especifique el usuario a borrar\n"
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr "No se pudieron establecer los valores predeterminados\n"
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr "El usuario %s está fuera del rango de ID para el dominio\n"
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr "No eliminando el directorio de inicio - no pertenece al usuario\n"
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr " Imposible eliminar el directorio de inicio: %s\n"
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+"No existe ese usuario en el dominio local. La eliminación de usuarios se "
+"permite en el dominio local.\n"
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr "Error interno. No se pudo eliminar el usuario.\n"
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr "Falta memoria\n"
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr "%s se debe ejecutar como root\n"
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr "El UID del usuario"
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr "El GID o nombre de grupo del usuario"
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr "La cadena de comentarios"
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Directorio de inicio"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr "Shell de ingreso"
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Grupos"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr "Crear el directorio del usuario si no existe"
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+"La opción de nunca crear el directorio del usuario, anula la configurada"
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr "Debe especificar un directorio esqueleto alternativo"
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr "Especifique el usuario a agregar\n"
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr "Los grupos deben estar en el mismo dominio que el usuario\n"
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr "No se pudo encontrar el grupo %s en el dominio local\n"
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr "No se pudo obtener la información del grupo del usuario\n"
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr "El UID seleccionado está fuera del rango permitido\n"
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr "No se pudo obtener información del usuario\n"
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+"El directorio de inicio del usuario ya existe, no copiar datos desde el "
+"esqueleto\n"
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr "No se pudo crear el directorio personal del usuario: %s\n"
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr "No se pudo crear el receptor de correo del usuario: %s\n"
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr "No se pudo asignar el ID para el usuario - ¿el dominio estará lleno?\n"
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr "Ya existe un usuario o grupo con el mismo nombre o ID\n"
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr "Error en la transacción. No se pudo agregar el usuario.\n"
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr "Especifique el grupo a agregar\n"
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr "No se pudo asignar el ID para el grupo - ¿el dominio estará lleno?\n"
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr "Ya existe un grupo con el mismo nombre o GID\n"
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr "Error en la transacción. No se pudo agregar el grupo.\n"
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr "El GID del Usuario"
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr "Grupos a los que se debe agregar este usuario"
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr "Grupos desde los que hay que eliminar este usuario"
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "Bloquear la cuenta"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "Desbloquear la cuenta"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr "Especifique el usuario a modificar\n"
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+"No se pudo encontrar el usuario en el dominio local, la modificación de los "
+"usuarios se permite solamente en el dominio local\n"
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+"No se pudo modificar el usuario - verifique si los nombres de grupo son "
+"correctos\n"
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+"No se pudo modificar el usuario - ¿no será ya miembro de esos grupos?\n"
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr "Error de transacción. No se pudo modificar el usuario.\n"
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Las contraseñas no coinciden"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Proveedor de cambio de contraseña"
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Contraseña: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nueva contraseña: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Reingrese la contraseña nueva:"
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "La contraseña ha expirado."
diff --git a/src/po/fr.po b/src/po/fr.po
new file mode 100644
index 00000000..5ad66510
--- /dev/null
+++ b/src/po/fr.po
@@ -0,0 +1,692 @@
+# French translation of SSS Client
+# Copyright (C) 2009 Red Hat, Inc.
+# This file is distributed under the same license as the SSSD package.
+# Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: fr\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-11-17 21:05+0100\n"
+"Last-Translator: Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>\n"
+"Language-Team: Français <fedora-trans-fr@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr ""
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr ""
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr ""
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr ""
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr ""
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr ""
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr ""
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+#, fuzzy
+msgid "Password change provider"
+msgstr "Le mot de passe a expiré."
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr ""
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr ""
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr ""
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr ""
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr ""
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr ""
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr ""
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr ""
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr ""
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr ""
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr ""
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr ""
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr ""
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr ""
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr ""
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr ""
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr ""
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr ""
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr ""
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr ""
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr ""
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr ""
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr ""
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr ""
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr ""
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr ""
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr ""
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr ""
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr ""
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr ""
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr ""
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Les mots de passe ne correspondent pas"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Le mot de passe a expiré."
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Mot de passe : "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nouveau mot de passe : "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Retaper le nouveau mot de passe : "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "Le mot de passe a expiré."
diff --git a/src/po/it.po b/src/po/it.po
new file mode 100644
index 00000000..3d331281
--- /dev/null
+++ b/src/po/it.po
@@ -0,0 +1,702 @@
+# Italian translations for sss_daemon package.
+# Copyright (C) 2009 Red Hat, Inc.
+# This file is distributed under the same license as the sss_daemon package.
+# Marina Latini <deneb.alphacygni@gmail.com>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: it\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Marina Latini <deneb.alphacygni@gmail.com>\n"
+"Language-Team: Italian <fedora-trans-it@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Italian\n"
+"X-Poedit-Country: ITALY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr "Impostare il livello di dettaglio dello storico dei file di log"
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr "Scrivere i messaggi di debug nei file di log"
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr "Comando per avviare il servizio"
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr "Numero di tentativi di connessione al provider di dati"
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr "Avvio dei servizi SSSD"
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr "Avvio dei domini SSSD"
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr "Espressione regolare per analizzare nome utente e dominio"
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr "Utenti che SSSD dovrebbe ignorare esplicitamente"
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr "Gruppi che SSSD dovrebbe ignorare esplicitamente"
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr "Identità del provider"
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr "Autenticazione del provider"
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr ""
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr "ID utente minimo"
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr "ID utente massimo"
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr "Consentire l'enumerazione di tutti gli utenti/gruppi"
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "Dominio IPA"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "indirizzi del server IPA"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr ""
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Indirizzi del server Kerberos"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr ""
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr "Abilita la validazione delle credenziali"
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr "file che contiene certificati CA"
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr "Richiedere la verifica del certificato TLS"
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr "Specificare il meccanismo sasl da usare"
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr "Specificare l'id di autorizzazione sasl da usare"
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr "Ambito di applicazione della ricerca degli utenti"
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Attributo del nome utente"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "Attributo UID"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr "Attributo del GID primario"
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "Attributo GECOS"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "Attributo UUID"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr ""
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr "Politica per controllare la scadenza della password"
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr "Stack PAM da usare"
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr "Errore di impostazione della località\n"
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr "Specificare il gruppo da eliminare\n"
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+#, fuzzy
+msgid "Error initializing the tools - no local domain\n"
+msgstr "Errore durante l'inizializzazione degli strumenti\n"
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr "Errore durante l'inizializzazione degli strumenti\n"
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr "Errore interno. Impossibile rimuovere il gruppo.\n"
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr "Il GID del gruppo"
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr "Specificare il gruppo da modificare\n"
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr "Errore interno nell'analisi dei parametri\n"
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+"Impossibile modificare il gruppo - controllare che i nomi dei gruppi siano "
+"corretti\n"
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+"Impossibile modificare il gruppo - controllare che il nome del gruppo sia "
+"corretto\n"
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr "Errore della transazione. Impossibile modificare il gruppo.\n"
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+#, fuzzy
+msgid "Do not remove home directory and mail spool"
+msgstr "Non rimuovere la cartella home e la coda di posta"
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr "Forza la rimozione dei file non di proprietà dell'utente"
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr "Specificare l'utente da cancellare\n"
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr "Impossibile impostare i valori predefiniti\n"
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, fuzzy, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr "Impossibile rimuovere la cartella home: %s\n"
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr "errore interno. Impossibile rimuovere l'utente.\n"
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr "%s deve essere eseguito come root\n"
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr "L'UID dell'utente"
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr "Il GID o il nome del gruppo dell'utente"
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Home directory"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr ""
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Gruppi"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr "Specificare un utente da aggiungere\n"
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr "Specificare un gruppo da aggiungere\n"
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr "Errore della transazione. Impossibile aggiungere il gruppo.\n"
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr "Il GID dell'utente"
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr "Gruppi a cui aggiungere questo utente"
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr "Gruppi da cui rimuovere questo utente"
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "Bloccare l'account"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "Sbloccare l'account"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr "Specificare l'utente da modificare\n"
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+"Impossibile modificare l'utente - controllare che i nomi dei gruppi siano "
+"corretti\n"
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr "Impossibile modificare l'utente - utente già membro di gruppi?\n"
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr "Errore della transazione. Impossibile modificare l'utente.\n"
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Le password non coincidono"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "La password è scaduta."
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Password: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nuova password: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "reinserire la nuova password: "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "La password è scaduta."
diff --git a/src/po/ja.po b/src/po/ja.po
new file mode 100644
index 00000000..4323f222
--- /dev/null
+++ b/src/po/ja.po
@@ -0,0 +1,692 @@
+# translation of sss_daemon_ja.po to Japanese
+# Copyright (C) YEAR Red Hat, Inc.
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Noriko Mizumoto <noriko@fedoraproject.org>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: sss_daemon_ja\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-11-18 09:48+1000\n"
+"Last-Translator: Noriko Mizumoto <noriko@fedoraproject.org>\n"
+"Language-Team: Japanese <fedora-trans-ja@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr ""
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr ""
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr ""
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr ""
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr ""
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr ""
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr ""
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr ""
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr ""
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr ""
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr ""
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr ""
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr ""
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr ""
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr ""
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr ""
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr ""
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr ""
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr ""
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr ""
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr ""
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr ""
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr ""
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr ""
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr ""
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr ""
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr ""
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr ""
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr ""
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr ""
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr ""
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr ""
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr ""
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr ""
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr ""
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr ""
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr ""
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr ""
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr ""
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr ""
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+msgid "Password change failed. "
+msgstr ""
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr ""
diff --git a/src/po/nl.po b/src/po/nl.po
new file mode 100644
index 00000000..e3dd47fd
--- /dev/null
+++ b/src/po/nl.po
@@ -0,0 +1,693 @@
+# translation of sssd.master.sss_daemon.po to Dutch
+# Copyright (C) YEAR Red Hat, Inc.
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Richard van der Luit <nippur@fedoraproject.org>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: sssd.master.sss_daemon\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-11-19 12:19+0100\n"
+"Last-Translator: Richard van der Luit <nippur@fedoraproject.org>\n"
+"Language-Team: Dutch <nl@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr ""
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr ""
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr ""
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr ""
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr ""
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr ""
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr ""
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr ""
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr ""
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr ""
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr ""
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr ""
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr ""
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr ""
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr ""
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr ""
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr ""
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr ""
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr ""
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr ""
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr ""
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr ""
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr ""
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr ""
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr ""
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr ""
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr ""
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr ""
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr ""
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr ""
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr ""
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr ""
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr ""
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr ""
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr ""
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr ""
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr ""
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr ""
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr ""
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Wachtwoorden komen niet overeen"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Wachtwoord is verlopen."
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Wachtwoord: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nieuw Wachtwoord: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Voer nieuw wachtwoord nogmaals in: "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "Wachtwoord is verlopen."
diff --git a/src/po/pl.po b/src/po/pl.po
new file mode 100644
index 00000000..dcbc72d8
--- /dev/null
+++ b/src/po/pl.po
@@ -0,0 +1,718 @@
+# translation of pl.po to Polish
+# Piotr DrÄ…g <piotrdrag@gmail.com>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pl\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2010-02-06 01:03+0100\n"
+"Last-Translator: Piotr DrÄ…g <piotrdrag@gmail.com>\n"
+"Language-Team: Polish <trans-pl@lists.fedoraproject.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr "Ustawia liczbę komunikatów dziennika debugowania"
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr "Dołącza daty w dziennikach debugowania"
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr "Zapisuje komunikaty debugowania do plików dziennika"
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr "Czas oczekiwania na ping przed ponownym uruchomieniem usługi"
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr "Polecenie do uruchomienia usługi"
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr "Liczba prób połączenia do dostawców danych"
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr "Usługi SSSD do uruchomienia"
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr "Domeny SSSD do uruchomienia"
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr "Czas oczekiwania na komunikaty wysyłane przez SBUS"
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr "Wyrażenie regularne do przetworzenia nazwy użytkownika i domeny"
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr "Format zgodny z printf do wyświetlania pełnych nazw"
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr "Czas oczekiwania pamięci podręcznej wyliczania (sekundy)"
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr "Czas oczekiwania aktualizacji tła pamięci podręcznej wpisów (sekundy)"
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr "Ujemny czas oczekiwania pamięci podręcznej (sekundy)"
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr "Użytkownicy, którzy powinni być bezpośrednio ignorowani przez SSSD"
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr "Grupy, które powinny być bezpośrednio ignorowane przez SSSD"
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr "Czy filtrowani użytkownicy powinni pojawiać się w grupach"
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr "Wartość pola hasła, jaką dostawca NSS powinien zwrócić"
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+"Jak długo umożliwiać logowania w pamięci podręcznej między logowaniami w "
+"trybie online (dni)"
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr "Ile nieudanych prób zalogowania jest dozwolonych w trybie offline"
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+"Ile czasu (minut) nie pozwalać na zalogowanie po osiągnięciu "
+"offline_failed_login_attempts"
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr "Dostawca tożsamości"
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr "Dostawca uwierzytelniania"
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr "Dostawca kontroli dostępu"
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr "Dostawca zmiany hasła"
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr "Minimalny identyfikator użytkownika"
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr "Maksymalny identyfikator użytkownika"
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr "Czas oczekiwania na ping przed ponownym uruchomieniem domeny"
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr "Włącza wyliczanie wszystkich użytkowników/grup"
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr "Dane uwierzytelniające pamięci podręcznej dla logowań w trybie offline"
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr "Przechowuje mieszanie haseł"
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr "Wyświetla użytkowników/grupy w pełnej formie"
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr "Czas oczekiwania pamięci podręcznej wpisów (sekundy)"
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "Domena IPA"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "Adres serwera IPA"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr "Nazwa komputera klienta IPA"
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Adres serwera Kerberos"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr "Obszar Kerberos"
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr "Czas oczekiwania na uwierzytelnienie"
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+"Katalog do przechowywania pamięci podręcznych danych uwierzytelniających"
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr "Położenie pamięci podręcznej danych uwierzytelniających użytkownika"
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr "Położenie tablicy kluczy do sprawdzania danych uwierzytelniających"
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr "WÅ‚Ä…cza sprawdzanie danych uwierzytelniajÄ…cych"
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr "Główna usługa zmiany hasła"
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr "ldap_uri, adres URI serwera LDAP"
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr "Domyślna podstawowa DN"
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr "Typ Schema do użycia na serwerze LDAP, RFC2307"
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr "Domyślne DN dowiązania"
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr "Typ tokenu uwierzytelniania domyślnego DN dowiązania"
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr "Token uwierzytelniania domyślnego DN dowiązania"
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr "Czas do próby połączenia"
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr "Czas do próby synchronicznych działań LDAP"
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr "Czas między próbami ponownego połączenia w trybie offline"
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr "plik zawierajÄ…cy certyfikaty CA"
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr "Wymaga sprawdzenia certyfikatu TLS"
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr "Podaje używany mechanizm SASL"
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr "Podaje używany identyfikator upoważnienia SASL"
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr "Tablica kluczy usługi Kerberos"
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr "Używa uwierzytelniania Kerberos dla połączenia LDAP"
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr "Podąża za odsyłaniami LDAP"
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr "Czas oczekiwania na żądanie wyszukiwania"
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr "Czas między aktualizacjami wyliczania"
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr "Wymaga TLS dla wyszukiwania identyfikatorów, fałsz"
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr "Podstawowe DN dla wyszukiwania użytkowników"
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr "Zakres wyszukiwania użytkowników"
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr "Filtruje wyszukiwania użytkowników"
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr "Klasa obiektów dla użytkowników"
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Atrybut nazwy użytkownika"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "Atrybut UID"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr "Pierwszy atrybut GID"
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "Atrybut GECOS"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr "Atrybut katalogu domowego"
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr "Atrybut powłoki"
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "Atrybut UUID"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr "Atrybut głównego użytkownika (dla Kerberos)"
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr "ImiÄ™ i nazwisko"
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr "Atrybut memberOf"
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr "Atrybut czasu modyfikacji"
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr "Polityka do oszacowania wygaszenia hasła"
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr "Domyślna powłoka, /bin/bash"
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr "Podstawa katalogów domowych"
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr "Nazwa używanej biblioteki NSS"
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr "Używany stos PAM"
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr "Poziom debugowania, z jakim uruchomić"
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr "BÅ‚Ä…d podczas ustawiania lokalizacji\n"
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr "Proszę podać grupę do usunięcia\n"
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr "Błąd podczas inicjowania narzędzi - brak lokalnej domeny\n"
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr "Błąd podczas inicjowania narzędzi\n"
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr "Podano nieprawidłową domenę w FQDN\n"
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr "Grupa %s jest poza określonym zakresem identyfikatorów dla domeny\n"
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+"Nie ma takiej grupy w lokalnej domenie. Usuwanie grup jest dozwolone tylko w "
+"lokalnej domenie.\n"
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr "Wewnętrzny błąd. Nie można usunąć grupy.\n"
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr "Grupy, do których dodać tę grupę"
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr "Grupy, z których usunąć tę grupę"
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr "GID grupy"
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr "Proszę podać grupę do zmodyfikowania\n"
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+"Nie można odnaleźć grupy w lokalnej domenie, modyfikowanie grup jest "
+"dozwolone tylko w lokalnej domenie\n"
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr "Wewnętrzny błąd podczas przetwarzania parametrów\n"
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr "Członkowie grupy muszą być w tej samej domenie co grupa nadrzędna\n"
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+"Nie można odnaleźć grupy %s w lokalnej domenie, tylko grupy w lokalnej "
+"domenie sÄ… dozwolone\n"
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr "Wybrany GID jest spoza dozwolonego zakresu\n"
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+"Nie można zmodyfikować grupy - proszę sprawdzić, czy nazwy członków grupy są "
+"poprawne\n"
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+"Nie można zmodyfikować grupy - proszę sprawdzić, czy nazwa grupy jest "
+"poprawna\n"
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr "Błąd transakcji. Nie można zmodyfikować grupy.\n"
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr "Usuwa katalog domowy i bufor poczty"
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr "Nie usuwa katalogu domowego i bufora poczty"
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr "Wymusza usunięcie plików, których właścicielem nie jest użytkownik"
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr "Proszę podać użytkownika do usunięcia\n"
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr "Nie można ustawić domyślnych wartości\n"
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+"Użytkownik %s jest poza określonym zakresem identyfikatorów dla domeny\n"
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+"Katalog domowy nie zostanie usunięty - użytkownik nie jest właścicielem\n"
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr "Nie można usunąć katalogu domowego: %s\n"
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+"Nie ma takiego użytkownika w lokalnej domenie. Usuwanie użytkowników jest "
+"dozwolone tylko w lokalnej domenie.\n"
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr "Wewnętrzny błąd. Nie można usunąć użytkownika.\n"
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr "Brak pamięci\n"
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr "%s musi zostać uruchomione jako root\n"
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr "UID użytkownika"
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr "GID nazwy grupy użytkownika"
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr "CiÄ…g komentarza"
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Katalog domowy"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr "Powłoka logowania"
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Grupy"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr "Utworzy katalog użytkownika, jeśli nie istnieje"
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr "Nigdy nie tworzy katalogu użytkownika, zastępuje konfigurację"
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr "Proszę podać alternatywny katalog szkieletu"
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr "Proszę podać użytkownika do dodania\n"
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr "Grupy muszą być w tej samej domenie co użytkownik\n"
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr "Nie można odnaleźć grupy %s w lokalnej domenie\n"
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr "Nie można uzyskać informacji o grupie dla użytkownika\n"
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr "Wybrany UID jest spoza dozwolonego zakresu\n"
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr "Nie można uzyskać informacji o użytkowniku\n"
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+"Katalog domowy użytkownika już istnieje, dane z katalogu szkieletu nie "
+"zostanÄ… skopiowane\n"
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr "Nie można utworzyć katalogu domowego użytkownika: %s\n"
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr "Nie można utworzyć buforu poczty użytkownika: %s\n"
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+"Nie można przydzielić identyfikatora użytkownikowi - czy domena jest pełna?\n"
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+"Użytkownik lub grupa o tej samej nazwie lub identyfikatorze już istnieje\n"
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr "Błąd transakcji. Nie można dodać użytkownika.\n"
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr "Proszę podać grupę do dodania\n"
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr "Nie można przydzielić identyfikatora grupie - czy domena jest pełna?\n"
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr "Grupa o tej samej nazwie lub GID już istnieje\n"
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr "Błąd transakcji. Nie można dodać grupy.\n"
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr "GID użytkownika"
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr "Grupy, do których dodać tego użytkownika"
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr "Grupy, z których usunąć tego użytkownika"
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "Zablokowanie konta"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "Odblokowanie konta"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr "Proszę podać użytkownika do zmodyfikowania\n"
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+"Nie można odnaleźć użytkownika w lokalnej domenie, modyfikowanie "
+"użytkowników jest dozwolone tylko w lokalnej domenie\n"
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+"Nie można zmodyfikować użytkownika - proszę sprawdzić, czy nazwy grup są "
+"poprawne\n"
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+"Nie można zmodyfikować użytkownika - czy użytkownik jest już członkiem "
+"grup?\n"
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr "Błąd transakcji. Nie można zmodyfikować użytkownika.\n"
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Hasła nie zgadzają się"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr "Uwierzytelnienie w trybie offline"
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ", hasło w pamięci podręcznej wygaśnie za: "
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Dostawca zmiany hasła"
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Hasło: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nowe hasło: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Proszę ponownie podać nowe hasło: "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "Hasło wygasło."
diff --git a/src/po/pt.po b/src/po/pt.po
new file mode 100644
index 00000000..8f35958f
--- /dev/null
+++ b/src/po/pt.po
@@ -0,0 +1,712 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Red Hat, Inc.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: sssd.master.sss_daemon\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-12-08 18:06+0100\n"
+"Last-Translator: Rui Gouveia <rui.gouveia@globaltek.pt>\n"
+"Language-Team: PT <fedora-trans-pt@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Portuguese\n"
+"X-Poedit-Country: PORTUGAL\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr "Definir a verbosidade dos registos de depuração"
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr "Incluir data e hora nos registos de depuração"
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr "Gravar as mensagens de depuração em ficheiros de registo"
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr "Foi excedido o tempo do ping antes de reiniciar o serviço"
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr "Comando para iniciar serviço"
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr "Número de vezes para tentar ligação aos Fornecedores de Dados"
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr "Serviços SSSD a iniciar"
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr "Domínios SSSD a iniciar"
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr "Limite de tempo para mensagens enviadas sobre SBUS"
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr "Expressão regular para obter nome do utilizar e domínio"
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr "Formato compatível com o printf para apresentar nomes completos"
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr "Validade da cache de enumeração (segundos)"
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr "Validade da actualização da cache em segundo plano (segundos)"
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr "Validade da cache negativa (segundos)"
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr "Utilizadores que o SSSD devem explicitamente ignorar"
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr "Grupos que o SSSD devem explicitamente ignorar"
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr "Devem os utilizadores filtrados aparecer em grupos"
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr "O valor do campo da senha que o fornecedor NSS deve retornar"
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+"Durante quanto tempo devem ser permitidas as caches de sessões entre sessões "
+"online (dias)"
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr "Fornecedor de identidade"
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr "Fornecedor de autenticação"
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr "Fornecedor de controle de acesso"
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr "Fornecedor de Alteração de Senha"
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr "ID de utilizador mínimo"
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr "ID de utilizador máximo"
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr "Duração do ping antes de reiniciar o domínio"
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr "Permitir enumeração de todos os utilizadores/grupos"
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr "Efectuar cache de credenciais para sessões em modo desligado"
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr "Guardar hashes da senha"
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr "Apresentar utilizadores/grupos na forma completa"
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr "Validade da cache (segundos)"
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "Domínio IPA"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "Endereço do servidor IPA"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr "Nome da máquina do cliente IPA"
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Endereço do servidor Kerberos"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr "Reino Kerberos"
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr "Tempo de expiração da autenticação"
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr "Directório para armazenar as caches de credenciais"
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr "Localização da cache de credenciais dos utilizadores"
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr "Localização da tabela de chaves (keytab) para validar credenciais"
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr "Activar validação de credenciais"
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr "O principal do serviço de alteração de senha"
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr "ldap_uri, O URI do servidor LDAP"
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr "A base DN por omissão"
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr "O tipo de Schema em utilização no servidor LDAP, rfc2307"
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr "O DN por omissão para a ligação"
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr "O tipo de token de autenticação do bind DN por omissão"
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr "O token de autenticação do bind DN por omissão"
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr "Período de tempo para tentar ligação"
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr "Tempo de espera para tentar operações LDAP síncronas"
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr "Tempo de espera entre tentativas para re-conectar quando desligado"
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr "ficheiro que contêm os certificados CA"
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr "Obriga a verificação de certificados TLS"
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr "Especificar mecanismo sasl a utilizar"
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr "Especifique o id sasl para utilizar na autorização"
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr "Separador chave do serviço Kerberos"
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr "Utilizar autenticação Kerberos para ligações LDAP"
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr "Tempo de espera por um pedido de pesquisa"
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr "Período de tempo entre enumeração de actualizações"
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr "Requer TLS para consultas de ID, falso"
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr "DN base para pesquisa de utilizadores"
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr "Âmbito das pesquisas do utilizador"
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr "Filtro para as pesquisas do utilizador"
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr "Objectclass para utilizadores"
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Atributo do nome do utilizador"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "Atributo UID"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr "Atributo GID primário"
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "Atributo GECOS"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr "Atributo da pasta pessoal"
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr "Atributo da Shell"
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "Atributo UUID"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr "Atributo principal do utilizador (para Kerberos)"
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr "Nome Completo"
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr "Atributo memberOf"
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr "Atributo da alteração da data"
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr "Politica para avaliar a expiração da senha"
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr "Shell pré-definida, /bin/bash"
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr "Directório base para as pastas pessoais"
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr "O nome da biblioteca NSS a utilizar"
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr "Stack PAM a utilizar"
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr "O nível de depuração a utilizar durante a execução"
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr "Erro ao definir a configuração regional\n"
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr "Especifique grupo a remover\n"
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+#, fuzzy
+msgid "Error initializing the tools - no local domain\n"
+msgstr "Erro ao inicializar a ferramenta\n"
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr "Erro ao inicializar a ferramenta\n"
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr "Domínio inválido especificado no FQDN\n"
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr "O grupo %s está fora do intervalo de IDs para o domínio\n"
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+"Grupo não existe no domínio local. Apenas é permitido remover grupos no "
+"domínio local.\n"
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr "Erro interno. Incapaz de remover grupo.\n"
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr "Grupos para adicionar este grupo"
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr "Grupos para remover este projecto"
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr "O GID do grupo"
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr "Especifique grupo a modificar\n"
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+"Grupo não foi encontrado no domínio local. Apenas é permitido modificar "
+"grupos no domínio local\n"
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr "Erro interno ao processar parâmetros\n"
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr "Grupos membro têm de estar no mesmo domínio do grupo pai\n"
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+"Grupo %s não foi encontrado no domínio local. Apenas são permitidos grupos "
+"no domínio local\n"
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr "O GID seleccionado está fora do intervalo permitido\n"
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+"Incapaz de modificar grupo - verifique que o nome do grupo membro está "
+"correcto\n"
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+"Incapaz de modificar grupo - verifique que o nome do grupo está correcto\n"
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr "Erro de transacção. Não foi possível modificar o grupo.\n"
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr "Remover pasta pessoal e spool de correio"
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr "Não remover pasta pessoal e spool de correio"
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr "Forçar a remoção de ficheiros não pertencentes ao utilizador"
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr "Especificar o utilizador a remover\n"
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr "Incapaz de definir valores por omissão\n"
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr "O utilizador %s está fora do intervalo de IDs para o domínio\n"
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr "Pasta pessoal não removida - não pertence ao utilizador\n"
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr "Incapaz de remover pasta pessoal: %s\n"
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+"Utilizador não existe no domínio local. Apenas é permitido remover "
+"utilizadores no domínio local.\n"
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr "Erro interno. Incapaz de remover utilizador.\n"
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr "Memória esgotada\n"
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr "%s tem de executar como root\n"
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr "O UID do utilizador"
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr "O GID ou nome do grupo do utilizador"
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr "Texto do comentário"
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Pasta pessoal"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr "Shell"
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Grupos"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr "Criar pasta pessoal do utilizador, se ainda não existir"
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr "Nunca criar pasta pessoal do utilizador. Sobrepõem-se à configuração"
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr "Indique um directório skeleton alternativo"
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr "Indique utilizador a adicionar\n"
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr "Os grupos têm de pertencer ao mesmo domínio que o utilizador\n"
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr "Incapaz de encontrar o grupo %s no domínio local\n"
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr "Incapaz de obter informação do grupo para o utilizador\n"
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr "O UID seleccionado está fora do intervalo permitido\n"
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr "Incapaz de obter informação acerca do utilizador\n"
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+"A pasta pessoal do utilizador já existe. Conteúdo skeldir não copiado\n"
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr "Incapaz de criar pasta pessoal do utilizador: %s\n"
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr "Incapaz de criar o ficheiro de correio do utilizador: %s\n"
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr "Incapaz de alocar um ID para o utilizador - domínio cheio?\n"
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr "Já existe um utilizador ou grupo com o mesmo nome ou ID\n"
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr "Erro na transacção. Não foi possível adicionar o utilizador.\n"
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr "Indique grupo a adicionar\n"
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr "Incapaz de alocar um ID para o grupo - domínio cheio?\n"
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr "Já existe um grupo com o mesmo nome ou GID\n"
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr "Erro de transacção. Não foi possível adicionar o grupo.\n"
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr "O GID do utilizador"
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr "Grupos para adicionar este utilizador"
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr "Grupos para remover este utilizador"
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "Desactivar Conta"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "Activar a Conta"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr "Especifique utilizador a modificar\n"
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+"Utilizador não foi encontrado no domínio local. Apenas é permitido modificar "
+"utilizadores no domínio local\n"
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+"Incapaz de modificar utilizador - verifique se o nome do grupo está "
+"correcto\n"
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr "Incapaz de modificar utilizador - utilizador já é membro de grupos?\n"
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr "Erro na transacção. Não foi possível modificar o utilizador.\n"
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Senhas não coincidem"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Fornecedor de Alteração de Senha"
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Senha: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nova Senha: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Digite a senha novamente: "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "Senha expirou."
diff --git a/src/po/sss_daemon.pot b/src/po/sss_daemon.pot
new file mode 100644
index 00000000..ec29a084
--- /dev/null
+++ b/src/po/sss_daemon.pot
@@ -0,0 +1,691 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Red Hat, Inc.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr ""
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr ""
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr ""
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr ""
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr ""
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr ""
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr ""
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr ""
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr ""
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr ""
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr ""
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr ""
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr ""
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr ""
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr ""
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr ""
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr ""
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr ""
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr ""
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr ""
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr ""
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr ""
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr ""
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr ""
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr ""
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr ""
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr ""
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr ""
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr ""
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr ""
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr ""
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr ""
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr ""
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr ""
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr ""
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr ""
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr ""
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr ""
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr ""
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr ""
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr ""
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr ""
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr ""
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr ""
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr ""
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr ""
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr ""
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr ""
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr ""
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr ""
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr ""
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr ""
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr ""
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr ""
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr ""
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr ""
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr ""
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr ""
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr ""
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr ""
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr ""
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr ""
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr ""
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+msgid "Error initializing the tools - no local domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr ""
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr ""
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr ""
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr ""
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr ""
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr ""
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr ""
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr ""
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr ""
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr ""
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr ""
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr ""
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr ""
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr ""
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr ""
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr ""
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr ""
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr ""
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr ""
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr ""
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr ""
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr ""
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr ""
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr ""
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr ""
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr ""
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr ""
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr ""
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr ""
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr ""
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr ""
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr ""
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr ""
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr ""
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr ""
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr ""
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr ""
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+msgid "Password change failed. "
+msgstr ""
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr ""
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr ""
diff --git a/src/po/sv.po b/src/po/sv.po
new file mode 100644
index 00000000..f5be1b1c
--- /dev/null
+++ b/src/po/sv.po
@@ -0,0 +1,711 @@
+# Swedish messages for sssd server.
+# Copyright © 2009 Red Hat, Inc.
+# This file is distributed under the same license as the sssd package.
+# Göran Uddeborg <goeran@uddeborg.se>, 2009.
+#
+# $Revision: 1.4 $
+msgid ""
+msgstr ""
+"Project-Id-Version: sss_server\n"
+"Report-Msgid-Bugs-To: sssd-devel@lists.fedorahosted.org\n"
+"POT-Creation-Date: 2010-02-17 09:21-0500\n"
+"PO-Revision-Date: 2009-12-30 17:58+0100\n"
+"Last-Translator: Göran Uddeborg <goeran@uddeborg.se>\n"
+"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: config/SSSDConfig.py:39
+msgid "Set the verbosity of the debug logging"
+msgstr "Ange pratsamhet för felsökningsloggning"
+
+#: config/SSSDConfig.py:40
+msgid "Include timestamps in debug logs"
+msgstr "Inkludera tidsstämplar i felsökningsloggar"
+
+#: config/SSSDConfig.py:41
+msgid "Write debug messages to logfiles"
+msgstr "Skriv felmeddelanden till loggfiler"
+
+#: config/SSSDConfig.py:42
+msgid "Ping timeout before restarting service"
+msgstr "Ping-tidsgräns före tjänst startas om"
+
+#: config/SSSDConfig.py:43
+msgid "Command to start service"
+msgstr "Kommando för att starta tjänst"
+
+#: config/SSSDConfig.py:44
+msgid "Number of times to attempt connection to Data Providers"
+msgstr "Antal gånger att försöka ansluta till dataleverantörer"
+
+#: config/SSSDConfig.py:47
+msgid "SSSD Services to start"
+msgstr "SSSD-tjänster att starta"
+
+#: config/SSSDConfig.py:48
+msgid "SSSD Domains to start"
+msgstr "SSSD-domäner att starta"
+
+#: config/SSSDConfig.py:49
+msgid "Timeout for messages sent over the SBUS"
+msgstr "Tidsgräns för meddelanden skickade via SBUS"
+
+#: config/SSSDConfig.py:50
+msgid "Regex to parse username and domain"
+msgstr "Reguljäruttryck för att tolka användarnamn och domän"
+
+#: config/SSSDConfig.py:51
+msgid "Printf-compatible format for displaying fully-qualified names"
+msgstr "Printf-kompatibla format för att visa fullständigt kvalificerade namn"
+
+#: config/SSSDConfig.py:54
+msgid "Enumeration cache timeout length (seconds)"
+msgstr "Tidsgränslängd för uppräkningscache (sekunder)"
+
+#: config/SSSDConfig.py:55
+msgid "Entry cache background update timeout length (seconds)"
+msgstr "Tidsgränslängd för bakgrundsuppdateringar av postcache (sekunder)"
+
+#: config/SSSDConfig.py:56
+msgid "Negative cache timeout length (seconds)"
+msgstr "Tidsgränslängd för negativ cache (sekunder)"
+
+#: config/SSSDConfig.py:57
+msgid "Users that SSSD should explicitly ignore"
+msgstr "Användare som SSSD uttryckligen skall bortse ifrån"
+
+#: config/SSSDConfig.py:58
+msgid "Groups that SSSD should explicitly ignore"
+msgstr "Grupper som SSSD uttryckligen skall bortse ifrån"
+
+#: config/SSSDConfig.py:59
+msgid "Should filtered users appear in groups"
+msgstr "Skall filtrerade användare förekomma i grupper"
+
+#: config/SSSDConfig.py:60
+msgid "The value of the password field the NSS provider should return"
+msgstr "Värdet på lösenordfältet som NSS-leverantörer skall returnera"
+
+#: config/SSSDConfig.py:63
+msgid "How long to allow cached logins between online logins (days)"
+msgstr ""
+"Hur länge sparade inloggningar tillåts mellan online-inloggningar (dagar)"
+
+#: config/SSSDConfig.py:64
+msgid "How many failed logins attempts are allowed when offline"
+msgstr ""
+
+#: config/SSSDConfig.py:65
+msgid ""
+"How long (minutes) to deny login after offline_failed_login_attempts has "
+"been reached"
+msgstr ""
+
+#: config/SSSDConfig.py:68
+msgid "Identity provider"
+msgstr "Identifiera leverantör"
+
+#: config/SSSDConfig.py:69
+msgid "Authentication provider"
+msgstr "Autentiseringsleverantör"
+
+#: config/SSSDConfig.py:70
+msgid "Access control provider"
+msgstr "Leverantör av åtkomstkontroll"
+
+#: config/SSSDConfig.py:71
+msgid "Password change provider"
+msgstr "Leverantör av lösenordsändringar"
+
+#: config/SSSDConfig.py:74
+msgid "Minimum user ID"
+msgstr "Minsta användar-ID"
+
+#: config/SSSDConfig.py:75
+msgid "Maximum user ID"
+msgstr "Största användar-ID"
+
+#: config/SSSDConfig.py:76
+msgid "Ping timeout before restarting domain"
+msgstr "Ping-tidsgräns före domän startas om"
+
+#: config/SSSDConfig.py:77
+msgid "Enable enumerating all users/groups"
+msgstr "Aktivera uppräkning av alla användare/grupper"
+
+#: config/SSSDConfig.py:78
+msgid "Cache credentials for offline login"
+msgstr "Cache-kreditiv för frånkopplad inloggning"
+
+#: config/SSSDConfig.py:79
+msgid "Store password hashes"
+msgstr "Lagra lösenords-kontrollsummor"
+
+#: config/SSSDConfig.py:80
+msgid "Display users/groups in fully-qualified form"
+msgstr "Visa användare/grupper i fullständigt kvalificerat format"
+
+#: config/SSSDConfig.py:81
+msgid "Entry cache timeout length (seconds)"
+msgstr "Tidsgränslängd för postcache (sekunder)"
+
+#: config/SSSDConfig.py:84
+msgid "IPA domain"
+msgstr "IPA-domän"
+
+#: config/SSSDConfig.py:85
+msgid "IPA server address"
+msgstr "IPA-serveradress"
+
+#: config/SSSDConfig.py:86
+msgid "IPA client hostname"
+msgstr "IPA-klienvärdnamn"
+
+#: config/SSSDConfig.py:89 config/SSSDConfig.py:116
+msgid "Kerberos server address"
+msgstr "Kerberosserveradress"
+
+#: config/SSSDConfig.py:90 config/SSSDConfig.py:117
+msgid "Kerberos realm"
+msgstr "Kerberosrike"
+
+#: config/SSSDConfig.py:91
+msgid "Authentication timeout"
+msgstr "Autentiseringstidsgräns"
+
+#: config/SSSDConfig.py:94
+msgid "Directory to store credential caches"
+msgstr "Katalog att lagra kreditiv-cachar i"
+
+#: config/SSSDConfig.py:95
+msgid "Location of the user's credential cache"
+msgstr "Plats för användarens kreditiv-cache"
+
+#: config/SSSDConfig.py:96
+msgid "Location of the keytab to validate credentials"
+msgstr "Plats för nyckeltabellen för att validera kreditiv"
+
+#: config/SSSDConfig.py:97
+msgid "Enable credential validation"
+msgstr "Aktivera validering av kreditiv"
+
+#: config/SSSDConfig.py:100
+msgid "The principal of the change password service"
+msgstr "Huvudmannen för tjänsten att ändra lösenord"
+
+#: config/SSSDConfig.py:103
+msgid "ldap_uri, The URI of the LDAP server"
+msgstr "ldap_uri, URI:n för LDAP-servern"
+
+#: config/SSSDConfig.py:104
+msgid "The default base DN"
+msgstr "Standard bas-DN"
+
+#: config/SSSDConfig.py:105
+msgid "The Schema Type in use on the LDAP server, rfc2307"
+msgstr "Schematypen som används i LDAP-servern, rfc2307"
+
+#: config/SSSDConfig.py:106
+msgid "The default bind DN"
+msgstr "Standard bindnings-DN"
+
+#: config/SSSDConfig.py:107
+msgid "The type of the authentication token of the default bind DN"
+msgstr "Typen på autenticerings-token för standard bindnings-DN"
+
+#: config/SSSDConfig.py:108
+msgid "The authentication token of the default bind DN"
+msgstr "Autenticerings-token för standard bindnings-DN"
+
+#: config/SSSDConfig.py:109
+msgid "Length of time to attempt connection"
+msgstr "Tidslängd att försöka ansluta"
+
+#: config/SSSDConfig.py:110
+msgid "Length of time to attempt synchronous LDAP operations"
+msgstr "Tidslängd att försök synkrona LDAP-operationer"
+
+#: config/SSSDConfig.py:111
+msgid "Length of time between attempts to reconnect while offline"
+msgstr "Tidslängd mellan försök att återansluta under frånkoppling"
+
+#: config/SSSDConfig.py:112
+msgid "file that contains CA certificates"
+msgstr "fil som innehåller CA-certifikat"
+
+#: config/SSSDConfig.py:113
+msgid "Require TLS certificate verification"
+msgstr "Kräv TLS-certifikatverifiering"
+
+#: config/SSSDConfig.py:114
+msgid "Specify the sasl mechanism to use"
+msgstr "Ange sasl-mekanismen att använda"
+
+#: config/SSSDConfig.py:115
+msgid "Specify the sasl authorization id to use"
+msgstr "Ange sasl-auktorisering-id att använda"
+
+#: config/SSSDConfig.py:118
+msgid "Kerberos service keytab"
+msgstr "Kerberostjänstens nyckeltabell"
+
+#: config/SSSDConfig.py:119
+msgid "Use Kerberos auth for LDAP connection"
+msgstr "Avnänd Kerberosautenticering för LDAP-anslutning"
+
+#: config/SSSDConfig.py:120
+msgid "Follow LDAP referrals"
+msgstr ""
+
+#: config/SSSDConfig.py:123
+msgid "Length of time to wait for a search request"
+msgstr "Tidslängd att vänta på en sökbegäran"
+
+#: config/SSSDConfig.py:124
+msgid "Length of time between enumeration updates"
+msgstr "Tidslängd mellan uppräkningsuppdateringar"
+
+#: config/SSSDConfig.py:125
+msgid "Require TLS for ID lookups, false"
+msgstr "Kräv TLS för ID-uppslagningar, falsk"
+
+#: config/SSSDConfig.py:126
+msgid "Base DN for user lookups"
+msgstr "Bas-DN för användaruppslagningar"
+
+#: config/SSSDConfig.py:127
+msgid "Scope of user lookups"
+msgstr "Omfång av användaruppslagningar"
+
+#: config/SSSDConfig.py:128
+msgid "Filter for user lookups"
+msgstr "Filter för användaruppslagningar"
+
+#: config/SSSDConfig.py:129
+msgid "Objectclass for users"
+msgstr "Objektklass för användare"
+
+#: config/SSSDConfig.py:130
+msgid "Username attribute"
+msgstr "Användarnamnsattribut"
+
+#: config/SSSDConfig.py:131
+msgid "UID attribute"
+msgstr "UID-attribut"
+
+#: config/SSSDConfig.py:132
+msgid "Primary GID attribute"
+msgstr "Primärt GID-attribut"
+
+#: config/SSSDConfig.py:133
+msgid "GECOS attribute"
+msgstr "GECOS-attribut"
+
+#: config/SSSDConfig.py:134
+msgid "Home directory attribute"
+msgstr "Hemkatalogattribut"
+
+#: config/SSSDConfig.py:135
+msgid "Shell attribute"
+msgstr "Skalattribut"
+
+#: config/SSSDConfig.py:136
+msgid "UUID attribute"
+msgstr "UUID-attribut"
+
+#: config/SSSDConfig.py:137
+msgid "User principal attribute (for Kerberos)"
+msgstr "Användarens huvudmansattribut (för Kerberos)"
+
+#: config/SSSDConfig.py:138
+msgid "Full Name"
+msgstr "Fullständigt namn"
+
+#: config/SSSDConfig.py:139
+msgid "memberOf attribute"
+msgstr "medlemAv-attribut"
+
+#: config/SSSDConfig.py:140
+msgid "Modification time attribute"
+msgstr "Modifieringstidsattribut"
+
+#: config/SSSDConfig.py:143
+msgid "Policy to evaluate the password expiration"
+msgstr "Policy för att utvärdera utgång av lösenord"
+
+#: config/SSSDConfig.py:146
+msgid "Default shell, /bin/bash"
+msgstr "Standardskal, /bin/bash"
+
+#: config/SSSDConfig.py:147
+msgid "Base for home directories"
+msgstr "Bas för hemkataloger"
+
+#: config/SSSDConfig.py:150
+msgid "The name of the NSS library to use"
+msgstr "Namnet på NSS-biblioteket att använda"
+
+#: config/SSSDConfig.py:153
+msgid "PAM stack to use"
+msgstr "PAM-stack att använda"
+
+#: tools/sss_groupdel.c:43 tools/sss_groupmod.c:42 tools/sss_userdel.c:45
+#: tools/sss_useradd.c:114 tools/sss_groupadd.c:41 tools/sss_usermod.c:46
+msgid "The debug level to run with"
+msgstr "Felsökningsnivå att köra med"
+
+#: tools/sss_groupdel.c:52 tools/sss_groupmod.c:63 tools/sss_userdel.c:57
+#: tools/sss_useradd.c:137 tools/sss_groupadd.c:56 tools/sss_usermod.c:70
+msgid "Error setting the locale\n"
+msgstr "Fel när lokalen sattes\n"
+
+#: tools/sss_groupdel.c:70
+msgid "Specify group to delete\n"
+msgstr "Ange grupp att ta bort\n"
+
+#: tools/sss_groupdel.c:81 tools/sss_groupmod.c:111 tools/sss_userdel.c:102
+#: tools/sss_useradd.c:183 tools/sss_groupadd.c:86 tools/sss_usermod.c:126
+#, fuzzy
+msgid "Error initializing the tools - no local domain\n"
+msgstr "Fel vid initiering av verktygen\n"
+
+#: tools/sss_groupdel.c:83 tools/sss_groupmod.c:113 tools/sss_userdel.c:104
+#: tools/sss_useradd.c:185 tools/sss_groupadd.c:88 tools/sss_usermod.c:128
+msgid "Error initializing the tools\n"
+msgstr "Fel vid initiering av verktygen\n"
+
+#: tools/sss_groupdel.c:92 tools/sss_groupmod.c:121 tools/sss_userdel.c:113
+#: tools/sss_useradd.c:194 tools/sss_groupadd.c:97 tools/sss_usermod.c:137
+msgid "Invalid domain specified in FQDN\n"
+msgstr "Ogiltig domän angiven i FQDN\n"
+
+#: tools/sss_groupdel.c:107
+#, c-format
+msgid "Group %s is outside the defined ID range for domain\n"
+msgstr "Grupp %s är utanför det definierade ID-intervallet för domänen\n"
+
+#: tools/sss_groupdel.c:136
+msgid ""
+"No such group in local domain. Removing groups only allowed in local "
+"domain.\n"
+msgstr ""
+"Ingen sådan grupp i den lokala domänen. Att ta bort grupper är endast "
+"tillåtet i den lokala domänen.\n"
+
+#: tools/sss_groupdel.c:141
+msgid "Internal error. Could not remove group.\n"
+msgstr "Internt fel. Det gick inte att ta bort gruppen.\n"
+
+#: tools/sss_groupmod.c:44
+msgid "Groups to add this group to"
+msgstr "Grupper att lägga till denna grupp till"
+
+#: tools/sss_groupmod.c:46
+msgid "Groups to remove this group from"
+msgstr "Grupper att ta bort denna grupp från"
+
+#: tools/sss_groupmod.c:48 tools/sss_groupadd.c:43
+msgid "The GID of the group"
+msgstr "GID:t för gruppen"
+
+#: tools/sss_groupmod.c:98
+msgid "Specify group to modify\n"
+msgstr "Ange grupp att ändra\n"
+
+#: tools/sss_groupmod.c:130
+msgid ""
+"Cannot find group in local domain, modifying groups is allowed only in local "
+"domain\n"
+msgstr ""
+"Ken inte hitta gruppen i den lokala domänen, att ändra grupper är endast "
+"tillåtet i den lokala domänen\n"
+
+#: tools/sss_groupmod.c:143 tools/sss_groupmod.c:170 tools/sss_useradd.c:203
+#: tools/sss_usermod.c:162 tools/sss_usermod.c:189
+msgid "Internal error while parsing parameters\n"
+msgstr "Internt fel vid tolkning av parametrar\n"
+
+#: tools/sss_groupmod.c:151 tools/sss_groupmod.c:178
+msgid "Member groups must be in the same domain as parent group\n"
+msgstr "Medlemsgrupper måster ligga i samma domän som föräldragrupper\n"
+
+#: tools/sss_groupmod.c:159 tools/sss_groupmod.c:186 tools/sss_usermod.c:178
+#: tools/sss_usermod.c:205
+#, c-format
+msgid ""
+"Cannot find group %s in local domain, only groups in local domain are "
+"allowed\n"
+msgstr ""
+"Kan inte hitta grupp %s i den lokala domänen, endast grupper i den lokala "
+"domänen är tillåtna\n"
+
+#: tools/sss_groupmod.c:194 tools/sss_groupadd.c:106
+msgid "The selected GID is outside the allowed range\n"
+msgstr "Den valda GID:n är utanför det tillåtna intervallet\n"
+
+#: tools/sss_groupmod.c:222
+msgid "Could not modify group - check if member group names are correct\n"
+msgstr ""
+"Det gick inte att ändra gruppen - kontrollera om medlemsgruppsnamnen är "
+"riktiga\n"
+
+#: tools/sss_groupmod.c:226
+msgid "Could not modify group - check if groupname is correct\n"
+msgstr ""
+"Det gick inte att ändra gruppen - kontrollera om gruppnamnet är riktigt\n"
+
+#: tools/sss_groupmod.c:230
+msgid "Transaction error. Could not modify group.\n"
+msgstr "Transaktionsfel. Det gick inte att ändra gruppen.\n"
+
+#: tools/sss_userdel.c:46
+msgid "Remove home directory and mail spool"
+msgstr "Ta bort hemkatalog och brevlåda"
+
+#: tools/sss_userdel.c:47
+msgid "Do not remove home directory and mail spool"
+msgstr "Ta inte bort hemkatalog och brevlåda"
+
+#: tools/sss_userdel.c:48
+msgid "Force removal of files not owned by the user"
+msgstr "Framtvinga borttagning av filer som inte ägs av användaren"
+
+#: tools/sss_userdel.c:91
+msgid "Specify user to delete\n"
+msgstr "Ange användare att ta bort\n"
+
+#: tools/sss_userdel.c:123 tools/sss_useradd.c:244
+msgid "Cannot set default values\n"
+msgstr "Kan inte sätta standardvärden\n"
+
+#: tools/sss_userdel.c:141
+#, c-format
+msgid "User %s is outside the defined ID range for domain\n"
+msgstr "Användare %s är utanför det definierade ID-intervallet för domänen\n"
+
+#: tools/sss_userdel.c:172
+msgid "Not removing home dir - not owned by user\n"
+msgstr "Tar inte bort hemkatalogen - ägs inte av användaren\n"
+
+#: tools/sss_userdel.c:174
+#, c-format
+msgid "Cannot remove homedir: %s\n"
+msgstr "Kan inte ta bort hemkatalogen: %s\n"
+
+#: tools/sss_userdel.c:186
+msgid ""
+"No such user in local domain. Removing users only allowed in local domain.\n"
+msgstr ""
+"Ingen sådan användare i den lokala domänen. Det går endast att ta bort "
+"användare i den lokala domänen.\n"
+
+#: tools/sss_userdel.c:191
+msgid "Internal error. Could not remove user.\n"
+msgstr "Internt fel. Det gick inte att ta bort användaren.\n"
+
+#: tools/tools_util.c:292
+msgid "Out of memory\n"
+msgstr "Slut på minne\n"
+
+#: tools/tools_util.h:34
+#, c-format
+msgid "%s must be run as root\n"
+msgstr "%s måste köras som root\n"
+
+#: tools/sss_useradd.c:115 tools/sss_usermod.c:47
+msgid "The UID of the user"
+msgstr "Användarens UID"
+
+#: tools/sss_useradd.c:116
+msgid "The GID or group name of the user"
+msgstr "Användarens GID eller gruppnamn"
+
+#: tools/sss_useradd.c:117 tools/sss_usermod.c:49
+msgid "The comment string"
+msgstr "Kommentarsträngen"
+
+#: tools/sss_useradd.c:118 tools/sss_usermod.c:50
+msgid "Home directory"
+msgstr "Hemkatalogen"
+
+#: tools/sss_useradd.c:119 tools/sss_usermod.c:51
+msgid "Login shell"
+msgstr "Inloggningsskalet"
+
+#: tools/sss_useradd.c:120
+msgid "Groups"
+msgstr "Grupper"
+
+#: tools/sss_useradd.c:121
+msgid "Create user's directory if it does not exist"
+msgstr "Skapa användarens katalog om den inte redan finns"
+
+#: tools/sss_useradd.c:122
+msgid "Never create user's directory, overrides config"
+msgstr "Skapa aldrig användarens katalog, åsidosätter konfigurationen"
+
+#: tools/sss_useradd.c:123
+msgid "Specify an alternative skeleton directory"
+msgstr "Ange en alternativ skelettkatalog"
+
+#: tools/sss_useradd.c:172
+msgid "Specify user to add\n"
+msgstr "Ange en användare att lägga till\n"
+
+#: tools/sss_useradd.c:211 tools/sss_usermod.c:170 tools/sss_usermod.c:197
+msgid "Groups must be in the same domain as user\n"
+msgstr "Grupper måste finnas i samma domän som användaren\n"
+
+#: tools/sss_useradd.c:219
+#, c-format
+msgid "Cannot find group %s in local domain\n"
+msgstr "Hittar inte grupp %s i den lokala domänen\n"
+
+#: tools/sss_useradd.c:229
+msgid "Cannot get group information for the user\n"
+msgstr "Kan inte få gruppinformation för användaren\n"
+
+#: tools/sss_useradd.c:251 tools/sss_usermod.c:153
+msgid "The selected UID is outside the allowed range\n"
+msgstr "Den valda UID:n är utanför det tillåtna intervallet\n"
+
+#: tools/sss_useradd.c:285
+msgid "Cannot get info about the user\n"
+msgstr "Kan inte få information om användaren\n"
+
+#: tools/sss_useradd.c:299
+msgid "User's home directory already exists, not copying data from skeldir\n"
+msgstr ""
+"Användarens hemkatalog finns redan, kopierar inte data från "
+"skelettkatalogen\n"
+
+#: tools/sss_useradd.c:302
+#, c-format
+msgid "Cannot create user's home directory: %s\n"
+msgstr "Kan inte skapa användarens hemkatalog: %s\n"
+
+#: tools/sss_useradd.c:313
+#, c-format
+msgid "Cannot create user's mail spool: %s\n"
+msgstr "Kan inte skapa användarens brevlåda: %s\n"
+
+#: tools/sss_useradd.c:325
+msgid "Could not allocate ID for the user - domain full?\n"
+msgstr "Det gick inte att allokera ID för användaren - full domän?\n"
+
+#: tools/sss_useradd.c:329
+msgid "A user or group with the same name or ID already exists\n"
+msgstr "En användare eller grupp med samma namn eller ID finns redan\n"
+
+#: tools/sss_useradd.c:335
+msgid "Transaction error. Could not add user.\n"
+msgstr "Transaktionsfel. Det gick inte att lägga till användaren.\n"
+
+#: tools/sss_groupadd.c:75
+msgid "Specify group to add\n"
+msgstr "Ange en grupp att lägga till\n"
+
+#: tools/sss_groupadd.c:133
+msgid "Could not allocate ID for the group - domain full?\n"
+msgstr "Det gick inte att allokera ID för gruppen - full domän?\n"
+
+#: tools/sss_groupadd.c:137
+msgid "A group with the same name or GID already exists\n"
+msgstr "En grupp med samma namn eller GID finns redan\n"
+
+#: tools/sss_groupadd.c:142
+msgid "Transaction error. Could not add group.\n"
+msgstr "Transaktionsfel. Det gick inte att lägga till gruppen.\n"
+
+#: tools/sss_usermod.c:48
+msgid "The GID of the user"
+msgstr "Användarens GID"
+
+#: tools/sss_usermod.c:52
+msgid "Groups to add this user to"
+msgstr "Grupper att lägga till denna användare till"
+
+#: tools/sss_usermod.c:53
+msgid "Groups to remove this user from"
+msgstr "Grupper att ta bort denna användare ifrån"
+
+#: tools/sss_usermod.c:54
+msgid "Lock the account"
+msgstr "LÃ¥s kontot"
+
+#: tools/sss_usermod.c:55
+msgid "Unlock the account"
+msgstr "LÃ¥s upp kontot"
+
+#: tools/sss_usermod.c:115
+msgid "Specify user to modify\n"
+msgstr "Ange användare att ändra\n"
+
+#: tools/sss_usermod.c:146
+msgid ""
+"Cannot find user in local domain, modifying users is allowed only in local "
+"domain\n"
+msgstr ""
+"Det gick inte att hitta användaren i den lokala domänen, det går bara att "
+"ändra användare i den lokala domänen\n"
+
+#: tools/sss_usermod.c:241
+msgid "Could not modify user - check if group names are correct\n"
+msgstr ""
+"Det gick inte att ändra användaren - kontrollera att gruppnamnen är riktiga\n"
+
+#: tools/sss_usermod.c:245
+msgid "Could not modify user - user already member of groups?\n"
+msgstr ""
+"Det gick inte att ändra användaren - är användaren redan medlem i grupper?\n"
+
+#: tools/sss_usermod.c:249
+msgid "Transaction error. Could not modify user.\n"
+msgstr "Transaktionsfel. Det gick inte att ändra användaren.\n"
+
+#: sss_client/pam_sss.c:342
+msgid "Passwords do not match"
+msgstr "Lösenorden stämmer inte överens"
+
+#: sss_client/pam_sss.c:411
+msgid "Offline authentication"
+msgstr ""
+
+#: sss_client/pam_sss.c:412
+msgid ", your cached password will expire at: "
+msgstr ""
+
+#: sss_client/pam_sss.c:462
+msgid "Offline authentication, authentication is denied until: "
+msgstr ""
+
+#: sss_client/pam_sss.c:489
+msgid "System is offline, password change not possible"
+msgstr ""
+
+#: sss_client/pam_sss.c:519
+#, fuzzy
+msgid "Password change failed. "
+msgstr "Leverantör av lösenordsändringar"
+
+#: sss_client/pam_sss.c:520
+msgid "Server message: "
+msgstr ""
+
+#: sss_client/pam_sss.c:855
+msgid "Password: "
+msgstr "Lösenord: "
+
+#: sss_client/pam_sss.c:887
+msgid "New Password: "
+msgstr "Nytt lösenord: "
+
+#: sss_client/pam_sss.c:888
+msgid "Reenter new Password: "
+msgstr "Skriv det nya lösenordet igen: "
+
+#: sss_client/pam_sss.c:1092
+msgid "Password has expired."
+msgstr "Lösenordet har gått ut."
diff --git a/src/providers/child_common.c b/src/providers/child_common.c
new file mode 100644
index 00000000..2ad0f04e
--- /dev/null
+++ b/src/providers/child_common.c
@@ -0,0 +1,416 @@
+/*
+ SSSD
+
+ Common helper functions to be used in child processes
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <fcntl.h>
+#include <tevent.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/find_uid.h"
+#include "db/sysdb.h"
+#include "providers/child_common.h"
+
+/* Async communication with the child process via a pipe */
+
+struct write_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+ size_t written;
+};
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *buf, size_t len, int fd)
+{
+ struct tevent_req *req;
+ struct write_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct write_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = buf;
+ state->len = len;
+ state->written = 0;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_WRITE,
+ write_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct write_pipe_state *state = tevent_req_data(req,
+ struct write_pipe_state);
+ ssize_t size;
+
+ if (flags & TEVENT_FD_READ) {
+ DEBUG(1, ("write_pipe_done called with TEVENT_FD_READ,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = write(state->fd,
+ state->buf + state->written,
+ state->len - state->written);
+ if (size == -1) {
+ if (errno == EAGAIN || errno == EINTR) return;
+ DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+ tevent_req_error(req, errno);
+ return;
+
+ } else if (size >= 0) {
+ state->written += size;
+ if (state->written > state->len) {
+ DEBUG(1, ("write to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ } else {
+ DEBUG(1, ("unexpected return value of write [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ if (state->len == state->written) {
+ DEBUG(6, ("All data has been sent!\n"));
+ tevent_req_done(req);
+ return;
+ }
+}
+
+int write_pipe_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+struct read_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+};
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd)
+{
+ struct tevent_req *req;
+ struct read_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct read_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = talloc_array(state, uint8_t, MAX_CHILD_MSG_SIZE);
+ state->len = 0;
+ if (state->buf == NULL) goto fail;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ,
+ read_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct read_pipe_state *state = tevent_req_data(req,
+ struct read_pipe_state);
+ ssize_t size;
+ errno_t err;
+
+ if (flags & TEVENT_FD_WRITE) {
+ DEBUG(1, ("read_pipe_done called with TEVENT_FD_WRITE,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = read(state->fd,
+ state->buf + state->len,
+ MAX_CHILD_MSG_SIZE - state->len);
+ if (size == -1) {
+ err = errno;
+ if (err == EAGAIN || err == EINTR) {
+ return;
+ }
+
+ DEBUG(1, ("read failed [%d][%s].\n", err, strerror(err)));
+ tevent_req_error(req, err);
+ return;
+
+ } else if (size > 0) {
+ state->len += size;
+ if (state->len > MAX_CHILD_MSG_SIZE) {
+ DEBUG(1, ("read to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ } else if (size == 0) {
+ DEBUG(6, ("EOF received, client finished\n"));
+ tevent_req_done(req);
+ return;
+
+ } else {
+ DEBUG(1, ("unexpected return value of read [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+}
+
+int read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct read_pipe_state *state;
+ state = tevent_req_data(req, struct read_pipe_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_steal(mem_ctx, state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+/* The pipes to communicate with the child must be nonblocking */
+void fd_nonblocking(int fd)
+{
+ int flags;
+ int ret;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ ret = errno;
+ DEBUG(1, ("F_GETFL failed [%d][%s].\n", ret, strerror(ret)));
+ return;
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ ret = errno;
+ DEBUG(1, ("F_SETFL failed [%d][%s].\n", ret, strerror(ret)));
+ }
+
+ return;
+}
+
+void child_sig_handler(struct tevent_context *ev,
+ struct tevent_signal *sige, int signum,
+ int count, void *__siginfo, void *pvt)
+{
+ int ret;
+ int child_status;
+
+ DEBUG(7, ("Waiting for [%d] childeren.\n", count));
+ do {
+ errno = 0;
+ ret = waitpid(-1, &child_status, WNOHANG);
+
+ if (ret == -1) {
+ DEBUG(1, ("waitpid failed [%d][%s].\n", errno, strerror(errno)));
+ } else if (ret == 0) {
+ DEBUG(1, ("waitpid did not found a child with changed status.\n"));
+ } else {
+ if (WEXITSTATUS(child_status) != 0) {
+ DEBUG(1, ("child [%d] failed with status [%d].\n", ret,
+ child_status));
+ } else {
+ DEBUG(4, ("child [%d] finished successful.\n", ret));
+ }
+ }
+
+ --count;
+ } while (count < 0);
+
+ return;
+}
+
+static errno_t prepare_child_argv(TALLOC_CTX *mem_ctx,
+ int child_debug_fd,
+ const char *binary,
+ char ***_argv)
+{
+ uint_t argc = 3; /* program name, debug_level and NULL */
+ char ** argv;
+ errno_t ret = EINVAL;
+
+ /* Save the current state in case an interrupt changes it */
+ bool child_debug_to_file = debug_to_file;
+ bool child_debug_timestamps = debug_timestamps;
+
+ if (child_debug_to_file) argc++;
+ if (!child_debug_timestamps) argc++;
+
+ /* program name, debug_level,
+ * debug_to_file, debug_timestamps
+ * and NULL */
+ argv = talloc_array(mem_ctx, char *, argc);
+ if (argv == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ return ENOMEM;
+ }
+
+ argv[--argc] = NULL;
+
+ argv[--argc] = talloc_asprintf(argv, "--debug-level=%d",
+ debug_level);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (child_debug_to_file) {
+ argv[--argc] = talloc_asprintf(argv, "--debug-fd=%d",
+ child_debug_fd);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!child_debug_timestamps) {
+ argv[--argc] = talloc_strdup(argv, "--debug-timestamps=0");
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ argv[--argc] = talloc_strdup(argv, binary);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (argc != 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+
+ *_argv = argv;
+
+ return EOK;
+
+fail:
+ talloc_free(argv);
+ return ret;
+}
+
+errno_t exec_child(TALLOC_CTX *mem_ctx,
+ int *pipefd_to_child, int *pipefd_from_child,
+ const char *binary, int debug_fd)
+{
+ int ret;
+ errno_t err;
+ char **argv;
+
+ close(pipefd_to_child[1]);
+ ret = dup2(pipefd_to_child[0], STDIN_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ close(pipefd_from_child[0]);
+ ret = dup2(pipefd_from_child[1], STDOUT_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ ret = prepare_child_argv(mem_ctx, debug_fd,
+ binary, &argv);
+ if (ret != EOK) {
+ DEBUG(1, ("prepare_child_argv.\n"));
+ return ret;
+ }
+
+ ret = execv(binary, argv);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("execv failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ return EOK;
+}
+
+void child_cleanup(int readfd, int writefd)
+{
+ int ret;
+
+ if (readfd != -1) {
+ ret = close(readfd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+ if (writefd != -1) {
+ ret = close(writefd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+}
diff --git a/src/providers/child_common.h b/src/providers/child_common.h
new file mode 100644
index 00000000..a441df3c
--- /dev/null
+++ b/src/providers/child_common.h
@@ -0,0 +1,73 @@
+/*
+ SSSD
+
+ Common helper functions to be used in child processes
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __CHILD_COMMON_H__
+#define __CHILD_COMMON_H__
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <tevent.h>
+
+#include "util/util.h"
+
+#define IN_BUF_SIZE 512
+#define MAX_CHILD_MSG_SIZE 255
+
+struct response {
+ size_t max_size;
+ size_t size;
+ uint8_t *buf;
+};
+
+struct io_buffer {
+ uint8_t *data;
+ size_t size;
+};
+
+/* Async communication with the child process via a pipe */
+struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *buf, size_t len, int fd);
+int write_pipe_recv(struct tevent_req *req);
+
+struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd);
+int read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len);
+
+/* The pipes to communicate with the child must be nonblocking */
+void fd_nonblocking(int fd);
+
+void child_sig_handler(struct tevent_context *ev,
+ struct tevent_signal *sige, int signum,
+ int count, void *__siginfo, void *pvt);
+
+errno_t exec_child(TALLOC_CTX *mem_ctx,
+ int *pipefd_to_child, int *pipefd_from_child,
+ const char *binary, int debug_fd);
+
+void child_cleanup(int readfd, int writefd);
+
+#endif /* __CHILD_COMMON_H__ */
diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
new file mode 100644
index 00000000..76ba4cff
--- /dev/null
+++ b/src/providers/data_provider.h
@@ -0,0 +1,219 @@
+/*
+ SSSD
+
+ Data Provider, private header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DATA_PROVIDER_H__
+#define __DATA_PROVIDER_H__
+
+#include <stdint.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stdbool.h>
+#include "talloc.h"
+#include "tevent.h"
+#include "ldb.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "sbus/sbus_client.h"
+#include "sss_client/sss_cli.h"
+
+#define DATA_PROVIDER_VERSION 0x0001
+#define DATA_PROVIDER_SERVICE_NAME "dp"
+#define DATA_PROVIDER_PIPE "private/sbus-dp"
+
+#define DATA_PROVIDER_DB_FILE "sssd.ldb"
+#define DATA_PROVIDER_DB_CONF_SEC "config/services/nss"
+
+#define MOD_OFFLINE 0x0000
+#define MOD_ONLINE 0x0001
+
+#define DP_INTERFACE "org.freedesktop.sssd.dataprovider"
+#define DP_PATH "/org/freedesktop/sssd/dataprovider"
+
+#define BE_PROVIDE_ACC_INFO (1<<8)
+#define BE_PROVIDE_PAM (1<<9)
+#define BE_PROVIDE_POLICY (1<<10)
+
+#define DP_METHOD_REGISTER "RegisterService"
+#define DP_METHOD_ONLINE "getOnline"
+#define DP_METHOD_GETACCTINFO "getAccountInfo"
+#define DP_METHOD_PAMHANDLER "pamHandler"
+
+#define DP_ERR_OK 0
+#define DP_ERR_OFFLINE 1
+#define DP_ERR_TIMEOUT 2
+#define DP_ERR_FATAL 3
+
+#define BE_ATTR_CORE 1
+#define BE_ATTR_MEM 2
+#define BE_ATTR_ALL 3
+
+#define BE_FILTER_NAME 1
+#define BE_FILTER_IDNUM 2
+
+#define BE_REQ_USER 0x0001
+#define BE_REQ_GROUP 0x0002
+#define BE_REQ_INITGROUPS 0x0003
+#define BE_REQ_FAST 0x1000
+
+/* AUTH related common data and functions */
+
+#define DEBUG_PAM_DATA(level, pd) do { \
+ if (level <= debug_level) pam_print_data(level, pd); \
+} while(0);
+
+
+struct response_data {
+ int32_t type;
+ int32_t len;
+ uint8_t *data;
+ struct response_data *next;
+};
+
+struct pam_data {
+ int cmd;
+ uint32_t authtok_type;
+ uint32_t authtok_size;
+ uint32_t newauthtok_type;
+ uint32_t newauthtok_size;
+ char *domain;
+ char *user;
+ char *service;
+ char *tty;
+ char *ruser;
+ char *rhost;
+ uint8_t *authtok;
+ uint8_t *newauthtok;
+ uint32_t cli_pid;
+
+ int pam_status;
+ int response_delay;
+ struct response_data *resp_list;
+
+ bool offline_auth;
+ bool last_auth_saved;
+ int priv;
+ uid_t pw_uid;
+ gid_t gr_gid;
+
+ const char *upn;
+};
+
+/* from dp_auth_util.c */
+void pam_print_data(int l, struct pam_data *pd);
+int pam_add_response(struct pam_data *pd,
+ enum response_type type,
+ int len, const uint8_t *data);
+
+bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd,
+ DBusError *dbus_error);
+
+bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd,
+ DBusError *dbus_error);
+
+int dp_common_send_id(struct sbus_connection *conn, uint16_t version,
+ const char *name, const char *domain);
+
+/* from dp_sbus.c */
+int dp_get_sbus_address(TALLOC_CTX *mem_ctx,
+ char **address, const char *domain_name);
+
+
+/* Helpers */
+
+#define NULL_STRING { .string = NULL }
+#define NULL_BLOB { .blob = { NULL, 0 } }
+#define NULL_NUMBER { .number = 0 }
+#define BOOL_FALSE { .boolean = false }
+#define BOOL_TRUE { .boolean = true }
+
+enum dp_opt_type {
+ DP_OPT_STRING,
+ DP_OPT_BLOB,
+ DP_OPT_NUMBER,
+ DP_OPT_BOOL
+};
+
+struct dp_opt_blob {
+ uint8_t *data;
+ size_t length;
+};
+
+union dp_opt_value {
+ const char *cstring;
+ char *string;
+ struct dp_opt_blob blob;
+ int number;
+ bool boolean;
+};
+
+struct dp_option {
+ const char *opt_name;
+ enum dp_opt_type type;
+ union dp_opt_value def_val;
+ union dp_opt_value val;
+};
+
+int dp_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option *def_opts,
+ int num_opts,
+ struct dp_option **_opts);
+
+int dp_copy_options(TALLOC_CTX *memctx,
+ struct dp_option *src_opts,
+ int num_opts,
+ struct dp_option **_opts);
+
+const char *_dp_opt_get_cstring(struct dp_option *opts,
+ int id, const char *location);
+char *_dp_opt_get_string(struct dp_option *opts,
+ int id, const char *location);
+struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts,
+ int id, const char *location);
+int _dp_opt_get_int(struct dp_option *opts,
+ int id, const char *location);
+bool _dp_opt_get_bool(struct dp_option *opts,
+ int id, const char *location);
+#define dp_opt_get_cstring(o, i) _dp_opt_get_cstring(o, i, __FUNCTION__)
+#define dp_opt_get_string(o, i) _dp_opt_get_string(o, i, __FUNCTION__)
+#define dp_opt_get_blob(o, i) _dp_opt_get_blob(o, i, __FUNCTION__)
+#define dp_opt_get_int(o, i) _dp_opt_get_int(o, i, __FUNCTION__)
+#define dp_opt_get_bool(o, i) _dp_opt_get_bool(o, i, __FUNCTION__)
+
+int _dp_opt_set_string(struct dp_option *opts, int id,
+ const char *s, const char *location);
+int _dp_opt_set_blob(struct dp_option *opts, int id,
+ struct dp_opt_blob b, const char *location);
+int _dp_opt_set_int(struct dp_option *opts, int id,
+ int i, const char *location);
+int _dp_opt_set_bool(struct dp_option *opts, int id,
+ bool b, const char *location);
+#define dp_opt_set_string(o, i, v) _dp_opt_set_string(o, i, v, __FUNCTION__)
+#define dp_opt_set_blob(o, i, v) _dp_opt_set_blob(o, i, v, __FUNCTION__)
+#define dp_opt_set_int(o, i, v) _dp_opt_set_int(o, i, v, __FUNCTION__)
+#define dp_opt_set_bool(o, i, v) _dp_opt_set_bool(o, i, v, __FUNCTION__)
+
+#endif /* __DATA_PROVIDER_ */
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
new file mode 100644
index 00000000..93cae070
--- /dev/null
+++ b/src/providers/data_provider_be.c
@@ -0,0 +1,1235 @@
+/*
+ SSSD
+
+ Data Provider Process
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+#include "popt.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "providers/dp_backend.h"
+#include "providers/fail_over.h"
+#include "resolv/async_resolv.h"
+#include "monitor/monitor_interfaces.h"
+
+#define MSG_TARGET_NO_CONFIGURED "sssd_be: The requested target is not configured"
+
+#define ACCESS_PERMIT "permit"
+#define ACCESS_DENY "deny"
+#define NO_PROVIDER "none"
+
+static int data_provider_res_init(DBusMessage *message,
+ struct sbus_connection *conn);
+static int data_provider_go_offline(DBusMessage *message,
+ struct sbus_connection *conn);
+
+struct sbus_method monitor_be_methods[] = {
+ { MON_CLI_METHOD_PING, monitor_common_pong },
+ { MON_CLI_METHOD_RES_INIT, data_provider_res_init },
+ { MON_CLI_METHOD_OFFLINE, data_provider_go_offline },
+ { NULL, NULL }
+};
+
+struct sbus_interface monitor_be_interface = {
+ MONITOR_INTERFACE,
+ MONITOR_PATH,
+ SBUS_DEFAULT_VTABLE,
+ monitor_be_methods,
+ NULL
+};
+
+static int client_registration(DBusMessage *message, struct sbus_connection *conn);
+static int be_check_online(DBusMessage *message, struct sbus_connection *conn);
+static int be_get_account_info(DBusMessage *message, struct sbus_connection *conn);
+static int be_pam_handler(DBusMessage *message, struct sbus_connection *conn);
+
+struct sbus_method be_methods[] = {
+ { DP_METHOD_REGISTER, client_registration },
+ { DP_METHOD_ONLINE, be_check_online },
+ { DP_METHOD_GETACCTINFO, be_get_account_info },
+ { DP_METHOD_PAMHANDLER, be_pam_handler },
+ { NULL, NULL }
+};
+
+struct sbus_interface be_interface = {
+ DP_INTERFACE,
+ DP_PATH,
+ SBUS_DEFAULT_VTABLE,
+ be_methods,
+ NULL
+};
+
+static struct bet_data bet_data[] = {
+ {BET_NULL, NULL, NULL},
+ {BET_ID, CONFDB_DOMAIN_ID_PROVIDER, "sssm_%s_init"},
+ {BET_AUTH, CONFDB_DOMAIN_AUTH_PROVIDER, "sssm_%s_auth_init"},
+ {BET_ACCESS, CONFDB_DOMAIN_ACCESS_PROVIDER, "sssm_%s_access_init"},
+ {BET_CHPASS, CONFDB_DOMAIN_CHPASS_PROVIDER, "sssm_%s_chpass_init"},
+ {BET_MAX, NULL, NULL}
+};
+
+struct be_async_req {
+ be_req_fn_t fn;
+ struct be_req *req;
+};
+
+static void be_async_req_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct be_async_req *async_req;
+
+ async_req = talloc_get_type(pvt, struct be_async_req);
+
+ async_req->fn(async_req->req);
+}
+
+static int be_file_request(struct be_ctx *ctx,
+ be_req_fn_t fn,
+ struct be_req *req)
+{
+ struct be_async_req *areq;
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ if (!fn || !req) return EINVAL;
+
+ areq = talloc(req, struct be_async_req);
+ if (!areq) {
+ return ENOMEM;
+ }
+ areq->fn = fn;
+ areq->req = req;
+
+ /* fire immediately */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ te = tevent_add_timer(ctx->ev, req, tv, be_async_req_handler, areq);
+ if (te == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+bool be_is_offline(struct be_ctx *ctx)
+{
+ time_t now = time(NULL);
+
+ /* check if we are past the offline blackout timeout */
+ /* FIXME: get offline_timeout from configuration */
+ if (ctx->offstat.went_offline + 60 < now) {
+ ctx->offstat.offline = false;
+ }
+
+ return ctx->offstat.offline;
+}
+
+void be_mark_offline(struct be_ctx *ctx)
+{
+ DEBUG(8, ("Going offline!\n"));
+
+ ctx->offstat.went_offline = time(NULL);
+ ctx->offstat.offline = true;
+}
+
+static int be_check_online(DBusMessage *message, struct sbus_connection *conn)
+{
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+ void *user_data;
+ dbus_uint16_t online;
+ dbus_uint16_t err_maj = 0;
+ dbus_uint32_t err_min = 0;
+ static const char *err_msg = "Success";
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ if (be_is_offline(becli->bectx)) {
+ online = MOD_OFFLINE;
+ } else {
+ online = MOD_ONLINE;
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &online,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ return EIO;
+ }
+
+ dbus_conn = sbus_get_connection(becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ return EOK;
+}
+
+static char *dp_err_to_string(TALLOC_CTX *memctx, int dp_err_type, int errnum)
+{
+ switch (dp_err_type) {
+ case DP_ERR_OK:
+ return talloc_strdup(memctx, "Success");
+ break;
+
+ case DP_ERR_OFFLINE:
+ return talloc_asprintf(memctx,
+ "Provider is Offline (%s)",
+ strerror(errnum));
+ break;
+
+ case DP_ERR_TIMEOUT:
+ return talloc_asprintf(memctx,
+ "Request timed out (%s)",
+ strerror(errnum));
+ break;
+
+ case DP_ERR_FATAL:
+ default:
+ return talloc_asprintf(memctx,
+ "Internal Error (%s)",
+ strerror(errnum));
+ break;
+ }
+
+ return NULL;
+}
+
+
+static void acctinfo_callback(struct be_req *req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+ dbus_uint16_t err_maj = 0;
+ dbus_uint32_t err_min = 0;
+ const char *err_msg = NULL;
+
+ reply = (DBusMessage *)req->pvt;
+
+ if (reply) {
+ /* Return a reply if one was requested
+ * There may not be one if this request began
+ * while we were offline
+ */
+
+ err_maj = dp_err_type;
+ err_min = errnum;
+ if (errstr) {
+ err_msg = errstr;
+ } else {
+ err_msg = dp_err_to_string(req, dp_err_type, errnum);
+ }
+ if (!err_msg) {
+ DEBUG(1, ("Failed to set err_msg, Out of memory?\n"));
+ err_msg = "OOM";
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ return;
+ }
+
+ dbus_conn = sbus_get_connection(req->becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+ }
+
+ /* finally free the request */
+ talloc_free(req);
+}
+
+static int be_get_account_info(DBusMessage *message, struct sbus_connection *conn)
+{
+ struct be_acct_req *req;
+ struct be_req *be_req;
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t dbret;
+ void *user_data;
+ uint32_t type;
+ char *filter;
+ int filter_type;
+ uint32_t attr_type;
+ char *filter_val;
+ int ret;
+ dbus_uint16_t err_maj;
+ dbus_uint32_t err_min;
+ const char *err_msg;
+
+ be_req = NULL;
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ dbus_error_init(&dbus_error);
+
+ ret = dbus_message_get_args(message, &dbus_error,
+ DBUS_TYPE_UINT32, &type,
+ DBUS_TYPE_UINT32, &attr_type,
+ DBUS_TYPE_STRING, &filter,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1,("Failed, to parse message!\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ return EIO;
+ }
+
+ DEBUG(4, ("Got request for [%u][%d][%s]\n", type, attr_type, filter));
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ /* If we are offline and fast reply was requested
+ * return offline immediately
+ */
+ if ((type & BE_REQ_FAST) && becli->bectx->offstat.offline) {
+ /* Send back an immediate reply */
+ err_maj = DP_ERR_OFFLINE;
+ err_min = EAGAIN;
+ err_msg = "Fast reply - offline";
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) return EIO;
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+ reply = NULL;
+ /* This reply will be queued and sent
+ * when we reenter the mainloop.
+ *
+ * Continue processing in case we are
+ * going back online.
+ */
+ }
+
+ if ((attr_type != BE_ATTR_CORE) &&
+ (attr_type != BE_ATTR_MEM) &&
+ (attr_type != BE_ATTR_ALL)) {
+ /* Unrecognized attr type */
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Invalid Attrs Parameter";
+ goto done;
+ }
+
+ if (filter) {
+ if (strncmp(filter, "name=", 5) == 0) {
+ filter_type = BE_FILTER_NAME;
+ filter_val = &filter[5];
+ } else if (strncmp(filter, "idnumber=", 9) == 0) {
+ filter_type = BE_FILTER_IDNUM;
+ filter_val = &filter[9];
+ } else {
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Invalid Filter";
+ goto done;
+ }
+ } else {
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Missing Filter Parameter";
+ goto done;
+ }
+
+ /* process request */
+ be_req = talloc(becli, struct be_req);
+ if (!be_req) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ENOMEM;
+ err_msg = "Out of memory";
+ goto done;
+ }
+ be_req->becli = becli;
+ be_req->be_ctx = becli->bectx;
+ be_req->fn = acctinfo_callback;
+ be_req->pvt = reply;
+
+ req = talloc(be_req, struct be_acct_req);
+ if (!req) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ENOMEM;
+ err_msg = "Out of memory";
+ goto done;
+ }
+ req->entry_type = type;
+ req->attr_type = (int)attr_type;
+ req->filter_type = filter_type;
+ req->filter_value = talloc_strdup(req, filter_val);
+
+ be_req->req_data = req;
+
+ ret = be_file_request(becli->bectx,
+ becli->bectx->bet_info[BET_ID].bet_ops->handler,
+ be_req);
+ if (ret != EOK) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ret;
+ err_msg = "Failed to file request";
+ goto done;
+ }
+
+ return EOK;
+
+done:
+ if (be_req) {
+ talloc_free(be_req);
+ }
+
+ if (reply) {
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) return EIO;
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+ }
+
+ return EOK;
+}
+
+static void be_pam_handler_callback(struct be_req *req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ struct pam_data *pd;
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+
+ DEBUG(4, ("Backend returned: (%d, %d, %s) [%s]\n",
+ dp_err_type, errnum, errstr?errstr:"<NULL>",
+ dp_err_to_string(req, dp_err_type, 0)));
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ DEBUG(4, ("Sending result [%d][%s]\n", pd->pam_status, pd->domain));
+ reply = (DBusMessage *)req->pvt;
+ dbret = dp_pack_pam_response(reply, pd);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ dbus_message_unref(reply);
+ return;
+ }
+
+ dbus_conn = sbus_get_connection(req->becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Sent result [%d][%s]\n", pd->pam_status, pd->domain));
+
+ talloc_free(req);
+}
+
+static int be_pam_handler(DBusMessage *message, struct sbus_connection *conn)
+{
+ DBusError dbus_error;
+ DBusMessage *reply;
+ struct be_client *becli;
+ dbus_bool_t ret;
+ void *user_data;
+ struct pam_data *pd = NULL;
+ struct be_req *be_req = NULL;
+ enum bet_type target = BET_NULL;
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) {
+ DEBUG(1, ("dbus_message_new_method_return failed, cannot send reply.\n"));
+ return ENOMEM;
+ }
+
+ be_req = talloc_zero(becli, struct be_req);
+ if (!be_req) {
+ DEBUG(7, ("talloc_zero failed.\n"));
+ goto done;
+ }
+
+ be_req->becli = becli;
+ be_req->be_ctx = becli->bectx;
+ be_req->fn = be_pam_handler_callback;
+ be_req->pvt = reply;
+
+ pd = talloc_zero(be_req, struct pam_data);
+ if (!pd) {
+ talloc_free(be_req);
+ return ENOMEM;
+ }
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+ pd->domain = talloc_strdup(pd, becli->bectx->domain->name);
+ if (pd->domain == NULL) {
+ talloc_free(be_req);
+ return ENOMEM;
+ }
+
+ dbus_error_init(&dbus_error);
+
+ ret = dp_unpack_pam_request(message, pd, &dbus_error);
+ if (!ret) {
+ DEBUG(1,("Failed, to parse message!\n"));
+ talloc_free(be_req);
+ return EIO;
+ }
+
+ DEBUG(4, ("Got request with the following data\n"));
+ DEBUG_PAM_DATA(4, pd);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ target = BET_AUTH;
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ target = BET_ACCESS;
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ target = BET_CHPASS;
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ goto done;
+ break;
+ default:
+ DEBUG(7, ("Unsupported PAM command [%d].\n", pd->cmd));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ goto done;
+ }
+
+ /* return an error if corresponding backend target is not configured */
+ if (!becli->bectx->bet_info[target].bet_ops) {
+ DEBUG(7, ("Undefined backend target.\n"));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(MSG_TARGET_NO_CONFIGURED),
+ (const uint8_t *) MSG_TARGET_NO_CONFIGURED);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ goto done;
+ }
+
+ be_req->req_data = pd;
+
+ ret = be_file_request(becli->bectx,
+ becli->bectx->bet_info[target].bet_ops->handler,
+ be_req);
+ if (ret != EOK) {
+ DEBUG(7, ("be_file_request failed.\n"));
+ goto done;
+ }
+
+ return EOK;
+
+done:
+
+ DEBUG(4, ("Sending result [%d][%s]\n",
+ pd->pam_status, pd->domain));
+
+ ret = dp_pack_pam_response(reply, pd);
+ if (!ret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ talloc_free(be_req);
+ dbus_message_unref(reply);
+ return EIO;
+ }
+
+ /* send reply back immediately */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ talloc_free(be_req);
+
+ return EOK;
+}
+
+static int be_client_destructor(void *ctx)
+{
+ struct be_client *becli = talloc_get_type(ctx, struct be_client);
+ if (becli->bectx) {
+ if (becli->bectx->nss_cli == becli) {
+ DEBUG(4, ("Removed NSS client\n"));
+ becli->bectx->nss_cli = NULL;
+ } else if (becli->bectx->pam_cli == becli) {
+ DEBUG(4, ("Removed PAM client\n"));
+ becli->bectx->pam_cli = NULL;
+ } else {
+ DEBUG(2, ("Unknown client removed ...\n"));
+ }
+ }
+ return 0;
+}
+
+static int client_registration(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ dbus_uint16_t version = DATA_PROVIDER_VERSION;
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_uint16_t cli_ver;
+ char *cli_name;
+ char *cli_domain;
+ dbus_bool_t dbret;
+ void *data;
+
+ data = sbus_conn_get_private_data(conn);
+ becli = talloc_get_type(data, struct be_client);
+ if (!becli) {
+ DEBUG(0, ("Connection holds no valid init data\n"));
+ return EINVAL;
+ }
+
+ /* First thing, cancel the timeout */
+ DEBUG(4, ("Cancel DP ID timeout [%p]\n", becli->timeout));
+ talloc_zfree(becli->timeout);
+
+ dbus_error_init(&dbus_error);
+
+ dbret = dbus_message_get_args(message, &dbus_error,
+ DBUS_TYPE_UINT16, &cli_ver,
+ DBUS_TYPE_STRING, &cli_name,
+ DBUS_TYPE_STRING, &cli_domain,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to parse message, killing connection\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ sbus_disconnect(conn);
+ /* FIXME: should we just talloc_zfree(conn) ? */
+ return EIO;
+ }
+
+ if (strcasecmp(cli_name, "NSS") == 0) {
+ becli->bectx->nss_cli = becli;
+ } else if (strcasecmp(cli_name, "PAM") == 0) {
+ becli->bectx->pam_cli = becli;
+ } else {
+ DEBUG(1, ("Unknown client! [%s]\n", cli_name));
+ }
+ talloc_set_destructor((TALLOC_CTX *)becli, be_client_destructor);
+
+ DEBUG(4, ("Added Frontend client [%s]\n", cli_name));
+
+ /* reply that all is ok */
+ reply = dbus_message_new_method_return(message);
+ if (!reply) {
+ DEBUG(0, ("Dbus Out of memory!\n"));
+ return ENOMEM;
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(0, ("Failed to build dbus reply\n"));
+ dbus_message_unref(reply);
+ sbus_disconnect(conn);
+ return EIO;
+ }
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ becli->initialized = true;
+ return EOK;
+}
+
+static void init_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct be_client *becli;
+
+ DEBUG(2, ("Client timed out before Identification [%p]!\n", te));
+
+ becli = talloc_get_type(ptr, struct be_client);
+
+ sbus_disconnect(becli->conn);
+ talloc_zfree(becli);
+}
+
+static int be_client_init(struct sbus_connection *conn, void *data)
+{
+ struct be_ctx *bectx;
+ struct be_client *becli;
+ struct timeval tv;
+
+ bectx = talloc_get_type(data, struct be_ctx);
+
+ /* hang off this memory to the connection so that when the connection
+ * is freed we can potentially call a destructor */
+
+ becli = talloc(conn, struct be_client);
+ if (!becli) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+ becli->bectx = bectx;
+ becli->conn = conn;
+ becli->initialized = false;
+
+ /* 5 seconds should be plenty */
+ tv = tevent_timeval_current_ofs(5, 0);
+
+ becli->timeout = tevent_add_timer(bectx->ev, becli,
+ tv, init_timeout, becli);
+ if (!becli->timeout) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+ DEBUG(4, ("Set-up Backend ID timeout [%p]\n", becli->timeout));
+
+ /* Attach the client context to the connection context, so that it is
+ * always available when we need to manage the connection. */
+ sbus_conn_set_private_data(conn, becli);
+
+ return EOK;
+}
+
+/* be_srv_init
+ * set up per-domain sbus channel */
+static int be_srv_init(struct be_ctx *ctx)
+{
+ char *sbus_address;
+ int ret;
+
+ /* Set up SBUS connection to the monitor */
+ ret = dp_get_sbus_address(ctx, &sbus_address, ctx->domain->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not get sbus backend address.\n"));
+ return ret;
+ }
+
+ ret = sbus_new_server(ctx, ctx->ev, sbus_address,
+ &be_interface, &ctx->sbus_srv,
+ be_client_init, ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not set up sbus server.\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* mon_cli_init
+ * sbus channel to the monitor daemon */
+static int mon_cli_init(struct be_ctx *ctx)
+{
+ char *sbus_address;
+ int ret;
+
+ /* Set up SBUS connection to the monitor */
+ ret = monitor_get_sbus_address(ctx, &sbus_address);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not locate monitor address.\n"));
+ return ret;
+ }
+
+ ret = sbus_client_init(ctx, ctx->ev, sbus_address,
+ &monitor_be_interface, &ctx->mon_conn,
+ NULL, ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to connect to monitor services.\n"));
+ return ret;
+ }
+
+ /* Identify ourselves to the monitor */
+ ret = monitor_common_send_id(ctx->mon_conn,
+ ctx->identity,
+ DATA_PROVIDER_VERSION);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to identify to the monitor!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void be_target_access_permit(struct be_req *be_req)
+{
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ DEBUG(9, ("be_target_access_permit called, returning PAM_SUCCESS.\n"));
+
+ pd->pam_status = PAM_SUCCESS;
+ be_req->fn(be_req, DP_ERR_OK, PAM_SUCCESS, NULL);
+}
+
+static struct bet_ops be_target_access_permit_ops = {
+ .check_online = NULL,
+ .handler = be_target_access_permit,
+ .finalize = NULL
+};
+
+static void be_target_access_deny(struct be_req *be_req)
+{
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ DEBUG(9, ("be_target_access_deny called, returning PAM_PERM_DENIED.\n"));
+
+ pd->pam_status = PAM_PERM_DENIED;
+ be_req->fn(be_req, DP_ERR_OK, PAM_PERM_DENIED, NULL);
+}
+
+static struct bet_ops be_target_access_deny_ops = {
+ .check_online = NULL,
+ .handler = be_target_access_deny,
+ .finalize = NULL
+};
+
+static int load_backend_module(struct be_ctx *ctx,
+ enum bet_type bet_type,
+ struct bet_info *bet_info,
+ const char *default_mod_name)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret = EINVAL;
+ bool already_loaded = false;
+ int lb=0;
+ char *mod_name = NULL;
+ char *path = NULL;
+ void *handle;
+ char *mod_init_fn_name = NULL;
+ bet_init_fn_t mod_init_fn = NULL;
+
+ (*bet_info).mod_name = NULL;
+ (*bet_info).bet_ops = NULL;
+ (*bet_info).pvt_bet_data = NULL;
+
+ if (bet_type <= BET_NULL || bet_type >= BET_MAX ||
+ bet_type != bet_data[bet_type].bet_type) {
+ DEBUG(2, ("invalid bet_type or bet_data corrupted.\n"));
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(ctx);
+ if (!tmp_ctx) {
+ DEBUG(7, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(ctx->cdb, tmp_ctx, ctx->conf_path,
+ bet_data[bet_type].option_name, NULL,
+ &mod_name);
+ if (ret != EOK) {
+ ret = EFAULT;
+ goto done;
+ }
+ if (!mod_name) {
+ if (default_mod_name != NULL) {
+ DEBUG(5, ("no module name found in confdb, using [%s].\n",
+ default_mod_name));
+ mod_name = talloc_strdup(ctx, default_mod_name);
+ } else {
+ ret = ENOENT;
+ goto done;
+ }
+ }
+
+ if (strcasecmp(mod_name, NO_PROVIDER) == 0) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ if (bet_type == BET_ACCESS) {
+ if (strcmp(mod_name, ACCESS_PERMIT) == 0) {
+ (*bet_info).bet_ops = &be_target_access_permit_ops;
+ (*bet_info).pvt_bet_data = NULL;
+ (*bet_info).mod_name = talloc_strdup(ctx, ACCESS_PERMIT);
+
+ ret = EOK;
+ goto done;
+ }
+ if (strcmp(mod_name, ACCESS_DENY) == 0) {
+ (*bet_info).bet_ops = &be_target_access_deny_ops;
+ (*bet_info).pvt_bet_data = NULL;
+ (*bet_info).mod_name = talloc_strdup(ctx, ACCESS_DENY);
+
+ ret = EOK;
+ goto done;
+ }
+ }
+
+ mod_init_fn_name = talloc_asprintf(tmp_ctx,
+ bet_data[bet_type].mod_init_fn_name_fmt,
+ mod_name);
+ if (mod_init_fn_name == NULL) {
+ DEBUG(7, ("talloc_asprintf failed\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+
+ lb = 0;
+ while(ctx->loaded_be[lb].be_name != NULL) {
+ if (strncmp(ctx->loaded_be[lb].be_name, mod_name,
+ strlen(mod_name)) == 0) {
+ DEBUG(7, ("Backend [%s] already loaded.\n", mod_name));
+ already_loaded = true;
+ break;
+ }
+
+ ++lb;
+ if (lb >= BET_MAX) {
+ DEBUG(2, ("Backend context corrupted.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ if (!already_loaded) {
+ path = talloc_asprintf(tmp_ctx, "%s/libsss_%s.so",
+ DATA_PROVIDER_PLUGINS_PATH, mod_name);
+ if (!path) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(7, ("Loading backend [%s] with path [%s].\n", mod_name, path));
+ handle = dlopen(path, RTLD_NOW);
+ if (!handle) {
+ DEBUG(0, ("Unable to load %s module with path (%s), error: %s\n",
+ mod_name, path, dlerror()));
+ ret = ELIBACC;
+ goto done;
+ }
+
+ ctx->loaded_be[lb].be_name = talloc_strdup(ctx, mod_name);
+ ctx->loaded_be[lb].handle = handle;
+ }
+
+ mod_init_fn = (bet_init_fn_t)dlsym(ctx->loaded_be[lb].handle,
+ mod_init_fn_name);
+ if (mod_init_fn == NULL) {
+ if (default_mod_name != NULL &&
+ strcmp(default_mod_name, mod_name) == 0 ) {
+ /* If the default is used and fails we indicate this to the caller
+ * by returning ENOENT. Ths way the caller can decide how to
+ * handle the different types of error conditions. */
+ ret = ENOENT;
+ } else {
+ DEBUG(0, ("Unable to load init fn %s from module %s, error: %s\n",
+ mod_init_fn_name, mod_name, dlerror()));
+ ret = ELIBBAD;
+ }
+ goto done;
+ }
+
+ ret = mod_init_fn(ctx, &(*bet_info).bet_ops, &(*bet_info).pvt_bet_data);
+ if (ret != EOK) {
+ DEBUG(0, ("Error (%d) in module (%s) initialization (%s)!\n",
+ ret, mod_name, mod_init_fn_name));
+ goto done;
+ }
+
+ (*bet_info).mod_name = talloc_strdup(ctx, mod_name);
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void signal_be_offline(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct be_ctx *ctx = talloc_get_type(private_data, struct be_ctx);
+ be_mark_offline(ctx);
+}
+
+int be_process_init(TALLOC_CTX *mem_ctx,
+ const char *be_domain,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb)
+{
+ struct be_ctx *ctx;
+ struct tevent_signal *tes;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct be_ctx);
+ if (!ctx) {
+ DEBUG(0, ("fatal error initializing be_ctx\n"));
+ return ENOMEM;
+ }
+ ctx->ev = ev;
+ ctx->cdb = cdb;
+ ctx->identity = talloc_asprintf(ctx, "%%BE_%s", be_domain);
+ ctx->conf_path = talloc_asprintf(ctx, CONFDB_DOMAIN_PATH_TMPL, be_domain);
+ if (!ctx->identity || !ctx->conf_path) {
+ DEBUG(0, ("Out of memory!?\n"));
+ return ENOMEM;
+ }
+
+ ret = be_init_failover(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing failover context\n"));
+ return ret;
+ }
+
+ ret = confdb_get_domain(cdb, be_domain, &ctx->domain);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error retrieving domain configuration\n"));
+ return ret;
+ }
+
+ ret = sysdb_domain_init(ctx, ev, ctx->domain, DB_PATH, &ctx->sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error opening cache database\n"));
+ return ret;
+ }
+
+ ret = mon_cli_init(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up monitor bus\n"));
+ return ret;
+ }
+
+ ret = be_srv_init(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up server bus\n"));
+ return ret;
+ }
+
+ ret = load_backend_module(ctx, BET_ID,
+ &ctx->bet_info[BET_ID], NULL);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(9, ("ID backend target successfully loaded from provider [%s].\n",
+ ctx->bet_info[BET_ID].mod_name));
+
+ ret = load_backend_module(ctx, BET_AUTH,
+ &ctx->bet_info[BET_AUTH],
+ ctx->bet_info[BET_ID].mod_name);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(1, ("No authentication module provided for [%s] !!\n",
+ be_domain));
+ } else {
+ DEBUG(9, ("AUTH backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_AUTH].mod_name));
+ }
+
+ ret = load_backend_module(ctx, BET_ACCESS, &ctx->bet_info[BET_ACCESS],
+ ACCESS_PERMIT);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to setup ACCESS backend.\n"));
+ return ret;
+ }
+ DEBUG(9, ("ACCESS backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_ACCESS].mod_name));
+
+ ret = load_backend_module(ctx, BET_CHPASS,
+ &ctx->bet_info[BET_CHPASS],
+ ctx->bet_info[BET_AUTH].mod_name);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(1, ("No change password module provided for [%s] !!\n",
+ be_domain));
+ } else {
+ DEBUG(9, ("CHPASS backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_CHPASS].mod_name));
+ }
+
+ /* Handle SIGUSR1 to force offline behavior */
+ BlockSignals(false, SIGUSR1);
+ tes = tevent_add_signal(ctx->ev, ctx, SIGUSR1, 0,
+ signal_be_offline, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ char *be_domain = NULL;
+ char *srv_name = NULL;
+ char *conf_entry = NULL;
+ struct main_context *main_ctx;
+ int ret;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ {"domain", 0, POPT_ARG_STRING, &be_domain, 0,
+ _("Domain of the information provider (mandatory)"), NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+
+ if (be_domain == NULL) {
+ fprintf(stderr, "\nMissing option, --domain is a mandatory option.\n\n");
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+
+ poptFreeContext(pc);
+
+
+ /* set up things like debug , signals, daemonization, etc... */
+ debug_log_file = talloc_asprintf(NULL, "sssd_%s", be_domain);
+ if (!debug_log_file) return 2;
+
+ srv_name = talloc_asprintf(NULL, "sssd[be[%s]]", be_domain);
+ if (!srv_name) return 2;
+
+ conf_entry = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, be_domain);
+ if (!conf_entry) return 2;
+
+ ret = server_setup(srv_name, 0, conf_entry, &main_ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not set up mainloop [%d]\n", ret));
+ return 2;
+ }
+
+ ret = die_if_parent_died();
+ if (ret != EOK) {
+ /* This is not fatal, don't return */
+ DEBUG(2, ("Could not set up to exit when parent process does\n"));
+ }
+
+ ret = be_process_init(main_ctx,
+ be_domain,
+ main_ctx->event_ctx,
+ main_ctx->confdb_ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not initialize backend [%d]\n", ret));
+ return 3;
+ }
+
+ DEBUG(1, ("Backend provider (%s) started!\n", be_domain));
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ return 0;
+}
+
+static int data_provider_res_init(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ resolv_reread_configuration();
+
+ return monitor_common_res_init(message, conn);
+}
+
+static int data_provider_go_offline(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ struct be_ctx *be_ctx;
+ be_ctx = talloc_get_type(sbus_conn_get_private_data(conn), struct be_ctx);
+ be_mark_offline(be_ctx);
+ return monitor_common_pong(message, conn);
+}
diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c
new file mode 100644
index 00000000..7d024048
--- /dev/null
+++ b/src/providers/data_provider_fo.c
@@ -0,0 +1,356 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include "providers/dp_backend.h"
+#include "resolv/async_resolv.h"
+
+struct be_svc_callback {
+ struct be_svc_callback *prev;
+ struct be_svc_callback *next;
+
+ struct be_svc_data *svc;
+
+ be_svc_callback_fn_t *fn;
+ void *private_data;
+};
+
+struct be_svc_data {
+ struct be_svc_data *prev;
+ struct be_svc_data *next;
+
+ const char *name;
+ struct fo_service *fo_service;
+
+ struct fo_server *last_good_srv;
+
+ struct be_svc_callback *callbacks;
+};
+
+struct be_failover_ctx {
+ struct fo_ctx *fo_ctx;
+ struct resolv_ctx *resolv;
+
+ struct be_svc_data *svcs;
+};
+
+int be_init_failover(struct be_ctx *ctx)
+{
+ int ret;
+
+ if (ctx->be_fo != NULL) {
+ return EOK;
+ }
+
+ ctx->be_fo = talloc_zero(ctx, struct be_failover_ctx);
+ if (!ctx->be_fo) {
+ return ENOMEM;
+ }
+
+ ret = resolv_init(ctx, ctx->ev, 5, &ctx->be_fo->resolv);
+ if (ret != EOK) {
+ talloc_zfree(ctx->be_fo);
+ return ret;
+ }
+
+ /* todo get timeout from configuration */
+ ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, 30);
+ if (!ctx->be_fo->fo_ctx) {
+ talloc_zfree(ctx->be_fo);
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static int be_svc_data_destroy(void *memptr)
+{
+ struct be_svc_data *svc;
+
+ svc = talloc_get_type(memptr, struct be_svc_data);
+
+ while (svc->callbacks) {
+ /* callbacks removes themselves from the list,
+ * so this while will freem them all and then terminate */
+ talloc_free(svc->callbacks);
+ }
+
+ return 0;
+}
+
+int be_fo_add_service(struct be_ctx *ctx, const char *service_name)
+{
+ struct fo_service *service;
+ struct be_svc_data *svc;
+ int ret;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ DEBUG(6, ("Failover service already initialized!\n"));
+ /* we already have a service up and configured,
+ * can happen when using both id and auth provider
+ */
+ return EOK;
+ }
+ }
+
+ /* if not in the be service list, try to create new one */
+
+ ret = fo_new_service(ctx->be_fo->fo_ctx, service_name, &service);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ return ret;
+ }
+
+ svc = talloc_zero(ctx->be_fo, struct be_svc_data);
+ if (!svc) {
+ return ENOMEM;
+ }
+ talloc_set_destructor((TALLOC_CTX *)svc, be_svc_data_destroy);
+
+ svc->name = talloc_strdup(svc, service_name);
+ if (!svc->name) {
+ talloc_zfree(svc);
+ return ENOMEM;
+ }
+ svc->fo_service = service;
+
+ DLIST_ADD(ctx->be_fo->svcs, svc);
+
+ return EOK;
+}
+
+static int be_svc_callback_destroy(void *memptr)
+{
+ struct be_svc_callback *callback;
+
+ callback = talloc_get_type(memptr, struct be_svc_callback);
+
+ if (callback->svc) {
+ DLIST_REMOVE(callback->svc->callbacks, callback);
+ }
+
+ return 0;
+}
+
+int be_fo_service_add_callback(TALLOC_CTX *memctx,
+ struct be_ctx *ctx, const char *service_name,
+ be_svc_callback_fn_t *fn, void *private_data)
+{
+ struct be_svc_callback *callback;
+ struct be_svc_data *svc;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ break;
+ }
+ }
+ if (NULL == svc) {
+ return ENOENT;
+ }
+
+ callback = talloc_zero(memctx, struct be_svc_callback);
+ if (!callback) {
+ return ENOMEM;
+ }
+ talloc_set_destructor((TALLOC_CTX *)callback, be_svc_callback_destroy);
+
+ callback->svc = svc;
+ callback->fn = fn;
+ callback->private_data = private_data;
+
+ DLIST_ADD(svc->callbacks, callback);
+
+ return EOK;
+}
+
+int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
+ const char *server, int port, void *user_data)
+{
+ struct be_svc_data *svc;
+ int ret;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ break;
+ }
+ }
+ if (NULL == svc) {
+ return ENOENT;
+ }
+
+ ret = fo_add_server(svc->fo_service, server, port, user_data);
+ if (ret && ret != EEXIST) {
+ DEBUG(1, ("Failed to add server to failover service\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+struct be_resolve_server_state {
+ struct tevent_context *ev;
+ struct be_ctx *ctx;
+
+ struct be_svc_data *svc;
+ int attempts;
+
+ struct fo_server *srv;
+};
+
+static void be_resolve_server_done(struct tevent_req *subreq);
+
+struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *ctx,
+ const char *service_name)
+{
+ struct tevent_req *req, *subreq;
+ struct be_resolve_server_state *state;
+ struct be_svc_data *svc;
+
+ req = tevent_req_create(memctx, &state, struct be_resolve_server_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ state->svc = svc;
+ break;
+ }
+ }
+
+ if (NULL == svc) {
+ tevent_req_error(req, EINVAL);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ state->attempts = 0;
+
+ subreq = fo_resolve_service_send(state, ev,
+ ctx->be_fo->resolv, svc->fo_service);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, be_resolve_server_done, req);
+
+ return req;
+}
+
+static void be_resolve_server_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct be_resolve_server_state *state = tevent_req_data(req,
+ struct be_resolve_server_state);
+ struct be_svc_callback *callback;
+ int ret;
+
+ ret = fo_resolve_service_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ switch (ret) {
+ case EOK:
+ if (!state->srv) {
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ break;
+
+ case ENOENT:
+ /* all servers have been tried and none
+ * was found good, go offline */
+ tevent_req_error(req, EIO);
+ return;
+
+ default:
+ /* mark server as bad and retry */
+ if (!state->srv) {
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ DEBUG(6, ("Couldn't resolve server (%s), resolver returned (%d)\n",
+ fo_get_server_name(state->srv), ret));
+
+ state->attempts++;
+ if (state->attempts >= 10) {
+ DEBUG(2, ("Failed to find a server after 10 attempts\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ /* now try next one */
+ DEBUG(6, ("Trying with the next one!\n"));
+ subreq = fo_resolve_service_send(state, state->ev,
+ state->ctx->be_fo->resolv,
+ state->svc->fo_service);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, be_resolve_server_done, req);
+
+ return;
+ }
+
+ /* all fine we got the server */
+
+ if (debug_level >= 4) {
+ struct hostent *srvaddr;
+ char ipaddr[128];
+ srvaddr = fo_get_server_hostent(state->srv);
+ inet_ntop(srvaddr->h_addrtype, srvaddr->h_addr_list[0],
+ ipaddr, 128);
+
+ DEBUG(4, ("Found address for server %s: [%s]\n",
+ fo_get_server_name(state->srv), ipaddr));
+ }
+
+ /* now call all svc callbacks if server changed */
+ if (state->srv != state->svc->last_good_srv) {
+ state->svc->last_good_srv = state->srv;
+
+ DLIST_FOR_EACH(callback, state->svc->callbacks) {
+ callback->fn(callback->private_data, state->srv);
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv)
+{
+ struct be_resolve_server_state *state = tevent_req_data(req,
+ struct be_resolve_server_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (srv) {
+ *srv = state->srv;
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/data_provider_opts.c b/src/providers/data_provider_opts.c
new file mode 100644
index 00000000..98283e43
--- /dev/null
+++ b/src/providers/data_provider_opts.c
@@ -0,0 +1,384 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "data_provider.h"
+
+/* =Retrieve-Options====================================================== */
+
+int dp_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option *def_opts,
+ int num_opts,
+ struct dp_option **_opts)
+{
+ struct dp_option *opts;
+ int i, ret;
+
+ opts = talloc_zero_array(memctx, struct dp_option, num_opts);
+ if (!opts) return ENOMEM;
+
+ for (i = 0; i < num_opts; i++) {
+ char *tmp;
+
+ opts[i].opt_name = def_opts[i].opt_name;
+ opts[i].type = def_opts[i].type;
+ opts[i].def_val = def_opts[i].def_val;
+ ret = EOK;
+
+ switch (def_opts[i].type) {
+ case DP_OPT_STRING:
+ ret = confdb_get_string(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.cstring,
+ &opts[i].val.string);
+ if (ret != EOK ||
+ ((opts[i].def_val.string != NULL) &&
+ (opts[i].val.string == NULL))) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ if (ret == EOK) ret = EINVAL;
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %s\n",
+ opts[i].opt_name, opts[i].val.cstring));
+ break;
+
+ case DP_OPT_BLOB:
+ ret = confdb_get_string(cdb, opts, conf_path,
+ opts[i].opt_name,
+ NULL, &tmp);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+
+ if (tmp) {
+ opts[i].val.blob.data = (uint8_t *)tmp;
+ opts[i].val.blob.length = strlen(tmp);
+ } else {
+ opts[i].val.blob.data = NULL;
+ opts[i].val.blob.length = 0;
+ }
+
+ DEBUG(6, ("Option %s has %s binary value.\n",
+ opts[i].opt_name,
+ opts[i].val.blob.length?"a":"no"));
+ break;
+
+ case DP_OPT_NUMBER:
+ ret = confdb_get_int(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.number,
+ &opts[i].val.number);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %d\n",
+ opts[i].opt_name, opts[i].val.number));
+ break;
+
+ case DP_OPT_BOOL:
+ ret = confdb_get_bool(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.boolean,
+ &opts[i].val.boolean);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s is %s\n",
+ opts[i].opt_name,
+ opts[i].val.boolean?"TRUE":"FALSE"));
+ break;
+ }
+ }
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) talloc_zfree(opts);
+ return ret;
+}
+
+/* =Basic-Option-Helpers================================================== */
+
+int dp_copy_options(TALLOC_CTX *memctx,
+ struct dp_option *src_opts,
+ int num_opts,
+ struct dp_option **_opts)
+{
+ struct dp_option *opts;
+ int i, ret;
+
+ opts = talloc_zero_array(memctx, struct dp_option, num_opts);
+ if (!opts) return ENOMEM;
+
+ for (i = 0; i < num_opts; i++) {
+ opts[i].opt_name = src_opts[i].opt_name;
+ opts[i].type = src_opts[i].type;
+ opts[i].def_val = src_opts[i].def_val;
+ ret = EOK;
+
+ switch (src_opts[i].type) {
+ case DP_OPT_STRING:
+ if (src_opts[i].val.string) {
+ ret = dp_opt_set_string(opts, i, src_opts[i].val.string);
+ } else if (src_opts[i].def_val.string) {
+ ret = dp_opt_set_string(opts, i, src_opts[i].def_val.string);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to copy value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %s\n",
+ opts[i].opt_name, opts[i].val.cstring));
+ break;
+
+ case DP_OPT_BLOB:
+ if (src_opts[i].val.blob.data) {
+ ret = dp_opt_set_blob(opts, i, src_opts[i].val.blob);
+ } else if (src_opts[i].def_val.blob.data) {
+ ret = dp_opt_set_blob(opts, i, src_opts[i].def_val.blob);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has %s binary value.\n",
+ opts[i].opt_name,
+ opts[i].val.blob.length?"a":"no"));
+ break;
+
+ case DP_OPT_NUMBER:
+ if (src_opts[i].val.number) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].val.number);
+ } else if (src_opts[i].def_val.number) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].def_val.number);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %d\n",
+ opts[i].opt_name, opts[i].val.number));
+ break;
+
+ case DP_OPT_BOOL:
+ if (src_opts[i].val.boolean) {
+ ret = dp_opt_set_bool(opts, i, src_opts[i].val.boolean);
+ } else if (src_opts[i].def_val.boolean) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].def_val.boolean);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s is %s\n",
+ opts[i].opt_name,
+ opts[i].val.boolean?"TRUE":"FALSE"));
+ break;
+ }
+ }
+
+ *_opts = opts;
+
+done:
+ if (ret != EOK) talloc_zfree(opts);
+ return ret;
+}
+
+static const char *dp_opt_type_to_string(enum dp_opt_type type)
+{
+ switch (type) {
+ case DP_OPT_STRING:
+ return "String";
+ case DP_OPT_BLOB:
+ return "Blob";
+ case DP_OPT_NUMBER:
+ return "Number";
+ case DP_OPT_BOOL:
+ return "Boolean";
+ }
+ return NULL;
+}
+
+/* Getters */
+const char *_dp_opt_get_cstring(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return NULL;
+ }
+ return opts[id].val.cstring;
+}
+
+char *_dp_opt_get_string(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return NULL;
+ }
+ return opts[id].val.string;
+}
+
+struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts,
+ int id, const char *location)
+{
+ struct dp_opt_blob null_blob = { NULL, 0 };
+ if (opts[id].type != DP_OPT_BLOB) {
+ DEBUG(0, ("[%s] Requested type 'Blob' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return null_blob;
+ }
+ return opts[id].val.blob;
+}
+
+int _dp_opt_get_int(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_NUMBER) {
+ DEBUG(0, ("[%s] Requested type 'Number' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return 0;
+ }
+ return opts[id].val.number;
+}
+
+bool _dp_opt_get_bool(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_BOOL) {
+ DEBUG(0, ("[%s] Requested type 'Boolean' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return false;
+ }
+ return opts[id].val.boolean;
+}
+
+/* Setters */
+int _dp_opt_set_string(struct dp_option *opts, int id,
+ const char *s, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ if (opts[id].val.string) {
+ talloc_zfree(opts[id].val.string);
+ }
+ if (s) {
+ opts[id].val.string = talloc_strdup(opts, s);
+ if (!opts[id].val.string) {
+ DEBUG(0, ("talloc_strdup() failed!\n"));
+ return ENOMEM;
+ }
+ }
+
+ return EOK;
+}
+
+int _dp_opt_set_blob(struct dp_option *opts, int id,
+ struct dp_opt_blob b, const char *location)
+{
+ if (opts[id].type != DP_OPT_BLOB) {
+ DEBUG(0, ("[%s] Requested type 'Blob' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ if (opts[id].val.blob.data) {
+ talloc_zfree(opts[id].val.blob.data);
+ opts[id].val.blob.length = 0;
+ }
+ if (b.data) {
+ opts[id].val.blob.data = talloc_memdup(opts, b.data, b.length);
+ if (!opts[id].val.blob.data) {
+ DEBUG(0, ("talloc_memdup() failed!\n"));
+ return ENOMEM;
+ }
+ }
+ opts[id].val.blob.length = b.length;
+
+ return EOK;
+}
+
+int _dp_opt_set_int(struct dp_option *opts, int id,
+ int i, const char *location)
+{
+ if (opts[id].type != DP_OPT_NUMBER) {
+ DEBUG(0, ("[%s] Requested type 'Number' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ opts[id].val.number = i;
+
+ return EOK;
+}
+
+int _dp_opt_set_bool(struct dp_option *opts, int id,
+ bool b, const char *location)
+{
+ if (opts[id].type != DP_OPT_BOOL) {
+ DEBUG(0, ("[%s] Requested type 'Boolean' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ opts[id].val.boolean = b;
+
+ return EOK;
+}
+
diff --git a/src/providers/dp_auth_util.c b/src/providers/dp_auth_util.c
new file mode 100644
index 00000000..39cc0f60
--- /dev/null
+++ b/src/providers/dp_auth_util.c
@@ -0,0 +1,414 @@
+/*
+ SSSD
+
+ Data Provider, auth utils
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "data_provider.h"
+
+void pam_print_data(int l, struct pam_data *pd)
+{
+ DEBUG(l, ("command: %d\n", pd->cmd));
+ DEBUG(l, ("domain: %s\n", pd->domain));
+ DEBUG(l, ("user: %s\n", pd->user));
+ DEBUG(l, ("service: %s\n", pd->service));
+ DEBUG(l, ("tty: %s\n", pd->tty));
+ DEBUG(l, ("ruser: %s\n", pd->ruser));
+ DEBUG(l, ("rhost: %s\n", pd->rhost));
+ DEBUG(l, ("authtok type: %d\n", pd->authtok_type));
+ DEBUG(l, ("authtok size: %d\n", pd->authtok_size));
+ DEBUG(l, ("newauthtok type: %d\n", pd->newauthtok_type));
+ DEBUG(l, ("newauthtok size: %d\n", pd->newauthtok_size));
+ DEBUG(l, ("priv: %d\n", pd->priv));
+ DEBUG(l, ("pw_uid: %d\n", pd->pw_uid));
+ DEBUG(l, ("gr_gid: %d\n", pd->gr_gid));
+ DEBUG(l, ("cli_pid: %d\n", pd->cli_pid));
+}
+
+int pam_add_response(struct pam_data *pd, enum response_type type,
+ int len, const uint8_t *data)
+{
+ struct response_data *new;
+
+ new = talloc(pd, struct response_data);
+ if (new == NULL) return ENOMEM;
+
+ new->type = type;
+ new->len = len;
+ new->data = talloc_memdup(pd, data, len);
+ if (new->data == NULL) return ENOMEM;
+ new->next = pd->resp_list;
+ pd->resp_list = new;
+
+ return EOK;
+}
+
+bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd)
+{
+ int ret;
+
+ if (pd->user == NULL || pd->domain == NULL) return false;
+ if (pd->service == NULL) pd->service = talloc_strdup(pd, "");
+ if (pd->tty == NULL) pd->tty = talloc_strdup(pd, "");
+ if (pd->ruser == NULL) pd->ruser = talloc_strdup(pd, "");
+ if (pd->rhost == NULL) pd->rhost = talloc_strdup(pd, "");
+
+
+ ret = dbus_message_append_args(msg,
+ DBUS_TYPE_INT32, &(pd->cmd),
+ DBUS_TYPE_STRING, &(pd->domain),
+ DBUS_TYPE_STRING, &(pd->user),
+ DBUS_TYPE_STRING, &(pd->service),
+ DBUS_TYPE_STRING, &(pd->tty),
+ DBUS_TYPE_STRING, &(pd->ruser),
+ DBUS_TYPE_STRING, &(pd->rhost),
+ DBUS_TYPE_UINT32, &(pd->authtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->authtok),
+ (pd->authtok_size),
+ DBUS_TYPE_UINT32, &(pd->newauthtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->newauthtok),
+ pd->newauthtok_size,
+ DBUS_TYPE_INT32, &(pd->priv),
+ DBUS_TYPE_INT32, &(pd->pw_uid),
+ DBUS_TYPE_INT32, &(pd->gr_gid),
+ DBUS_TYPE_UINT32, &(pd->cli_pid),
+ DBUS_TYPE_INVALID);
+
+ return ret;
+}
+
+bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error)
+{
+ int ret;
+
+ ret = dbus_message_get_args(msg, dbus_error,
+ DBUS_TYPE_INT32, &(pd->cmd),
+ DBUS_TYPE_STRING, &(pd->domain),
+ DBUS_TYPE_STRING, &(pd->user),
+ DBUS_TYPE_STRING, &(pd->service),
+ DBUS_TYPE_STRING, &(pd->tty),
+ DBUS_TYPE_STRING, &(pd->ruser),
+ DBUS_TYPE_STRING, &(pd->rhost),
+ DBUS_TYPE_UINT32, &(pd->authtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->authtok),
+ &(pd->authtok_size),
+ DBUS_TYPE_UINT32, &(pd->newauthtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->newauthtok),
+ &(pd->newauthtok_size),
+ DBUS_TYPE_INT32, &(pd->priv),
+ DBUS_TYPE_INT32, &(pd->pw_uid),
+ DBUS_TYPE_INT32, &(pd->gr_gid),
+ DBUS_TYPE_UINT32, &(pd->cli_pid),
+ DBUS_TYPE_INVALID);
+
+ return ret;
+}
+
+bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd)
+{
+ dbus_bool_t dbret;
+ struct response_data *resp;
+ DBusMessageIter iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ DBusMessageIter data_iter;
+
+ dbus_message_iter_init_append(msg, &iter);
+
+ /* Append the PAM status */
+ dbret = dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_UINT32, &(pd->pam_status));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Append the domain */
+ dbret = dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_STRING, &(pd->domain));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Create an array of response structures */
+ dbret = dbus_message_iter_open_container(&iter,
+ DBUS_TYPE_ARRAY, "(uay)",
+ &array_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ resp = pd->resp_list;
+ while (resp != NULL) {
+ /* Create a DBUS struct */
+ dbret = dbus_message_iter_open_container(&array_iter,
+ DBUS_TYPE_STRUCT, NULL,
+ &struct_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ /* Add the response type */
+ dbret = dbus_message_iter_append_basic(&struct_iter,
+ DBUS_TYPE_UINT32,
+ &(resp->type));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Add the response message */
+ dbret = dbus_message_iter_open_container(&struct_iter,
+ DBUS_TYPE_ARRAY, "y",
+ &data_iter);
+ if (!dbret) {
+ return false;
+ }
+ dbret = dbus_message_iter_append_fixed_array(&data_iter,
+ DBUS_TYPE_BYTE, &(resp->data), resp->len);
+ if (!dbret) {
+ return false;
+ }
+ dbret = dbus_message_iter_close_container(&struct_iter, &data_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ resp = resp->next;
+ dbret = dbus_message_iter_close_container(&array_iter, &struct_iter);
+ if (!dbret) {
+ return false;
+ }
+ }
+
+ /* Close the struct array */
+ dbret = dbus_message_iter_close_container(&iter, &array_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ return true;
+}
+
+bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error)
+{
+ DBusMessageIter iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ DBusMessageIter sub_iter;
+ int type;
+ int len;
+ const uint8_t *data;
+
+ if (!dbus_message_iter_init(msg, &iter)) {
+ DEBUG(1, ("pam response has no arguments.\n"));
+ return false;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&iter, &(pd->pam_status));
+
+ if (!dbus_message_iter_next(&iter)) {
+ DEBUG(1, ("pam response has too few arguments.\n"));
+ return false;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&iter, &(pd->domain));
+
+ if (!dbus_message_iter_next(&iter)) {
+ DEBUG(1, ("pam response has too few arguments.\n"));
+ return false;
+ }
+
+ /* After this point will be an array of pam data */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ DEBUG(1, ("pam response format error.\n"));
+ DEBUG(1, ("Type was %c\n", (char)dbus_message_iter_get_arg_type(&iter)));
+ return false;
+ }
+
+ if (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&iter, &array_iter);
+ while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+ /* Read in a pam data struct */
+ if (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_STRUCT) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+ /* PAM data struct contains a type and a byte-array of data */
+
+ /* Get the pam data type */
+ if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_UINT32) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&struct_iter, &type);
+
+ if (!dbus_message_iter_next(&struct_iter)) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ /* Get the byte array */
+ if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&struct_iter) != DBUS_TYPE_BYTE) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&struct_iter, &sub_iter);
+ dbus_message_iter_get_fixed_array(&sub_iter, &data, &len);
+
+ pam_add_response(pd, type, len, data);
+ dbus_message_iter_next(&array_iter);
+ }
+
+ return true;
+}
+
+static void id_callback(DBusPendingCall *pending, void *ptr)
+{
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t ret;
+ dbus_uint16_t dp_ver;
+ int type;
+
+ dbus_error_init(&dbus_error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("Severe error. A reply callback was called but no"
+ " reply was received and no timeout occurred\n"));
+
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dbus_message_get_args(reply, &dbus_error,
+ DBUS_TYPE_UINT16, &dp_ver,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to parse message\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ DEBUG(4, ("Got id ack and version (%d) from DP\n", dp_ver));
+
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+ DEBUG(0,("The Monitor returned an error [%s]\n",
+ dbus_message_get_error_name(reply)));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+
+ /* FIXME: Destroy this connection ? */
+ break;
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+int dp_common_send_id(struct sbus_connection *conn, uint16_t version,
+ const char *name, const char *domain)
+{
+ DBusPendingCall *pending_reply;
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ dbus_bool_t ret;
+
+ dbus_conn = sbus_get_connection(conn);
+
+ /* create the message */
+ msg = dbus_message_new_method_call(NULL,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_REGISTER);
+ if (msg == NULL) {
+ DEBUG(0, ("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ DEBUG(4, ("Sending ID to DP: (%d,%s,%s)\n",
+ version, name, domain));
+
+ ret = dbus_message_append_args(msg,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &domain,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to build message\n"));
+ return EIO;
+ }
+
+ ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ 30000 /* TODO: set timeout */);
+ if (!ret || !pending_reply) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, id_callback, NULL, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
new file mode 100644
index 00000000..f1069d0d
--- /dev/null
+++ b/src/providers/dp_backend.h
@@ -0,0 +1,142 @@
+/*
+ SSSD
+
+ Data Provider, private header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DP_BACKEND_H__
+#define __DP_BACKEND_H__
+
+#include "providers/data_provider.h"
+#include "providers/fail_over.h"
+#include "db/sysdb.h"
+
+struct be_ctx;
+struct bet_ops;
+struct be_req;
+
+typedef int (*bet_init_fn_t)(TALLOC_CTX *, struct bet_ops **, void **);
+typedef void (*be_shutdown_fn)(void *);
+typedef void (*be_req_fn_t)(struct be_req *);
+typedef void (*be_async_callback_t)(struct be_req *, int, int, const char *);
+
+enum bet_type {
+ BET_NULL = 0,
+ BET_ID,
+ BET_AUTH,
+ BET_ACCESS,
+ BET_CHPASS,
+ BET_MAX
+};
+
+struct bet_data {
+ enum bet_type bet_type;
+ const char *option_name;
+ const char *mod_init_fn_name_fmt;
+};
+
+struct loaded_be {
+ char *be_name;
+ void *handle;
+};
+
+struct bet_info {
+ enum bet_type bet_type;
+ struct bet_ops *bet_ops;
+ void *pvt_bet_data;
+ char *mod_name;
+};
+
+struct be_offline_status {
+ time_t went_offline;
+ bool offline;
+};
+
+struct be_client {
+ struct be_ctx *bectx;
+ struct sbus_connection *conn;
+ struct tevent_timer *timeout;
+ bool initialized;
+};
+
+struct be_failover_ctx;
+
+struct be_ctx {
+ struct tevent_context *ev;
+ struct confdb_ctx *cdb;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ const char *identity;
+ const char *conf_path;
+ struct be_failover_ctx *be_fo;
+
+ struct be_offline_status offstat;
+
+ struct sbus_connection *mon_conn;
+ struct sbus_connection *sbus_srv;
+
+ struct be_client *nss_cli;
+ struct be_client *pam_cli;
+
+ struct loaded_be loaded_be[BET_MAX];
+ struct bet_info bet_info[BET_MAX];
+};
+
+struct bet_ops {
+ be_req_fn_t check_online;
+ be_req_fn_t handler;
+ be_req_fn_t finalize;
+};
+
+struct be_req {
+ struct be_client *becli;
+ struct be_ctx *be_ctx;
+ void *req_data;
+
+ be_async_callback_t fn;
+ void *pvt;
+};
+
+struct be_acct_req {
+ int entry_type;
+ int attr_type;
+ int filter_type;
+ char *filter_value;
+};
+
+bool be_is_offline(struct be_ctx *ctx);
+void be_mark_offline(struct be_ctx *ctx);
+
+/* from data_provider_fo.c */
+typedef void (be_svc_callback_fn_t)(void *, struct fo_server *);
+
+int be_init_failover(struct be_ctx *ctx);
+int be_fo_add_service(struct be_ctx *ctx, const char *service_name);
+int be_fo_service_add_callback(TALLOC_CTX *memctx,
+ struct be_ctx *ctx, const char *service_name,
+ be_svc_callback_fn_t *fn, void *private_data);
+int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
+ const char *server, int port, void *user_data);
+
+struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *ctx,
+ const char *service_name);
+int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv);
+
+#endif /* __DP_BACKEND_H___ */
diff --git a/src/providers/dp_sbus.c b/src/providers/dp_sbus.c
new file mode 100644
index 00000000..f9dd2821
--- /dev/null
+++ b/src/providers/dp_sbus.c
@@ -0,0 +1,45 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include "talloc.h"
+#include "tevent.h"
+#include "confdb/confdb.h"
+#include "sbus/sssd_dbus.h"
+#include "providers/data_provider.h"
+
+int dp_get_sbus_address(TALLOC_CTX *mem_ctx,
+ char **address, const char *domain_name)
+{
+ char *default_address;
+
+ *address = NULL;
+ default_address = talloc_asprintf(mem_ctx, "unix:path=%s/%s_%s",
+ PIPE_PATH, DATA_PROVIDER_PIPE,
+ domain_name);
+ if (default_address == NULL) {
+ return ENOMEM;
+ }
+
+ *address = default_address;
+ return EOK;
+}
+
diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c
new file mode 100644
index 00000000..7560b89e
--- /dev/null
+++ b/src/providers/fail_over.c
@@ -0,0 +1,651 @@
+/*
+ SSSD
+
+ Fail over helper functions.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/time.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <strings.h>
+#include <talloc.h>
+
+#include "util/dlinklist.h"
+#include "util/refcount.h"
+#include "util/util.h"
+#include "providers/fail_over.h"
+#include "resolv/async_resolv.h"
+
+#define STATUS_DIFF(p, now) ((now).tv_sec - (p)->last_status_change.tv_sec)
+#define SERVER_NAME(s) ((s)->common ? (s)->common->name : "(no name)")
+
+#define DEFAULT_PORT_STATUS PORT_NEUTRAL
+#define DEFAULT_SERVER_STATUS SERVER_NAME_NOT_RESOLVED
+
+struct fo_ctx {
+ struct fo_service *service_list;
+ struct server_common *server_common_list;
+
+ /* Settings. */
+ time_t retry_timeout;
+};
+
+struct fo_service {
+ struct fo_service *prev;
+ struct fo_service *next;
+
+ struct fo_ctx *ctx;
+ char *name;
+ struct fo_server *active_server;
+ struct fo_server *last_tried_server;
+ struct fo_server *server_list;
+};
+
+struct fo_server {
+ struct fo_server *prev;
+ struct fo_server *next;
+
+ void *user_data;
+ int port;
+ int port_status;
+ struct fo_service *service;
+ struct timeval last_status_change;
+ struct server_common *common;
+};
+
+struct server_common {
+ REFCOUNT_COMMON;
+
+ struct server_common *prev;
+ struct server_common *next;
+
+ char *name;
+ struct hostent *hostent;
+ struct resolve_service_request *request_list;
+ int server_status;
+ struct timeval last_status_change;
+};
+
+struct resolve_service_request {
+ struct resolve_service_request *prev;
+ struct resolve_service_request *next;
+
+ struct server_common *server_common;
+ struct tevent_req *req;
+};
+
+struct status {
+ int value;
+ struct timeval last_change;
+};
+
+struct fo_ctx *
+fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout)
+{
+ struct fo_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct fo_ctx);
+ if (ctx == NULL) {
+ DEBUG(1, ("No memory\n"));
+ return NULL;
+ }
+
+ ctx->retry_timeout = retry_timeout;
+
+ DEBUG(3, ("Created new fail over context, retry timeout is %d\n",
+ retry_timeout));
+ return ctx;
+}
+
+static const char *
+str_port_status(enum port_status status)
+{
+ switch (status) {
+ case PORT_NEUTRAL:
+ return "neutral";
+ case PORT_WORKING:
+ return "working";
+ case PORT_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown port status";
+}
+
+static const char *
+str_server_status(enum server_status status)
+{
+ switch (status) {
+ case SERVER_NAME_NOT_RESOLVED:
+ return "name not resolved";
+ case SERVER_RESOLVING_NAME:
+ return "resolving name";
+ case SERVER_NAME_RESOLVED:
+ return "name resolved";
+ case SERVER_WORKING:
+ return "working";
+ case SERVER_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown server status";
+}
+
+/*
+ * This function will return the status of the server. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum server_status
+get_server_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ if (server->common == NULL)
+ return SERVER_NAME_RESOLVED;
+
+ DEBUG(7, ("Status of server '%s' is '%s'\n", SERVER_NAME(server),
+ str_server_status(server->common->server_status)));
+
+ timeout = server->service->ctx->retry_timeout;
+ if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) {
+ gettimeofday(&tv, NULL);
+ if (STATUS_DIFF(server->common, tv) > timeout) {
+ DEBUG(4, ("Reseting the server status of '%s'\n",
+ SERVER_NAME(server)));
+ server->common->server_status = SERVER_NAME_NOT_RESOLVED;
+ server->last_status_change.tv_sec = 0;
+ }
+ }
+
+ return server->common->server_status;
+}
+
+/*
+ * This function will return the status of the service. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum port_status
+get_port_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ DEBUG(7, ("Port status of port %d for server '%s' is '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(server->port_status)));
+
+ timeout = server->service->ctx->retry_timeout;
+ if (timeout != 0 && server->port_status == PORT_NOT_WORKING) {
+ gettimeofday(&tv, NULL);
+ if (STATUS_DIFF(server, tv) > timeout) {
+ DEBUG(4, ("Reseting the status of port %d for server '%s'\n",
+ server->port, SERVER_NAME(server)));
+ server->port_status = PORT_NEUTRAL;
+ server->last_status_change.tv_sec = tv.tv_sec;
+ }
+ }
+
+ return server->port_status;
+}
+
+static int
+server_works(struct fo_server *server)
+{
+ if (get_server_status(server) == SERVER_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_works(struct fo_server *server)
+{
+ if (!server_works(server))
+ return 0;
+ if (get_port_status(server) == PORT_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_destructor(struct fo_service *service)
+{
+ DLIST_REMOVE(service->ctx->service_list, service);
+ return 0;
+}
+
+int
+fo_new_service(struct fo_ctx *ctx, const char *name,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+ int ret;
+
+ DEBUG(3, ("Creating new service '%s'\n", name));
+ ret = fo_get_service(ctx, name, &service);
+ if (ret == EOK) {
+ DEBUG(5, ("Service '%s' already exists\n", name));
+ if (_service) {
+ *_service = service;
+ }
+ return EEXIST;
+ } else if (ret != ENOENT) {
+ return ret;
+ }
+
+ service = talloc_zero(ctx, struct fo_service);
+ if (service == NULL)
+ return ENOMEM;
+
+ service->name = talloc_strdup(service, name);
+ if (service->name == NULL) {
+ talloc_free(service);
+ return ENOMEM;
+ }
+
+ service->ctx = ctx;
+ DLIST_ADD(ctx->service_list, service);
+
+ talloc_set_destructor(service, service_destructor);
+ if (_service) {
+ *_service = service;
+ }
+
+ return EOK;
+}
+
+int
+fo_get_service(struct fo_ctx *ctx, const char *name,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+
+ DLIST_FOR_EACH(service, ctx->service_list) {
+ if (!strcmp(name, service->name)) {
+ *_service = service;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int
+get_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name,
+ struct server_common **_common)
+{
+ struct server_common *common;
+
+ DLIST_FOR_EACH(common, ctx->server_common_list) {
+ if (!strcmp(name, common->name)) {
+ *_common = rc_reference(mem_ctx, struct server_common, common);
+ if (_common == NULL)
+ return ENOMEM;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static struct server_common *
+create_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name)
+{
+ struct server_common *common;
+
+ common = rc_alloc(mem_ctx, struct server_common);
+ if (common == NULL)
+ return NULL;
+
+ common->name = talloc_strdup(common, name);
+ if (common->name == NULL) {
+ talloc_free(common);
+ return NULL;
+ }
+
+ common->prev = NULL;
+ common->next = NULL;
+ common->hostent = NULL;
+ common->request_list = NULL;
+ common->server_status = DEFAULT_SERVER_STATUS;
+ common->last_status_change.tv_sec = 0;
+ common->last_status_change.tv_usec = 0;
+
+ DLIST_ADD_END(ctx->server_common_list, common, struct server_common *);
+ return common;
+}
+
+int
+fo_add_server(struct fo_service *service, const char *name, int port,
+ void *user_data)
+{
+ struct fo_server *server;
+ int ret;
+
+ DEBUG(3, ("Adding new server '%s', to service '%s'\n",
+ name ? name : "(no name)", service->name));
+ DLIST_FOR_EACH(server, service->server_list) {
+ if (server->port != port || server->user_data != user_data)
+ continue;
+ if (name == NULL && server->common == NULL) {
+ return EEXIST;
+ } else if (name != NULL && server->common != NULL) {
+ if (!strcmp(name, server->common->name))
+ return EEXIST;
+ }
+ }
+
+ server = talloc_zero(service, struct fo_server);
+ if (server == NULL)
+ return ENOMEM;
+
+ server->port = port;
+ server->user_data = user_data;
+ server->service = service;
+ server->port_status = DEFAULT_PORT_STATUS;
+
+ if (name != NULL) {
+ ret = get_server_common(server, service->ctx, name, &server->common);
+ if (ret == ENOENT) {
+ server->common = create_server_common(server, service->ctx, name);
+ if (server->common == NULL) {
+ talloc_free(server);
+ return ENOMEM;
+ }
+ } else if (ret != EOK) {
+ talloc_free(server);
+ return ret;
+ }
+ }
+
+ DLIST_ADD_END(service->server_list, server, struct fo_server *);
+
+ return EOK;
+}
+
+static int
+get_first_server_entity(struct fo_service *service, struct fo_server **_server)
+{
+ struct fo_server *server;
+
+ /* If we already have a working server, use that one. */
+ server = service->active_server;
+ if (server != NULL) {
+ if (service_works(server)) {
+ goto done;
+ }
+ service->active_server = NULL;
+ }
+
+ /*
+ * Otherwise iterate through the server list.
+ */
+
+ /* First, try servers after the last one we tried. */
+ if (service->last_tried_server != NULL) {
+ DLIST_FOR_EACH(server, service->last_tried_server->next) {
+ if (service_works(server)) {
+ goto done;
+ }
+ }
+ }
+
+ /* If none were found, try at the start. */
+ DLIST_FOR_EACH(server, service->server_list) {
+ if (service_works(server)) {
+ goto done;
+ }
+ if (server == service->last_tried_server) {
+ break;
+ }
+ }
+
+ service->last_tried_server = NULL;
+ return ENOENT;
+
+done:
+ service->last_tried_server = server;
+ *_server = server;
+ return EOK;
+}
+
+static int
+resolve_service_request_destructor(struct resolve_service_request *request)
+{
+ DLIST_REMOVE(request->server_common->request_list, request);
+ return 0;
+}
+
+static int
+set_lookup_hook(struct fo_server *server, struct tevent_req *req)
+{
+ struct resolve_service_request *request;
+
+ request = talloc(req, struct resolve_service_request);
+ if (request == NULL) {
+ DEBUG(1, ("No memory\n"));
+ talloc_free(request);
+ return ENOMEM;
+ }
+ request->server_common = server->common;
+ request->req = req;
+ DLIST_ADD(server->common->request_list, request);
+ talloc_set_destructor(request, resolve_service_request_destructor);
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get server to connect to. *
+ *******************************************************************/
+
+struct resolve_service_state {
+ struct fo_server *server;
+};
+
+static void fo_resolve_service_done(struct tevent_req *subreq);
+
+struct tevent_req *
+fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *resolv, struct fo_service *service)
+{
+ int ret;
+ struct fo_server *server;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct resolve_service_state *state;
+
+ DEBUG(4, ("Trying to resolve service '%s'\n", service->name));
+ req = tevent_req_create(mem_ctx, &state, struct resolve_service_state);
+ if (req == NULL)
+ return NULL;
+
+ ret = get_first_server_entity(service, &server);
+ if (ret != EOK) {
+ DEBUG(1, ("No available servers for service '%s'\n", service->name));
+ goto done;
+ }
+
+ state->server = server;
+
+ if (server->common == NULL) {
+ /* This server doesn't have a name, we don't do name resolution. */
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ switch (get_server_status(server)) {
+ case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */
+ subreq = resolv_gethostbyname_send(server->common, ev, resolv,
+ server->common->name);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, fo_resolve_service_done, server->common);
+ fo_set_server_status(server, SERVER_RESOLVING_NAME);
+ /* FALLTHROUGH */
+ case SERVER_RESOLVING_NAME:
+ /* Name resolution is already under way. Just add ourselves into the
+ * waiting queue so we get notified after the operation is finished. */
+ ret = set_lookup_hook(server, req);
+ if (ret != EOK)
+ goto done;
+ break;
+ default: /* The name is already resolved. Return immediately. */
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ break;
+ }
+
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void set_server_common_status(struct server_common *common,
+ enum server_status status);
+
+static void
+fo_resolve_service_done(struct tevent_req *subreq)
+{
+ int resolv_status;
+ struct resolve_service_request *request;
+ struct server_common *common;
+ int ret;
+
+ common = tevent_req_callback_data(subreq, struct server_common);
+
+ if (common->hostent != NULL) {
+ talloc_zfree(common->hostent);
+ }
+
+ ret = resolv_gethostbyname_recv(subreq, common,
+ &resolv_status, NULL, &common->hostent);
+ talloc_free(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to resolve server '%s': %s\n", common->name,
+ resolv_strerror(resolv_status)));
+ set_server_common_status(common, SERVER_NOT_WORKING);
+ } else {
+ set_server_common_status(common, SERVER_NAME_RESOLVED);
+ }
+
+ /* Take care of all requests for this server. */
+ while ((request = common->request_list) != NULL) {
+ DLIST_REMOVE(common->request_list, request);
+ if (resolv_status) {
+ /* FIXME FIXME: resolv_status is an ARES error.
+ * but any caller will expect classic error codes.
+ * also the send() function may return ENOENT, so this mix
+ * IS explosive (ENOENT = 2 = ARES_EFORMER) */
+ tevent_req_error(request->req, resolv_status);
+ } else {
+ tevent_req_done(request->req);
+ }
+ }
+}
+
+int
+fo_resolve_service_recv(struct tevent_req *req, struct fo_server **server)
+{
+ struct resolve_service_state *state;
+
+ state = tevent_req_data(req, struct resolve_service_state);
+
+ /* always return the server if asked for, otherwise the caller
+ * cannot mark it as faulty in case we return an error */
+ if (server)
+ *server = state->server;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+set_server_common_status(struct server_common *common,
+ enum server_status status)
+{
+ DEBUG(4, ("Marking server '%s' as '%s'\n", common->name,
+ str_server_status(status)));
+
+ common->server_status = status;
+ gettimeofday(&common->last_status_change, NULL);
+}
+
+void
+fo_set_server_status(struct fo_server *server, enum server_status status)
+{
+ if (server->common == NULL) {
+ DEBUG(1, ("Bug: Trying to set server status of a name-less server\n"));
+ return;
+ }
+
+ set_server_common_status(server->common, status);
+}
+
+void
+fo_set_port_status(struct fo_server *server, enum port_status status)
+{
+ DEBUG(4, ("Marking port %d of server '%s' as '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(status)));
+
+ server->port_status = status;
+ gettimeofday(&server->last_status_change, NULL);
+ if (status == PORT_WORKING) {
+ fo_set_server_status(server, SERVER_WORKING);
+ server->service->active_server = server;
+ }
+}
+
+void *
+fo_get_server_user_data(struct fo_server *server)
+{
+ return server->user_data;
+}
+
+int
+fo_get_server_port(struct fo_server *server)
+{
+ return server->port;
+}
+
+const char *fo_get_server_name(struct fo_server *server)
+{
+ return server->common->name;
+}
+
+struct hostent *
+fo_get_server_hostent(struct fo_server *server)
+{
+ if (server->common == NULL) {
+ DEBUG(1, ("Bug: Trying to get hostent from a name-less server\n"));
+ return NULL;
+ }
+ return server->common->hostent;
+}
diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h
new file mode 100644
index 00000000..e581fbaf
--- /dev/null
+++ b/src/providers/fail_over.h
@@ -0,0 +1,108 @@
+#ifndef __FAIL_OVER_H__
+#define __FAIL_OVER_H__
+
+#include <stdbool.h>
+#include <talloc.h>
+
+/* Some forward declarations that don't have to do anything with fail over. */
+struct hostent;
+struct resolv_ctx;
+struct tevent_context;
+struct tevent_req;
+
+enum port_status {
+ PORT_NEUTRAL, /* We didn't try this port yet. */
+ PORT_WORKING, /* This port was reported to work. */
+ PORT_NOT_WORKING /* This port was reported to not work. */
+};
+
+enum server_status {
+ SERVER_NAME_NOT_RESOLVED, /* We didn't yet resolved the host name. */
+ SERVER_RESOLVING_NAME, /* Name resolving is in progress. */
+ SERVER_NAME_RESOLVED, /* We resolved the host name but didn't try to connect. */
+ SERVER_WORKING, /* We successfully connected to the server. */
+ SERVER_NOT_WORKING /* We tried and failed to connect to the server. */
+};
+
+struct fo_ctx;
+struct fo_service;
+struct fo_server;
+
+/*
+ * Create a new fail over context. The 'retry_timeout' argument specifies the
+ * duration in seconds of how long a server or port will be considered
+ * non-working after being marked as such.
+ */
+struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx,
+ time_t retry_timeout);
+
+/*
+ * Create a new service structure for 'ctx', saving it to the location pointed
+ * to by '_service'. The needed memory will be allocated from 'ctx'.
+ * Service name will be set to 'name'.
+ */
+int fo_new_service(struct fo_ctx *ctx,
+ const char *name,
+ struct fo_service **_service);
+
+/*
+ * Look up service named 'name' from the 'ctx' service list. Target of
+ * '_service' will be set to the service if it was found.
+ */
+int fo_get_service(struct fo_ctx *ctx,
+ const char *name,
+ struct fo_service **_service);
+
+/*
+ * Adds a server 'name' to the 'service'. Port 'port' will be used for
+ * connection. If 'name' is NULL, no server resolution will be done.
+ */
+int fo_add_server(struct fo_service *service,
+ const char *name,
+ int port,
+ void *user_data);
+
+/*
+ * Request the first server from the service's list of servers. It is only
+ * considered if it is not marked as not working (or the retry interval already
+ * passed). If the server address wasn't resolved yet, it will be done.
+ */
+struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv,
+ struct fo_service *service);
+
+int fo_resolve_service_recv(struct tevent_req *req,
+ struct fo_server **server);
+
+/*
+ * Set feedback about 'server'. Caller should use this to indicate a problem
+ * with the server itself, not only with the service on that server. This
+ * should be used, for example, when the IP address of the server can't be
+ * reached. This setting can affect other services as well, since they can
+ * share the same server.
+ */
+void fo_set_server_status(struct fo_server *server,
+ enum server_status status);
+
+/*
+ * Set feedback about the port status. This function should be used when
+ * the server itself is working but the service is not. When status is set
+ * to PORT_WORKING, 'server' is also marked as an "active server" for its
+ * service. When the next fo_resolve_service_send() function is called, this
+ * server will be preferred. This will hold as long as it is not marked as
+ * not-working.
+ */
+void fo_set_port_status(struct fo_server *server,
+ enum port_status status);
+
+
+void *fo_get_server_user_data(struct fo_server *server);
+
+int fo_get_server_port(struct fo_server *server);
+
+const char *fo_get_server_name(struct fo_server *server);
+
+struct hostent *fo_get_server_hostent(struct fo_server *server);
+
+#endif /* !__FAIL_OVER_H__ */
diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c
new file mode 100644
index 00000000..7dfe1fd9
--- /dev/null
+++ b/src/providers/ipa/ipa_access.c
@@ -0,0 +1,1823 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Access control
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/param.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ipa/ipa_common.h"
+#include "providers/ipa/ipa_access.h"
+#include "providers/ipa/ipa_timerules.h"
+
+#define IPA_HOST_MEMBEROF "memberOf"
+#define IPA_HOST_SERVERHOSTNAME "serverHostName"
+#define IPA_HOST_FQDN "fqdn"
+#define IPA_ACCESS_RULE_TYPE "accessRuleType"
+#define IPA_MEMBER_USER "memberUser"
+#define IPA_USER_CATEGORY "userCategory"
+#define IPA_SERVICE_NAME "serviceName"
+#define IPA_SOURCE_HOST "sourceHost"
+#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory"
+#define IPA_EXTERNAL_HOST "externalHost"
+#define IPA_ACCESS_TIME "accessTime"
+#define IPA_UNIQUE_ID "ipauniqueid"
+#define IPA_ENABLED_FLAG "ipaenabledflag"
+#define IPA_MEMBER_HOST "memberHost"
+#define IPA_HOST_CATEGORY "hostCategory"
+#define IPA_CN "cn"
+
+#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,dc=%s"
+#define IPA_HBAC_BASE_TMPL "cn=hbac,dc=%s"
+
+#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE
+
+#define HBAC_RULES_SUBDIR "hbac_rules"
+#define HBAC_HOSTS_SUBDIR "hbac_hosts"
+
+static errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count,
+ struct ldb_message **msgs,
+ struct sysdb_attrs ***attrs)
+{
+ int i;
+ struct sysdb_attrs **a;
+
+ a = talloc_array(mem_ctx, struct sysdb_attrs *, count);
+ if (a == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ return ENOMEM;
+ }
+
+ for (i = 0; i < count; i++) {
+ a[i] = talloc(a, struct sysdb_attrs);
+ if (a[i] == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ talloc_free(a);
+ return ENOMEM;
+ }
+ a[i]->num = msgs[i]->num_elements;
+ a[i]->a = talloc_steal(a[i], msgs[i]->elements);
+ }
+
+ *attrs = a;
+
+ return EOK;
+}
+
+static void ipa_access_reply(struct be_req *be_req, int pam_status)
+{
+ struct pam_data *pd;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+ pd->pam_status = pam_status;
+
+ if (pam_status == PAM_SUCCESS) {
+ be_req->fn(be_req, DP_ERR_OK, pam_status, NULL);
+ } else {
+ be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL);
+ }
+}
+
+struct hbac_get_user_info_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;;
+ struct sysdb_handle *handle;
+
+ const char *user;
+ const char *user_orig_dn;
+ struct ldb_dn *user_dn;
+ size_t groups_count;
+ const char **groups;
+};
+
+static void search_user_done(struct tevent_req *subreq);
+static void search_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *hbac_get_user_info_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ const char *user)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_user_info_state *state;
+ int ret;
+ const char **attrs;
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_user_info_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->be_ctx = be_ctx;
+ state->handle = NULL;
+ state->user = user;
+ state->user_orig_dn = NULL;
+ state->user_dn = NULL;
+ state->groups_count = 0;
+ state->groups = NULL;
+
+ attrs = talloc_array(state, const char *, 2);
+ if (attrs == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ attrs[0] = SYSDB_ORIG_DN;
+ attrs[1] = NULL;
+
+ subreq = sysdb_search_user_by_name_send(state, ev, be_ctx->sysdb, NULL,
+ be_ctx->domain, user, attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_user_by_name_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, search_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void search_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int ret;
+ const char **attrs;
+ const char *dummy;
+ struct ldb_message *user_msg;
+
+
+ ret = sysdb_search_user_recv(subreq, state, &user_msg);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(9, ("Found user info for user [%s].\n", state->user));
+ state->user_dn = talloc_steal(state, user_msg->dn);
+ dummy = ldb_msg_find_attr_as_string(user_msg, SYSDB_ORIG_DN, NULL);
+ if (dummy == NULL) {
+ DEBUG(1, ("Original DN of user [%s] not available.\n", state->user));
+ ret = EINVAL;
+ goto failed;
+ }
+ state->user_orig_dn = talloc_strdup(state, dummy);
+ if (state->user_dn == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+ DEBUG(9, ("Found original DN [%s] for user [%s].\n", state->user_orig_dn,
+ state->user));
+
+ attrs = talloc_array(state, const char *, 2);
+ if (attrs == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+ attrs[0] = SYSDB_ORIG_DN;
+ attrs[1] = NULL;
+
+ subreq = sysdb_asq_search_send(state, state->ev, state->be_ctx->sysdb, NULL,
+ state->be_ctx->domain, state->user_dn, NULL,
+ SYSDB_MEMBEROF, attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_asq_search_send failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ tevent_req_set_callback(subreq, search_groups_done, req);
+ return;
+
+failed:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void search_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int ret;
+ int i;
+ struct ldb_message **msg;
+
+ ret = sysdb_asq_search_recv(subreq, state, &state->groups_count, &msg);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->groups_count == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ state->groups = talloc_array(state, const char *, state->groups_count);
+ if (state->groups == NULL) {
+ DEBUG(1, ("talloc_groups failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ for(i = 0; i < state->groups_count; i++) {
+ if (msg[i]->num_elements != 1) {
+ DEBUG(1, ("Unexpected number of elements.\n"));
+ ret = EINVAL;
+ goto failed;
+ }
+
+ if (msg[i]->elements[0].num_values != 1) {
+ DEBUG(1, ("Unexpected number of values.\n"));
+ ret = EINVAL;
+ goto failed;
+ }
+
+ state->groups[i] = talloc_strndup(state->groups,
+ (const char *) msg[i]->elements[0].values[0].data,
+ msg[i]->elements[0].values[0].length);
+ if (state->groups[i] == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ DEBUG(9, ("Found group [%s].\n", state->groups[i]));
+ }
+
+ tevent_req_done(req);
+ return;
+
+failed:
+ talloc_free(state->groups);
+ tevent_req_error(req, ret);
+ return;
+}
+
+static int hbac_get_user_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ const char **user_dn, size_t *groups_count,
+ const char ***groups)
+{
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int i;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *user_dn = talloc_steal(memctx, state->user_orig_dn);
+ *groups_count = state->groups_count;
+ for (i = 0; i < state->groups_count; i++) {
+ talloc_steal(memctx, state->groups[i]);
+ }
+ *groups = talloc_steal(memctx, state->groups);
+
+ return EOK;
+}
+
+
+struct hbac_get_host_info_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *sdap_ctx;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ bool offline;
+
+ char *host_filter;
+ char *host_search_base;
+ const char **host_attrs;
+
+ struct sysdb_attrs **host_reply_list;
+ size_t host_reply_count;
+ size_t current_item;
+ struct hbac_host_info **hbac_host_info;
+};
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq);
+static void hbac_get_host_memberof_done(struct tevent_req *subreq);
+static void hbac_get_host_info_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_get_host_info_store_prepare(struct tevent_req *req);
+static void hbac_get_host_info_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_host_info_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ bool offline,
+ struct sdap_id_ctx *sdap_ctx,
+ struct sysdb_ctx *sysdb,
+ const char *ipa_domain,
+ const char **hostnames)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_host_info_state *state;
+ int ret;
+ int i;
+
+ if (hostnames == NULL || ipa_domain == NULL) {
+ DEBUG(1, ("Missing hostnames or domain.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_host_info_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sdap_ctx = sdap_ctx;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->offline = offline;
+
+ state->host_reply_list = NULL;
+ state->host_reply_count = 0;
+ state->current_item = 0;
+ state->hbac_host_info = NULL;
+
+ state->host_filter = talloc_asprintf(state, "(|");
+ if (state->host_filter == NULL) {
+ DEBUG(1, ("Failed to create filter.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ for (i = 0; hostnames[i] != NULL; i++) {
+ state->host_filter = talloc_asprintf_append(state->host_filter,
+ "(&(objectclass=ipaHost)"
+ "(|(fqdn=%s)(serverhostname=%s)))",
+ hostnames[i], hostnames[i]);
+ if (state->host_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ state->host_filter = talloc_asprintf_append(state->host_filter, ")");
+ if (state->host_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->host_search_base = talloc_asprintf(state, IPA_HOST_BASE_TMPL,
+ ipa_domain);
+ if (state->host_search_base == NULL) {
+ DEBUG(1, ("Failed to create host search base.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->host_attrs = talloc_array(state, const char *, 7);
+ if (state->host_attrs == NULL) {
+ DEBUG(1, ("Failed to allocate host attribute list.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->host_attrs[0] = IPA_HOST_MEMBEROF;
+ state->host_attrs[1] = IPA_HOST_SERVERHOSTNAME;
+ state->host_attrs[2] = IPA_HOST_FQDN;
+ state->host_attrs[3] = "objectClass";
+ state->host_attrs[4] = SYSDB_ORIG_DN;
+ state->host_attrs[5] = SYSDB_ORIG_MEMBEROF;
+ state->host_attrs[6] = NULL;
+
+ if (offline) {
+ subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL,
+ state->sdap_ctx->be->domain,
+ state->host_filter, HBAC_HOSTS_SUBDIR,
+ state->host_attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_custom_send.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return req;
+ }
+
+ if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+ if (sdap_ctx->gsh != NULL) {
+ talloc_zfree(sdap_ctx->gsh);
+ }
+
+ subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts,
+ sdap_ctx->be, sdap_ctx->service, NULL);
+ if (!subreq) {
+ DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_info_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->host_search_base,
+ LDAP_SCOPE_SUB,
+ state->host_filter,
+ state->host_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+ NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->host_search_base,
+ LDAP_SCOPE_SUB,
+ state->host_filter,
+ state->host_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_memberof_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+ int i;
+ int v;
+ struct ldb_message_element *el;
+ struct hbac_host_info **hhi;
+ struct ldb_message **msgs;
+
+ if (state->offline) {
+ ret = sysdb_search_custom_recv(subreq, state, &state->host_reply_count,
+ &msgs);
+ } else {
+ ret = sdap_get_generic_recv(subreq, state, &state->host_reply_count,
+ &state->host_reply_list);
+ }
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->host_reply_count == 0) {
+ DEBUG(1, ("No hosts not found in IPA server.\n"));
+ ret = ENOENT;
+ goto fail;
+ }
+
+ if (state->offline) {
+ ret = msgs2attrs_array(state, state->host_reply_count, msgs,
+ &state->host_reply_list);
+ talloc_zfree(msgs);
+ if (ret != EOK) {
+ DEBUG(1, ("msgs2attrs_array failed.\n"));
+ goto fail;
+ }
+ }
+
+ hhi = talloc_array(state, struct hbac_host_info *, state->host_reply_count + 1);
+ if (hhi == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ memset(hhi, 0,
+ sizeof(struct hbac_host_info *) * (state->host_reply_count + 1));
+
+ for (i = 0; i < state->host_reply_count; i++) {
+ hhi[i] = talloc_zero(hhi, struct hbac_host_info);
+ if (hhi[i] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i], SYSDB_ORIG_DN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("OriginalDN: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->dn = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->dn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ IPA_HOST_SERVERHOSTNAME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("ServerHostName: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->serverhostname = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->serverhostname == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ IPA_HOST_FQDN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("FQDN: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->fqdn = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->fqdn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ state->offline ? SYSDB_ORIG_MEMBEROF :
+ IPA_HOST_MEMBEROF,
+ &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+
+ hhi[i]->memberof = talloc_array(hhi, const char *, el->num_values + 1);
+ if (hhi[i]->memberof == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ memset(hhi[i]->memberof, 0,
+ sizeof(const char *) * (el->num_values + 1));
+
+ for(v = 0; v < el->num_values; v++) {
+ DEBUG(9, ("%s: [%.*s].\n", IPA_HOST_MEMBEROF, el->values[v].length,
+ (const char *)el->values[v].data));
+ hhi[i]->memberof[v] = talloc_strndup(hhi,
+ (const char *)el->values[v].data,
+ el->values[v].length);
+ if (hhi[i]->memberof[v] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ }
+
+ state->hbac_host_info = hhi;
+
+ if (state->offline) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_get_host_info_sysdb_transaction_started, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_info_sysdb_transaction_started(
+ struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item = 0;
+ hbac_get_host_info_store_prepare(req);
+ return;
+}
+
+static void hbac_get_host_info_store_prepare(struct tevent_req *req)
+{
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+ char *object_name;
+ struct ldb_message_element *el;
+ struct tevent_req *subreq;
+
+ if (state->current_item < state->host_reply_count) {
+ ret = sysdb_attrs_get_el(state->host_reply_list[state->current_item],
+ IPA_HOST_FQDN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ object_name = talloc_strndup(state, (const char *)el->values[0].data,
+ el->values[0].length);
+ if (object_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(9, ("Fqdn [%s].\n", object_name));
+
+
+ ret = sysdb_attrs_replace_name(
+ state->host_reply_list[state->current_item],
+ IPA_HOST_MEMBEROF, SYSDB_ORIG_MEMBEROF);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_replace_name failed.\n"));
+ goto fail;
+ }
+
+ subreq = sysdb_store_custom_send(state, state->ev,
+ state->handle,
+ state->sdap_ctx->be->domain,
+ object_name,
+ HBAC_HOSTS_SUBDIR,
+ state->host_reply_list[state->current_item]);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_info_store_done, req);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_info_store_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sysdb_store_custom_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item++;
+ hbac_get_host_info_store_prepare(req);
+}
+
+static int hbac_get_host_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct hbac_host_info ***hhi)
+{
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *hhi = talloc_steal(memctx, state->hbac_host_info);
+ return EOK;
+}
+
+
+struct hbac_get_rules_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *sdap_ctx;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ bool offline;
+
+ const char *host_dn;
+ const char **memberof;
+ char *hbac_filter;
+ char *hbac_search_base;
+ const char **hbac_attrs;
+
+ struct ldb_message *old_rules;
+ struct sysdb_attrs **hbac_reply_list;
+ size_t hbac_reply_count;
+ int current_item;
+};
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq);
+static void hbac_rule_get_done(struct tevent_req *subreq);
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq);
+static void hbac_rule_store_prepare(struct tevent_req *req);
+static void hbac_rule_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_rules_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ bool offline,
+ struct sdap_id_ctx *sdap_ctx,
+ struct sysdb_ctx *sysdb,
+ const char *ipa_domain,
+ const char *host_dn,
+ const char **memberof)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_rules_state *state;
+ int ret;
+ int i;
+
+ if (host_dn == NULL || ipa_domain == NULL) {
+ DEBUG(1, ("Missing host_dn or domain.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_rules_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->offline = offline;
+ state->sdap_ctx = sdap_ctx;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->host_dn = host_dn;
+ state->memberof = memberof;
+
+ state->old_rules = NULL;
+ state->hbac_reply_list = NULL;
+ state->hbac_reply_count = 0;
+ state->current_item = 0;
+
+ state->hbac_search_base = talloc_asprintf(state, IPA_HBAC_BASE_TMPL,
+ ipa_domain);
+ if (state->hbac_search_base == NULL) {
+ DEBUG(1, ("Failed to create HBAC search base.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->hbac_attrs = talloc_array(state, const char *, 16);
+ if (state->hbac_attrs == NULL) {
+ DEBUG(1, ("Failed to allocate HBAC attribute list.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->hbac_attrs[0] = IPA_ACCESS_RULE_TYPE;
+ state->hbac_attrs[1] = IPA_MEMBER_USER;
+ state->hbac_attrs[2] = IPA_USER_CATEGORY;
+ state->hbac_attrs[3] = IPA_SERVICE_NAME;
+ state->hbac_attrs[4] = IPA_SOURCE_HOST;
+ state->hbac_attrs[5] = IPA_SOURCE_HOST_CATEGORY;
+ state->hbac_attrs[6] = IPA_EXTERNAL_HOST;
+ state->hbac_attrs[7] = IPA_ACCESS_TIME;
+ state->hbac_attrs[8] = IPA_UNIQUE_ID;
+ state->hbac_attrs[9] = IPA_ENABLED_FLAG;
+ state->hbac_attrs[10] = IPA_CN;
+ state->hbac_attrs[11] = "objectclass";
+ state->hbac_attrs[12] = IPA_MEMBER_HOST;
+ state->hbac_attrs[13] = IPA_HOST_CATEGORY;
+ state->hbac_attrs[14] = SYSDB_ORIG_DN;
+ state->hbac_attrs[15] = NULL;
+
+ state->hbac_filter = talloc_asprintf(state,
+ "(&(objectclass=ipaHBACRule)"
+ "(|(%s=%s)(%s=%s)",
+ IPA_HOST_CATEGORY, "all",
+ IPA_MEMBER_HOST, host_dn);
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ for (i = 0; memberof[i] != NULL; i++) {
+ state->hbac_filter = talloc_asprintf_append(state->hbac_filter,
+ "(%s=%s)",
+ IPA_MEMBER_HOST,
+ memberof[i]);
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ state->hbac_filter = talloc_asprintf_append(state->hbac_filter, "))");
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ DEBUG(9, ("HBAC rule filter: [%s].\n", state->hbac_filter));
+
+ if (offline) {
+ subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL,
+ state->sdap_ctx->be->domain,
+ state->hbac_filter, HBAC_RULES_SUBDIR,
+ state->hbac_attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+
+ return req;
+ }
+
+ if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+ if (sdap_ctx->gsh != NULL) {
+ talloc_zfree(sdap_ctx->gsh);
+ }
+
+ subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts,
+ sdap_ctx->be, sdap_ctx->service, NULL);
+ if (!subreq) {
+ DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_rules_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->hbac_search_base,
+ LDAP_SCOPE_SUB,
+ state->hbac_filter,
+ state->hbac_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+ NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->hbac_search_base,
+ LDAP_SCOPE_SUB,
+ state->hbac_filter,
+ state->hbac_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ int i;
+ struct ldb_message_element *el;
+ struct ldb_message **msgs;
+
+ if (state->offline) {
+ ret = sysdb_search_custom_recv(subreq, state, &state->hbac_reply_count,
+ &msgs);
+ } else {
+ ret = sdap_get_generic_recv(subreq, state, &state->hbac_reply_count,
+ &state->hbac_reply_list);
+ }
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->offline) {
+ ret = msgs2attrs_array(state, state->hbac_reply_count, msgs,
+ &state->hbac_reply_list);
+ talloc_zfree(msgs);
+ if (ret != EOK) {
+ DEBUG(1, ("msgs2attrs_array failed.\n"));
+ goto fail;
+ }
+ }
+
+ for (i = 0; i < state->hbac_reply_count; i++) {
+ ret = sysdb_attrs_get_el(state->hbac_reply_list[i], SYSDB_ORIG_DN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(1, ("Missing original DN.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("OriginalDN: [%s].\n", (const char *)el->values[0].data));
+ }
+
+ if (state->hbac_reply_count == 0 || state->offline) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_rule_sysdb_transaction_started, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ struct ldb_dn *hbac_base_dn;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ hbac_base_dn = sysdb_custom_subtree_dn(state->sysdb, state,
+ state->sdap_ctx->be->domain->name,
+ HBAC_RULES_SUBDIR);
+ if (hbac_base_dn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ subreq = sysdb_delete_recursive_send(state, state->ev, state->handle,
+ hbac_base_dn, true);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_delete_recursive_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_rule_sysdb_delete_done, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sysdb_delete_recursive_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item = 0;
+ hbac_rule_store_prepare(req);
+}
+
+static void hbac_rule_store_prepare(struct tevent_req *req)
+{
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ struct ldb_message_element *el;
+ struct tevent_req *subreq;
+ char *object_name;
+
+ if (state->current_item < state->hbac_reply_count) {
+
+ ret = sysdb_attrs_get_el(state->hbac_reply_list[state->current_item],
+ IPA_UNIQUE_ID, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ object_name = talloc_strndup(state, (const char *)el->values[0].data,
+ el->values[0].length);
+ if (object_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(9, ("IPAUniqueId: [%s].\n", object_name));
+
+ subreq = sysdb_store_custom_send(state, state->ev,
+ state->handle,
+ state->sdap_ctx->be->domain,
+ object_name,
+ HBAC_RULES_SUBDIR,
+ state->hbac_reply_list[state->current_item]);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_store_done, req);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_store_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sysdb_store_custom_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item++;
+ hbac_rule_store_prepare(req);
+}
+
+static int hbac_get_rules_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ size_t *hbac_rule_count,
+ struct sysdb_attrs ***hbac_rule_list)
+{
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int i;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *hbac_rule_count = state->hbac_reply_count;
+ *hbac_rule_list = talloc_steal(memctx, state->hbac_reply_list);
+ for (i = 0; i < state->hbac_reply_count; i++) {
+ talloc_steal(memctx, state->hbac_reply_list[i]);
+ }
+ return EOK;
+}
+
+enum hbac_result {
+ HBAC_ALLOW = 1,
+ HBAC_DENY,
+ HBAC_NOT_APPLICABLE
+};
+
+enum check_result {
+ RULE_APPLICABLE = 0,
+ RULE_NOT_APPLICABLE,
+ RULE_ERROR
+};
+
+enum check_result check_service(struct pam_data *pd,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ struct ldb_message_element *el;
+
+ if (pd->service == NULL) {
+ DEBUG(1, ("No service in pam data, assuming error.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SERVICE_NAME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No services in rule specified, assuming rule applies.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ if (strncasecmp(pd->service, (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("Service [%s] found, rule applies.\n",
+ pd->service));
+ return RULE_APPLICABLE;
+ }
+ }
+ DEBUG(9, ("No matching service found, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+enum check_result check_access_time(struct time_rules_ctx *tr_ctx,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message_element *el;
+ char *rule;
+ time_t now;
+ bool result;
+
+ now = time(NULL);
+ if (now == (time_t) -1) {
+ DEBUG(1, ("time failed [%d][%s].\n", errno, strerror(errno)));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_TIME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No access time specified, assuming rule applies.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return RULE_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ rule = talloc_strndup(tmp_ctx, (const char *) el->values[i].data,
+ el->values[i].length);
+ ret = check_time_rule(tmp_ctx, tr_ctx, rule, now, &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_time_rule failed.\n"));
+ ret = RULE_ERROR;
+ goto done;
+ }
+
+ if (result) {
+ DEBUG(9, ("Current time [%d] matches rule [%s].\n", now, rule));
+ ret = RULE_APPLICABLE;
+ goto done;
+ }
+ }
+ }
+
+ ret = RULE_NOT_APPLICABLE;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+enum check_result check_user(struct hbac_ctx *hbac_ctx,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ int g;
+ struct ldb_message_element *el;
+
+ if (hbac_ctx->user_dn == NULL) {
+ DEBUG(1, ("No user DN available, this should never happen.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_USER_CATEGORY, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("USer category is not set.\n"));
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ if (strncasecmp("all", (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("User category is set to 'all', rule applies.\n"));
+ return RULE_APPLICABLE;
+ }
+ DEBUG(9, ("Unsupported user category [%.*s].\n",
+ el->values[i].length,
+ (char *) el->values[i].data));
+ }
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No user specified, rule does not apply.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ DEBUG(9, ("Searching matches for [%.*s].\n", el->values[i].length,
+ (const char *) el->values[i].data));
+ DEBUG(9, ("Checking user [%s].\n", hbac_ctx->user_dn));
+ if (strncmp(hbac_ctx->user_dn, (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("User [%s] found, rule applies.\n",
+ hbac_ctx->user_dn));
+ return RULE_APPLICABLE;
+ }
+
+ for (g = 0; g < hbac_ctx->groups_count; g++) {
+ DEBUG(9, ("Checking group [%s].\n", hbac_ctx->groups[g]));
+ if (strncmp(hbac_ctx->groups[g],
+ (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("Group [%s] found, rule applies.\n",
+ hbac_ctx->groups[g]));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ DEBUG(9, ("No matching user found, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+enum check_result check_remote_hosts(const char *rhost,
+ struct hbac_host_info *hhi,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ int m;
+ struct ldb_message_element *cat_el;
+ struct ldb_message_element *src_el;
+ struct ldb_message_element *ext_el;
+
+ if (hhi == NULL && (rhost == NULL || *rhost == '\0')) {
+ DEBUG(1, ("No remote host information specified, assuming error.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST_CATEGORY, &cat_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (cat_el->num_values == 0) {
+ DEBUG(9, ("Source host category not set.\n"));
+ } else {
+ for(i = 0; i < cat_el->num_values; i++) {
+ if (strncasecmp("all", (const char *) cat_el->values[i].data,
+ cat_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host category is set to 'all', "
+ "rule applies.\n"));
+ return RULE_APPLICABLE;
+ }
+ DEBUG(9, ("Unsupported source hosts category [%.*s].\n",
+ cat_el->values[i].length,
+ (char *) cat_el->values[i].data));
+ }
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST, &src_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &ext_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+
+ if (src_el->num_values == 0 && ext_el->num_values == 0) {
+ DEBUG(9, ("No remote host specified in rule, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ } else {
+ if (hhi != NULL) {
+ for (i = 0; i < src_el->num_values; i++) {
+ if (strncasecmp(hhi->dn, (const char *) src_el->values[i].data,
+ src_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host [%s] found, rule applies.\n",
+ hhi->dn));
+ return RULE_APPLICABLE;
+ }
+ for (m = 0; hhi->memberof[m] != NULL; m++) {
+ if (strncasecmp(hhi->memberof[m],
+ (const char *) src_el->values[i].data,
+ src_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host group [%s] found, rule applies.\n",
+ hhi->memberof[m]));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ }
+
+ if (rhost != NULL && *rhost != '\0') {
+ for (i = 0; i < ext_el->num_values; i++) {
+ if (strncasecmp(rhost, (const char *) ext_el->values[i].data,
+ ext_el->values[i].length) == 0) {
+ DEBUG(9, ("External host [%s] found, rule applies.\n",
+ rhost));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ DEBUG(9, ("No matching remote host found.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+static errno_t check_if_rule_applies(enum hbac_result *result,
+ struct hbac_ctx *hbac_ctx,
+ struct sysdb_attrs *rule_attrs) {
+ int ret;
+ struct ldb_message_element *el;
+ enum hbac_result rule_type;
+ char *rule_name;
+ struct pam_data *pd = hbac_ctx->pd;
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_CN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return ret;
+ }
+ if (el->num_values == 0) {
+ DEBUG(4, ("rule has no name, assuming '(none)'.\n"));
+ rule_name = talloc_strdup(rule_attrs, "(none)");
+ } else {
+ rule_name = talloc_strndup(rule_attrs, (const char*) el->values[0].data,
+ el->values[0].length);
+ }
+ if (rule_name == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ DEBUG(9, ("Processsing rule [%s].\n", rule_name));
+
+ /* rule type */
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_RULE_TYPE, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return ret;
+ }
+ if (el->num_values == 0) {
+ DEBUG(4, ("rule has no type, assuming 'deny'.\n"));
+ rule_type = HBAC_DENY;
+ } else if (el->num_values == 1) {
+ if (strncasecmp((const char *) el->values[0].data, "allow",
+ el->values[0].length) == 0) {
+ rule_type = HBAC_ALLOW;
+ } else {
+ rule_type = HBAC_DENY;
+ }
+ } else {
+ DEBUG(1, ("rule has an unsupported number of values [%d].\n",
+ el->num_values));
+ return EINVAL;
+ }
+
+ ret = check_service(pd, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_user(hbac_ctx, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_access_time(hbac_ctx->tr_ctx, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_remote_hosts(pd->rhost, hbac_ctx->remote_hhi, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ *result = rule_type;
+
+ return EOK;
+
+not_applicable:
+ if (ret == RULE_NOT_APPLICABLE) {
+ *result = HBAC_NOT_APPLICABLE;
+ } else {
+ *result = HBAC_DENY;
+ }
+ return EOK;
+}
+
+static int evaluate_ipa_hbac_rules(struct hbac_ctx *hbac_ctx,
+ bool *access_allowed)
+{
+ bool allow_matched = false;
+ enum hbac_result result;
+ int ret;
+ int i;
+
+ *access_allowed = false;
+
+ for (i = 0; i < hbac_ctx->hbac_rule_count ; i++) {
+
+ ret = check_if_rule_applies(&result, hbac_ctx,
+ hbac_ctx->hbac_rule_list[i]);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_rule_applies failed.\n"));
+ return ret;
+ }
+
+ switch (result) {
+ case HBAC_DENY:
+ DEBUG(3, ("Access denied by single rule.\n"));
+ return EOK;
+ break;
+ case HBAC_ALLOW:
+ allow_matched = true;
+ DEBUG(9, ("Current rule allows access.\n"));
+ break;
+ default:
+ DEBUG(9, ("Current rule does not apply.\n"));
+ }
+
+ }
+
+ *access_allowed = allow_matched;
+
+ return EOK;
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req);
+static void hbac_get_rules_done(struct tevent_req *req);
+static void hbac_get_user_info_done(struct tevent_req *req);
+
+void ipa_access_handler(struct be_req *be_req)
+{
+ struct tevent_req *req;
+ struct pam_data *pd;
+ struct hbac_ctx *hbac_ctx;
+ int pam_status = PAM_SYSTEM_ERR;
+ struct ipa_access_ctx *ipa_access_ctx;
+ const char *hostlist[3];
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ hbac_ctx = talloc_zero(be_req, struct hbac_ctx);
+ if (hbac_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ goto fail;
+ }
+ hbac_ctx->be_req = be_req;
+ hbac_ctx->pd = pd;
+ ipa_access_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+ struct ipa_access_ctx);
+ hbac_ctx->sdap_ctx = ipa_access_ctx->sdap_ctx;
+ hbac_ctx->ipa_options = ipa_access_ctx->ipa_options;
+ hbac_ctx->tr_ctx = ipa_access_ctx->tr_ctx;
+ hbac_ctx->offline = be_is_offline(be_req->be_ctx);
+
+ DEBUG(9, ("Connection status is [%s].\n", hbac_ctx->offline ? "offline" :
+ "online"));
+
+
+ hostlist[0] = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (hostlist[0] == NULL) {
+ DEBUG(1, ("ipa_hostname not available.\n"));
+ goto fail;
+ }
+ if (pd->rhost != NULL && *pd->rhost != '\0') {
+ hostlist[1] = pd->rhost;
+ } else {
+ hostlist[1] = NULL;
+ pd->rhost = dp_opt_get_string(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (pd->rhost == NULL) {
+ DEBUG(1, ("ipa_hostname not available.\n"));
+ goto fail;
+ }
+ }
+ hostlist[2] = NULL;
+
+ req = hbac_get_host_info_send(hbac_ctx, be_req->be_ctx->ev,
+ hbac_ctx->offline,
+ hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb,
+ dp_opt_get_string(hbac_ctx->ipa_options,
+ IPA_DOMAIN),
+ hostlist);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_host_info_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_host_info_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ const char *ipa_hostname;
+ struct hbac_host_info *local_hhi = NULL;
+ int i;
+
+ ret = hbac_get_host_info_recv(req, hbac_ctx, &hbac_ctx->hbac_host_info);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ipa_hostname = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (ipa_hostname == NULL) {
+ DEBUG(1, ("Missing ipa_hostname, this should never happen.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ for (i = 0; hbac_ctx->hbac_host_info[i] != NULL; i++) {
+ if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn, ipa_hostname) == 0 ||
+ strcmp(hbac_ctx->hbac_host_info[i]->serverhostname,
+ ipa_hostname) == 0) {
+ local_hhi = hbac_ctx->hbac_host_info[i];
+ }
+ if (hbac_ctx->pd->rhost != NULL && *hbac_ctx->pd->rhost != '\0') {
+ if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn,
+ hbac_ctx->pd->rhost) == 0 ||
+ strcmp(hbac_ctx->hbac_host_info[i]->serverhostname,
+ hbac_ctx->pd->rhost) == 0) {
+ hbac_ctx->remote_hhi = hbac_ctx->hbac_host_info[i];
+ }
+ }
+ }
+ if (local_hhi == NULL) {
+ DEBUG(1, ("Missing host info for [%s].\n", ipa_hostname));
+ ret = EINVAL;
+ goto fail;
+ }
+ req = hbac_get_rules_send(hbac_ctx, be_req->be_ctx->ev, hbac_ctx->offline,
+ hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb,
+ dp_opt_get_string(hbac_ctx->ipa_options,
+ IPA_DOMAIN),
+ local_hhi->dn, local_hhi->memberof);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_rules_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_rules_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_rules_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct pam_data *pd = hbac_ctx->pd;
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+
+ ret = hbac_get_rules_recv(req, hbac_ctx, &hbac_ctx->hbac_rule_count,
+ &hbac_ctx->hbac_rule_list);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ req = hbac_get_user_info_send(hbac_ctx, be_req->be_ctx->ev, be_req->be_ctx,
+ pd->user);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_user_info_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_user_info_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_user_info_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ bool access_allowed = false;
+
+ ret = hbac_get_user_info_recv(req, hbac_ctx, &hbac_ctx->user_dn,
+ &hbac_ctx->groups_count,
+ &hbac_ctx->groups);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ ret = evaluate_ipa_hbac_rules(hbac_ctx, &access_allowed);
+ if (ret != EOK) {
+ DEBUG(1, ("evaluate_ipa_hbac_rules failed.\n"));
+ goto failed;
+ }
+
+ if (access_allowed) {
+ pam_status = PAM_SUCCESS;
+ DEBUG(5, ("Access allowed.\n"));
+ } else {
+ pam_status = PAM_PERM_DENIED;
+ DEBUG(3, ("Access denied.\n"));
+ }
+
+failed:
+ ipa_access_reply(be_req, pam_status);
+}
diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h
new file mode 100644
index 00000000..bd221c57
--- /dev/null
+++ b/src/providers/ipa/ipa_access.h
@@ -0,0 +1,66 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Access control
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef _IPA_ACCESS_H_
+#define _IPA_ACCESS_H_
+
+#include "providers/ldap/ldap_common.h"
+
+enum ipa_access_mode {
+ IPA_ACCESS_DENY = 0,
+ IPA_ACCESS_ALLOW
+};
+
+struct hbac_host_info {
+ const char *fqdn;
+ const char *serverhostname;
+ const char *dn;
+ const char **memberof;
+};
+
+struct ipa_access_ctx {
+ struct sdap_id_ctx *sdap_ctx;
+ struct dp_option *ipa_options;
+ struct time_rules_ctx *tr_ctx;
+};
+
+struct hbac_ctx {
+ struct sdap_id_ctx *sdap_ctx;
+ struct dp_option *ipa_options;
+ struct time_rules_ctx *tr_ctx;
+ struct be_req *be_req;
+ struct pam_data *pd;
+ struct hbac_host_info **hbac_host_info;
+ struct hbac_host_info *remote_hhi;
+ struct sysdb_attrs **hbac_rule_list;
+ size_t hbac_rule_count;
+ const char *user_dn;
+ size_t groups_count;
+ const char **groups;
+ bool offline;
+};
+
+void ipa_access_handler(struct be_req *be_req);
+
+#endif /* _IPA_ACCESS_H_ */
diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c
new file mode 100644
index 00000000..86b72e49
--- /dev/null
+++ b/src/providers/ipa/ipa_auth.c
@@ -0,0 +1,313 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Authentication
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/param.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/ipa/ipa_common.h"
+
+struct ipa_auth_ctx {
+ struct sdap_auth_ctx *sdap_auth_ctx;
+ struct krb5_ctx *krb5_ctx;
+ struct be_req *be_req;
+ be_async_callback_t callback;
+ void *pvt;
+ bool password_migration;
+
+ int dp_err_type;
+ int errnum;
+ char *errstr;
+};
+
+static void ipa_auth_reply(struct ipa_auth_ctx *ipa_auth_ctx)
+{
+ struct pam_data *pd;
+ struct be_req *be_req = ipa_auth_ctx->be_req;
+ be_req->fn = ipa_auth_ctx->callback;
+ be_req->pvt = ipa_auth_ctx->pvt;
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+ int dp_err_type = ipa_auth_ctx->dp_err_type;
+ char *errstr = ipa_auth_ctx->errstr;
+
+ talloc_zfree(ipa_auth_ctx);
+ DEBUG(9, ("sending [%d] [%d] [%s].\n", dp_err_type, pd->pam_status,
+ errstr));
+
+ be_req->fn(be_req, dp_err_type, pd->pam_status, errstr);
+}
+
+struct ipa_auth_handler_state {
+ struct tevent_context *ev;
+
+ int dp_err_type;
+ int errnum;
+ char *errstr;
+};
+
+static void ipa_auth_handler_callback(struct be_req *be_req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr);
+
+static struct tevent_req *ipa_auth_handler_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_req *be_req,
+ be_req_fn_t auth_handler)
+{
+ struct ipa_auth_handler_state *state;
+ struct tevent_req *req;
+
+ req = tevent_req_create(memctx, &state, struct ipa_auth_handler_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ be_req->fn = ipa_auth_handler_callback;
+ be_req->pvt = req;
+
+ auth_handler(be_req);
+
+ return req;
+}
+
+static void ipa_auth_handler_callback(struct be_req *be_req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ struct tevent_req *req = talloc_get_type(be_req->pvt, struct tevent_req);
+ struct ipa_auth_handler_state *state = tevent_req_data(req,
+ struct ipa_auth_handler_state);
+
+ DEBUG(9, ("received from handler [%d] [%d] [%s].\n", dp_err_type, errnum,
+ errstr));
+ state->dp_err_type = dp_err_type;
+ state->errnum = errnum;
+ state->errstr = talloc_strdup(state, errstr);
+
+ tevent_req_post(req, state->ev);
+ tevent_req_done(req);
+ return;
+}
+
+static int ipa_auth_handler_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ int *dp_err_type, int *errnum,
+ char **errstr)
+{
+ struct ipa_auth_handler_state *state = tevent_req_data(req,
+ struct ipa_auth_handler_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (err) return err;
+ return EIO;
+ }
+
+ *dp_err_type = state->dp_err_type;
+ *errnum = state->errnum;
+ *errstr = talloc_steal(memctx, state->errstr);
+
+ return EOK;
+}
+
+
+static void ipa_auth_handler_done(struct tevent_req *req);
+static void ipa_auth_ldap_done(struct tevent_req *req);
+static void ipa_auth_handler_retry_done(struct tevent_req *req);
+
+void ipa_auth(struct be_req *be_req)
+{
+ struct tevent_req *req;
+ struct ipa_auth_ctx *ipa_auth_ctx;
+ struct sdap_id_ctx *sdap_id_ctx;
+
+ ipa_auth_ctx = talloc_zero(be_req, struct ipa_auth_ctx);
+ if (ipa_auth_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ be_req->fn(be_req, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL);
+ }
+
+ ipa_auth_ctx->callback = be_req->fn;
+ ipa_auth_ctx->pvt = be_req->pvt;
+
+ ipa_auth_ctx->be_req = be_req;
+
+ ipa_auth_ctx->sdap_auth_ctx = talloc_zero(ipa_auth_ctx,
+ struct sdap_auth_ctx);
+ if (ipa_auth_ctx->sdap_auth_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ goto fail;
+ }
+
+ sdap_id_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ ipa_auth_ctx->sdap_auth_ctx->be = sdap_id_ctx->be;
+ ipa_auth_ctx->sdap_auth_ctx->opts = sdap_id_ctx->opts;
+
+ ipa_auth_ctx->krb5_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct krb5_ctx);
+
+/* TODO: test and activate when server side support is available */
+ ipa_auth_ctx->password_migration = false;
+
+ ipa_auth_ctx->dp_err_type = DP_ERR_FATAL;
+ ipa_auth_ctx->errnum = EIO;
+ ipa_auth_ctx->errstr = NULL;
+
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ krb5_pam_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_handler_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_handler_done, ipa_auth_ctx);
+ return;
+
+fail:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_handler_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ goto done;
+ }
+
+ if (ipa_auth_ctx->password_migration && pd->pam_status == PAM_CRED_ERR) {
+ DEBUG(1, ("Assuming Kerberos password is missing, "
+ "starting password migration.\n"));
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data =
+ ipa_auth_ctx->sdap_auth_ctx;
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ sdap_pam_auth_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_ldap_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_ldap_done, ipa_auth_ctx);
+ return;
+ }
+
+done:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_ldap_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ goto done;
+ }
+
+ if (pd->pam_status == PAM_SUCCESS) {
+ DEBUG(1, ("LDAP authentication succeded, "
+ "trying Kerberos authentication again.\n"));
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx;
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ krb5_pam_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_ldap_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_handler_retry_done, ipa_auth_ctx);
+ return;
+ }
+
+done:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_handler_retry_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ }
+
+ ipa_auth_reply(ipa_auth_ctx);
+}
diff --git a/src/providers/ipa/ipa_auth.h b/src/providers/ipa/ipa_auth.h
new file mode 100644
index 00000000..3079bbd1
--- /dev/null
+++ b/src/providers/ipa/ipa_auth.h
@@ -0,0 +1,32 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Authentication
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef _IPA_AUTH_H_
+#define _IPA_AUTH_H_
+
+#include "providers/dp_backend.h"
+
+void ipa_auth(struct be_req *be_req);
+
+#endif /* _IPA_AUTH_H_ */
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
new file mode 100644
index 00000000..7686227a
--- /dev/null
+++ b/src/providers/ipa/ipa_common.c
@@ -0,0 +1,597 @@
+/*
+ SSSD
+
+ IPA Provider Common Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <netdb.h>
+#include <ctype.h>
+#include "providers/ipa/ipa_common.h"
+
+struct dp_option ipa_basic_opts[] = {
+ { "ipa_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ipa_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ipa_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+};
+
+struct dp_option ipa_def_ldap_opts[] = {
+ { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING},
+ { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB },
+ { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING },
+ { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING },
+ { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
+ { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER },
+ { "entry_cache_timeout", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER },
+ { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_sasl_mech", DP_OPT_STRING, { "GSSAPI" } , NULL_STRING },
+ { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ /* use the same parm name as the krb5 module so we set it only once */
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
+ { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+struct sdap_attr_map ipa_attr_map[] = {
+ { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map ipa_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL },
+ { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map ipa_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "member", SYSDB_MEMBER, NULL },
+ { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+struct dp_option ipa_def_krb5_opts[] = {
+ { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING },
+ { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING},
+ { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING },
+ { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER },
+ { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING },
+ { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn)
+{
+ const char *s;
+ char *dn;
+ char *p;
+ int l;
+
+ s = domain;
+ dn = talloc_strdup(memctx, "dc=");
+
+ while ((p = strchr(s, '.'))) {
+ l = p - s;
+ dn = talloc_asprintf_append_buffer(dn, "%.*s,dc=", l, s);
+ if (!dn) {
+ return ENOMEM;
+ }
+ s = p + 1;
+ }
+ dn = talloc_strdup_append_buffer(dn, s);
+ if (!dn) {
+ return ENOMEM;
+ }
+
+ *basedn = dn;
+ return EOK;
+}
+
+int ipa_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sss_domain_info *dom,
+ struct ipa_options **_opts)
+{
+ struct ipa_options *opts;
+ char *domain;
+ char *server;
+ char *ipa_hostname;
+ int ret;
+ char hostname[HOST_NAME_MAX + 1];
+
+ opts = talloc_zero(memctx, struct ipa_options);
+ if (!opts) return ENOMEM;
+
+ ret = dp_get_options(opts, cdb, conf_path,
+ ipa_basic_opts,
+ IPA_OPTS_BASIC,
+ &opts->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ domain = dp_opt_get_string(opts->basic, IPA_DOMAIN);
+ if (!domain) {
+ ret = dp_opt_set_string(opts->basic, IPA_DOMAIN, dom->name);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ /* FIXME: Make non-fatal once we have discovery */
+ server = dp_opt_get_string(opts->basic, IPA_SERVER);
+ if (!server) {
+ DEBUG(0, ("Can't find ipa server, missing option!\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME);
+ if (ipa_hostname == NULL) {
+ ret = gethostname(hostname, HOST_NAME_MAX);
+ if (ret != EOK) {
+ DEBUG(1, ("gethostname failed [%d][%s].\n", errno,
+ strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+ hostname[HOST_NAME_MAX] = '\0';
+ DEBUG(9, ("Setting ipa_hostname to [%s].\n", hostname));
+ ret = dp_opt_set_string(opts->basic, IPA_HOSTNAME, hostname);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+ return ret;
+}
+
+int ipa_get_id_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts)
+{
+ TALLOC_CTX *tmpctx;
+ char *hostname;
+ char *basedn;
+ char *realm;
+ char *value;
+ int ret;
+ int i;
+
+ /* self check test, this should never fail, unless someone forgot
+ * to properly update the code after new ldap options have been added */
+ if (SDAP_OPTS_BASIC != IPA_OPTS_BASIC_TEST) {
+ DEBUG(0, ("Option numbers do not match (%d != %d)\n",
+ SDAP_OPTS_BASIC, IPA_OPTS_BASIC_TEST));
+ abort();
+ }
+
+ tmpctx = talloc_new(ipa_opts);
+ if (!tmpctx) {
+ return ENOMEM;
+ }
+
+ ipa_opts->id = talloc_zero(ipa_opts, struct sdap_options);
+ if (!ipa_opts->id) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* get sdap options */
+ ret = dp_get_options(ipa_opts->id, cdb, conf_path,
+ ipa_def_ldap_opts,
+ SDAP_OPTS_BASIC,
+ &ipa_opts->id->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)) {
+ ret = domain_to_basedn(tmpctx,
+ dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN),
+ &basedn);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* FIXME: get values by querying IPA */
+ /* set search base */
+ value = talloc_asprintf(tmpctx, "cn=accounts,%s", basedn);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)));
+ }
+
+ /* set the ldap_sasl_authid if the ipa_hostname override was specified */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID)) {
+ hostname = dp_opt_get_string(ipa_opts->basic, IPA_HOSTNAME);
+ if (hostname) {
+ value = talloc_asprintf(tmpctx, "host/%s", hostname);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_SASL_AUTHID, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_SASL_AUTHID].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID)));
+ }
+
+ /* set krb realm */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)) {
+ realm = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN);
+ for (i = 0; realm[i]; i++) {
+ realm[i] = toupper(realm[i]);
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_KRB5_REALM, realm);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_KRB5_REALM].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)));
+ }
+
+ /* fix schema to IPAv1 for now */
+ ipa_opts->id->schema_type = SDAP_SCHEMA_IPA_V1;
+
+ /* set user/group search bases if they are not specified */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_USER_SEARCH_BASE)) {
+ ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_USER_SEARCH_BASE,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_USER_SEARCH_BASE)));
+ }
+
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_GROUP_SEARCH_BASE)) {
+ ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_GROUP_SEARCH_BASE,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_GROUP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_GROUP_SEARCH_BASE)));
+ }
+
+ ret = sdap_get_map(ipa_opts->id, cdb, conf_path,
+ ipa_attr_map,
+ SDAP_AT_GENERAL,
+ &ipa_opts->id->gen_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(ipa_opts->id,
+ cdb, conf_path,
+ ipa_user_map,
+ SDAP_OPTS_USER,
+ &ipa_opts->id->user_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(ipa_opts->id,
+ cdb, conf_path,
+ ipa_group_map,
+ SDAP_OPTS_GROUP,
+ &ipa_opts->id->group_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+ *_opts = ipa_opts->id;
+
+done:
+ talloc_zfree(tmpctx);
+ if (ret != EOK) {
+ talloc_zfree(ipa_opts->id);
+ }
+ return ret;
+}
+
+/* the following define is used to keep track of * the options in the krb5
+ * module, so that if they change and ipa is not updated correspondingly
+ * this will trigger a runtime abort error */
+#define IPA_KRB5_OPTS_TEST 8
+
+int ipa_get_auth_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option **_opts)
+{
+ char *value;
+ int ret;
+ int i;
+
+ /* self check test, this should never fail, unless someone forgot
+ * to properly update the code after new ldap options have been added */
+ if (KRB5_OPTS != IPA_KRB5_OPTS_TEST) {
+ DEBUG(0, ("Option numbers do not match (%d != %d)\n",
+ KRB5_OPTS, IPA_KRB5_OPTS_TEST));
+ abort();
+ }
+
+ ipa_opts->auth = talloc_zero(ipa_opts, struct dp_option);
+ if (ipa_opts->auth == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* get krb5 options */
+ ret = dp_get_options(ipa_opts, cdb, conf_path,
+ ipa_def_krb5_opts,
+ KRB5_OPTS, &ipa_opts->auth);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* set krb realm */
+ if (NULL == dp_opt_get_string(ipa_opts->auth, KRB5_REALM)) {
+ value = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (i = 0; value[i]; i++) {
+ value[i] = toupper(value[i]);
+ }
+ ret = dp_opt_set_string(ipa_opts->auth, KRB5_REALM, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->auth[KRB5_REALM].opt_name,
+ dp_opt_get_string(ipa_opts->auth, KRB5_REALM)));
+ }
+
+ *_opts = ipa_opts->auth;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_opts->auth);
+ }
+ return ret;
+}
+
+static void ipa_resolve_callback(void *private_data, struct fo_server *server)
+{
+ struct ipa_service *service;
+ struct hostent *srvaddr;
+ char *address;
+ char *new_uri;
+ int ret;
+
+ service = talloc_get_type(private_data, struct ipa_service);
+ if (!service) {
+ DEBUG(1, ("FATAL: Bad private_data\n"));
+ return;
+ }
+
+ srvaddr = fo_get_server_hostent(server);
+ if (!srvaddr) {
+ DEBUG(1, ("FATAL: No hostent available for server (%s)\n",
+ fo_get_server_name(server)));
+ return;
+ }
+
+ address = talloc_asprintf(service, "%s", srvaddr->h_name);
+ if (!address) {
+ DEBUG(1, ("Failed to copy address ...\n"));
+ return;
+ }
+
+ new_uri = talloc_asprintf(service, "ldap://%s", address);
+ if (!new_uri) {
+ DEBUG(2, ("Failed to copy URI ...\n"));
+ talloc_free(address);
+ return;
+ }
+
+ /* free old one and replace with new one */
+ talloc_zfree(service->sdap->uri);
+ service->sdap->uri = new_uri;
+ talloc_zfree(service->krb5_service->address);
+ service->krb5_service->address = address;
+
+ ret = write_kdcinfo_file(service->krb5_service->realm, address);
+ if (ret != EOK) {
+ DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n"));
+ }
+
+}
+
+int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *servers, const char *domain,
+ struct ipa_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ipa_service *service;
+ char **list = NULL;
+ char *realm;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct ipa_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+ service->sdap = talloc_zero(service, struct sdap_service);
+ if (!service->sdap) {
+ ret = ENOMEM;
+ goto done;
+ }
+ service->krb5_service = talloc_zero(service, struct krb5_service);
+ if (!service->krb5_service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, "IPA");
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->sdap->name = talloc_strdup(service, "IPA");
+ if (!service->sdap->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ service->krb5_service->name = talloc_strdup(service, "IPA");
+ if (!service->krb5_service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ realm = talloc_strdup(service, domain);
+ if (!realm) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (i = 0; realm[i]; i++) {
+ realm[i] = toupper(realm[i]);
+ }
+ service->krb5_service->realm = realm;
+
+ /* split server parm into a list */
+ ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ /* now for each one add a new server to the failover service */
+ for (i = 0; list[i]; i++) {
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL);
+ if (ret && ret != EEXIST) {
+ DEBUG(0, ("Failed to add server\n"));
+ goto done;
+ }
+
+ DEBUG(6, ("Added Server %s\n", list[i]));
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, "IPA",
+ ipa_resolve_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
new file mode 100644
index 00000000..60c7313f
--- /dev/null
+++ b/src/providers/ipa/ipa_common.h
@@ -0,0 +1,83 @@
+/*
+ SSSD
+
+ IPA Common utility code
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _IPA_COMMON_H_
+#define _IPA_COMMON_H_
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/krb5/krb5_common.h"
+
+struct ipa_service {
+ struct sdap_service *sdap;
+ struct krb5_service *krb5_service;
+};
+
+/* the following define is used to keep track of the options in the ldap
+ * module, so that if they change and ipa is not updated correspondingly
+ * this will trigger a runtime abort error */
+#define IPA_OPTS_BASIC_TEST 31
+
+enum ipa_basic_opt {
+ IPA_DOMAIN = 0,
+ IPA_SERVER,
+ IPA_HOSTNAME,
+
+ IPA_OPTS_BASIC /* opts counter */
+};
+
+struct ipa_options {
+ struct dp_option *basic;
+
+ struct ipa_service *service;
+
+ /* id provider */
+ struct sdap_options *id;
+ struct sdap_id_ctx *id_ctx;
+
+ /* auth and chpass provider */
+ struct dp_option *auth;
+ struct krb5_ctx *auth_ctx;
+};
+
+/* options parsers */
+int ipa_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sss_domain_info *dom,
+ struct ipa_options **_opts);
+
+int ipa_get_id_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts);
+
+int ipa_get_auth_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option **_opts);
+
+int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *servers, const char *domain,
+ struct ipa_service **_service);
+
+#endif /* _IPA_COMMON_H_ */
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
new file mode 100644
index 00000000..10b9257a
--- /dev/null
+++ b/src/providers/ipa/ipa_init.c
@@ -0,0 +1,293 @@
+/*
+ SSSD
+
+ IPA Provider Initialization functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "providers/child_common.h"
+#include "providers/ipa/ipa_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/ipa/ipa_auth.h"
+#include "providers/ipa/ipa_access.h"
+#include "providers/ipa/ipa_timerules.h"
+
+struct ipa_options *ipa_options = NULL;
+
+/* Id Handler */
+struct bet_ops ipa_id_ops = {
+ .handler = sdap_account_info_handler,
+ .finalize = NULL
+};
+
+struct bet_ops ipa_auth_ops = {
+ .handler = ipa_auth,
+ .finalize = NULL,
+};
+
+struct bet_ops ipa_chpass_ops = {
+ .handler = krb5_pam_handler,
+ .finalize = NULL,
+};
+
+struct bet_ops ipa_access_ops = {
+ .handler = ipa_access_handler,
+ .finalize = NULL
+};
+
+int common_ipa_init(struct be_ctx *bectx)
+{
+ const char *ipa_servers;
+ const char *ipa_domain;
+ int ret;
+
+ ret = ipa_get_options(bectx, bectx->cdb,
+ bectx->conf_path,
+ bectx->domain, &ipa_options);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER);
+ if (!ipa_servers) {
+ DEBUG(0, ("Missing ipa_server option!\n"));
+ return EINVAL;
+ }
+
+ ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN);
+ if (!ipa_domain) {
+ DEBUG(0, ("Missing ipa_domain option!\n"));
+ return EINVAL;
+ }
+
+ ret = ipa_service_init(ipa_options, bectx, ipa_servers, ipa_domain,
+ &ipa_options->service);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to init IPA failover service!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+int sssm_ipa_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_id_ctx *ctx;
+ int ret;
+
+ if (!ipa_options) {
+ ret = common_ipa_init(bectx);
+ if (ret != EOK) {
+ return ret;
+ }
+ }
+
+ if (ipa_options->id_ctx) {
+ /* already initialized */
+ *ops = &ipa_id_ops;
+ *pvt_data = ipa_options->id_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(ipa_options, struct sdap_id_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+ ctx->service = ipa_options->service->sdap;
+ ipa_options->id_ctx = ctx;
+
+ ret = ipa_get_id_options(ipa_options, bectx->cdb,
+ bectx->conf_path,
+ &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = sdap_id_setup_tasks(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_child(ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_child failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &ipa_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_options->id_ctx);
+ }
+ return ret;
+}
+
+int sssm_ipa_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct krb5_ctx *ctx;
+ struct tevent_signal *sige;
+ FILE *debug_filep;
+ unsigned v;
+ int ret;
+
+ if (!ipa_options) {
+ ret = common_ipa_init(bectx);
+ if (ret != EOK) {
+ return ret;
+ }
+ }
+
+ if (ipa_options->auth_ctx) {
+ /* already initialized */
+ *ops = &ipa_auth_ops;
+ *pvt_data = ipa_options->auth_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(bectx, struct krb5_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->service = ipa_options->service->krb5_service;
+ ipa_options->auth_ctx = ctx;
+
+ ret = ipa_get_auth_options(ipa_options, bectx->cdb,
+ bectx->conf_path,
+ &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = check_and_export_options(ctx->opts, bectx->domain);
+ if (ret != EOK) {
+ DEBUG(1, ("check_and_export_opts failed.\n"));
+ goto done;
+ }
+
+ sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (debug_to_file != 0) {
+ ret = open_debug_file_ex("krb5_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ctx->child_debug_fd = fileno(debug_filep);
+ if (ctx->child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+
+ v = fcntl(ctx->child_debug_fd, F_GETFD, 0);
+ fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ *ops = &ipa_auth_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_options->auth_ctx);
+ }
+ return ret;
+}
+
+int sssm_ipa_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+ ret = sssm_ipa_auth_init(bectx, ops, pvt_data);
+ *ops = &ipa_chpass_ops;
+ return ret;
+}
+
+int sssm_ipa_access_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+ struct ipa_access_ctx *ipa_access_ctx;
+
+ ipa_access_ctx = talloc_zero(bectx, struct ipa_access_ctx);
+ if (ipa_access_ctx == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = sssm_ipa_init(bectx, ops, (void **) &ipa_access_ctx->sdap_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("sssm_ipa_init failed.\n"));
+ goto done;
+ }
+
+ ret = dp_copy_options(ipa_access_ctx, ipa_options->basic,
+ IPA_OPTS_BASIC, &ipa_access_ctx->ipa_options);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_copy_options failed.\n"));
+ goto done;
+ }
+
+ ret = init_time_rules_parser(ipa_access_ctx, &ipa_access_ctx->tr_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_time_rules_parser failed.\n"));
+ goto done;
+ }
+
+ *ops = &ipa_access_ops;
+ *pvt_data = ipa_access_ctx;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ipa_access_ctx);
+ }
+ return ret;
+}
diff --git a/src/providers/ipa/ipa_timerules.c b/src/providers/ipa/ipa_timerules.c
new file mode 100644
index 00000000..1a52eef1
--- /dev/null
+++ b/src/providers/ipa/ipa_timerules.c
@@ -0,0 +1,1186 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _XOPEN_SOURCE /* strptime() needs this */
+
+#include <pcre.h>
+#include <talloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "providers/ipa/ipa_timerules.h"
+#include "util/util.h"
+
+#define JMP_NEOK(variable) do { \
+ if (variable != EOK) goto done; \
+} while (0)
+
+#define JMP_NEOK_LABEL(variable, label) do { \
+ if (variable != EOK) goto label; \
+} while (0)
+
+#define CHECK_PTR(ptr) do { \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+} while (0)
+
+#define CHECK_PTR_JMP(ptr) do { \
+ if (ptr == NULL) { \
+ ret = ENOMEM; \
+ goto done; \
+ } \
+} while (0)
+
+#define BUFFER_OR_JUMP(ctx, ptr, count) do { \
+ ptr = talloc_array(ctx, unsigned char, count); \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+ memset(ptr, 0, sizeof(unsigned char)*count); \
+} while (0)
+
+#define TEST_BIT_RANGE(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(&bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+#define TEST_BIT_RANGE_PTR(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+/* number of match offsets when matching pcre regexes */
+#define OVEC_SIZE 30
+
+/* regular expressions describing syntax of our HBAC grammar */
+#define RGX_WEEKLY "day (?P<day_of_week>(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)"
+
+#define RGX_MDAY "(?P<mperspec_day>day) (?P<interval_day>[0-9,-]+) "
+#define RGX_MWEEK "(?P<mperspec_week>week) (?P<interval_week>[0-9,-]+) "RGX_WEEKLY
+#define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK
+
+#define RGX_YDAY "(?P<yperspec_day>day) (?P<day_of_year>[0-9,-]+) "
+#define RGX_YWEEK "(?P<yperspec_week>week) (?P<week_of_year>[0-9,-]+) "RGX_WEEKLY
+#define RGX_YMONTH "(?P<yperspec_month>month) (?P<month_number>[0-9,-]+) (?P<m_period>.*?)$"
+#define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY
+
+#define RGX_TIMESPEC "(?P<timeFrom>[0-9]{4}) ~ (?P<timeTo>[0-9]{4})"
+
+#define RGX_GENERALIZED "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})(?P<hour>[0-9]{2})?(?P<minute>[0-9]{2})?(?P<second>[0-9]{2})?"
+
+#define RGX_PERIODIC "^periodic (?P<perspec>daily|weekly|monthly|yearly) (?P<period>.*?)"RGX_TIMESPEC"$"
+#define RGX_ABSOLUTE "^absolute (?P<from>\\S+) ~ (?P<to>\\S+)$"
+
+/* limits on various parameters */
+#define DAY_OF_WEEK_MAX 7
+#define DAY_OF_MONTH_MAX 31
+#define WEEK_OF_MONTH_MAX 5
+#define WEEK_OF_YEAR_MAX 54
+#define DAY_OF_YEAR_MAX 366
+#define MONTH_MAX 12
+#define HOUR_MAX 23
+#define MINUTE_MAX 59
+
+/* limits on sizes of buffers for bit arrays */
+#define DAY_OF_MONTH_BUFSIZE 8
+#define DAY_OF_YEAR_BUFSIZE 44
+#define WEEK_OF_YEAR_BUFSIZE 13
+#define MONTH_BUFSIZE 2
+#define HOUR_BUFSIZE 4
+#define MINUTE_BUFSIZE 8
+
+/* Lookup tables for translating names of days and months */
+static const char *names_day_of_week[] =
+ { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL };
+static const char *names_months[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Nov", "Dec", NULL };
+
+/*
+ * Timelib knows two types of ranges - periodic and absolute
+ */
+enum rangetypes {
+ TYPE_ABSOLUTE,
+ TYPE_PERIODIC
+};
+
+struct absolute_range {
+ time_t time_from;
+ time_t time_to;
+};
+
+struct periodic_range {
+ unsigned char day_of_week;
+ unsigned char *day_of_month;
+ unsigned char *day_of_year;
+ unsigned char week_of_month;
+ unsigned char *week_of_year;
+ unsigned char *month;
+ unsigned char *hour;
+ unsigned char *minute;
+};
+
+/*
+ * Context of one time rule being analyzed
+ */
+struct range_ctx {
+ /* main context with precompiled patterns */
+ struct time_rules_ctx *trctx;
+ /* enum rangetypes */
+ enum rangetypes type;
+
+ struct absolute_range *abs;
+ struct periodic_range *per;
+};
+
+
+/*
+ * The context of one regular expression
+ */
+struct parse_ctx {
+ /* the regular expression used for one parsing */
+ pcre *re;
+ /* number of matches */
+ int matches;
+ /* vector of matches */
+ int *ovec;
+};
+
+/* indexes to the array of precompiled regexes */
+enum timelib_rgx {
+ LP_RGX_GENERALIZED,
+ LP_RGX_MDAY,
+ LP_RGX_MWEEK,
+ LP_RGX_YEARLY,
+ LP_RGX_WEEKLY,
+ LP_RGX_ABSOLUTE,
+ LP_RGX_PERIODIC,
+ LP_RGX_MAX,
+};
+
+/* matches the indexes */
+static const char *lookup_table[] = {
+ RGX_GENERALIZED,
+ RGX_MDAY,
+ RGX_MWEEK,
+ RGX_YEARLY,
+ RGX_WEEKLY,
+ RGX_ABSOLUTE,
+ RGX_PERIODIC,
+ NULL,
+};
+
+/*
+ * Main struct passed outside
+ * holds precompiled regular expressions
+ */
+struct time_rules_ctx {
+ pcre *re[LP_RGX_MAX];
+};
+
+/*******************************************************************
+ * helper function - bit arrays *
+ *******************************************************************/
+
+/* set a single bit in a bitmap */
+static void set_bit(unsigned char *bitmap, unsigned int bit)
+{
+ bitmap[bit/CHAR_BIT] |= 1 << (bit%CHAR_BIT);
+}
+
+/*
+ * This function is based on bit_nset macro written originally by Paul Vixie,
+ * copyrighted by The Regents of the University of California, as found
+ * in tarball of fcron, file bitstring.h
+ */
+static void set_bit_range(unsigned char *bitmap, unsigned int start,
+ unsigned int stop)
+{
+ int startbyte = start/CHAR_BIT;
+ int stopbyte = stop/CHAR_BIT;
+
+ if (startbyte == stopbyte) {
+ bitmap[startbyte] |= ((0xff << (start & 0x7)) &
+ (0xff >> (CHAR_BIT- 1 - (stop & 0x7))));
+ } else {
+ bitmap[startbyte] |= 0xff << (start & 0x7);
+ while (++startbyte < stopbyte) {
+ bitmap[startbyte] |= 0xff;
+ }
+ bitmap[stopbyte] |= 0xff >> (CHAR_BIT- 1 - (stop & 0x7));
+ }
+}
+
+static int test_bit(unsigned char *bitmap, unsigned int bit)
+{
+ return (int)(bitmap[bit/CHAR_BIT] >> (bit%CHAR_BIT)) & 1;
+}
+
+/*******************************************************************
+ * parsing intervals *
+ *******************************************************************/
+
+/*
+ * Some ranges allow symbolic names, like Mon..Sun for names of day.
+ * This routine takes a list of symbolic names as NAME_ARRAY and the
+ * one we're looking for as KEY and returns its index or -1 when not
+ * found. The last member of NAME_ARRAY must be NULL.
+ */
+static int name_index(const char **name_array, const char *key, int min)
+{
+ int index = 0;
+ const char *one;
+
+ if (name_array == NULL) {
+ return -1;
+ }
+
+ while ((one = name_array[index]) != NULL) {
+ if (strcmp(key,one) == 0) {
+ return index+min;
+ }
+ index++;
+ }
+
+ return -1;
+}
+
+/*
+ * Sets appropriate bits given by an interval in STR (in form of 1,5-7,10) to
+ * a bitfield given in OUT. Does no boundary checking. STR can also contain
+ * symbolic names, these would be given in TRANSLATE.
+ */
+static int interval2bitfield(TALLOC_CTX *mem_ctx,
+ unsigned char *out,
+ const char *str,
+ int min, int max,
+ const char **translate)
+{
+ char *copy;
+ char *next, *token;
+ int tokval, tokmax;
+ char *end_ptr;
+ int ret;
+ char *dash;
+
+ DEBUG(9, ("Converting '%s' to interval\n", str));
+
+ copy = talloc_strdup(mem_ctx, str);
+ CHECK_PTR(copy);
+
+ next = copy;
+ while (next) {
+ token = next;
+ next = strchr(next, ',');
+ if (next) {
+ *next = '\0';
+ next++;
+ }
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr == '\0' && errno == 0) {
+ if (tokval <= max && tokval >= 0) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((dash = strchr(token, '-')) != NULL){
+ *dash = '\0';
+ ++dash;
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokval = name_index(translate, token, min);
+ if (tokval == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+ errno = 0;
+ tokmax = strtol(dash, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokmax = name_index(translate, dash, min);
+ if (tokmax == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+
+ if (tokval <= max && tokmax <= max &&
+ tokval >= min && tokmax >= min) {
+ if (tokmax > tokval) {
+ DEBUG(7, ("Setting interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, tokval, tokmax);
+ } else {
+ /* Interval wraps around - i.e. from 18.00 to 06.00 */
+ DEBUG(7, ("Setting inverted interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, min, tokmax);
+ set_bit_range(out, tokval, max);
+ }
+ continue;
+ } else {
+ /* tokval or tokmax are not between <min, max> */
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((tokval = name_index(translate, token, min)) != -1) {
+ /* Try to translate one token by name */
+ if (tokval <= max) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = EOK;
+done:
+ talloc_free(copy);
+ return ret;
+}
+
+/*******************************************************************
+ * wrappers around regexp handling *
+ *******************************************************************/
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX. The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int copy_substring(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ char **out)
+{
+ const char *result = NULL;
+ int ret;
+ char *o = NULL;
+
+ result = NULL;
+
+ ret = pcre_get_named_substring(pctx->re, str, pctx->ovec,
+ pctx->matches, substr_name, &result);
+ if (ret < 0 || result == NULL) {
+ DEBUG(5, ("named substring '%s' does not exist in '%s'\n",
+ substr_name, str));
+ return ENOENT;
+ }
+
+ o = talloc_strdup(pctx, result);
+ pcre_free_substring(result);
+ if (o == NULL) {
+ return ENOMEM;
+ }
+
+ DEBUG(9, ("Copied substring named '%s' value '%s'\n", substr_name, o));
+
+ *out = o;
+ return EOK;
+}
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX and converts it to an integer.
+ * The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int substring_strtol(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ int *out)
+{
+ char *substr = NULL;
+ int ret;
+ int val;
+ char *err_ptr;
+
+ ret = copy_substring(pctx, str, substr_name, &substr);
+ if (ret != EOK) {
+ DEBUG(5, ("substring '%s' does not exist\n", substr_name));
+ return ret;
+ }
+
+ errno = 0;
+ val = strtol(substr, &err_ptr, 10);
+ if (substr == '\0' || *err_ptr != '\0' || errno != 0) {
+ DEBUG(5, ("substring '%s' does not contain an integerexist\n",
+ substr));
+ talloc_free(substr);
+ return EINVAL;
+ }
+
+ *out = val;
+ talloc_free(substr);
+ return EOK;
+}
+
+/*
+ * Compiles a regular expression REGEXP and tries to match it against the
+ * string STR. Fills in structure _PCTX with info about matching.
+ *
+ * Returns EOK on no error, EFAULT on bad regexp, EINVAL when it cannot
+ * match the regexp.
+ */
+static int matches_regexp(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ enum timelib_rgx regex,
+ struct parse_ctx **_pctx)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ pctx = talloc_zero(ctx, struct parse_ctx);
+ CHECK_PTR(pctx);
+ pctx->ovec = talloc_array(pctx, int, OVEC_SIZE);
+ CHECK_PTR_JMP(pctx->ovec);
+ pctx->re = trctx->re[regex];
+
+ ret = pcre_exec(pctx->re, NULL, str, strlen(str), 0, PCRE_NOTEMPTY, pctx->ovec, OVEC_SIZE);
+ if (ret <= 0) {
+ DEBUG(8, ("string '%s' did *NOT* match regexp '%s'\n", str, lookup_table[regex]));
+ ret = EINVAL;
+ goto done;
+ }
+ DEBUG(8, ("string '%s' matched regexp '%s'\n", str, lookup_table[regex]));
+
+ pctx->matches = ret;
+ *_pctx = pctx;
+ return EOK;
+
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * date/time helper functions *
+ *******************************************************************/
+
+/*
+ * Returns week number as an integer
+ * This may seem ugly, but I think it's actually less error prone
+ * than writing my own routine
+ */
+static int weeknum(const struct tm *t)
+{
+ char buf[3];
+
+ if (!strftime(buf, 3, "%U", t)) {
+ return -1;
+ }
+
+ /* %U returns 0-53, we want 1-54 */
+ return atoi(buf)+1;
+}
+
+/*
+ * Return the week of the month
+ * Range is 1 to 5
+ */
+static int get_week_of_month(const struct tm *t)
+{
+ int fs; /* first sunday */
+
+ fs = (t->tm_mday % 7) - t->tm_wday;
+ if (fs <= 0) {
+ fs += 7;
+ }
+
+ return (t->tm_mday <= fs) ? 1 : (2 + (t->tm_mday - fs - 1) / 7);
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void abs2tm(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year %= 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon--;
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void tm2abs(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year += 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon++;
+}
+
+/*******************************************************************
+ * parsing of HBAC rules themselves *
+ *******************************************************************/
+
+/*
+ * Parses generalized time string given in STR and fills the
+ * information into OUT.
+ */
+static int parse_generalized_time(struct parse_ctx *pctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ time_t *out)
+{
+ int ret;
+ struct parse_ctx *gctx = NULL;
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_isdst = -1;
+
+ ret = matches_regexp(pctx, trctx, str, LP_RGX_GENERALIZED, &gctx);
+ JMP_NEOK(ret);
+
+ /* compulsory */
+ ret = substring_strtol(gctx, str, "year", &tm.tm_year);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "month", &tm.tm_mon);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "day", &tm.tm_mday);
+ JMP_NEOK(ret);
+ /* optional */
+ ret = substring_strtol(gctx, str, "hour", &tm.tm_hour);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "minute", &tm.tm_min);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "second", &tm.tm_sec);
+ JMP_NEOK_LABEL(ret, enoent);
+
+enoent:
+ if (ret == ENOENT) {
+ ret = EOK;
+ }
+
+ abs2tm(&tm);
+
+ *out = mktime(&tm);
+ DEBUG(3, ("converted to time: '%s'\n", ctime(out)));
+ if (*out == -1) {
+ ret = EINVAL;
+ }
+done:
+ talloc_free(gctx);
+ return ret;
+}
+
+/*
+ * Parses absolute timerange string given in STR and fills the
+ * information into ABS.
+ */
+static int parse_absolute(struct absolute_range *absr,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *from = NULL, *to = NULL;
+ int ret;
+
+ ret = copy_substring(pctx, str, "from", &from);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'from' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ ret = copy_substring(pctx, str, "to", &to);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'to' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, from, &absr->time_from);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - first part\n"));
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, to, &absr->time_to);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - second part\n"));
+ goto done;
+ }
+
+ if (difftime(absr->time_to, absr->time_from) < 0) {
+ DEBUG(1, ("Not a valid interval\n"));
+ ret = EINVAL;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(from);
+ talloc_free(to);
+ return ret;
+}
+
+static int parse_hhmm(const char *str, int *hour, int *min)
+{
+ struct tm t;
+ char *err;
+
+ err = strptime(str, "%H%M", &t);
+ if (*err != '\0') {
+ return EINVAL;
+ }
+
+ *hour = t.tm_hour;
+ *min = t.tm_min;
+
+ return EOK;
+}
+
+/*
+ * Parses monthly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_monthly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *mpctx = NULL;
+ char *match = NULL;
+ char *mperspec = NULL;
+
+ /* This code would be much less ugly if RHEL5 PCRE knew about PCRE_DUPNAMES */
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MDAY, &mpctx);
+ if (ret == EOK) {
+ ret = copy_substring(mpctx, str, "mperspec_day", &mperspec);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "interval_day", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ctx, per->day_of_month, DAY_OF_MONTH_BUFSIZE);
+ ret = interval2bitfield(mpctx, per->day_of_month, match,
+ 1, DAY_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+ } else {
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MWEEK, &mpctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "mperspec_week", &mperspec);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "interval_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->week_of_month, match,
+ 1, WEEK_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+done:
+ talloc_free(mpctx);
+ return ret;
+}
+
+/*
+ * Parses yearly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_yearly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *ypctx = NULL;
+ char *match = NULL;
+ char *yperspec = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_YEARLY, &ypctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(ypctx, str, "yperspec_day", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "day_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->day_of_year, DAY_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->day_of_year, match,
+ 1, DAY_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_week", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "week_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->week_of_year, WEEK_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->week_of_year, match,
+ 1, WEEK_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(ypctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_month", &yperspec);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "month_number", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->month, MONTH_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->month, match,
+ 1, MONTH_MAX, names_months);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "m_period", &match);
+ JMP_NEOK(ret);
+ DEBUG(7, ("Monthly year period - calling parse_periodic_monthly()\n"));
+ ret = parse_periodic_monthly(ypctx, trctx, per, match);
+ JMP_NEOK(ret);
+
+done:
+ talloc_free(ypctx);
+ return ret;
+}
+
+/*
+ * Parses weekly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_weekly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *wpctx = NULL;
+ char *dow = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_WEEKLY, &wpctx);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(wpctx, str, "day_of_week", &dow);
+ JMP_NEOK(ret);
+ DEBUG(8, ("day_of_week = '%s'\n", dow));
+
+ ret = interval2bitfield(wpctx, &per->day_of_week, dow,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+
+done:
+ talloc_free(wpctx);
+ return ret;
+}
+
+static int parse_periodic_time(struct periodic_range *per,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ int ret;
+
+ int hour_from;
+ int hour_to;
+ int min_from;
+ int min_to;
+
+ /* parse out the time */
+ ret = copy_substring(pctx, str, "timeFrom", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_from, &min_from);
+ DEBUG(7, ("Parsed timeFrom: %d:%d\n", hour_from, min_from));
+ JMP_NEOK(ret);
+
+ talloc_free(substr);
+ ret = copy_substring(pctx, str, "timeTo", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_to, &min_to);
+ DEBUG(7, ("Parsed timeTo: %d:%d\n", hour_to, min_to));
+ JMP_NEOK(ret);
+
+ /* set the interval */
+ if (hour_from > hour_to ) {
+ set_bit_range(per->hour, 0, hour_to);
+ set_bit_range(per->hour, hour_from, HOUR_MAX);
+ } else {
+ set_bit_range(per->hour, hour_from, hour_to);
+ }
+
+ if (min_from > min_to) {
+ set_bit_range(per->minute, 0, min_to);
+ set_bit_range(per->minute, min_from, MINUTE_MAX);
+ } else {
+ set_bit_range(per->minute, min_from, min_to);
+ }
+
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic(struct periodic_range *per,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ char *period = NULL;
+ int ret;
+
+ /* These are mandatory */
+ BUFFER_OR_JUMP(per, per->hour, HOUR_BUFSIZE);
+ BUFFER_OR_JUMP(per, per->minute, MINUTE_BUFSIZE);
+
+ ret = copy_substring(pctx, str, "perspec", &substr);
+ JMP_NEOK(ret);
+ ret = copy_substring(pctx, str, "period", &period);
+ JMP_NEOK(ret);
+
+ if (strcmp(substr, "yearly") == 0) {
+ DEBUG(5, ("periodic yearly\n"));
+ ret = parse_periodic_yearly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "monthly") == 0) {
+ DEBUG(5, ("periodic monthly\n"));
+ ret = parse_periodic_monthly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "weekly") == 0) {
+ DEBUG(5, ("periodic weekly\n"));
+ ret = parse_periodic_weekly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "daily") == 0) {
+ DEBUG(5, ("periodic daily\n"));
+ } else {
+ DEBUG(1, ("Cannot determine periodic rule type"
+ "(perspec = '%s', period = '%s')\n", substr, period));
+ ret = EINVAL;
+ goto done;
+ }
+
+ talloc_free(period);
+
+ ret = parse_periodic_time(per, pctx, str);
+ JMP_NEOK(ret);
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses time specification given in string RULE into range_ctx
+ * context CTX.
+ */
+static int parse_timespec(struct range_ctx *ctx, const char *rule)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_ABSOLUTE, &pctx) == EOK) {
+ DEBUG(5, ("Matched absolute range\n"));
+ ctx->type = TYPE_ABSOLUTE;
+ ctx->abs = talloc_zero(ctx, struct absolute_range);
+ CHECK_PTR_JMP(ctx->abs);
+
+ ret = parse_absolute(ctx->abs, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_PERIODIC, &pctx) == EOK) {
+ DEBUG(5, ("Matched periodic range\n"));
+ ctx->type = TYPE_PERIODIC;
+ ctx->per = talloc_zero(ctx, struct periodic_range);
+ CHECK_PTR_JMP(ctx->per);
+
+ ret = parse_periodic(ctx->per, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else {
+ DEBUG(1, ("Cannot determine rule type\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * validation of rules against time_t *
+ *******************************************************************/
+
+static int absolute_timerange_valid(struct absolute_range *absr,
+ const time_t now,
+ bool *result)
+{
+ if (difftime(absr->time_from, now) > 0) {
+ DEBUG(3, ("Absolute timerange invalid (before interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ if (difftime(absr->time_to, now) < 0) {
+ DEBUG(3, ("Absolute timerange invalid (after interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ DEBUG(3, ("Absolute timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+static int periodic_timerange_valid(struct periodic_range *per,
+ const time_t now,
+ bool *result)
+{
+ struct tm tm_now;
+ int wnum;
+ int wom;
+
+ memset(&tm_now, 0, sizeof(struct tm));
+ if (localtime_r(&now, &tm_now) == NULL) {
+ DEBUG(0, ("Cannot convert time_t to struct tm\n"));
+ return EFAULT;
+ }
+ DEBUG(9, ("Got struct tm value %s", asctime(&tm_now)));
+ tm2abs(&tm_now);
+
+ wnum = weeknum(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week number is %d\n", wnum));
+
+ wom = get_week_of_month(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week of number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week of month number is %d\n", wom));
+
+ /* The validation itself */
+ TEST_BIT_RANGE(per->day_of_week, tm_now.tm_wday, result);
+ DEBUG(9, ("day of week OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_month, tm_now.tm_mday, result);
+ DEBUG(9, ("day of month OK\n"));
+ TEST_BIT_RANGE(per->week_of_month, wom, result);
+ DEBUG(9, ("week of month OK\n"));
+ TEST_BIT_RANGE_PTR(per->week_of_year, wnum, result);
+ DEBUG(9, ("week of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->month, tm_now.tm_mon, result);
+ DEBUG(9, ("month OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_year, tm_now.tm_yday, result);
+ DEBUG(9, ("day of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->hour, tm_now.tm_hour, result);
+ DEBUG(9, ("hour OK\n"));
+ TEST_BIT_RANGE_PTR(per->minute, tm_now.tm_min, result);
+ DEBUG(9, ("minute OK\n"));
+
+ DEBUG(3, ("Periodic timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+/*
+ * Returns EOK if the timerange in range_ctx context is valid compared against a
+ * given time_t value in NOW, returns ERANGE if the time value is outside the
+ * specified range.
+ */
+static int timerange_valid(struct range_ctx *ctx,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+
+ switch(ctx->type) {
+ case TYPE_ABSOLUTE:
+ DEBUG(7, ("Checking absolute range\n"));
+ ret = absolute_timerange_valid(ctx->abs, now, result);
+ break;
+
+ case TYPE_PERIODIC:
+ DEBUG(7, ("Checking periodic range\n"));
+ ret = periodic_timerange_valid(ctx->per, now, result);
+ break;
+
+ default:
+ DEBUG(1, ("Unknown range type (%d)\n", ctx->type));
+ ret = EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ * public interface *
+ *******************************************************************/
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule is valid in the current time, ERANGE if not and
+ * EINVAL if the rule cannot be parsed
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+ struct range_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct range_ctx);
+ CHECK_PTR_JMP(ctx);
+ ctx->trctx = trctx;
+
+ DEBUG(9, ("Got time_t value %s", ctime(&now)));
+
+ ret = parse_timespec(ctx, str);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse the time specification (%d)\n", ret));
+ goto done;
+ }
+
+ ret = timerange_valid(ctx, now, result);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot check the time range (%d)\n", ret));
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(ctx);
+ return EOK;
+}
+
+/*
+ * Frees the resources taken by the precompiled rules
+ */
+static int time_rules_parser_destructor(struct time_rules_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i< LP_RGX_MAX; ++i) {
+ pcre_free(ctx->re[i]);
+ ctx->re[i] = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Initializes the parser by precompiling the regular expressions
+ * for later use
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out)
+{
+ const char *errstr;
+ int errval;
+ int errpos;
+ int ret;
+ int i;
+ struct time_rules_ctx *ctx = NULL;
+
+ ctx = talloc_zero(mem_ctx, struct time_rules_ctx);
+ CHECK_PTR(ctx);
+ talloc_set_destructor(ctx, time_rules_parser_destructor);
+
+ /* Precompile regular expressions */
+ for (i = LP_RGX_GENERALIZED; i< LP_RGX_MAX; ++i) {
+ ctx->re[i] = pcre_compile2(lookup_table[i],
+ 0,
+ &errval,
+ &errstr,
+ &errpos,
+ NULL);
+
+ if (ctx->re[i] == NULL) {
+ DEBUG(0, ("Invalid Regular Expression pattern '%s' at position %d"
+ " (Error: %d [%s])\n", lookup_table[i],
+ errpos, errval, errstr));
+ ret = EFAULT;
+ goto done;
+ }
+
+ }
+
+ *_out = ctx;
+ return EOK;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
diff --git a/src/providers/ipa/ipa_timerules.h b/src/providers/ipa/ipa_timerules.h
new file mode 100644
index 00000000..e1beaa22
--- /dev/null
+++ b/src/providers/ipa/ipa_timerules.h
@@ -0,0 +1,56 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __IPA_TIMERULES_H_
+#define __IPA_TIMERULES_H_
+
+#include <stdbool.h>
+#include <talloc.h>
+
+/* Opaque structure given after init */
+struct time_rules_ctx;
+
+/*
+ * Init the parser. Destroy the allocated resources by simply
+ * talloc_free()-ing the time_rules_ctx
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out);
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule can be parsed, error code if not. If the time
+ * given in the NOW parameter would be accepted by the rule, it stores true in
+ * RESULT, false otherwise.
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result);
+
+#endif /* __IPA_TIMERULES_H_ */
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
new file mode 100644
index 00000000..a2dadc80
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.c
@@ -0,0 +1,1193 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <errno.h>
+#include <sys/time.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/find_uid.h"
+#include "db/sysdb.h"
+#include "providers/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#else
+#define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child"
+#endif
+
+static errno_t add_krb5_env(struct dp_option *opts, const char *ccname,
+ struct pam_data *pd)
+{
+ int ret;
+ const char *dummy;
+ char *env;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ if (ccname != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_REALM);
+ if (dummy != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+ if (dummy != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname,
+ bool *result)
+{
+ int ret;
+ size_t offset = 0;
+ struct stat stat_buf;
+ const char *filename;
+ bool active;
+
+ *result = false;
+
+ if (ccname == NULL || *ccname == '\0') {
+ return EINVAL;
+ }
+
+ if (strncmp(ccname, "FILE:", 5) == 0) {
+ offset = 5;
+ }
+
+ filename = ccname + offset;
+
+ if (filename[0] != '/') {
+ DEBUG(1, ("Only absolute path names are allowed"));
+ return EINVAL;
+ }
+
+ ret = lstat(filename, &stat_buf);
+
+ if (ret == -1 && errno != ENOENT) {
+ DEBUG(1, ("stat failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ } else if (ret == EOK) {
+ if (stat_buf.st_uid != uid) {
+ DEBUG(1, ("Cache file [%s] exists, but is owned by [%d] instead of "
+ "[%d].\n", filename, stat_buf.st_uid, uid));
+ return EINVAL;
+ }
+
+ if (!S_ISREG(stat_buf.st_mode)) {
+ DEBUG(1, ("Cache file [%s] exists, but is not a regular file.\n",
+ filename));
+ return EINVAL;
+ }
+ }
+
+ ret = check_if_uid_is_active(uid, &active);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_uid_is_active failed.\n"));
+ return ret;
+ }
+
+ if (!active) {
+ DEBUG(5, ("User [%d] is not active\n", uid));
+ } else {
+ DEBUG(9, ("User [%d] is still active, reusing ccache file [%s].\n",
+ uid, filename));
+ *result = true;
+ }
+ return EOK;
+}
+
+struct krb5_save_ccname_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+ const char *name;
+ struct sysdb_attrs *attrs;
+};
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq);
+static void krb5_set_user_attr_done(struct tevent_req *subreq);
+
+static struct tevent_req *krb5_save_ccname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *ccname)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct krb5_save_ccname_state *state;
+ int ret;
+
+ if (name == NULL || ccname == NULL) {
+ DEBUG(1, ("Missing user or ccache name.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct krb5_save_ccname_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->domain = domain;
+ state->name = name;
+
+ state->attrs = sysdb_new_attrs(state);
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_CCACHE_FILE, ccname);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
+ goto failed;
+ }
+
+ subreq = sysdb_transaction_send(state, ev, sysdb);
+ if (subreq == NULL) {
+ goto failed;
+ }
+ tevent_req_set_callback(subreq, krb5_save_ccname_trans, req);
+
+ return req;
+
+failed:
+ talloc_free(req);
+ return NULL;
+}
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct krb5_save_ccname_state *state = tevent_req_data(req,
+ struct krb5_save_ccname_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->attrs, SYSDB_MOD_REP);
+ if (subreq == NULL) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, krb5_set_user_attr_done, req);
+}
+
+static void krb5_set_user_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct krb5_save_ccname_state *state = tevent_req_data(req,
+ struct krb5_save_ccname_state);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ return;
+}
+
+int krb5_save_ccname_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+ const char *keytab;
+ uint32_t validate;
+
+ keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB);
+ if (keytab == NULL) {
+ DEBUG(1, ("Missing keytab option.\n"));
+ return EINVAL;
+ }
+
+ validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0;
+
+ buf = talloc(kr, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ buf->size = 9*sizeof(uint32_t) + strlen(kr->pd->upn) + strlen(kr->ccname) +
+ strlen(keytab) +
+ kr->pd->authtok_size;
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ buf->size += sizeof(uint32_t) + kr->pd->newauthtok_size;
+ }
+
+ buf->data = talloc_size(kr, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+ COPY_UINT32(&buf->data[rp], &kr->pd->cmd, rp);
+ COPY_UINT32(&buf->data[rp], &kr->pd->pw_uid, rp);
+ COPY_UINT32(&buf->data[rp], &kr->pd->gr_gid, rp);
+ COPY_UINT32(&buf->data[rp], &validate, rp);
+ COPY_UINT32(&buf->data[rp], &kr->is_offline, rp);
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->pd->upn), rp);
+ COPY_MEM(&buf->data[rp], kr->pd->upn, rp, strlen(kr->pd->upn));
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->ccname), rp);
+ COPY_MEM(&buf->data[rp], kr->ccname, rp, strlen(kr->ccname));
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab), rp);
+ COPY_MEM(&buf->data[rp], keytab, rp, strlen(keytab));
+
+ COPY_UINT32(&buf->data[rp], &kr->pd->authtok_size, rp);
+ COPY_MEM(&buf->data[rp], kr->pd->authtok, rp, kr->pd->authtok_size);
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ COPY_UINT32(&buf->data[rp], &kr->pd->newauthtok_size, rp);
+ COPY_MEM(&buf->data[rp], kr->pd->newauthtok,
+ rp, kr->pd->newauthtok_size);
+ }
+
+ *io_buf = buf;
+
+ return EOK;
+}
+
+static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req)
+{
+ struct pam_data *pd;
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ return talloc_get_type(be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct krb5_ctx);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ return talloc_get_type(be_req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct krb5_ctx);
+ break;
+ default:
+ DEBUG(1, ("Unsupported PAM task.\n"));
+ return NULL;
+ }
+}
+
+static void krb_reply(struct be_req *req, int dp_err, int result);
+
+static void krb5_child_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct krb5child_req *kr = talloc_get_type(pvt, struct krb5child_req);
+ struct be_req *be_req = kr->req;
+ struct pam_data *pd = kr->pd;
+ int ret;
+
+ if (kr->timeout_handler == NULL) {
+ return;
+ }
+
+ DEBUG(9, ("timeout for child [%d] reached.\n", kr->child_pid));
+
+ ret = kill(kr->child_pid, SIGKILL);
+ if (ret == -1) {
+ DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+ talloc_zfree(kr);
+
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ be_mark_offline(be_req->be_ctx);
+
+ krb_reply(be_req, DP_ERR_OFFLINE, pd->pam_status);
+}
+
+static errno_t activate_child_timeout_handler(struct krb5child_req *kr)
+{
+ struct timeval tv;
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv,
+ dp_opt_get_int(kr->krb5_ctx->opts,
+ KRB5_AUTH_TIMEOUT),
+ 0);
+ kr->timeout_handler = tevent_add_timer(kr->req->be_ctx->ev, kr, tv,
+ krb5_child_timeout, kr);
+ if (kr->timeout_handler == NULL) {
+ DEBUG(1, ("tevent_add_timer failed.\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static int krb5_cleanup(void *ptr)
+{
+ struct krb5child_req *kr = talloc_get_type(ptr, struct krb5child_req);
+
+ if (kr == NULL) return EOK;
+
+ child_cleanup(kr->read_from_child_fd, kr->write_to_child_fd);
+ memset(kr, 0, sizeof(struct krb5child_req));
+
+ return EOK;
+}
+
+static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req)
+{
+ struct krb5child_req *kr = NULL;
+ struct krb5_ctx *krb5_ctx;
+ struct pam_data *pd;
+ errno_t err;
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ krb5_ctx = get_krb5_ctx(req);
+ if (krb5_ctx == NULL) {
+ DEBUG(1, ("Kerberos context not available.\n"));
+ err = EINVAL;
+ goto failed;
+ }
+
+ kr = talloc_zero(req, struct krb5child_req);
+ if (kr == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ err = ENOMEM;
+ goto failed;
+ }
+ kr->read_from_child_fd = -1;
+ kr->write_to_child_fd = -1;
+ kr->is_offline = false;
+ kr->active_ccache_present = true;
+ talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
+
+ kr->pd = pd;
+ kr->req = req;
+ kr->krb5_ctx = krb5_ctx;
+
+ *krb5_req = kr;
+
+ return EOK;
+
+failed:
+ talloc_zfree(kr);
+
+ return err;
+}
+
+static errno_t fork_child(struct krb5child_req *kr)
+{
+ int pipefd_to_child[2];
+ int pipefd_from_child[2];
+ pid_t pid;
+ int ret;
+ errno_t err;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ /* We need to keep the root privileges to read the keytab file if
+ * validation is enabled, otherwise we can drop them here and run
+ * krb5_child with user privileges.
+ * If authtok_size is zero we are offline and want to create an empty
+ * ccache file. In this case we can drop the privileges, too. */
+ if (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ||
+ kr->pd->authtok_size == 0) {
+ ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid);
+ if (ret != EOK) {
+ DEBUG(1, ("become_user failed.\n"));
+ return ret;
+ }
+ }
+
+ err = exec_child(kr,
+ pipefd_to_child, pipefd_from_child,
+ KRB5_CHILD, kr->krb5_ctx->child_debug_fd);
+ if (err != EOK) {
+ DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n",
+ err, strerror(err)));
+ return err;
+ }
+ } else if (pid > 0) { /* parent */
+ kr->child_pid = pid;
+ kr->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ kr->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ fd_nonblocking(kr->read_from_child_fd);
+ fd_nonblocking(kr->write_to_child_fd);
+
+ err = activate_child_timeout_handler(kr);
+ if (err != EOK) {
+ DEBUG(1, ("activate_child_timeout_handler failed.\n"));
+ }
+
+ } else { /* error */
+ err = errno;
+ DEBUG(1, ("fork failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+
+ return EOK;
+}
+
+struct handle_child_state {
+ struct tevent_context *ev;
+ struct krb5child_req *kr;
+ uint8_t *buf;
+ ssize_t len;
+};
+
+static void handle_child_step(struct tevent_req *subreq);
+static void handle_child_done(struct tevent_req *subreq);
+
+static struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct krb5child_req *kr)
+{
+ struct tevent_req *req, *subreq;
+ struct handle_child_state *state;
+ struct io_buffer *buf;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct handle_child_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->kr = kr;
+ state->buf = NULL;
+ state->len = 0;
+
+ ret = create_send_buffer(kr, &buf);
+ if (ret != EOK) {
+ DEBUG(1, ("create_send_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = fork_child(kr);
+ if (ret != EOK) {
+ DEBUG(1, ("fork_child failed.\n"));
+ goto fail;
+ }
+
+ subreq = write_pipe_send(state, ev, buf->data, buf->size,
+ kr->write_to_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, handle_child_step, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void handle_child_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ close(state->kr->write_to_child_fd);
+ state->kr->write_to_child_fd = -1;
+
+ subreq = read_pipe_send(state, state->ev, state->kr->read_from_child_fd);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, handle_child_done, req);
+}
+
+static void handle_child_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ close(state->kr->read_from_child_fd);
+ state->kr->read_from_child_fd = -1;
+
+ tevent_req_done(req);
+ return;
+}
+
+static int handle_child_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_move(mem_ctx, &state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res);
+static void krb5_resolve_done(struct tevent_req *req);
+static void krb5_save_ccname_done(struct tevent_req *req);
+static void krb5_child_done(struct tevent_req *req);
+static void krb5_pam_handler_cache_done(struct tevent_req *treq);
+
+void krb5_pam_handler(struct be_req *be_req)
+{
+ struct pam_data *pd;
+ const char **attrs;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ goto done;
+ break;
+ default:
+ DEBUG(4, ("krb5 does not handles pam task %d.\n", pd->cmd));
+ pam_status = PAM_MODULE_UNKNOWN;
+ dp_err = DP_ERR_OK;
+ goto done;
+ }
+
+ if (be_is_offline(be_req->be_ctx) &&
+ (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+ DEBUG(9, ("Password changes are not possible while offline.\n"));
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ attrs = talloc_array(be_req, const char *, 4);
+ if (attrs == NULL) {
+ goto done;
+ }
+
+ attrs[0] = SYSDB_UPN;
+ attrs[1] = SYSDB_HOMEDIR;
+ attrs[2] = SYSDB_CCACHE_FILE;
+ attrs[3] = NULL;
+
+ ret = sysdb_get_user_attr(be_req, be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user, attrs,
+ get_user_attr_done, be_req);
+
+ if (ret) {
+ goto done;
+ }
+
+ return;
+
+done:
+ pd->pam_status = pam_status;
+
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res)
+{
+ struct be_req *be_req = talloc_get_type(pvt, struct be_req);
+ struct krb5_ctx *krb5_ctx;
+ struct krb5child_req *kr = NULL;
+ struct tevent_req *req;
+ krb5_error_code kerr;
+ int ret;
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ int pam_status=PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ const char *ccache_file = NULL;
+ const char *realm;
+
+ ret = krb5_setup(be_req, &kr);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_setup failed.\n"));
+ goto failed;
+ }
+
+ krb5_ctx = kr->krb5_ctx;
+
+ if (err != LDB_SUCCESS) {
+ DEBUG(5, ("sysdb search for upn of user [%s] failed.\n", pd->user));
+ goto failed;
+ }
+
+ realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(1, ("Missing Kerberos realm.\n"));
+ goto failed;
+ }
+
+ switch (res->count) {
+ case 0:
+ DEBUG(5, ("No attributes for user [%s] found.\n", pd->user));
+ goto failed;
+ break;
+
+ case 1:
+ pd->upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL);
+ if (pd->upn == NULL) {
+ /* NOTE: this is a hack, works only in some environments */
+ pd->upn = talloc_asprintf(be_req, "%s@%s", pd->user, realm);
+ if (pd->upn == NULL) {
+ DEBUG(1, ("failed to build simple upn.\n"));
+ goto failed;
+ }
+ DEBUG(9, ("Using simple UPN [%s].\n", pd->upn));
+ }
+
+ kr->homedir = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR,
+ NULL);
+ if (kr->homedir == NULL) {
+ DEBUG(4, ("Home directory for user [%s] not known.\n", pd->user));
+ }
+
+ ccache_file = ldb_msg_find_attr_as_string(res->msgs[0],
+ SYSDB_CCACHE_FILE,
+ NULL);
+ if (ccache_file != NULL) {
+ ret = check_if_ccache_file_is_used(pd->pw_uid, ccache_file,
+ &kr->active_ccache_present);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_ccache_file_is_used failed.\n"));
+ goto failed;
+ }
+
+ kerr = check_for_valid_tgt(ccache_file, realm, pd->upn,
+ &kr->valid_tgt_present);
+ if (kerr != 0) {
+ DEBUG(1, ("check_for_valid_tgt failed.\n"));
+ goto failed;
+ }
+ } else {
+ kr->active_ccache_present = false;
+ kr->valid_tgt_present = false;
+ DEBUG(4, ("No ccache file for user [%s] found.\n", pd->user));
+ }
+ DEBUG(9, ("Ccache_file is [%s] and is %s active and TGT is %s valid.\n",
+ ccache_file ? ccache_file : "not set",
+ kr->active_ccache_present ? "" : "not",
+ kr->valid_tgt_present ? "" : "not"));
+ kr->ccname = ccache_file;
+ break;
+
+ default:
+ DEBUG(1, ("A user search by name (%s) returned > 1 results!\n",
+ pd->user));
+ goto failed;
+ break;
+ }
+
+ req = be_resolve_server_send(kr, be_req->be_ctx->ev, be_req->be_ctx,
+ krb5_ctx->service->name);
+ if (req == NULL) {
+ DEBUG(1, ("handle_child_send failed.\n"));
+ goto failed;
+ }
+
+ tevent_req_set_callback(req, krb5_resolve_done, kr);
+
+ return;
+
+failed:
+ talloc_free(kr);
+
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_resolve_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ char *msg;
+ size_t offset = 0;
+
+ ret = be_resolve_server_recv(req, &kr->srv);
+ talloc_zfree(req);
+ if (ret) {
+ /* all servers have been tried and none
+ * was found good, setting offline,
+ * but we still have to call the child to setup
+ * the ccache file. */
+ be_mark_offline(be_req->be_ctx);
+ kr->is_offline = true;
+ }
+
+ if (kr->ccname == NULL ||
+ (be_is_offline(be_req->be_ctx) && !kr->active_ccache_present &&
+ !kr->valid_tgt_present) ||
+ (!be_is_offline(be_req->be_ctx) && !kr->active_ccache_present)) {
+ DEBUG(9, ("Recreating ccache file.\n"));
+ if (kr->ccname != NULL) {
+ if (strncmp(kr->ccname, "FILE:", 5) == 0) {
+ offset = 5;
+ }
+ if (kr->ccname[offset] != '/') {
+ DEBUG(1, ("Ccache file name [%s] is not an absolute path.\n",
+ kr->ccname + offset));
+ goto done;
+ }
+ ret = unlink(kr->ccname + offset);
+ if (ret == -1 && errno != ENOENT) {
+ DEBUG(1, ("unlink [%s] failed [%d][%s].\n", kr->ccname,
+ errno, strerror(errno)));
+ goto done;
+ }
+ }
+ kr->ccname = expand_ccname_template(kr, kr,
+ dp_opt_get_cstring(kr->krb5_ctx->opts,
+ KRB5_CCNAME_TMPL)
+ );
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("expand_ccname_template failed.\n"));
+ goto done;
+ }
+ }
+
+ if (be_is_offline(be_req->be_ctx)) {
+ DEBUG(9, ("Preparing for offline operation.\n"));
+ kr->is_offline = true;
+
+ if (kr->valid_tgt_present) {
+ DEBUG(9, ("Valid TGT available, nothing to do.\n"));
+ msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname);
+ if (msg == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1,
+ (uint8_t *) msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+ memset(pd->authtok, 0, pd->authtok_size);
+ pd->authtok_size = 0;
+
+ if (kr->active_ccache_present) {
+ req = krb5_save_ccname_send(kr, be_req->be_ctx->ev,
+ be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user,
+ kr->ccname);
+ if (req == NULL) {
+ DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+ return;
+ }
+ }
+
+ req = handle_child_send(kr, be_req->be_ctx->ev, kr);
+ if (req == NULL) {
+ DEBUG(1, ("handle_child_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_child_done, kr);
+ return;
+
+done:
+ talloc_free(kr);
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_child_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ int ret;
+ uint8_t *buf = NULL;
+ ssize_t len = -1;
+ ssize_t pref_len;
+ int p;
+ int32_t *msg_status;
+ int32_t *msg_type;
+ int32_t *msg_len;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+
+ ret = handle_child_recv(req, pd, &buf, &len);
+ talloc_zfree(kr->timeout_handler);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret)));
+ goto done;
+ }
+
+ if ((size_t) len < 3*sizeof(int32_t)) {
+ DEBUG(1, ("message too short.\n"));
+ goto done;
+ }
+
+ p=0;
+ msg_status = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ msg_type = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ msg_len = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ DEBUG(4, ("child response [%d][%d][%d].\n", *msg_status, *msg_type,
+ *msg_len));
+
+ if ((p + *msg_len) != len) {
+ DEBUG(1, ("message format error.\n"));
+ goto done;
+ }
+
+ if (*msg_status != PAM_SUCCESS && *msg_status != PAM_AUTHINFO_UNAVAIL) {
+ pam_status = *msg_status;
+ dp_err = DP_ERR_OK;
+
+ ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+
+ goto done;
+ } else {
+ pd->pam_status = *msg_status;
+ }
+
+ if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ goto done;
+ }
+
+ pref_len = strlen(CCACHE_ENV_NAME)+1;
+ if (*msg_len > pref_len &&
+ strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
+ kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
+ *msg_len-pref_len);
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ goto done;
+ }
+ } else {
+ DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len,
+ &buf[p]));
+ goto done;
+ }
+
+ if (*msg_status == PAM_AUTHINFO_UNAVAIL) {
+ if (kr->srv != NULL) {
+ fo_set_port_status(kr->srv, PORT_NOT_WORKING);
+ }
+ be_mark_offline(be_req->be_ctx);
+ kr->is_offline = true;
+ } else if (kr->srv != NULL) {
+ fo_set_port_status(kr->srv, PORT_WORKING);
+ }
+
+ struct sysdb_attrs *attrs;
+ attrs = sysdb_new_attrs(kr);
+ ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
+ goto done;
+ }
+
+ req = krb5_save_ccname_send(kr, be_req->be_ctx->ev, be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user, kr->ccname);
+ if (req == NULL) {
+ DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+ return;
+done:
+ talloc_free(kr);
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_save_ccname_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ struct krb5_ctx *krb5_ctx = kr->krb5_ctx;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+ char *password = NULL;
+
+ if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+ ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd);
+ if (ret != EOK) {
+ DEBUG(1, ("add_krb5_env failed.\n"));
+ goto failed;
+ }
+ }
+
+ ret = sysdb_set_user_attr_recv(req);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("Saving ccache name failed.\n"));
+ goto failed;
+ }
+
+ if (kr->is_offline) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto failed;
+ }
+
+ if (be_req->be_ctx->domain->cache_credentials == TRUE) {
+
+ /* password caching failures are not fatal errors */
+ pd->pam_status = PAM_SUCCESS;
+
+ switch(pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ password = talloc_size(be_req, pd->authtok_size + 1);
+ if (password != NULL) {
+ memcpy(password, pd->authtok, pd->authtok_size);
+ password[pd->authtok_size] = '\0';
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ password = talloc_size(be_req, pd->newauthtok_size + 1);
+ if (password != NULL) {
+ memcpy(password, pd->newauthtok, pd->newauthtok_size);
+ password[pd->newauthtok_size] = '\0';
+ }
+ break;
+ default:
+ DEBUG(0, ("unsupported PAM command [%d].\n", pd->cmd));
+ }
+
+ if (password == NULL) {
+ DEBUG(0, ("password not available, offline auth may not work.\n"));
+ goto failed;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ req = sysdb_cache_password_send(be_req, be_req->be_ctx->ev,
+ be_req->be_ctx->sysdb, NULL,
+ be_req->be_ctx->domain, pd->user,
+ password);
+ if (req == NULL) {
+ DEBUG(2, ("cache_password_send failed, offline auth may not work.\n"));
+ goto failed;
+ }
+ tevent_req_set_callback(req, krb5_pam_handler_cache_done, be_req);
+ return;
+ }
+
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+
+failed:
+ talloc_free(kr);
+
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_pam_handler_cache_done(struct tevent_req *subreq)
+{
+ struct be_req *be_req = tevent_req_callback_data(subreq, struct be_req);
+ int ret;
+
+ /* password caching failures are not fatal errors */
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* so we just log it any return */
+ if (ret) {
+ DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
+ ret, strerror(ret)));
+ }
+
+ krb_reply(be_req, DP_ERR_OK, PAM_SUCCESS);
+}
+
+static void krb_reply(struct be_req *req, int dp_err, int result)
+{
+ req->fn(req, dp_err, result, NULL);
+}
+
diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h
new file mode 100644
index 00000000..a011af89
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.h
@@ -0,0 +1,91 @@
+/*
+ SSSD
+
+ Kerberos Backend, private header file
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+
+ 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/>.
+*/
+
+#ifndef __KRB5_AUTH_H__
+#define __KRB5_AUTH_H__
+
+#include "util/sss_krb5.h"
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_common.h"
+
+#define CCACHE_ENV_NAME "KRB5CCNAME"
+#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE"
+
+typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
+
+struct krb5child_req {
+ pid_t child_pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+
+ struct be_req *req;
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+
+ struct tevent_timer *timeout_handler;
+
+ const char *ccname;
+ const char *homedir;
+ bool is_offline;
+ struct fo_server *srv;
+ bool active_ccache_present;
+ bool valid_tgt_present;
+};
+
+struct fo_service;
+
+struct krb5_ctx {
+ /* opts taken from kinit */
+ /* in seconds */
+ krb5_deltat starttime;
+ krb5_deltat lifetime;
+ krb5_deltat rlife;
+
+ int forwardable;
+ int proxiable;
+ int addresses;
+
+ int not_forwardable;
+ int not_proxiable;
+ int no_addresses;
+
+ int verbose;
+
+ char* principal_name;
+ char* service_name;
+ char* keytab_name;
+ char* k5_cache_name;
+ char* k4_cache_name;
+
+ action_type action;
+
+ struct dp_option *opts;
+ struct krb5_service *service;
+ int child_debug_fd;
+};
+
+void krb5_pam_handler(struct be_req *be_req);
+
+#endif /* __KRB5_AUTH_H__ */
diff --git a/src/providers/krb5/krb5_become_user.c b/src/providers/krb5/krb5_become_user.c
new file mode 100644
index 00000000..351f539a
--- /dev/null
+++ b/src/providers/krb5/krb5_become_user.c
@@ -0,0 +1,61 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <unistd.h>
+
+#include "util/util.h"
+
+errno_t become_user(uid_t uid, gid_t gid)
+{
+ int ret;
+
+ DEBUG(9, ("Trying to become user [%d][%d].\n", uid, gid));
+ ret = setgid(gid);
+ if (ret == -1) {
+ DEBUG(1, ("setgid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = setuid(uid);
+ if (ret == -1) {
+ DEBUG(1, ("setuid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = setegid(gid);
+ if (ret == -1) {
+ DEBUG(1, ("setegid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = seteuid(uid);
+ if (ret == -1) {
+ DEBUG(1, ("seteuid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
new file mode 100644
index 00000000..5e185940
--- /dev/null
+++ b/src/providers/krb5/krb5_child.c
@@ -0,0 +1,1030 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- tgt_req and changepw child
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/user_info_msg.h"
+#include "providers/child_common.h"
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+
+struct krb5_child_ctx {
+ /* opts taken from kinit */
+ /* in seconds */
+ krb5_deltat starttime;
+ krb5_deltat lifetime;
+ krb5_deltat rlife;
+
+ int forwardable;
+ int proxiable;
+ int addresses;
+
+ int not_forwardable;
+ int not_proxiable;
+ int no_addresses;
+
+ int verbose;
+
+ char* principal_name;
+ char* service_name;
+ char* keytab_name;
+ char* k5_cache_name;
+ char* k4_cache_name;
+
+ action_type action;
+
+ char *kdcip;
+ char *realm;
+ char *changepw_principle;
+ char *ccache_dir;
+ char *ccname_template;
+ int auth_timeout;
+
+ int child_debug_fd;
+};
+
+struct krb5_req {
+ krb5_context ctx;
+ krb5_principal princ;
+ char* name;
+ krb5_creds *creds;
+ krb5_get_init_creds_opt *options;
+ pid_t child_pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+
+ struct be_req *req;
+ struct pam_data *pd;
+ struct krb5_child_ctx *krb5_ctx;
+ errno_t (*child_req)(int fd, struct krb5_req *kr);
+
+ char *ccname;
+ char *keytab;
+ bool validate;
+};
+
+static krb5_context krb5_error_ctx;
+static const char *__krb5_error_msg;
+#define KRB5_DEBUG(level, krb5_error) do { \
+ __krb5_error_msg = sss_krb5_get_error_message(krb5_error_ctx, krb5_error); \
+ DEBUG(level, ("%d: [%d][%s]\n", __LINE__, krb5_error, __krb5_error_msg)); \
+ sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \
+} while(0);
+
+static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred)
+{
+ krb5_error_code kerr;
+ krb5_creds *cred = NULL;
+ krb5_data *krb5_realm;
+
+ cred = calloc(sizeof(krb5_creds), 1);
+ if (cred == NULL) {
+ DEBUG(1, ("calloc failed.\n"));
+ return ENOMEM;
+ }
+
+ kerr = krb5_copy_principal(kr->ctx, kr->princ, &cred->client);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_copy_principal failed.\n"));
+ goto done;
+ }
+
+ krb5_realm = krb5_princ_realm(kr->ctx, kr->princ);
+
+ kerr = krb5_build_principal_ext(kr->ctx, &cred->server,
+ krb5_realm->length, krb5_realm->data,
+ KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
+ krb5_realm->length, krb5_realm->data, 0);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_build_principal_ext failed.\n"));
+ goto done;
+ }
+
+done:
+ if (kerr != 0) {
+ if (cred != NULL && cred->client != NULL) {
+ krb5_free_principal(kr->ctx, cred->client);
+ }
+
+ free(cred);
+ } else {
+ *_cred = cred;
+ }
+
+ return kerr;
+}
+
+static krb5_error_code create_ccache_file(struct krb5_req *kr, krb5_creds *creds)
+{
+ krb5_error_code kerr;
+ krb5_ccache tmp_cc = NULL;
+ char *cc_file_name;
+ int fd = -1;
+ size_t ccname_len;
+ char *dummy;
+ char *tmp_ccname;
+ krb5_creds *l_cred;
+
+ if (strncmp(kr->ccname, "FILE:", 5) == 0) {
+ cc_file_name = kr->ccname + 5;
+ } else {
+ cc_file_name = kr->ccname;
+ }
+
+ if (cc_file_name[0] != '/') {
+ DEBUG(1, ("Ccache filename is not an absolute path.\n"));
+ return EINVAL;
+ }
+
+ dummy = strrchr(cc_file_name, '/');
+ tmp_ccname = talloc_strndup(kr, cc_file_name, (size_t) (dummy-cc_file_name));
+ if (tmp_ccname == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ tmp_ccname = talloc_asprintf_append(tmp_ccname, "/.krb5cc_dummy_XXXXXX");
+
+ fd = mkstemp(tmp_ccname);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ kerr = krb5_cc_resolve(kr->ctx, tmp_ccname, &tmp_cc);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_initialize(kr->ctx, tmp_cc, kr->princ);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+
+ if (creds == NULL) {
+ kerr = create_empty_cred(kr, &l_cred);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ } else {
+ l_cred = creds;
+ }
+
+ kerr = krb5_cc_store_cred(kr->ctx, tmp_cc, l_cred);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_close(kr->ctx, tmp_cc);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ tmp_cc = NULL;
+
+ ccname_len = strlen(cc_file_name);
+ if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) {
+ fd = mkstemp(cc_file_name);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ kerr = errno;
+ goto done;
+ }
+ }
+
+ kerr = rename(tmp_ccname, cc_file_name);
+ if (kerr == -1) {
+ DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+done:
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ if (kerr != 0 && tmp_cc != NULL) {
+ krb5_cc_destroy(kr->ctx, tmp_cc);
+ }
+ return kerr;
+}
+
+static struct response *init_response(TALLOC_CTX *mem_ctx) {
+ struct response *r;
+ r = talloc(mem_ctx, struct response);
+ r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE);
+ if (r->buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return NULL;
+ }
+ r->max_size = MAX_CHILD_MSG_SIZE;
+ r->size = 0;
+
+ return r;
+}
+
+static errno_t pack_response_packet(struct response *resp, int status, int type,
+ size_t len, const uint8_t *data)
+{
+ int p=0;
+
+ if ((3*sizeof(int32_t) + len +1) > resp->max_size) {
+ DEBUG(1, ("response message too big.\n"));
+ return ENOMEM;
+ }
+
+ COPY_INT32_VALUE(&resp->buf[p], status, p);
+ COPY_INT32_VALUE(&resp->buf[p], type, p);
+ COPY_INT32_VALUE(&resp->buf[p], len, p);
+ COPY_MEM(&resp->buf[p], data, p, len);
+
+ resp->size = p;
+
+ return EOK;
+}
+
+static struct response *prepare_response_message(struct krb5_req *kr,
+ krb5_error_code kerr,
+ char *user_error_message,
+ int pam_status)
+{
+ char *msg = NULL;
+ const char *krb5_msg = NULL;
+ int ret;
+ struct response *resp;
+ size_t user_resp_len;
+ uint8_t *user_resp;
+
+ resp = init_response(kr);
+ if (resp == NULL) {
+ DEBUG(1, ("init_response failed.\n"));
+ return NULL;
+ }
+
+ if (kerr == 0) {
+ if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_SYSTEM_INFO,
+ strlen("success") + 1,
+ (const uint8_t *) "success");
+ } else {
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("Error obtaining ccname.\n"));
+ return NULL;
+ }
+
+ msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
+ if (msg == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ return NULL;
+ }
+
+ ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_ENV_ITEM,
+ strlen(msg) + 1, (uint8_t *) msg);
+ talloc_zfree(msg);
+ }
+ } else {
+
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(kr, user_error_message,
+ &user_resp_len, &user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
+ talloc_zfree(user_error_message);
+ } else {
+ ret = pack_response_packet(resp, pam_status, SSS_PAM_USER_INFO,
+ user_resp_len, user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_response_packet failed.\n"));
+ talloc_zfree(user_error_message);
+ }
+ }
+ }
+
+ if (user_error_message == NULL) {
+ krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
+ if (krb5_msg == NULL) {
+ DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
+ return NULL;
+ }
+
+ ret = pack_response_packet(resp, pam_status, SSS_PAM_SYSTEM_INFO,
+ strlen(krb5_msg) + 1,
+ (const uint8_t *) krb5_msg);
+ sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
+ } else {
+
+ }
+
+ }
+
+ if (ret != EOK) {
+ DEBUG(1, ("pack_response_packet failed.\n"));
+ return NULL;
+ }
+
+ return resp;
+}
+
+static errno_t sendresponse(int fd, krb5_error_code kerr,
+ char *user_error_message, int pam_status,
+ struct krb5_req *kr)
+{
+ struct response *resp;
+ size_t written;
+ int ret;
+
+ resp = prepare_response_message(kr, kerr, user_error_message, pam_status);
+ if (resp == NULL) {
+ DEBUG(1, ("prepare_response_message failed.\n"));
+ return ENOMEM;
+ }
+
+ written = 0;
+ while (written < resp->size) {
+ ret = write(fd, resp->buf + written, resp->size - written);
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ }
+ ret = errno;
+ DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret)));
+ return ret;
+ }
+ written += ret;
+ }
+
+ return EOK;
+}
+
+static krb5_error_code validate_tgt(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+ krb5_error_code kt_err;
+ char *principal;
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_verify_init_creds_opt opt;
+
+ memset(&keytab, 0, sizeof(keytab));
+ kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab);
+ if (kerr != 0) {
+ DEBUG(1, ("error resolving keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ return kerr;
+ }
+
+ memset(&cursor, 0, sizeof(cursor));
+ kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ return kerr;
+ }
+
+ /* We look for the first entry from our realm or take the last one */
+ memset(&entry, 0, sizeof(entry));
+ while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) {
+ if (krb5_realm_compare(kr->ctx, entry.principal, kr->princ)) {
+ DEBUG(9, ("Found keytab entry with the realm of the credential.\n"));
+ break;
+ }
+
+ kerr = krb5_free_keytab_entry_contents(kr->ctx, &entry);
+ if (kerr != 0) {
+ DEBUG(1, ("Failed to free keytab entry.\n"));
+ }
+ memset(&entry, 0, sizeof(entry));
+ }
+
+ /* Close the keytab here. Even though we're using cursors, the file
+ * handle is stored in the krb5_keytab structure, and it gets
+ * overwritten when the verify_init_creds() call below creates its own
+ * cursor, creating a leak. */
+ kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_kt_end_seq_get failed, not verifying TGT.\n"));
+ goto done;
+ }
+
+ /* check if we got any errors from krb5_kt_next_entry */
+ if (kt_err != 0 && kt_err != KRB5_KT_END) {
+ DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ goto done;
+ }
+
+ /* Get the principal to which the key belongs, for logging purposes. */
+ principal = NULL;
+ kerr = krb5_unparse_name(kr->ctx, entry.principal, &principal);
+ if (kerr != 0) {
+ DEBUG(1, ("internal error parsing principal name, "
+ "not verifying TGT.\n"));
+ goto done;
+ }
+
+
+ krb5_verify_init_creds_opt_init(&opt);
+ kerr = krb5_verify_init_creds(kr->ctx, kr->creds, entry.principal, keytab,
+ NULL, &opt);
+
+ if (kerr == 0) {
+ DEBUG(5, ("TGT verified using key for [%s].\n", principal));
+ } else {
+ DEBUG(1 ,("TGT failed verification using key for [%s].\n", principal));
+ }
+
+done:
+ if (krb5_kt_close(kr->ctx, keytab) != 0) {
+ DEBUG(1, ("krb5_kt_close failed"));
+ }
+ if (krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0) {
+ DEBUG(1, ("Failed to free keytab entry.\n"));
+ }
+ if (principal != NULL) {
+ sss_krb5_free_unparsed_name(kr->ctx, principal);
+ }
+
+ return kerr;
+
+}
+
+static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
+ char *password)
+{
+ krb5_error_code kerr = 0;
+ int ret;
+
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ password, NULL, NULL, 0, NULL,
+ kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ return kerr;
+ }
+
+ if (kr->validate) {
+ kerr = validate_tgt(kr);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ return kerr;
+ }
+
+ /* We drop root privileges which were needed to read the keytab file
+ * for the validation validation of the credentials here to run the
+ * ccache I/O operations with user privileges. */
+ ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid);
+ if (ret != EOK) {
+ DEBUG(1, ("become_user failed.\n"));
+ return ret;
+ }
+ } else {
+ DEBUG(9, ("TGT validation is disabled.\n"));
+ }
+
+ kerr = create_ccache_file(kr, kr->creds);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = 0;
+
+done:
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ return kerr;
+
+}
+
+static errno_t changepw_child(int fd, struct krb5_req *kr)
+{
+ int ret;
+ krb5_error_code kerr = 0;
+ char *pass_str = NULL;
+ char *newpass_str = NULL;
+ int pam_status = PAM_SYSTEM_ERR;
+ int result_code = -1;
+ krb5_data result_code_string;
+ krb5_data result_string;
+ char *user_error_message = NULL;
+
+ pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
+ kr->pd->authtok_size);
+ if (pass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ pass_str, NULL, NULL, 0,
+ kr->krb5_ctx->changepw_principle,
+ kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ if (kerr == KRB5_KDC_UNREACH) {
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ }
+ goto sendresponse;
+ }
+
+ memset(pass_str, 0, kr->pd->authtok_size);
+ talloc_zfree(pass_str);
+ memset(kr->pd->authtok, 0, kr->pd->authtok_size);
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(9, ("Initial authentication for change password operation "
+ "successfull.\n"));
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ pam_status = PAM_SUCCESS;
+ goto sendresponse;
+ }
+
+ newpass_str = talloc_strndup(kr, (const char *) kr->pd->newauthtok,
+ kr->pd->newauthtok_size);
+ if (newpass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = krb5_change_password(kr->ctx, kr->creds, newpass_str, &result_code,
+ &result_code_string, &result_string);
+
+ if (kerr != 0 || result_code != 0) {
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ } else {
+ kerr = KRB5KRB_ERR_GENERIC;
+ }
+
+ if (result_code_string.length > 0) {
+ DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_code_string.length, result_code_string.data));
+ user_error_message = talloc_strndup(kr->pd, result_code_string.data,
+ result_code_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ }
+ }
+
+ if (result_string.length > 0) {
+ DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_string.length, result_string.data));
+ talloc_free(user_error_message);
+ user_error_message = talloc_strndup(kr->pd, result_string.data,
+ result_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ }
+ }
+
+ pam_status = PAM_AUTHTOK_ERR;
+ goto sendresponse;
+ }
+
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ kerr = get_and_save_tgt(kr, newpass_str);
+ memset(newpass_str, 0, kr->pd->newauthtok_size);
+ talloc_zfree(newpass_str);
+ memset(kr->pd->newauthtok, 0, kr->pd->newauthtok_size);
+
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ if (kerr == KRB5_KDC_UNREACH) {
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+
+sendresponse:
+ ret = sendresponse(fd, kerr, user_error_message, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t tgt_req_child(int fd, struct krb5_req *kr)
+{
+ int ret;
+ krb5_error_code kerr = 0;
+ char *pass_str = NULL;
+ int pam_status = PAM_SYSTEM_ERR;
+
+ pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
+ kr->pd->authtok_size);
+ if (pass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = get_and_save_tgt(kr, pass_str);
+
+ /* If the password is expired the KDC will always return
+ KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
+ not. In general the password can still be used to get a changepw ticket.
+ So we validate the password by trying to get a changepw ticket. */
+ if (kerr == KRB5KDC_ERR_KEY_EXP) {
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ pass_str, NULL, NULL, 0,
+ kr->krb5_ctx->changepw_principle,
+ kr->options);
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ if (kerr == 0) {
+ kerr = KRB5KDC_ERR_KEY_EXP;
+ }
+ }
+
+ memset(pass_str, 0, kr->pd->authtok_size);
+ talloc_zfree(pass_str);
+ memset(kr->pd->authtok, 0, kr->pd->authtok_size);
+
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ switch (kerr) {
+ case KRB5_KDC_UNREACH:
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case KRB5KDC_ERR_KEY_EXP:
+ pam_status = PAM_AUTHTOK_EXPIRED;
+ break;
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ pam_status = PAM_CRED_ERR;
+ break;
+ default:
+ pam_status = PAM_SYSTEM_ERR;
+ }
+ }
+
+sendresponse:
+ ret = sendresponse(fd, kerr, NULL, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t create_empty_ccache(int fd, struct krb5_req *kr)
+{
+ int ret;
+ int pam_status = PAM_SUCCESS;
+
+ ret = create_ccache_file(kr, NULL);
+ if (ret != 0) {
+ KRB5_DEBUG(1, ret);
+ pam_status = PAM_SYSTEM_ERR;
+ }
+
+ ret = sendresponse(fd, ret, NULL, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd,
+ char **ccname, char **keytab, uint32_t *validate,
+ uint32_t *offline)
+{
+ size_t p = 0;
+ uint32_t len;
+
+ COPY_UINT32_CHECK(&pd->cmd, buf + p, p, size);
+ COPY_UINT32_CHECK(&pd->pw_uid, buf + p, p, size);
+ COPY_UINT32_CHECK(&pd->gr_gid, buf + p, p, size);
+ COPY_UINT32_CHECK(validate, buf + p, p, size);
+ COPY_UINT32_CHECK(offline, buf + p, p, size);
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ pd->upn = talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->upn == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ *ccname = talloc_strndup(pd, (char *)(buf + p), len);
+ if (*ccname == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ *keytab = talloc_strndup(pd, (char *)(buf + p), len);
+ if (*keytab == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len) > size) return EINVAL;
+ pd->authtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->authtok == NULL) return ENOMEM;
+ pd->authtok_size = len + 1;
+ p += len;
+
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ if ((p + len) > size) return EINVAL;
+ pd->newauthtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->newauthtok == NULL) return ENOMEM;
+ pd->newauthtok_size = len + 1;
+ p += len;
+ } else {
+ pd->newauthtok = NULL;
+ pd->newauthtok_size = 0;
+ }
+
+ return EOK;
+}
+
+static int krb5_cleanup(void *ptr)
+{
+ struct krb5_req *kr = talloc_get_type(ptr, struct krb5_req);
+ if (kr == NULL) return EOK;
+
+ if (kr->options != NULL) {
+ sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options);
+ }
+
+ if (kr->creds != NULL) {
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ krb5_free_creds(kr->ctx, kr->creds);
+ }
+ if (kr->name != NULL)
+ sss_krb5_free_unparsed_name(kr->ctx, kr->name);
+ if (kr->princ != NULL)
+ krb5_free_principal(kr->ctx, kr->princ);
+ if (kr->ctx != NULL)
+ krb5_free_context(kr->ctx);
+
+ if (kr->krb5_ctx != NULL) {
+ memset(kr->krb5_ctx, 0, sizeof(struct krb5_child_ctx));
+ }
+ memset(kr, 0, sizeof(struct krb5_req));
+
+ return EOK;
+}
+
+static int krb5_setup(struct pam_data *pd, const char *user_princ_str,
+ uint32_t offline, struct krb5_req **krb5_req)
+{
+ struct krb5_req *kr = NULL;
+ krb5_error_code kerr = 0;
+
+ kr = talloc_zero(pd, struct krb5_req);
+ if (kr == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+ talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
+
+ kr->krb5_ctx = talloc_zero(kr, struct krb5_child_ctx);
+ if (kr->krb5_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+
+ kr->krb5_ctx->changepw_principle = getenv(SSSD_KRB5_CHANGEPW_PRINCIPLE);
+ if (kr->krb5_ctx->changepw_principle == NULL) {
+ DEBUG(1, ("Cannot read [%s] from environment.\n",
+ SSSD_KRB5_CHANGEPW_PRINCIPLE));
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ goto failed;
+ }
+ }
+
+ kr->krb5_ctx->realm = getenv(SSSD_KRB5_REALM);
+ if (kr->krb5_ctx->realm == NULL) {
+ DEBUG(2, ("Cannot read [%s] from environment.\n", SSSD_KRB5_REALM));
+ }
+
+ kr->pd = pd;
+
+ switch(pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ /* If we are offline, we need to create an empty ccache file */
+ if (offline) {
+ kr->child_req = create_empty_ccache;
+ } else {
+ kr->child_req = tgt_req_child;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ kr->child_req = changepw_child;
+ break;
+ default:
+ DEBUG(1, ("PAM command [%d] not supported.\n", pd->cmd));
+ kerr = EINVAL;
+ goto failed;
+ }
+
+ kerr = krb5_init_context(&kr->ctx);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kerr = krb5_parse_name(kr->ctx, user_princ_str, &kr->princ);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kr->creds = calloc(1, sizeof(krb5_creds));
+ if (kr->creds == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+
+ kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+/* TODO: set options, e.g.
+ * krb5_get_init_creds_opt_set_tkt_life
+ * krb5_get_init_creds_opt_set_renew_life
+ * krb5_get_init_creds_opt_set_forwardable
+ * krb5_get_init_creds_opt_set_proxiable
+ * krb5_get_init_creds_opt_set_etype_list
+ * krb5_get_init_creds_opt_set_address_list
+ * krb5_get_init_creds_opt_set_preauth_list
+ * krb5_get_init_creds_opt_set_salt
+ * krb5_get_init_creds_opt_set_change_password_prompt
+ * krb5_get_init_creds_opt_set_pa
+ */
+
+ *krb5_req = kr;
+ return EOK;
+
+failed:
+ talloc_free(kr);
+
+ return kerr;
+}
+
+int main(int argc, const char *argv[])
+{
+ uint8_t *buf = NULL;
+ int ret;
+ ssize_t len = 0;
+ struct pam_data *pd = NULL;
+ struct krb5_req *kr = NULL;
+ char *ccname;
+ char *keytab;
+ uint32_t validate;
+ uint32_t offline;
+ int opt;
+ poptContext pc;
+ int debug_fd = -1;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+ _("Debug level"), NULL},
+ {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+ _("Add debug timestamps"), NULL},
+ {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+ _("An open file descriptor for the debug logs"), NULL},
+ POPT_TABLEEND
+ };
+
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ _exit(-1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ DEBUG(7, ("krb5_child started.\n"));
+
+ pd = talloc(NULL, struct pam_data);
+ if (pd == NULL) {
+ DEBUG(1, ("malloc failed.\n"));
+ _exit(-1);
+ }
+
+ debug_prg_name = talloc_asprintf(pd, "[sssd[krb5_child[%d]]]", getpid());
+
+ if (debug_fd != -1) {
+ ret = set_debug_file_from_fd(debug_fd);
+ if (ret != EOK) {
+ DEBUG(1, ("set_debug_file_from_fd failed.\n"));
+ }
+ }
+
+ buf = talloc_size(pd, sizeof(uint8_t)*IN_BUF_SIZE);
+ if (buf == NULL) {
+ DEBUG(1, ("malloc failed.\n"));
+ _exit(-1);
+ }
+
+ while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) {
+ if (ret == -1) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+ goto fail;
+ } else if (ret > 0) {
+ len += ret;
+ if (len > IN_BUF_SIZE) {
+ DEBUG(1, ("read too much, this should never happen.\n"));
+ goto fail;
+ }
+ continue;
+ } else {
+ DEBUG(1, ("unexpected return code of read [%d].\n", ret));
+ goto fail;
+ }
+ }
+ close(STDIN_FILENO);
+
+ ret = unpack_buffer(buf, len, pd, &ccname, &keytab, &validate, &offline);
+ if (ret != EOK) {
+ DEBUG(1, ("unpack_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = krb5_setup(pd, pd->upn, offline, &kr);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_setup failed.\n"));
+ goto fail;
+ }
+ kr->ccname = ccname;
+ kr->keytab = keytab;
+ kr->validate = (validate == 0) ? false : true;
+
+ ret = kr->child_req(STDOUT_FILENO, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("Child request failed.\n"));
+ goto fail;
+ }
+
+ close(STDOUT_FILENO);
+ talloc_free(pd);
+
+ return 0;
+
+fail:
+ close(STDOUT_FILENO);
+ talloc_free(pd);
+ exit(-1);
+}
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
new file mode 100644
index 00000000..86676f44
--- /dev/null
+++ b/src/providers/krb5/krb5_common.c
@@ -0,0 +1,356 @@
+/*
+ SSSD
+
+ Kerberos Provider Common Functions
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2008-2009 Red Hat
+
+ 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_common.h"
+
+struct dp_option default_krb5_opts[] = {
+ { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING },
+ { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING},
+ { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING },
+ { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER },
+ { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING },
+ { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }
+};
+
+errno_t check_and_export_options(struct dp_option *opts,
+ struct sss_domain_info *dom)
+{
+ int ret;
+ char *value;
+ const char *realm;
+ const char *dummy;
+ struct stat stat_buf;
+ char **list;
+
+ realm = dp_opt_get_cstring(opts, KRB5_REALM);
+ if (realm == NULL) {
+ ret = dp_opt_set_string(opts, KRB5_REALM, dom->name);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_opt_set_string failed.\n"));
+ return ret;
+ }
+ realm = dom->name;
+ }
+
+ ret = setenv(SSSD_KRB5_REALM, realm, 1);
+ if (ret != EOK) {
+ DEBUG(2, ("setenv %s failed, authentication might fail.\n",
+ SSSD_KRB5_REALM));
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+ if (dummy == NULL) {
+ DEBUG(1, ("No KDC expicitly configured, using defaults"));
+ } else {
+ ret = split_on_separator(opts, dummy, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ return ret;
+ }
+ ret = write_kdcinfo_file(realm, list[0]);
+ if (ret != EOK) {
+ DEBUG(1, ("write_kdcinfo_file failed, "
+ "using kerberos defaults from /etc/krb5.conf"));
+ }
+ talloc_free(list);
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CCACHEDIR);
+ ret = lstat(dummy, &stat_buf);
+ if (ret != EOK) {
+ DEBUG(1, ("lstat for [%s] failed: [%d][%s].\n", dummy, errno,
+ strerror(errno)));
+ return ret;
+ }
+ if ( !S_ISDIR(stat_buf.st_mode) ) {
+ DEBUG(1, ("Value of krb5ccache_dir [%s] is not a directory.\n", dummy));
+ return EINVAL;
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CCNAME_TMPL);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing credential cache name template.\n"));
+ return EINVAL;
+ }
+ if (dummy[0] != '/' && strncmp(dummy, "FILE:", 5) != 0) {
+ DEBUG(1, ("Currently only file based credential caches are supported "
+ "and krb5ccname_template must start with '/' or 'FILE:'\n"));
+ return EINVAL;
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CHANGEPW_PRINC);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing change password principle.\n"));
+ return EINVAL;
+ }
+ if (strchr(dummy, '@') == NULL) {
+ value = talloc_asprintf(opts, "%s@%s", dummy, realm);
+ if (value == NULL) {
+ DEBUG(7, ("talloc_asprintf failed.\n"));
+ return ENOMEM;
+ }
+ ret = dp_opt_set_string(opts, KRB5_CHANGEPW_PRINC, value);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_opt_set_string failed.\n"));
+ return ret;
+ }
+ dummy = value;
+ }
+
+ ret = setenv(SSSD_KRB5_CHANGEPW_PRINCIPLE, dummy, 1);
+ if (ret != EOK) {
+ DEBUG(2, ("setenv %s failed, password change might fail.\n",
+ SSSD_KRB5_CHANGEPW_PRINCIPLE));
+ }
+
+ return EOK;
+}
+
+errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts)
+{
+ int ret;
+ struct dp_option *opts;
+
+ opts = talloc_zero(memctx, struct dp_option);
+ if (opts == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = dp_get_options(opts, cdb, conf_path, default_krb5_opts,
+ KRB5_OPTS, &opts);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_get_options failed.\n"));
+ goto done;
+ }
+
+ *_opts = opts;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+
+ return ret;
+}
+
+errno_t write_kdcinfo_file(const char *realm, const char *kdc)
+{
+ int ret;
+ int fd = -1;
+ char *tmp_name = NULL;
+ char *kdcinfo_name = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int kdc_len;
+
+ if (realm == NULL || *realm == '\0' || kdc == NULL || *kdc == '\0') {
+ DEBUG(1, ("Missing or empty realm or kdc.\n"));
+ return EINVAL;
+ }
+
+ kdc_len = strlen(kdc);
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.kdcinfo_dummy_XXXXXX");
+ if (tmp_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ kdcinfo_name = talloc_asprintf(tmp_ctx, KDCINFO_TMPL, realm);
+ if (kdcinfo_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ fd = mkstemp(tmp_name);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+
+ ret = write(fd, kdc, kdc_len);
+ if (ret == -1) {
+ DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+ if (ret != kdc_len) {
+ DEBUG(1, ("Partial write occured, this should never happen.\n"));
+ ret = EINTR;
+ goto done;
+ }
+
+ ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (ret == -1) {
+ DEBUG(1, ("fchmod failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+ ret = close(fd);
+ if (ret == -1) {
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+ ret = rename(tmp_name, kdcinfo_name);
+ if (ret == -1) {
+ DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void krb5_resolve_callback(void *private_data, struct fo_server *server)
+{
+ struct krb5_service *krb5_service;
+ struct hostent *srvaddr;
+ char *address;
+ int ret;
+
+ krb5_service = talloc_get_type(private_data, struct krb5_service);
+ if (!krb5_service) {
+ DEBUG(1, ("FATAL: Bad private_data\n"));
+ return;
+ }
+
+ srvaddr = fo_get_server_hostent(server);
+ if (!srvaddr) {
+ DEBUG(1, ("FATAL: No hostent available for server (%s)\n",
+ fo_get_server_name(server)));
+ return;
+ }
+
+ address = talloc_asprintf(krb5_service, "%s", srvaddr->h_name);
+ if (!address) {
+ DEBUG(1, ("Failed to copy address ...\n"));
+ return;
+ }
+
+ talloc_zfree(krb5_service->address);
+ krb5_service->address = address;
+
+ ret = write_kdcinfo_file(krb5_service->realm, address);
+ if (ret != EOK) {
+ DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n"));
+ }
+
+ return;
+}
+
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *servers,
+ const char *realm, struct krb5_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct krb5_service *service;
+ char **list = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct krb5_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, service_name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->name = talloc_strdup(service, service_name);
+ if (!service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ service->realm = talloc_strdup(service, realm);
+ if (!service->realm) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ for (i = 0; list[i]; i++) {
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, service_name, list[i], 0, NULL);
+ if (ret && ret != EEXIST) {
+ DEBUG(0, ("Failed to add server\n"));
+ goto done;
+ }
+
+ DEBUG(6, ("Added Server %s\n", list[i]));
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, service_name,
+ krb5_resolve_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
new file mode 100644
index 00000000..832ffcdd
--- /dev/null
+++ b/src/providers/krb5/krb5_common.h
@@ -0,0 +1,72 @@
+/*
+ SSSD
+
+ Kerberos Backend, common header file
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+
+ 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/>.
+*/
+
+#ifndef __KRB5_COMMON_H__
+#define __KRB5_COMMON_H__
+
+#include "config.h"
+#include <stdbool.h>
+
+#include "providers/dp_backend.h"
+#include "util/util.h"
+#include "util/sss_krb5.h"
+
+#define SSSD_KRB5_KDC "SSSD_KRB5_KDC"
+#define SSSD_KRB5_REALM "SSSD_KRB5_REALM"
+#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE"
+
+#define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s"
+
+enum krb5_opts {
+ KRB5_KDC = 0,
+ KRB5_REALM,
+ KRB5_CCACHEDIR,
+ KRB5_CCNAME_TMPL,
+ KRB5_CHANGEPW_PRINC,
+ KRB5_AUTH_TIMEOUT,
+ KRB5_KEYTAB,
+ KRB5_VALIDATE,
+
+ KRB5_OPTS
+};
+
+struct krb5_service {
+ char *name;
+ char *address;
+ char *realm;
+};
+
+errno_t check_and_export_options(struct dp_option *opts,
+ struct sss_domain_info *dom);
+
+errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts);
+
+errno_t write_kdcinfo_file(const char *realm, const char *kdc);
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *servers,
+ const char *realm, struct krb5_service **_service);
+#endif /* __KRB5_COMMON_H__ */
diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c
new file mode 100644
index 00000000..43cbc1bc
--- /dev/null
+++ b/src/providers/krb5/krb5_init.c
@@ -0,0 +1,152 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "providers/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_common.h"
+
+struct krb5_options {
+ struct dp_option *opts;
+ struct krb5_ctx *auth_ctx;
+};
+
+struct krb5_options *krb5_options = NULL;
+
+struct bet_ops krb5_auth_ops = {
+ .handler = krb5_pam_handler,
+ .finalize = NULL,
+};
+
+int sssm_krb5_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_auth_data)
+{
+ struct krb5_ctx *ctx = NULL;
+ int ret;
+ struct tevent_signal *sige;
+ unsigned v;
+ FILE *debug_filep;
+ const char *krb5_servers;
+ const char *krb5_realm;
+
+ if (krb5_options == NULL) {
+ krb5_options = talloc_zero(bectx, struct krb5_options);
+ if (krb5_options == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+ ret = krb5_get_options(krb5_options, bectx->cdb, bectx->conf_path,
+ &krb5_options->opts);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_get_options failed.\n"));
+ return ret;
+ }
+ }
+
+ if (krb5_options->auth_ctx != NULL) {
+ *ops = &krb5_auth_ops;
+ *pvt_auth_data = krb5_options->auth_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(bectx, struct krb5_ctx);
+ if (!ctx) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ ctx->action = INIT_PW;
+ ctx->opts = krb5_options->opts;
+
+ krb5_servers = dp_opt_get_string(ctx->opts, KRB5_KDC);
+ if (krb5_servers == NULL) {
+ DEBUG(0, ("Missing krb5_kdcip option!\n"));
+ return EINVAL;
+ }
+
+ krb5_realm = dp_opt_get_string(ctx->opts, KRB5_REALM);
+ if (krb5_realm == NULL) {
+ DEBUG(0, ("Missing krb5_realm option!\n"));
+ return EINVAL;
+ }
+
+ ret = krb5_service_init(ctx, bectx, "KRB5", krb5_servers, krb5_realm,
+ &ctx->service);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to init IPA failover service!\n"));
+ return ret;
+ }
+
+ ret = check_and_export_options(ctx->opts, bectx->domain);
+ if (ret != EOK) {
+ DEBUG(1, ("check_and_export_options failed.\n"));
+ goto fail;
+ }
+
+ sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (debug_to_file != 0) {
+ ret = open_debug_file_ex("krb5_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ctx->child_debug_fd = fileno(debug_filep);
+ if (ctx->child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ goto fail;
+ }
+
+ v = fcntl(ctx->child_debug_fd, F_GETFD, 0);
+ fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ *ops = &krb5_auth_ops;
+ *pvt_auth_data = ctx;
+ return EOK;
+
+fail:
+ talloc_free(ctx);
+ return ret;
+}
+
+int sssm_krb5_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_auth_data)
+{
+ return sssm_krb5_auth_init(bectx, ops, pvt_auth_data);
+}
diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c
new file mode 100644
index 00000000..489030af
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.c
@@ -0,0 +1,145 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <string.h>
+#include <stdlib.h>
+
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_auth.h"
+#include "util/util.h"
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template)
+{
+ char *copy;
+ char *p;
+ char *n;
+ char *result = NULL;
+ const char *dummy;
+
+ if (template == NULL) {
+ DEBUG(1, ("Missing template.\n"));
+ return NULL;
+ }
+
+ copy = talloc_strdup(mem_ctx, template);
+ if (copy == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return NULL;
+ }
+
+ result = talloc_strdup(mem_ctx, "");
+ if (result == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return NULL;
+ }
+
+ p = copy;
+ while ( (n = strchr(p, '%')) != NULL) {
+ *n = '\0';
+ n++;
+ if ( *n == '\0' ) {
+ DEBUG(1, ("format error, single %% at the end of the template.\n"));
+ return NULL;
+ }
+
+ switch( *n ) {
+ case 'u':
+ if (kr->pd->user == NULL) {
+ DEBUG(1, ("Cannot expand user name template "
+ "because user name is empty.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p,
+ kr->pd->user);
+ break;
+ case 'U':
+ if (kr->pd->pw_uid <= 0) {
+ DEBUG(1, ("Cannot expand uid template "
+ "because uid is invalid.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%d", p,
+ kr->pd->pw_uid);
+ break;
+ case 'p':
+ if (kr->pd->upn == NULL) {
+ DEBUG(1, ("Cannot expand user principle name template "
+ "because upn is empty.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->pd->upn);
+ break;
+ case '%':
+ result = talloc_asprintf_append(result, "%s%%", p);
+ break;
+ case 'r':
+ dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing kerberos realm.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ break;
+ case 'h':
+ if (kr->homedir == NULL) {
+ DEBUG(1, ("Cannot expand home directory template "
+ "because the path is not available.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->homedir);
+ break;
+ case 'd':
+ dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_CCACHEDIR);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing credential cache directory.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ break;
+ case 'P':
+ if (kr->pd->cli_pid == 0) {
+ DEBUG(1, ("Cannot expand PID template "
+ "because PID is not available.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%d", p,
+ kr->pd->cli_pid);
+ break;
+ default:
+ DEBUG(1, ("format error, unknown template [%%%c].\n", *n));
+ return NULL;
+ }
+
+ if (result == NULL) {
+ DEBUG(1, ("talloc_asprintf_append failed.\n"));
+ return NULL;
+ }
+
+ p = n + 1;
+ }
+
+ result = talloc_asprintf_append(result, "%s", p);
+
+ return result;
+}
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
new file mode 100644
index 00000000..7637041a
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.h
@@ -0,0 +1,39 @@
+/*
+ SSSD
+
+ Kerberos Backend, header file for utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+
+ 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/>.
+*/
+
+#ifndef __KRB5_UTILS_H__
+#define __KRB5_UTILS_H__
+
+#include <talloc.h>
+
+#include "providers/krb5/krb5_auth.h"
+#include "providers/data_provider.h"
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template);
+
+errno_t become_user(uid_t uid, gid_t gid);
+
+#endif /* __KRB5_UTILS_H__ */
diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
new file mode 100644
index 00000000..cfe8adb9
--- /dev/null
+++ b/src/providers/ldap/ldap_auth.c
@@ -0,0 +1,1055 @@
+/*
+ SSSD
+
+ LDAP Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2008 Red Hat
+
+ 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/>.
+*/
+
+#ifdef WITH_MOZLDAP
+#define LDAP_OPT_SUCCESS LDAP_SUCCESS
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ((ber_tag_t) 0x81U)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U)
+#endif
+
+#define _XOPEN_SOURCE 500 /* for strptime() */
+#include <time.h>
+#undef _XOPEN_SOURCE
+#include <errno.h>
+#include <sys/time.h>
+#include <strings.h>
+
+#include <shadow.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/user_info_msg.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+enum pwexpire {
+ PWEXPIRE_NONE = 0,
+ PWEXPIRE_LDAP_PASSWORD_POLICY,
+ PWEXPIRE_KERBEROS,
+ PWEXPIRE_SHADOW
+};
+
+static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now,
+ enum sdap_result *result)
+{
+ char *end;
+ struct tm tm = {0, 0, 0, 0, 0, 0, 0, 0, 0};
+ time_t expire_time;
+
+ *result = SDAP_AUTH_FAILED;
+
+ end = strptime(expire_date, "%Y%m%d%H%M%SZ", &tm);
+ if (end == NULL) {
+ DEBUG(1, ("Kerberos expire date [%s] invalid.\n", expire_date));
+ return EINVAL;
+ }
+ if (*end != '\0') {
+ DEBUG(1, ("Kerberos expire date [%s] contains extra characters.\n",
+ expire_date));
+ return EINVAL;
+ }
+
+ expire_time = mktime(&tm);
+ if (expire_time == -1) {
+ DEBUG(1, ("mktime failed to convert [%s].\n", expire_date));
+ return EINVAL;
+ }
+
+ tzset();
+ expire_time -= timezone;
+ DEBUG(9, ("Time info: tzname[0] [%s] tzname[1] [%s] timezone [%d] "
+ "daylight [%d] now [%d] expire_time [%d].\n", tzname[0],
+ tzname[1], timezone, daylight, now, expire_time));
+
+ if (difftime(now, expire_time) > 0.0) {
+ DEBUG(4, ("Kerberos password expired.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ } else {
+ *result = SDAP_AUTH_SUCCESS;
+ }
+
+ return EOK;
+}
+
+static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
+ enum sdap_result *result)
+{
+ long today;
+ long password_age;
+
+ if (spwd->sp_lstchg <= 0) {
+ DEBUG(4, ("Last change day is not set, new password needed.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ return EOK;
+ }
+
+ today = (long) (now / (60 * 60 *24));
+ password_age = today - spwd->sp_lstchg;
+ if (password_age < 0) {
+ DEBUG(2, ("The last password change time is in the future!.\n"));
+ *result = SDAP_AUTH_SUCCESS;
+ return EOK;
+ }
+
+ if ((spwd->sp_expire != -1 && today > spwd->sp_expire) ||
+ (spwd->sp_max != -1 && spwd->sp_inact != -1 &&
+ password_age > spwd->sp_max + spwd->sp_inact))
+ {
+ DEBUG(4, ("Account expired.\n"));
+ *result = SDAP_ACCT_EXPIRED;
+ return EOK;
+ }
+
+ if (spwd->sp_max != -1 && password_age > spwd->sp_max) {
+ DEBUG(4, ("Password expired.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ return EOK;
+ }
+
+/* TODO: evaluate spwd->min and spwd->warn */
+
+ *result = SDAP_AUTH_SUCCESS;
+ return EOK;
+}
+
+static errno_t string_to_shadowpw_days(const char *s, long *d)
+{
+ long l;
+ char *endptr;
+
+ if (s == NULL || *s == '\0') {
+ *d = -1;
+ return EOK;
+ }
+
+ errno = 0;
+ l = strtol(s, &endptr, 10);
+ if (errno != 0) {
+ DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ if (*endptr != '\0') {
+ DEBUG(1, ("Input string [%s] is invalid.\n", s));
+ return EINVAL;
+ }
+
+ if (*d < -1) {
+ DEBUG(1, ("Input string contains not allowed negative value [%d].\n",
+ *d));
+ return EINVAL;
+ }
+
+ *d = l;
+
+ return EOK;
+}
+
+static errno_t find_password_expiration_attributes(TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ struct dp_option *opts,
+ enum pwexpire *type, void **data)
+{
+ const char *mark;
+ const char *val;
+ struct spwd *spwd;
+ const char *pwd_policy;
+ int ret;
+
+ *type = PWEXPIRE_NONE;
+ *data = NULL;
+
+ pwd_policy = dp_opt_get_string(opts, SDAP_PWD_POLICY);
+ if (pwd_policy == NULL) {
+ DEBUG(1, ("Missing password policy.\n"));
+ return EINVAL;
+ }
+
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_PWD_ATTRIBUTE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found pwdAttribute, "
+ "assuming LDAP password policies are active.\n"));
+
+ *type = PWEXPIRE_LDAP_PASSWORD_POLICY;
+ return EOK;
+ }
+
+ if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) == 0) {
+ DEBUG(9, ("No password policy requested.\n"));
+ return EOK;
+ } else if (strcasecmp(pwd_policy, PWD_POL_OPT_MIT) == 0) {
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_LASTCHANGE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found Kerberos password expiration attributes.\n"))
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_EXPIRATION,
+ NULL);
+ if (val != NULL) {
+ *data = talloc_strdup(mem_ctx, val);
+ if (*data == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ *type = PWEXPIRE_KERBEROS;
+
+ return EOK;
+ }
+ } else {
+ DEBUG(1, ("No Kerberos password expiration attributes found, "
+ "but MIT Kerberos password policy was requested.\n"));
+ return EINVAL;
+ }
+ } else if (strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) == 0) {
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found shadow password expiration attributes.\n"))
+ spwd = talloc_zero(mem_ctx, struct spwd);
+ if (spwd == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_lstchg);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MIN, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_min);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MAX, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_max);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_WARNING, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_warn);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_INACTIVE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_inact);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_EXPIRE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_expire);
+ if (ret != EOK) goto shadow_fail;
+
+ *data = spwd;
+ *type = PWEXPIRE_SHADOW;
+
+ return EOK;
+ } else {
+ DEBUG(1, ("No shadow password attributes found, "
+ "but shadow password policy was requested.\n"));
+ return EINVAL;
+ }
+ }
+
+ DEBUG(9, ("No password expiration attributes found.\n"));
+ return EOK;
+
+shadow_fail:
+ talloc_free(spwd);
+ return ret;
+}
+
+/* ==Get-User-DN========================================================== */
+
+struct get_user_dn_state {
+ struct tevent_context *ev;
+ struct sdap_auth_ctx *ctx;
+ struct sdap_handle *sh;
+
+ const char **attrs;
+ const char *name;
+
+ char *dn;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+};
+
+static void get_user_dn_done(void *pvt, int err, struct ldb_result *res);
+
+struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_auth_ctx *ctx,
+ struct sdap_handle *sh,
+ const char *username)
+{
+ struct tevent_req *req;
+ struct get_user_dn_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct get_user_dn_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sh = sh;
+ state->name = username;
+
+ state->attrs = talloc_array(state, const char *, 11);
+ if (!state->attrs) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->attrs[0] = SYSDB_ORIG_DN;
+ state->attrs[1] = SYSDB_SHADOWPW_LASTCHANGE;
+ state->attrs[2] = SYSDB_SHADOWPW_MIN;
+ state->attrs[3] = SYSDB_SHADOWPW_MAX;
+ state->attrs[4] = SYSDB_SHADOWPW_WARNING;
+ state->attrs[5] = SYSDB_SHADOWPW_INACTIVE;
+ state->attrs[6] = SYSDB_SHADOWPW_EXPIRE;
+ state->attrs[7] = SYSDB_KRBPW_LASTCHANGE;
+ state->attrs[8] = SYSDB_KRBPW_EXPIRATION;
+ state->attrs[9] = SYSDB_PWD_ATTRIBUTE;
+ state->attrs[10] = NULL;
+
+ /* this sysdb call uses a sysdn operation, which means it will be
+ * schedule only after we return, no timer hack needed */
+ ret = sysdb_get_user_attr(state, state->ctx->be->sysdb,
+ state->ctx->be->domain, state->name,
+ state->attrs, get_user_dn_done, req);
+ if (ret) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void get_user_dn_done(void *pvt, int err, struct ldb_result *res)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct get_user_dn_state *state = tevent_req_data(req,
+ struct get_user_dn_state);
+ const char *dn;
+ int ret;
+
+ if (err != LDB_SUCCESS) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ /* FIXME: not in cache, needs a true search */
+ tevent_req_error(req, ENOENT);
+ break;
+
+ case 1:
+ dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL);
+ if (!dn) {
+ /* TODO: try to search ldap server ? */
+
+ /* FIXME: remove once we store originalDN on every call
+ * NOTE: this is wrong, works only with some DITs */
+ dn = talloc_asprintf(state, "%s=%s,%s",
+ state->ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ state->name,
+ dp_opt_get_string(state->ctx->opts->basic,
+ SDAP_USER_SEARCH_BASE));
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+ }
+
+ state->dn = talloc_strdup(state, dn);
+ if (!state->dn) {
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+
+ ret = find_password_expiration_attributes(state, res->msgs[0],
+ state->ctx->opts->basic,
+ &state->pw_expire_type,
+ &state->pw_expire_data);
+ if (ret != EOK) {
+ DEBUG(1, ("find_password_expiration_attributes failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+
+ tevent_req_done(req);
+ break;
+
+ default:
+ DEBUG(1, ("A user search by name (%s) returned > 1 results!\n",
+ state->name));
+ tevent_req_error(req, EFAULT);
+ break;
+ }
+}
+
+static int get_user_dn_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx, char **dn,
+ enum pwexpire *pw_expire_type,
+ void **pw_expire_data)
+{
+ struct get_user_dn_state *state = tevent_req_data(req,
+ struct get_user_dn_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *dn = talloc_steal(memctx, state->dn);
+ if (!*dn) return ENOMEM;
+
+ /* state->pw_expire_data may be NULL */
+ *pw_expire_data = talloc_steal(memctx, state->pw_expire_data);
+
+ *pw_expire_type = state->pw_expire_type;
+
+ return EOK;
+}
+
+/* ==Authenticate-User==================================================== */
+
+struct auth_state {
+ struct tevent_context *ev;
+ struct sdap_auth_ctx *ctx;
+ const char *username;
+ struct dp_opt_blob password;
+
+ struct sdap_handle *sh;
+
+ enum sdap_result result;
+ char *dn;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+
+ struct fo_server *srv;
+};
+
+static void auth_resolve_done(struct tevent_req *subreq);
+static void auth_connect_done(struct tevent_req *subreq);
+static void auth_get_user_dn_done(struct tevent_req *subreq);
+static void auth_bind_user_done(struct tevent_req *subreq);
+
+static struct tevent_req *auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_auth_ctx *ctx,
+ const char *username,
+ struct dp_opt_blob password)
+{
+ struct tevent_req *req, *subreq;
+ struct auth_state *state;
+
+ req = tevent_req_create(memctx, &state, struct auth_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->username = username;
+ state->password = password;
+ state->srv = NULL;
+
+ subreq = be_resolve_server_send(state, ev, ctx->be, ctx->service->name);
+ if (!subreq) goto fail;
+
+ tevent_req_set_callback(subreq, auth_resolve_done, req);
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void auth_resolve_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = be_resolve_server_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_connect_send(state, state->ev, state->ctx->opts,
+ state->ctx->service->uri, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_connect_done, req);
+}
+
+static void auth_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = sdap_connect_recv(subreq, state, &state->sh);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (state->srv) {
+ /* mark this server as bad if connection failed */
+ fo_set_port_status(state->srv, PORT_NOT_WORKING);
+ }
+
+ tevent_req_error(req, ret);
+ return;
+ } else if (state->srv) {
+ fo_set_port_status(state->srv, PORT_WORKING);
+ }
+
+ subreq = get_user_dn_send(state, state->ev,
+ state->ctx, state->sh,
+ state->username);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_get_user_dn_done, req);
+}
+
+static void auth_get_user_dn_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = get_user_dn_recv(subreq, state, &state->dn, &state->pw_expire_type,
+ &state->pw_expire_data);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_auth_send(state, state->ev, state->sh,
+ NULL, NULL, state->dn,
+ "password", state->password);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_bind_user_done, req);
+}
+
+static void auth_bind_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = sdap_auth_recv(subreq, &state->result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int auth_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh,
+ enum sdap_result *result, char **dn,
+ enum pwexpire *pw_expire_type, void **pw_expire_data)
+{
+ struct auth_state *state = tevent_req_data(req, struct auth_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ switch (tstate) {
+ case TEVENT_REQ_USER_ERROR:
+ if (err == ETIMEDOUT) *result = SDAP_UNAVAIL;
+ else *result = SDAP_ERROR;
+ return err;
+ default:
+ *result = SDAP_ERROR;
+ return EIO;
+ }
+ }
+
+ if (sh != NULL) {
+ *sh = talloc_steal(memctx, state->sh);
+ if (*sh == NULL) return ENOMEM;
+ }
+
+ if (dn != NULL) {
+ *dn = talloc_steal(memctx, state->dn);
+ if (*dn == NULL) return ENOMEM;
+ }
+
+ if (pw_expire_data != NULL) {
+ *pw_expire_data = talloc_steal(memctx, state->pw_expire_data);
+ }
+
+ *pw_expire_type = state->pw_expire_type;
+
+ *result = state->result;
+ return EOK;
+}
+
+/* ==Perform-Password-Change===================== */
+
+struct sdap_pam_chpass_state {
+ struct be_req *breq;
+ struct pam_data *pd;
+ const char *username;
+ char *dn;
+ char *password;
+ char *new_password;
+ struct sdap_handle *sh;
+};
+
+static void sdap_auth4chpass_done(struct tevent_req *req);
+static void sdap_pam_chpass_done(struct tevent_req *req);
+static void sdap_pam_auth_reply(struct be_req *breq, int dp_err, int result);
+
+void sdap_pam_chpass_handler(struct be_req *breq)
+{
+ struct sdap_pam_chpass_state *state;
+ struct sdap_auth_ctx *ctx;
+ struct tevent_req *subreq;
+ struct pam_data *pd;
+ struct dp_opt_blob authtok;
+ int dp_err = DP_ERR_FATAL;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct sdap_auth_ctx);
+ pd = talloc_get_type(breq->req_data, struct pam_data);
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ DEBUG(2, ("starting password change request for user [%s].\n", pd->user));
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+
+ if (pd->cmd != SSS_PAM_CHAUTHTOK && pd->cmd != SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(2, ("chpass target was called by wrong pam command.\n"));
+ goto done;
+ }
+
+ state = talloc_zero(breq, struct sdap_pam_chpass_state);
+ if (!state) goto done;
+
+ state->breq = breq;
+ state->pd = pd;
+ state->username = pd->user;
+ state->password = talloc_strndup(state,
+ (char *)pd->authtok, pd->authtok_size);
+ if (!state->password) goto done;
+ talloc_set_destructor((TALLOC_CTX *)state->password,
+ password_destructor);
+
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ state->new_password = talloc_strndup(state,
+ (char *)pd->newauthtok,
+ pd->newauthtok_size);
+ if (!state->new_password) goto done;
+ talloc_set_destructor((TALLOC_CTX *)state->new_password,
+ password_destructor);
+ }
+
+ authtok.data = (uint8_t *)state->password;
+ authtok.length = strlen(state->password);
+ subreq = auth_send(breq, breq->be_ctx->ev,
+ ctx, state->username, authtok);
+ if (!subreq) goto done;
+
+ tevent_req_set_callback(subreq, sdap_auth4chpass_done, state);
+ return;
+
+done:
+ sdap_pam_auth_reply(breq, dp_err, pd->pam_status);
+}
+
+static void sdap_auth4chpass_done(struct tevent_req *req)
+{
+ struct sdap_pam_chpass_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_chpass_state);
+ struct tevent_req *subreq;
+ enum sdap_result result;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+
+ ret = auth_recv(req, state, &state->sh,
+ &result, &state->dn,
+ &pw_expire_type, &pw_expire_data);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS &&
+ state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(9, ("Initial authentication for change password operation "
+ "successful.\n"));
+ state->pd->pam_status = PAM_SUCCESS;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS) {
+ switch (pw_expire_type) {
+ case PWEXPIRE_SHADOW:
+ ret = check_pwexpire_shadow(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_shadow failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_KERBEROS:
+ ret = check_pwexpire_kerberos(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_PW_EXPIRED) {
+ DEBUG(1, ("LDAP provider cannot change kerberos "
+ "passwords.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_LDAP_PASSWORD_POLICY:
+ case PWEXPIRE_NONE:
+ break;
+ default:
+ DEBUG(1, ("Unknow pasword expiration type.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ }
+
+ switch (result) {
+ case SDAP_AUTH_SUCCESS:
+ case SDAP_AUTH_PW_EXPIRED:
+ DEBUG(7, ("user [%s] successfully authenticated.\n", state->dn));
+ if (pw_expire_type == PWEXPIRE_SHADOW) {
+/* TODO: implement async ldap modify request */
+ DEBUG(1, ("Changing shadow password attributes not implemented.\n"));
+ state->pd->pam_status = PAM_MODULE_UNKNOWN;
+ goto done;
+ } else {
+ subreq = sdap_exop_modify_passwd_send(state,
+ state->breq->be_ctx->ev,
+ state->sh,
+ state->dn,
+ state->password,
+ state->new_password);
+
+ if (!subreq) {
+ DEBUG(2, ("Failed to change password for %s\n", state->username));
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sdap_pam_chpass_done, state);
+ return;
+ }
+ break;
+ case SDAP_AUTH_FAILED:
+ state->pd->pam_status = PAM_AUTH_ERR;
+ break;
+ default:
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+
+static void sdap_pam_chpass_done(struct tevent_req *req)
+{
+ struct sdap_pam_chpass_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_chpass_state);
+ enum sdap_result result;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+ char *user_error_message = NULL;
+ size_t msg_len;
+ uint8_t *msg;
+
+ ret = sdap_exop_modify_passwd_recv(req, state, &result, &user_error_message);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ switch (result) {
+ case SDAP_SUCCESS:
+ state->pd->pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ break;
+ default:
+ state->pd->pam_status = PAM_AUTHTOK_ERR;
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(state->pd, user_error_message,
+ &msg_len, &msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
+ } else {
+ ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, msg_len,
+ msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ }
+ }
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+/* ==Perform-User-Authentication-and-Password-Caching===================== */
+
+struct sdap_pam_auth_state {
+ struct be_req *breq;
+ struct pam_data *pd;
+ const char *username;
+ struct dp_opt_blob password;
+};
+
+static void sdap_pam_auth_done(struct tevent_req *req);
+static void sdap_password_cache_done(struct tevent_req *req);
+
+void sdap_pam_auth_handler(struct be_req *breq)
+{
+ struct sdap_pam_auth_state *state;
+ struct sdap_auth_ctx *ctx;
+ struct tevent_req *subreq;
+ struct pam_data *pd;
+ int dp_err = DP_ERR_FATAL;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct sdap_auth_ctx);
+ pd = talloc_get_type(breq->req_data, struct pam_data);
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+
+ state = talloc_zero(breq, struct sdap_pam_auth_state);
+ if (!state) goto done;
+
+ state->breq = breq;
+ state->pd = pd;
+ state->username = pd->user;
+ state->password.data = pd->authtok;
+ state->password.length = pd->authtok_size;
+
+ subreq = auth_send(breq, breq->be_ctx->ev, ctx,
+ state->username, state->password);
+ if (!subreq) goto done;
+
+ tevent_req_set_callback(subreq, sdap_pam_auth_done, state);
+ return;
+
+ case SSS_PAM_CHAUTHTOK:
+ break;
+
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ break;
+ default:
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ dp_err = DP_ERR_OK;
+ }
+
+done:
+ sdap_pam_auth_reply(breq, dp_err, pd->pam_status);
+}
+
+static void sdap_pam_auth_done(struct tevent_req *req)
+{
+ struct sdap_pam_auth_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_auth_state);
+ struct tevent_req *subreq;
+ enum sdap_result result;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+ int dp_err = DP_ERR_OK;
+ int ret;
+
+ ret = auth_recv(req, state, NULL,
+ &result, NULL,
+ &pw_expire_type, &pw_expire_data);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ dp_err = DP_ERR_FATAL;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS) {
+ switch (pw_expire_type) {
+ case PWEXPIRE_SHADOW:
+ ret = check_pwexpire_shadow(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_shadow failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_KERBEROS:
+ ret = check_pwexpire_kerberos(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_LDAP_PASSWORD_POLICY:
+ case PWEXPIRE_NONE:
+ break;
+ default:
+ DEBUG(1, ("Unknow pasword expiration type.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ }
+
+ switch (result) {
+ case SDAP_AUTH_SUCCESS:
+ state->pd->pam_status = PAM_SUCCESS;
+ break;
+ case SDAP_AUTH_FAILED:
+ state->pd->pam_status = PAM_PERM_DENIED;
+ break;
+ case SDAP_UNAVAIL:
+ state->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case SDAP_ACCT_EXPIRED:
+ state->pd->pam_status = PAM_ACCT_EXPIRED;
+ break;
+ case SDAP_AUTH_PW_EXPIRED:
+ state->pd->pam_status = PAM_AUTHTOK_EXPIRED;
+ break;
+ default:
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ dp_err = DP_ERR_FATAL;
+ }
+
+ if (result == SDAP_UNAVAIL) {
+ be_mark_offline(state->breq->be_ctx);
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS &&
+ state->breq->be_ctx->domain->cache_credentials) {
+
+ char *password = talloc_strndup(state, (char *)
+ state->password.data,
+ state->password.length);
+ /* password caching failures are not fatal errors */
+ if (!password) {
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ goto done;
+ }
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ subreq = sysdb_cache_password_send(state,
+ state->breq->be_ctx->ev,
+ state->breq->be_ctx->sysdb,
+ NULL,
+ state->breq->be_ctx->domain,
+ state->username, password);
+
+ /* password caching failures are not fatal errors */
+ if (!subreq) {
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sdap_password_cache_done, state);
+ return;
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+
+static void sdap_password_cache_done(struct tevent_req *subreq)
+{
+ struct sdap_pam_auth_state *state = tevent_req_callback_data(subreq,
+ struct sdap_pam_auth_state);
+ int ret;
+
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ /* password caching failures are not fatal errors */
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ } else {
+ DEBUG(4, ("Password successfully cached for %s\n", state->username));
+ }
+
+ sdap_pam_auth_reply(state->breq, DP_ERR_OK, state->pd->pam_status);
+}
+
+static void sdap_pam_auth_reply(struct be_req *req, int dp_err, int result)
+{
+ req->fn(req, dp_err, result, NULL);
+}
+
diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c
new file mode 100644
index 00000000..0d34be2c
--- /dev/null
+++ b/src/providers/ldap/ldap_child.c
@@ -0,0 +1,429 @@
+/*
+ SSSD
+
+ LDAP Backend Module -- prime ccache with TGT in a child process
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "providers/child_common.h"
+#include "providers/dp_backend.h"
+
+static krb5_context krb5_error_ctx;
+
+struct input_buffer {
+ const char *realm_str;
+ const char *princ_str;
+ const char *keytab_name;
+};
+
+static errno_t unpack_buffer(uint8_t *buf, size_t size,
+ struct input_buffer *ibuf)
+{
+ size_t p = 0;
+ uint32_t len;
+
+ DEBUG(7, ("total buffer size: %d\n", size));
+
+ /* realm_str size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("realm_str size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->realm_str = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got realm_str: %s\n", ibuf->realm_str));
+ if (ibuf->realm_str == NULL) return ENOMEM;
+ p += len;
+ }
+
+ /* princ_str size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("princ_str size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->princ_str = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got princ_str: %s\n", ibuf->princ_str));
+ if (ibuf->princ_str == NULL) return ENOMEM;
+ p += len;
+ }
+
+ /* keytab_name size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("keytab_name size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->keytab_name = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got keytab_name: %s\n", ibuf->keytab_name));
+ if (ibuf->keytab_name == NULL) return ENOMEM;
+ p += len;
+ }
+
+ return EOK;
+}
+
+static int pack_buffer(struct response *r, int result, const char *msg)
+{
+ int len;
+ int p = 0;
+
+ len = strlen(msg);
+ r->size = 2 * sizeof(uint32_t) + len;
+
+ /* result */
+ COPY_UINT32_VALUE(&r->buf[p], result, p);
+
+ /* message size */
+ COPY_UINT32_VALUE(&r->buf[p], len, p);
+
+ /* message itself */
+ COPY_MEM(&r->buf[p], msg, p, len);
+
+ return EOK;
+}
+
+static int ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ const char **ccname_out)
+{
+ char *ccname;
+ char *realm_name = NULL;
+ char *full_princ = NULL;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal kprinc;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ krb5_error_code krberr;
+ int ret;
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ DEBUG(2, ("Failed to init kerberos context\n"));
+ return EFAULT;
+ }
+
+ if (!realm_str) {
+ krberr = krb5_get_default_realm(context, &realm_name);
+ if (krberr) {
+ DEBUG(2, ("Failed to get default realm name: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+ } else {
+ realm_name = talloc_strdup(memctx, realm_str);
+ if (!realm_name) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (princ_str) {
+ if (!strchr(princ_str, '@')) {
+ full_princ = talloc_asprintf(memctx, "%s@%s",
+ princ_str, realm_name);
+ } else {
+ full_princ = talloc_strdup(memctx, princ_str);
+ }
+ } else {
+ char hostname[512];
+
+ ret = gethostname(hostname, 511);
+ if (ret == -1) {
+ ret = errno;
+ goto done;
+ }
+ hostname[511] = '\0';
+
+ full_princ = talloc_asprintf(memctx, "host/%s@%s",
+ hostname, realm_name);
+ }
+ if (!full_princ) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(4, ("Principal name is: [%s]\n", full_princ));
+
+ krberr = krb5_parse_name(context, full_princ, &kprinc);
+ if (krberr) {
+ DEBUG(2, ("Unable to build principal: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ if (keytab_name) {
+ krberr = krb5_kt_resolve(context, keytab_name, &keytab);
+ } else {
+ krberr = krb5_kt_default(context, &keytab);
+ }
+ if (krberr) {
+ DEBUG(2, ("Failed to read keytab file: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name);
+ if (!ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr) {
+ DEBUG(2, ("Failed to set cache name: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ /* set a very short lifetime, we don't keep the ticket around */
+ krb5_get_init_creds_opt_set_tkt_life(&options, 300);
+
+ krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc,
+ keytab, 0, NULL, &options);
+
+ if (krberr) {
+ DEBUG(2, ("Failed to init credentials: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ krberr = krb5_cc_initialize(context, ccache, kprinc);
+ if (krberr) {
+ DEBUG(2, ("Failed to init ccache: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ krberr = krb5_cc_store_cred(context, ccache, &my_creds);
+ if (krberr) {
+ DEBUG(2, ("Failed to store creds: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ret = EOK;
+ *ccname_out = ccname;
+
+done:
+ if (keytab) krb5_kt_close(context, keytab);
+ if (context) krb5_free_context(context);
+ return ret;
+}
+
+static int prepare_response(TALLOC_CTX *mem_ctx,
+ const char *ccname,
+ krb5_error_code kerr,
+ struct response **rsp)
+{
+ int ret;
+ struct response *r = NULL;
+ const char *krb5_msg = NULL;
+
+ r = talloc_zero(mem_ctx, struct response);
+ if (!r) return ENOMEM;
+
+ r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE);
+ if (r->buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return ENOMEM;
+ }
+ r->max_size = MAX_CHILD_MSG_SIZE;
+ r->size = 0;
+
+ if (kerr == 0) {
+ ret = pack_buffer(r, EOK, ccname);
+ } else {
+ krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
+ if (krb5_msg == NULL) {
+ DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = pack_buffer(r, EFAULT, krb5_msg);
+ sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
+ }
+
+ if (ret != EOK) {
+ DEBUG(1, ("pack_buffer failed\n"));
+ return ret;
+ }
+
+ *rsp = r;
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int ret;
+ int kerr;
+ int opt;
+ int debug_fd = -1;
+ poptContext pc;
+ TALLOC_CTX *main_ctx;
+ uint8_t *buf = NULL;
+ ssize_t len = 0;
+ const char *ccname = NULL;
+ struct input_buffer *ibuf = NULL;
+ struct response *resp = NULL;
+ size_t written;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+ _("Debug level"), NULL},
+ {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+ _("Add debug timestamps"), NULL},
+ {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+ _("An open file descriptor for the debug logs"), NULL},
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ _exit(-1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ DEBUG(7, ("ldap_child started.\n"));
+
+ main_ctx = talloc_new(NULL);
+ if (main_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ _exit(-1);
+ }
+
+ debug_prg_name = talloc_asprintf(main_ctx, "[sssd[ldap_child[%d]]]", getpid());
+
+ if (debug_fd != -1) {
+ ret = set_debug_file_from_fd(debug_fd);
+ if (ret != EOK) {
+ DEBUG(1, ("set_debug_file_from_fd failed.\n"));
+ }
+ }
+
+ buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ goto fail;
+ }
+
+ ibuf = talloc_zero(main_ctx, struct input_buffer);
+ if (ibuf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ goto fail;
+ }
+
+ while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) {
+ if (ret == -1) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+ goto fail;
+ } else if (ret > 0) {
+ len += ret;
+ if (len > IN_BUF_SIZE) {
+ DEBUG(1, ("read too much, this should never happen.\n"));
+ goto fail;
+ }
+ continue;
+ } else {
+ DEBUG(1, ("unexpected return code of read [%d].\n", ret));
+ goto fail;
+ }
+ }
+ close(STDIN_FILENO);
+
+ ret = unpack_buffer(buf, len, ibuf);
+ if (ret != EOK) {
+ DEBUG(1, ("unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)));
+ goto fail;
+ }
+
+ kerr = ldap_child_get_tgt_sync(main_ctx,
+ ibuf->realm_str, ibuf->princ_str,
+ ibuf->keytab_name, &ccname);
+ if (kerr != EOK) {
+ DEBUG(1, ("ldap_child_get_tgt_sync failed.\n"));
+ /* Do not return, must report failure */
+ }
+
+ ret = prepare_response(main_ctx, ccname, kerr, &resp);
+ if (ret != EOK) {
+ DEBUG(1, ("prepare_response failed. [%d][%s].\n", ret, strerror(ret)));
+ return ENOMEM;
+ }
+
+ written = 0;
+ while (written < resp->size) {
+ ret = write(STDOUT_FILENO, resp->buf + written, resp->size - written);
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ }
+ ret = errno;
+ DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret)));
+ return ret;
+ }
+ written += ret;
+ }
+
+ close(STDOUT_FILENO);
+ talloc_free(main_ctx);
+ _exit(0);
+
+fail:
+ close(STDOUT_FILENO);
+ talloc_free(main_ctx);
+ _exit(-1);
+}
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
new file mode 100644
index 00000000..bd4294f8
--- /dev/null
+++ b/src/providers/ldap/ldap_common.c
@@ -0,0 +1,589 @@
+/*
+ SSSD
+
+ LDAP Provider Common Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2008-2009 Red Hat
+
+ 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 "providers/ldap/ldap_common.h"
+#include "providers/fail_over.h"
+
+#include "util/sss_krb5.h"
+
+/* a fd the child process would log into */
+int ldap_child_debug_fd = -1;
+
+struct dp_option default_basic_opts[] = {
+ { "ldap_uri", DP_OPT_STRING, { "ldap://localhost" }, NULL_STRING },
+ { "ldap_search_base", DP_OPT_STRING, { "dc=example,dc=com" }, NULL_STRING },
+ { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING},
+ { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB },
+ { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING },
+ { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_schema", DP_OPT_STRING, { "rfc2307" }, NULL_STRING },
+ { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
+ { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER },
+ { "entry_cache_timoeut", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER },
+ { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_sasl_mech", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ /* use the same parm name as the krb5 module so we set it only once */
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
+ { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+struct sdap_attr_map generic_attr_map[] = {
+ { "ldap_entry_usn", NULL, SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", NULL, SYSDB_USN, NULL }
+};
+
+struct sdap_attr_map gen_ipa_attr_map[] = {
+ { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map gen_ad_attr_map[] = {
+ { "ldap_entry_usn", "uSNChanged", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "highestCommittedUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map rfc2307_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", NULL, SYSDB_MEMBEROF, NULL },
+ { "ldap_user_uuid", NULL, SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map rfc2307_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "memberuid", SYSDB_MEMBER, NULL },
+ { "ldap_group_uuid", NULL, SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+struct sdap_attr_map rfc2307bis_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL },
+ /* FIXME: this is 389ds specific */
+ { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map rfc2307bis_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "member", SYSDB_MEMBER, NULL },
+ /* FIXME: this is 389ds specific */
+ { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+int ldap_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts)
+{
+ struct sdap_attr_map *default_attr_map;
+ struct sdap_attr_map *default_user_map;
+ struct sdap_attr_map *default_group_map;
+ struct sdap_options *opts;
+ char *schema;
+ const char *pwd_policy;
+ int ret;
+
+ opts = talloc_zero(memctx, struct sdap_options);
+ if (!opts) return ENOMEM;
+
+ ret = dp_get_options(opts, cdb, conf_path,
+ default_basic_opts,
+ SDAP_OPTS_BASIC,
+ &opts->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* set user/group search bases if they are not */
+ if (NULL == dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE)) {
+ ret = dp_opt_set_string(opts->basic, SDAP_USER_SEARCH_BASE,
+ dp_opt_get_string(opts->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ opts->basic[SDAP_USER_SEARCH_BASE].opt_name,
+ dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE)));
+ }
+
+ if (NULL == dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE)) {
+ ret = dp_opt_set_string(opts->basic, SDAP_GROUP_SEARCH_BASE,
+ dp_opt_get_string(opts->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ opts->basic[SDAP_GROUP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE)));
+ }
+
+ pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY);
+ if (pwd_policy == NULL) {
+ DEBUG(1, ("Missing password policy, this may not happen.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) != 0 &&
+ strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) != 0 &&
+ strcasecmp(pwd_policy, PWD_POL_OPT_MIT) != 0) {
+ DEBUG(1, ("Unsupported password policy [%s].\n", pwd_policy));
+ ret = EINVAL;
+ goto done;
+ }
+
+
+#ifndef HAVE_LDAP_CONNCB
+ bool ldap_referrals;
+
+ ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS);
+ if (ldap_referrals) {
+ DEBUG(1, ("LDAP referrals are not supported, because the LDAP library "
+ "is too old, see sssd-ldap(5) for details.\n"));
+ ret = dp_opt_set_bool(opts->basic, SDAP_REFERRALS, false);
+ }
+#endif
+
+ /* schema type */
+ schema = dp_opt_get_string(opts->basic, SDAP_SCHEMA);
+ if (strcasecmp(schema, "rfc2307") == 0) {
+ opts->schema_type = SDAP_SCHEMA_RFC2307;
+ default_attr_map = generic_attr_map;
+ default_user_map = rfc2307_user_map;
+ default_group_map = rfc2307_group_map;
+ } else
+ if (strcasecmp(schema, "rfc2307bis") == 0) {
+ opts->schema_type = SDAP_SCHEMA_RFC2307BIS;
+ default_attr_map = generic_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else
+ if (strcasecmp(schema, "IPA") == 0) {
+ opts->schema_type = SDAP_SCHEMA_IPA_V1;
+ default_attr_map = gen_ipa_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else
+ if (strcasecmp(schema, "AD") == 0) {
+ opts->schema_type = SDAP_SCHEMA_AD;
+ default_attr_map = gen_ad_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else {
+ DEBUG(0, ("Unrecognized schema type: %s\n", schema));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_attr_map,
+ SDAP_AT_GENERAL,
+ &opts->gen_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_user_map,
+ SDAP_OPTS_USER,
+ &opts->user_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_group_map,
+ SDAP_OPTS_GROUP,
+ &opts->group_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+ return ret;
+}
+
+void sdap_handler_done(struct be_req *req, int dp_err,
+ int error, const char *errstr)
+{
+ return req->fn(req, dp_err, error, errstr);
+}
+
+bool sdap_connected(struct sdap_id_ctx *ctx)
+{
+ if (ctx->gsh) {
+ return ctx->gsh->connected;
+ }
+
+ return false;
+}
+
+void sdap_mark_offline(struct sdap_id_ctx *ctx)
+{
+ if (ctx->gsh) {
+ /* make sure we mark the connection as gone when we go offline so that
+ * we do not try to reuse a bad connection by mistale later */
+ talloc_zfree(ctx->gsh);
+ }
+
+ be_mark_offline(ctx->be);
+}
+
+bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx)
+{
+ int ret;
+ bool result = false;
+ const char *mech;
+ const char *realm;
+ char *ccname = NULL;
+ krb5_context context = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code krberr;
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_creds mcred;
+ krb5_creds cred;
+ char *server_name = NULL;
+ char *client_princ_str = NULL;
+ char *full_princ = NULL;
+ krb5_principal client_principal = NULL;
+ krb5_principal server_principal = NULL;
+ char hostname[512];
+ int l_errno;
+
+
+ mech = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_MECH);
+ if (mech == NULL || strcasecmp(mech, "GSSAPI") != 0) {
+ return false;
+ }
+
+ realm = dp_opt_get_string(ctx->opts->basic, SDAP_KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(3, ("Kerberos realm not available.\n"));
+ return false;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return false;
+ }
+
+ ccname = talloc_asprintf(tmp_ctx, "FILE:%s/ccache_%s", DB_PATH, realm);
+ if (ccname == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ DEBUG(1, ("Failed to init kerberos context\n"));
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_resolve failed.\n"));
+ goto done;
+ }
+
+ server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm);
+ if (server_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_parse_name(context, server_name, &server_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ client_princ_str = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_AUTHID);
+ if (client_princ_str) {
+ if (!strchr(client_princ_str, '@')) {
+ full_princ = talloc_asprintf(tmp_ctx, "%s@%s", client_princ_str,
+ realm);
+ } else {
+ full_princ = talloc_strdup(tmp_ctx, client_princ_str);
+ }
+ } else {
+ ret = gethostname(hostname, sizeof(hostname)-1);
+ if (ret == -1) {
+ l_errno = errno;
+ DEBUG(1, ("gethostname failed [%d][%s].\n", l_errno,
+ strerror(l_errno)));
+ goto done;
+ }
+ hostname[sizeof(hostname)-1] = '\0';
+
+ full_princ = talloc_asprintf(tmp_ctx, "host/%s@%s", hostname, realm);
+ }
+ if (!full_princ) {
+ DEBUG(1, ("Client principal not available.\n"));
+ goto done;
+ }
+ DEBUG(7, ("Client principal name is: [%s]\n", full_princ));
+ krberr = krb5_parse_name(context, full_princ, &client_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ memset(&mcred, 0, sizeof(mcred));
+ memset(&cred, 0, sizeof(mcred));
+ mcred.client = client_principal;
+ mcred.server = server_principal;
+
+ krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcred, &cred);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_retrieve_cred failed.\n"));
+ goto done;
+ }
+
+ DEBUG(7, ("TGT end time [%d].\n", cred.times.endtime));
+
+ if (cred.times.endtime <= time(NULL)) {
+ DEBUG(3, ("TGT is expired.\n"));
+ result = true;
+ }
+ krb5_free_cred_contents(context, &cred);
+
+done:
+ if (client_principal != NULL) {
+ krb5_free_principal(context, client_principal);
+ }
+ if (server_principal != NULL) {
+ krb5_free_principal(context, server_principal);
+ }
+ if (ccache != NULL) {
+ if (result) {
+ krb5_cc_destroy(context, ccache);
+ } else {
+ krb5_cc_close(context, ccache);
+ }
+ }
+ if (context != NULL) krb5_free_context(context);
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+int sdap_id_setup_tasks(struct sdap_id_ctx *ctx)
+{
+ struct timeval tv;
+ int ret = EOK;
+
+ /* set up enumeration task */
+ if (ctx->be->domain->enumerate) {
+ /* run the first one in a couple of seconds so that we have time to
+ * finish initializations first*/
+ tv = tevent_timeval_current_ofs(10, 0);
+ ret = ldap_id_enumerate_set_timer(ctx, tv);
+ } else {
+ /* the enumeration task, runs the cleanup process by itself,
+ * but if enumeration is not runnig we need to schedule it */
+
+ /* run the first one in a couple of seconds so that we have time to
+ * finish initializations first*/
+ tv = tevent_timeval_current_ofs(10, 0);
+ ret = ldap_id_cleanup_set_timer(ctx, tv);
+ }
+
+ return ret;
+}
+
+static void sdap_uri_callback(void *private_data, struct fo_server *server)
+{
+ struct sdap_service *service;
+ const char *tmp;
+ char *new_uri;
+
+ service = talloc_get_type(private_data, struct sdap_service);
+ if (!service) return;
+
+ tmp = (const char *)fo_get_server_user_data(server);
+ if (tmp && ldap_is_ldap_url(tmp)) {
+ new_uri = talloc_strdup(service, tmp);
+ } else {
+ new_uri = talloc_asprintf(service, "ldap://%s",
+ fo_get_server_name(server));
+ }
+ if (!new_uri) {
+ DEBUG(2, ("Failed to copy URI ...\n"));
+ return;
+ }
+
+ /* free old one and replace with new one */
+ talloc_zfree(service->uri);
+ service->uri = new_uri;
+}
+
+int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *urls,
+ struct sdap_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct sdap_service *service;
+ LDAPURLDesc *lud;
+ char **list = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct sdap_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, service_name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->name = talloc_strdup(service, service_name);
+ if (!service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* split server parm into a list */
+ ret = split_on_separator(tmp_ctx, urls, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ /* now for each URI add a new server to the failover service */
+ for (i = 0; list[i]; i++) {
+ ret = ldap_url_parse(list[i], &lud);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(0, ("Failed to parse ldap URI (%s)!\n", list[i]));
+ ret = EINVAL;
+ goto done;
+ }
+
+ DEBUG(6, ("Added URI %s\n", list[i]));
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, service->name,
+ lud->lud_host, lud->lud_port, list[i]);
+ if (ret) {
+ goto done;
+ }
+ ldap_free_urldesc(lud);
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, service->name,
+ sdap_uri_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
new file mode 100644
index 00000000..ff1ffb72
--- /dev/null
+++ b/src/providers/ldap/ldap_common.h
@@ -0,0 +1,115 @@
+/*
+ SSSD
+
+ LDAP Common utility code
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LDAP_COMMON_H_
+#define _LDAP_COMMON_H_
+
+#include "providers/dp_backend.h"
+#include "providers/ldap/sdap.h"
+#include "providers/fail_over.h"
+
+#define PWD_POL_OPT_NONE "none"
+#define PWD_POL_OPT_SHADOW "shadow"
+#define PWD_POL_OPT_MIT "mit_kerberos"
+
+/* a fd the child process would log into */
+extern int ldap_child_debug_fd;
+
+struct sdap_id_ctx {
+ struct be_ctx *be;
+ struct sdap_options *opts;
+ struct fo_service *fo_service;
+ struct sdap_service *service;
+
+ /* what rootDSE returns */
+ struct sysdb_attrs *rootDSE;
+
+ /* global sdap handler */
+ struct sdap_handle *gsh;
+
+ /* enumeration loop timer */
+ struct timeval last_enum;
+ /* cleanup loop timer */
+ struct timeval last_purge;
+
+ char *max_user_timestamp;
+ char *max_group_timestamp;
+};
+
+struct sdap_auth_ctx {
+ struct be_ctx *be;
+ struct sdap_options *opts;
+ struct fo_service *fo_service;
+ struct sdap_service *service;
+};
+
+/* id */
+void sdap_account_info_handler(struct be_req *breq);
+int sdap_id_setup_tasks(struct sdap_id_ctx *ctx);
+
+/* auth */
+void sdap_pam_auth_handler(struct be_req *breq);
+
+/* chpass */
+void sdap_pam_chpass_handler(struct be_req *breq);
+
+
+
+void sdap_handler_done(struct be_req *req, int dp_err,
+ int error, const char *errstr);
+
+int sdap_service_init(TALLOC_CTX *mmectx, struct be_ctx *ctx,
+ const char *service_name, const char *urls,
+ struct sdap_service **service);
+
+/* options parser */
+int ldap_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts);
+
+int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv);
+int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv);
+
+bool sdap_connected(struct sdap_id_ctx *ctx);
+void sdap_mark_offline(struct sdap_id_ctx *ctx);
+bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx);
+
+struct tevent_req *users_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type);
+int users_get_recv(struct tevent_req *req);
+
+struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type);
+int groups_get_recv(struct tevent_req *req);
+
+/* setup child logging */
+int setup_child(struct sdap_id_ctx *ctx);
+
+#endif /* _LDAP_COMMON_H_ */
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
new file mode 100644
index 00000000..4bbc07a6
--- /dev/null
+++ b/src/providers/ldap/ldap_id.c
@@ -0,0 +1,795 @@
+/*
+ SSSD
+
+ LDAP Identity Backend Module
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2008 Red Hat
+
+ 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+/* =Users-Related-Functions-(by-name,by-uid)============================== */
+
+struct users_get_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ int filter_type;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void users_get_connect_done(struct tevent_req *subreq);
+static void users_get_done(struct tevent_req *subreq);
+static void users_get_delete(struct tevent_req *subreq);
+
+struct tevent_req *users_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type)
+{
+ struct tevent_req *req, *subreq;
+ struct users_get_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct users_get_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = state->ctx->be->domain;
+ state->name = name;
+ state->filter_type = filter_type;
+
+ switch (filter_type) {
+ case BE_FILTER_NAME:
+ attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name;
+ break;
+ case BE_FILTER_IDNUM:
+ attr_name = ctx->opts->user_map[SDAP_AT_USER_UID].name;
+ break;
+ default:
+ ret = EINVAL;
+ goto fail;
+ }
+
+ state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ attr_name, name,
+ ctx->opts->user_map[SDAP_OC_USER].name);
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->user_map,
+ SDAP_OPTS_USER, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, users_get_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, users_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void users_get_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_done, req);
+}
+
+static void users_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ char *endptr;
+ uid_t uid;
+ int ret;
+
+ ret = sdap_get_users_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ if (strchr(state->name, '*')) {
+ /* it was an enumeration */
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->filter_type) {
+ case BE_FILTER_NAME:
+ subreq = sysdb_delete_user_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, state->name, 0);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_delete, req);
+ return;
+
+ case BE_FILTER_IDNUM:
+ errno = 0;
+ uid = (uid_t)strtol(state->name, &endptr, 0);
+ if (errno || *endptr || (state->name == endptr)) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ subreq = sysdb_delete_user_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, NULL, uid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_delete, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void users_get_delete(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User (%s) delete returned %d (%s)\n",
+ state->name, ret, strerror(ret)));
+ }
+
+ tevent_req_done(req);
+}
+
+int users_get_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Groups-Related-Functions-(by-name,by-uid)============================= */
+
+struct groups_get_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ int filter_type;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void groups_get_connect_done(struct tevent_req *subreq);
+static void groups_get_done(struct tevent_req *subreq);
+static void groups_get_delete(struct tevent_req *subreq);
+
+struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type)
+{
+ struct tevent_req *req, *subreq;
+ struct groups_get_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct groups_get_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = state->ctx->be->domain;
+ state->name = name;
+ state->filter_type = filter_type;
+
+ switch(filter_type) {
+ case BE_FILTER_NAME:
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+ break;
+ case BE_FILTER_IDNUM:
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_GID].name;
+ break;
+ default:
+ ret = EINVAL;
+ goto fail;
+ }
+
+ state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ attr_name, name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name);
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, groups_get_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, groups_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void groups_get_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_done, req);
+}
+
+static void groups_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ char *endptr;
+ gid_t gid;
+ int ret;
+
+ ret = sdap_get_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ if (strchr(state->name, '*')) {
+ /* it was an enumeration */
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->filter_type) {
+ case BE_FILTER_NAME:
+ subreq = sysdb_delete_group_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, state->name, 0);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_delete, req);
+ return;
+
+ case BE_FILTER_IDNUM:
+ errno = 0;
+ gid = (gid_t)strtol(state->name, &endptr, 0);
+ if (errno || *endptr || (state->name == endptr)) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ subreq = sysdb_delete_group_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, NULL, gid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_delete, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void groups_get_delete(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Group (%s) delete returned %d (%s)\n",
+ state->name, ret, strerror(ret)));
+ }
+
+ tevent_req_done(req);
+}
+
+int groups_get_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Get-Groups-for-User================================================== */
+
+struct groups_by_user_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ const char *name;
+ const char **attrs;
+};
+
+static void groups_by_user_connect_done(struct tevent_req *subreq);
+static void groups_by_user_done(struct tevent_req *subreq);
+
+static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct groups_by_user_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct groups_by_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->name = name;
+
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, groups_by_user_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_initgr_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->name, state->attrs);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, groups_by_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void groups_by_user_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_by_user_state *state = tevent_req_data(req,
+ struct groups_by_user_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_initgr_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->name, state->attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_by_user_done, req);
+}
+
+static void groups_by_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sdap_get_initgr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int groups_by_user_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+
+/* =Get-Account-Info-Call================================================= */
+
+/* FIXME: embed this function in sssd_be and only call out
+ * specific functions from modules ? */
+
+static void sdap_account_info_users_done(struct tevent_req *req);
+static void sdap_account_info_groups_done(struct tevent_req *req);
+static void sdap_account_info_initgr_done(struct tevent_req *req);
+
+void sdap_account_info_handler(struct be_req *breq)
+{
+ struct sdap_id_ctx *ctx;
+ struct be_acct_req *ar;
+ struct tevent_req *req;
+ const char *err = "Unknown Error";
+ int ret = EOK;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct sdap_id_ctx);
+
+ if (be_is_offline(ctx->be)) {
+ return sdap_handler_done(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
+ }
+
+ ar = talloc_get_type(breq->req_data, struct be_acct_req);
+
+ switch (ar->entry_type & 0xFFF) {
+ case BE_REQ_USER: /* user */
+
+ /* skip enumerations on demand */
+ if (strcmp(ar->filter_value, "*") == 0) {
+ return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success");
+ }
+
+ req = users_get_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value,
+ ar->filter_type,
+ ar->attr_type);
+ if (!req) {
+ return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory");
+ }
+
+ tevent_req_set_callback(req, sdap_account_info_users_done, breq);
+
+ break;
+
+ case BE_REQ_GROUP: /* group */
+
+ if (strcmp(ar->filter_value, "*") == 0) {
+ return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success");
+ }
+
+ /* skip enumerations on demand */
+ req = groups_get_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value,
+ ar->filter_type,
+ ar->attr_type);
+ if (!req) {
+ return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory");
+ }
+
+ tevent_req_set_callback(req, sdap_account_info_groups_done, breq);
+
+ break;
+
+ case BE_REQ_INITGROUPS: /* init groups for user */
+ if (ar->filter_type != BE_FILTER_NAME) {
+ ret = EINVAL;
+ err = "Invalid filter type";
+ break;
+ }
+ if (ar->attr_type != BE_ATTR_CORE) {
+ ret = EINVAL;
+ err = "Invalid attr type";
+ break;
+ }
+ if (strchr(ar->filter_value, '*')) {
+ ret = EINVAL;
+ err = "Invalid filter value";
+ break;
+ }
+ req = groups_by_user_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value);
+ if (!req) ret = ENOMEM;
+ /* tevent_req_set_callback(req, groups_by_user_done, breq); */
+
+ tevent_req_set_callback(req, sdap_account_info_initgr_done, breq);
+
+ break;
+
+ default: /*fail*/
+ ret = EINVAL;
+ err = "Invalid request type";
+ }
+
+ if (ret != EOK) return sdap_handler_done(breq, DP_ERR_FATAL, ret, err);
+}
+
+static void sdap_account_info_users_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = users_get_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Enum Users Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ sdap_handler_done(breq, dp_err, ret, error);
+}
+
+static void sdap_account_info_groups_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = groups_get_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Enum Groups Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ return sdap_handler_done(breq, dp_err, ret, error);
+}
+
+static void sdap_account_info_initgr_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = groups_by_user_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Init Groups Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ return sdap_handler_done(breq, dp_err, ret, error);
+}
+
diff --git a/src/providers/ldap/ldap_id_cleanup.c b/src/providers/ldap/ldap_id_cleanup.c
new file mode 100644
index 00000000..f3fb4443
--- /dev/null
+++ b/src/providers/ldap/ldap_id_cleanup.c
@@ -0,0 +1,555 @@
+/*
+ SSSD
+
+ LDAP Identity Cleanup Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+/* ==Cleanup-Task========================================================= */
+
+struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_reschedule(struct tevent_req *req);
+
+static void ldap_id_cleanup_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void ldap_id_cleanup_timer(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx);
+ struct tevent_timer *timeout;
+ struct tevent_req *req;
+ int delay;
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_cleanup_set_timer(ctx, tv);
+ return;
+ }
+
+ req = ldap_id_cleanup_send(ctx, ev, ctx);
+ if (!req) {
+ DEBUG(1, ("Failed to schedule cleanup, retrying later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_cleanup_set_timer(ctx, tv);
+ return;
+ }
+ tevent_req_set_callback(req, ldap_id_cleanup_reschedule, ctx);
+
+ /* if cleanup takes so long, either we try to cleanup too
+ * frequently, or something went seriously wrong */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ timeout = tevent_add_timer(ctx->be->ev, req, tv,
+ ldap_id_cleanup_timeout, req);
+ return;
+}
+
+static void ldap_id_cleanup_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ int delay;
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ DEBUG(1, ("Cleanup timed out! Timeout too small? (%ds)!\n", delay));
+
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+
+ talloc_zfree(req);
+}
+
+static void ldap_id_cleanup_reschedule(struct tevent_req *req)
+{
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ enum tevent_req_state tstate;
+ uint64_t err;
+ struct timeval tv;
+ int delay;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* On error schedule starting from now, not the last run */
+ tv = tevent_timeval_current();
+ } else {
+ tv = ctx->last_purge;
+ }
+ talloc_zfree(req);
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_add(&tv, delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+}
+
+
+
+int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv)
+{
+ struct tevent_timer *cleanup_task;
+
+ DEBUG(6, ("Scheduling next cleanup at %ld.%ld\n",
+ (long)tv.tv_sec, (long)tv.tv_usec));
+
+ cleanup_task = tevent_add_timer(ctx->be->ev, ctx,
+ tv, ldap_id_cleanup_timer, ctx);
+ if (!cleanup_task) {
+ DEBUG(0, ("FATAL: failed to setup cleanup task!\n"));
+ return EFAULT;
+ }
+
+ return EOK;
+}
+
+
+
+struct global_cleanup_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+};
+
+static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_users_done(struct tevent_req *subreq);
+static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct global_cleanup_state *state;
+ struct tevent_req *req, *subreq;
+
+ req = tevent_req_create(memctx, &state, struct global_cleanup_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ subreq = cleanup_users_send(state, ev, ctx);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req);
+
+ ctx->last_purge = tevent_timeval_current();
+
+ return req;
+}
+
+static void ldap_id_cleanup_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_cleanup_state *state = tevent_req_data(req,
+ struct global_cleanup_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ subreq = cleanup_groups_send(state, state->ev, state->ctx);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req);
+
+ return;
+
+fail:
+ if (err) {
+ DEBUG(9, ("User cleanup failed with: (%d)[%s]\n",
+ (int)err, strerror(err)));
+
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = cleanup_users_send(state, state->ev, state->ctx);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ }
+
+ DEBUG(1, ("Failed to cleanup users, retrying later!\n"));
+ tevent_req_done(req);
+}
+
+static void ldap_id_cleanup_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_cleanup_state *state = tevent_req_data(req,
+ struct global_cleanup_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ /* check if credentials are expired otherwise go offline on failures */
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = cleanup_groups_send(state, state->ev, state->ctx);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ DEBUG(1, ("Failed to cleanup groups (%d [%s]), retrying later!\n",
+ (int)err, strerror(err)));
+ tevent_req_done(req);
+}
+
+
+/* ==User-Cleanup-Process================================================= */
+
+struct cleanup_users_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ struct sysdb_handle *handle;
+
+ struct ldb_message **msgs;
+ size_t count;
+ int cur;
+};
+
+static void cleanup_users_process(struct tevent_req *subreq);
+static void cleanup_users_update(struct tevent_req *req);
+static void cleanup_users_up_done(struct tevent_req *subreq);
+
+static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct cleanup_users_state *state;
+ static const char *attrs[] = { SYSDB_NAME, NULL };
+ time_t now = time(NULL);
+ char *subfilter;
+
+ req = tevent_req_create(memctx, &state, struct cleanup_users_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = ctx->be->domain;
+ state->msgs = NULL;
+ state->count = 0;
+ state->cur = 0;
+
+ subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))",
+ SYSDB_CACHE_EXPIRE,
+ SYSDB_CACHE_EXPIRE, (long)now);
+ if (!subfilter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sysdb_search_users_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, subfilter, attrs);
+ if (!subreq) {
+ DEBUG(2, ("Failed to send entry search\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cleanup_users_process, req);
+
+ return req;
+}
+
+static void cleanup_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ int ret;
+
+ ret = sysdb_search_users_recv(subreq, state, &state->count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOENT) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(4, ("Found %d expired user entries!\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_done(req);
+ }
+
+ cleanup_users_update(req);
+}
+
+static void cleanup_users_update(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ const char *str;
+
+ str = ldb_msg_find_attr_as_string(state->msgs[state->cur],
+ SYSDB_NAME, NULL);
+ if (!str) {
+ DEBUG(2, ("Entry %s has no Name Attribute ?!?\n",
+ ldb_dn_get_linearized(state->msgs[state->cur]->dn)));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ subreq = users_get_send(state, state->ev, state->ctx,
+ str, BE_FILTER_NAME, BE_ATTR_CORE);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, cleanup_users_up_done, req);
+}
+
+static void cleanup_users_up_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ int ret;
+
+ ret = users_get_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User check returned: %d(%s)\n",
+ ret, strerror(ret)));
+ }
+
+ /* if the entry doesn't need to be purged, remove it from the list */
+ if (ret != ENOENT) {
+ talloc_zfree(state->msgs[state->cur]);
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ cleanup_users_update(req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+/* ==Group-Cleanup-Process================================================ */
+
+struct cleanup_groups_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ struct sysdb_handle *handle;
+
+ struct ldb_message **msgs;
+ size_t count;
+ int cur;
+};
+
+static void cleanup_groups_process(struct tevent_req *subreq);
+static void cleanup_groups_update(struct tevent_req *req);
+static void cleanup_groups_up_done(struct tevent_req *subreq);
+
+static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct cleanup_groups_state *state;
+ static const char *attrs[] = { SYSDB_NAME, NULL };
+ time_t now = time(NULL);
+ char *subfilter;
+
+ req = tevent_req_create(memctx, &state, struct cleanup_groups_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = ctx->be->domain;
+ state->msgs = NULL;
+ state->count = 0;
+ state->cur = 0;
+
+ subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))",
+ SYSDB_CACHE_EXPIRE,
+ SYSDB_CACHE_EXPIRE, (long)now);
+ if (!subfilter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sysdb_search_groups_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, subfilter, attrs);
+ if (!subreq) {
+ DEBUG(2, ("Failed to send entry search\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cleanup_groups_process, req);
+
+ return req;
+}
+
+static void cleanup_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ int ret;
+
+ ret = sysdb_search_groups_recv(subreq, state, &state->count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOENT) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(4, ("Found %d expired group entries!\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_done(req);
+ }
+
+ cleanup_groups_update(req);
+}
+
+static void cleanup_groups_update(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ const char *str;
+
+ str = ldb_msg_find_attr_as_string(state->msgs[state->cur],
+ SYSDB_NAME, NULL);
+ if (!str) {
+ DEBUG(2, ("Entry %s has no Name Attribute ?!?\n",
+ ldb_dn_get_linearized(state->msgs[state->cur]->dn)));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ subreq = groups_get_send(state, state->ev, state->ctx,
+ str, BE_FILTER_NAME, BE_ATTR_CORE);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, cleanup_groups_up_done, req);
+}
+
+static void cleanup_groups_up_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ int ret;
+
+ ret = groups_get_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User check returned: %d(%s)\n",
+ ret, strerror(ret)));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ cleanup_groups_update(req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
diff --git a/src/providers/ldap/ldap_id_enum.c b/src/providers/ldap/ldap_id_enum.c
new file mode 100644
index 00000000..bc06e8bd
--- /dev/null
+++ b/src/providers/ldap/ldap_id_enum.c
@@ -0,0 +1,608 @@
+/*
+ SSSD
+
+ LDAP Identity Enumeration
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+extern struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+
+/* ==Enumeration-Task===================================================== */
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+
+static void ldap_id_enumerate_reschedule(struct tevent_req *req);
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void ldap_id_enumerate_timer(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx);
+ struct tevent_timer *timeout;
+ struct tevent_req *req;
+ int delay;
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+ return;
+ }
+
+ req = ldap_id_enumerate_send(ev, ctx);
+ if (!req) {
+ DEBUG(1, ("Failed to schedule enumeration, retrying later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+ return;
+ }
+ tevent_req_set_callback(req, ldap_id_enumerate_reschedule, ctx);
+
+ /* if enumeration takes so long, either we try to enumerate too
+ * frequently, or something went seriously wrong */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ timeout = tevent_add_timer(ctx->be->ev, req, tv,
+ ldap_id_enumerate_timeout, req);
+ return;
+}
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ int delay;
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n", delay));
+
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+
+ talloc_zfree(req);
+}
+
+static void ldap_id_enumerate_reschedule(struct tevent_req *req)
+{
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ enum tevent_req_state tstate;
+ uint64_t err;
+ struct timeval tv;
+ int delay;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* On error schedule starting from now, not the last run */
+ tv = tevent_timeval_current();
+ } else {
+ tv = ctx->last_enum;
+ }
+ talloc_zfree(req);
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_add(&tv, delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+}
+
+
+
+int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv)
+{
+ struct tevent_timer *enum_task;
+
+ DEBUG(6, ("Scheduling next enumeration at %ld.%ld\n",
+ (long)tv.tv_sec, (long)tv.tv_usec));
+
+ enum_task = tevent_add_timer(ctx->be->ev, ctx,
+ tv, ldap_id_enumerate_timer, ctx);
+ if (!enum_task) {
+ DEBUG(0, ("FATAL: failed to setup enumeration task!\n"));
+ return EFAULT;
+ }
+
+ return EOK;
+}
+
+
+struct global_enum_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ bool purge;
+};
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge);
+static void ldap_id_enum_users_done(struct tevent_req *subreq);
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge);
+static void ldap_id_enum_groups_done(struct tevent_req *subreq);
+static void ldap_id_enum_cleanup_done(struct tevent_req *subreq);
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct global_enum_state *state;
+ struct tevent_req *req, *subreq;
+ int t;
+
+ req = tevent_req_create(ctx, &state, struct global_enum_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ ctx->last_enum = tevent_timeval_current();
+
+ t = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ if ((ctx->last_purge.tv_sec + t) < ctx->last_enum.tv_sec) {
+ state->purge = true;
+ } else {
+ state->purge = false;
+ }
+
+ subreq = enum_users_send(state, ev, ctx, state->purge);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_users_done, req);
+
+ return req;
+}
+
+static void ldap_id_enum_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_enum_state *state = tevent_req_data(req,
+ struct global_enum_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ subreq = enum_groups_send(state, state->ev, state->ctx, state->purge);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req);
+
+ return;
+
+fail:
+ if (err) {
+ DEBUG(9, ("User enumeration failed with: (%d)[%s]\n",
+ (int)err, strerror(err)));
+
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = enum_users_send(state, state->ev, state->ctx, state->purge);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_enum_users_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ }
+
+ DEBUG(1, ("Failed to enumerate users, retrying later!\n"));
+ tevent_req_done(req);
+}
+
+static void ldap_id_enum_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_enum_state *state = tevent_req_data(req,
+ struct global_enum_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ if (state->purge) {
+
+ subreq = ldap_id_cleanup_send(state, state->ev, state->ctx);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_cleanup_done, req);
+
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ /* check if credentials are expired otherwise go offline on failures */
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = enum_groups_send(state, state->ev, state->ctx, state->purge);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ DEBUG(1, ("Failed to enumerate groups (%d [%s]), retrying later!\n",
+ (int)err, strerror(err)));
+ tevent_req_done(req);
+}
+
+static void ldap_id_enum_cleanup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ talloc_zfree(subreq);
+ tevent_req_done(req);
+}
+
+
+/* ==User-Enumeration===================================================== */
+
+struct enum_users_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_users_connect_done(struct tevent_req *subreq);
+static void enum_users_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_users_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct enum_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ if (ctx->max_user_timestamp && !purge) {
+
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+ ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ ctx->opts->user_map[SDAP_OC_USER].name,
+ ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+ ctx->max_user_timestamp,
+ ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+ ctx->max_user_timestamp);
+ } else {
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s))",
+ ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ ctx->opts->user_map[SDAP_OC_USER].name);
+ }
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->user_map,
+ SDAP_OPTS_USER, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, enum_users_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts,
+ state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_users_op_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_users_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_users_op_done, req);
+}
+
+static void enum_users_op_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ char *timestamp;
+ int ret;
+
+ ret = sdap_get_users_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (timestamp) {
+ talloc_zfree(state->ctx->max_user_timestamp);
+ state->ctx->max_user_timestamp = talloc_steal(state->ctx, timestamp);
+ }
+
+ DEBUG(4, ("Users higher timestamp: [%s]\n",
+ state->ctx->max_user_timestamp));
+
+ tevent_req_done(req);
+}
+
+/* =Group-Enumeration===================================================== */
+
+struct enum_groups_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_groups_connect_done(struct tevent_req *subreq);
+static void enum_groups_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_groups_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct enum_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+
+ if (ctx->max_group_timestamp && !purge) {
+
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+ ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name,
+ ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+ ctx->max_group_timestamp,
+ ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+ ctx->max_group_timestamp);
+ } else {
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s))",
+ ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name);
+ }
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, enum_groups_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_groups_op_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_groups_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_groups_op_done, req);
+}
+
+static void enum_groups_op_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ char *timestamp;
+ int ret;
+
+ ret = sdap_get_groups_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (timestamp) {
+ talloc_zfree(state->ctx->max_group_timestamp);
+ state->ctx->max_group_timestamp = talloc_steal(state->ctx, timestamp);
+ }
+
+ DEBUG(4, ("Groups higher timestamp: [%s]\n",
+ state->ctx->max_group_timestamp));
+
+ tevent_req_done(req);
+}
+
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
new file mode 100644
index 00000000..b1f053fb
--- /dev/null
+++ b/src/providers/ldap/ldap_init.c
@@ -0,0 +1,179 @@
+/*
+ SSSD
+
+ LDAP Provider Initialization functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 "providers/child_common.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async_private.h"
+
+static void sdap_shutdown(struct be_req *req);
+
+/* Id Handler */
+struct bet_ops sdap_id_ops = {
+ .handler = sdap_account_info_handler,
+ .finalize = sdap_shutdown
+};
+
+/* Auth Handler */
+struct bet_ops sdap_auth_ops = {
+ .handler = sdap_pam_auth_handler,
+ .finalize = sdap_shutdown
+};
+
+/* Chpass Handler */
+struct bet_ops sdap_chpass_ops = {
+ .handler = sdap_pam_chpass_handler,
+ .finalize = sdap_shutdown
+};
+
+int sssm_ldap_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_id_ctx *ctx;
+ const char *urls;
+ int ret;
+
+ ctx = talloc_zero(bectx, struct sdap_id_ctx);
+ if (!ctx) return ENOMEM;
+
+ ctx->be = bectx;
+
+ ret = ldap_get_options(ctx, bectx->cdb,
+ bectx->conf_path, &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI);
+ if (!urls) {
+ DEBUG(0, ("Missing ldap_uri\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to initialize failover service!\n"));
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = sdap_id_setup_tasks(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_child(ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_child failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &sdap_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_ldap_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_auth_ctx *ctx;
+ const char *urls;
+ int ret;
+
+ ctx = talloc(bectx, struct sdap_auth_ctx);
+ if (!ctx) return ENOMEM;
+
+ ctx->be = bectx;
+
+ ret = ldap_get_options(ctx, bectx->cdb,
+ bectx->conf_path, &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI);
+ if (!urls) {
+ DEBUG(0, ("Missing ldap_uri\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to initialize failover service!\n"));
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &sdap_auth_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_ldap_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+
+ ret = sssm_ldap_auth_init(bectx, ops, pvt_data);
+
+ *ops = &sdap_chpass_ops;
+
+ return ret;
+}
+
+static void sdap_shutdown(struct be_req *req)
+{
+ /* TODO: Clean up any internal data */
+ sdap_handler_done(req, DP_ERR_OK, EOK, NULL);
+}
+
diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
new file mode 100644
index 00000000..39c67cc9
--- /dev/null
+++ b/src/providers/ldap/sdap.c
@@ -0,0 +1,388 @@
+/*
+ SSSD
+
+ LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@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/>.
+*/
+
+#define LDAP_DEPRECATED 1
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "providers/ldap/sdap.h"
+
+/* =Retrieve-Options====================================================== */
+
+int sdap_get_map(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_attr_map *def_map,
+ int num_entries,
+ struct sdap_attr_map **_map)
+{
+ struct sdap_attr_map *map;
+ int i, ret;
+
+ map = talloc_array(memctx, struct sdap_attr_map, num_entries);
+ if (!map) {
+ return ENOMEM;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+
+ map[i].opt_name = def_map[i].opt_name;
+ map[i].def_name = def_map[i].def_name;
+ map[i].sys_name = def_map[i].sys_name;
+
+ ret = confdb_get_string(cdb, map, conf_path,
+ map[i].opt_name,
+ map[i].def_name,
+ &map[i].name);
+ if ((ret != EOK) || (map[i].def_name && !map[i].name)) {
+ DEBUG(0, ("Failed to retrieve value for %s\n", map[i].opt_name));
+ if (ret != EOK) {
+ talloc_zfree(map);
+ return EINVAL;
+ }
+ }
+
+ DEBUG(5, ("Option %s has value %s\n", map[i].opt_name, map[i].name));
+ }
+
+ *_map = map;
+ return EOK;
+}
+
+/* =Parse-msg============================================================= */
+
+int sdap_parse_entry(TALLOC_CTX *memctx,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sdap_attr_map *map, int attrs_num,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+ struct sysdb_attrs *attrs;
+ BerElement *ber = NULL;
+ struct berval **vals;
+ struct ldb_val v;
+ char *str;
+ int lerrno;
+ int a, i, ret;
+ const char *name;
+ bool store;
+
+ lerrno = 0;
+ ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+
+ attrs = sysdb_new_attrs(memctx);
+ if (!attrs) return ENOMEM;
+
+ str = ldap_get_dn(sh->ldap, sm->msg);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("ldap_get_dn failed: %d(%s)\n",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+
+ DEBUG(9, ("OriginalDN: [%s].\n", str));
+ ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, str);
+ if (ret) goto fail;
+ if (_dn) {
+ *_dn = talloc_strdup(memctx, str);
+ if (!*_dn) {
+ ret = ENOMEM;
+ ldap_memfree(str);
+ goto fail;
+ }
+ }
+ ldap_memfree(str);
+
+ if (map) {
+ vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass");
+ if (!vals) {
+ DEBUG(1, ("Unknown entry type, no objectClasses found!\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ for (i = 0; vals[i]; i++) {
+ /* the objectclass is always the first name in the map */
+ if (strncasecmp(map[0].name,
+ vals[i]->bv_val, vals[i]->bv_len) == 0) {
+ /* ok it's an entry of the right type */
+ break;
+ }
+ }
+ if (!vals[i]) {
+ DEBUG(1, ("objectClass not matching: %s\n",
+ map[0].name));
+ ldap_value_free_len(vals);
+ ret = EINVAL;
+ goto fail;
+ }
+ ldap_value_free_len(vals);
+ }
+
+ str = ldap_first_attribute(sh->ldap, sm->msg, &ber);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("Entry has no attributes [%d(%s)]!?\n",
+ lerrno, ldap_err2string(lerrno)));
+ if (map) {
+ ret = EINVAL;
+ goto fail;
+ }
+ }
+ while (str) {
+ if (map) {
+ for (a = 1; a < attrs_num; a++) {
+ /* check if this attr is valid with the chosen schema */
+ if (!map[a].name) continue;
+ /* check if it is an attr we are interested in */
+ if (strcasecmp(str, map[a].name) == 0) break;
+ }
+ /* interesting attr */
+ if (a < attrs_num) {
+ store = true;
+ name = map[a].sys_name;
+ } else {
+ store = false;
+ }
+ } else {
+ name = str;
+ store = true;
+ }
+
+ if (store) {
+ vals = ldap_get_values_len(sh->ldap, sm->msg, str);
+ if (!vals) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("LDAP Library error: %d(%s)",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+ if (!vals[0]) {
+ DEBUG(1, ("Missing value after ldap_get_values() ??\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+ for (i = 0; vals[i]; i++) {
+ v.data = (uint8_t *)vals[i]->bv_val;
+ v.length = vals[i]->bv_len;
+
+ ret = sysdb_attrs_add_val(attrs, name, &v);
+ if (ret) goto fail;
+ }
+ ldap_value_free_len(vals);
+ }
+
+ ldap_memfree(str);
+ str = ldap_next_attribute(sh->ldap, sm->msg, ber);
+ }
+ ber_free(ber, 0);
+
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ if (lerrno) {
+ DEBUG(1, ("LDAP Library error: %d(%s)",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+
+ *_attrs = attrs;
+ return EOK;
+
+fail:
+ if (ber) ber_free(ber, 0);
+ talloc_free(attrs);
+ return ret;
+}
+
+/* This function converts an ldap message into a sysdb_attrs structure.
+ * It converts only known user attributes, the rest are ignored.
+ * If the entry is not that of an user an error is returned.
+ * The original DN is stored as an attribute named originalDN */
+
+int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+
+ return sdap_parse_entry(memctx, sh, sm, opts->user_map,
+ SDAP_OPTS_USER, _attrs, _dn);
+}
+
+/* This function converts an ldap message into a sysdb_attrs structure.
+ * It converts only known group attributes, the rest are ignored.
+ * If the entry is not that of an user an error is returned.
+ * The original DN is stored as an attribute named originalDN */
+
+int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+
+ return sdap_parse_entry(memctx, sh, sm, opts->group_map,
+ SDAP_OPTS_GROUP, _attrs, _dn);
+}
+
+/* =Get-DN-from-message=================================================== */
+
+int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
+ struct sdap_msg *sm, char **_dn)
+{
+ char *str;
+ int lerrno;
+
+ lerrno = 0;
+ ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+
+ str = ldap_get_dn(sh->ldap, sm->msg);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("ldap_get_dn failed: %d(%s)\n",
+ lerrno, ldap_err2string(lerrno)));
+ return EIO;
+ }
+
+ *_dn = talloc_strdup(memctx, str);
+ ldap_memfree(str);
+ if (!*_dn) return ENOMEM;
+
+ return EOK;
+}
+
+errno_t setup_tls_config(struct dp_option *basic_opts)
+{
+ int ret;
+ int ldap_opt_x_tls_require_cert;
+ const char *tls_opt;
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_REQCERT);
+ if (tls_opt) {
+ if (strcasecmp(tls_opt, "never") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_NEVER;
+ }
+ else if (strcasecmp(tls_opt, "allow") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_ALLOW;
+ }
+ else if (strcasecmp(tls_opt, "try") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_TRY;
+ }
+ else if (strcasecmp(tls_opt, "demand") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_DEMAND;
+ }
+ else if (strcasecmp(tls_opt, "hard") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_HARD;
+ }
+ else {
+ DEBUG(1, ("Unknown value for tls_reqcert.\n"));
+ return EINVAL;
+ }
+ /* LDAP_OPT_X_TLS_REQUIRE_CERT has to be set as a global option,
+ * because the SSL/TLS context is initialized from this value. */
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &ldap_opt_x_tls_require_cert);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERT);
+ if (tls_opt) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, tls_opt);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERTDIR);
+ if (tls_opt) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, tls_opt);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ return EOK;
+}
+
+
+bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse,
+ const char *sasl_mech)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *val;
+ int i;
+
+ if (!sasl_mech) return false;
+
+ for (i = 0; i < rootdse->num; i++) {
+ if (strcasecmp(rootdse->a[i].name, "supportedSASLMechanisms")) {
+ continue;
+ }
+ el = &rootdse->a[i];
+ break;
+ }
+
+ if (!el) {
+ /* no supported SASL Mechanism at all ? */
+ return false;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ val = &el->values[i];
+ if (strncasecmp(sasl_mech, (const char *)val->data, val->length)) {
+ continue;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+int build_attrs_from_map(TALLOC_CTX *memctx,
+ struct sdap_attr_map *map,
+ size_t size, const char ***_attrs)
+{
+ char **attrs;
+ int i, j;
+
+ attrs = talloc_array(memctx, char *, size + 1);
+ if (!attrs) return ENOMEM;
+
+ /* first attribute is "objectclass" not the specifc one */
+ attrs[0] = talloc_strdup(memctx, "objectClass");
+ if (!attrs[0]) return ENOMEM;
+
+ /* add the others */
+ for (i = j = 1; i < size; i++) {
+ if (map[i].name) {
+ attrs[j] = map[i].name;
+ j++;
+ }
+ }
+ attrs[j] = NULL;
+
+ *_attrs = (const char **)attrs;
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
new file mode 100644
index 00000000..16dbb784
--- /dev/null
+++ b/src/providers/ldap/sdap.h
@@ -0,0 +1,258 @@
+/*
+ SSSD
+
+ LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@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/>.
+*/
+
+#ifndef _SDAP_H_
+#define _SDAP_H_
+
+#include "providers/dp_backend.h"
+#include <ldap.h>
+#include "util/sss_ldap.h"
+
+struct sdap_msg {
+ struct sdap_msg *next;
+ LDAPMessage *msg;
+};
+
+struct sdap_op;
+
+typedef void (sdap_op_callback_t)(struct sdap_op *op,
+ struct sdap_msg *, int, void *);
+
+struct sdap_handle;
+
+struct sdap_op {
+ struct sdap_op *prev, *next;
+ struct sdap_handle *sh;
+
+ int msgid;
+ bool done;
+
+ sdap_op_callback_t *callback;
+ void *data;
+
+ struct tevent_context *ev;
+ struct sdap_msg *list;
+ struct sdap_msg *last;
+};
+
+struct fd_event_item {
+ struct fd_event_item *prev;
+ struct fd_event_item *next;
+
+ int fd;
+ struct tevent_fd *fde;
+};
+
+struct ldap_cb_data {
+ struct sdap_handle *sh;
+ struct tevent_context *ev;
+ struct fd_event_item *fd_list;
+};
+
+struct sdap_handle {
+ LDAP *ldap;
+ bool connected;
+
+#ifdef HAVE_LDAP_CONNCB
+ struct ldap_conncb *conncb;
+#else
+ struct tevent_fd *fde;
+#endif
+
+ struct sdap_op *ops;
+};
+
+struct sdap_service {
+ char *name;
+ char *uri;
+};
+
+#define SYSDB_SHADOWPW_LASTCHANGE "shadowLastChange"
+#define SYSDB_SHADOWPW_MIN "shadowMin"
+#define SYSDB_SHADOWPW_MAX "shadowMax"
+#define SYSDB_SHADOWPW_WARNING "shadowWarning"
+#define SYSDB_SHADOWPW_INACTIVE "shadowInactive"
+#define SYSDB_SHADOWPW_EXPIRE "shadowExpire"
+#define SYSDB_SHADOWPW_FLAG "shadowFlag"
+
+#define SYSDB_KRBPW_LASTCHANGE "krbLastPwdChange"
+#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration"
+
+#define SYSDB_PWD_ATTRIBUTE "pwdAttribute"
+
+enum sdap_result {
+ SDAP_SUCCESS,
+ SDAP_NOT_FOUND,
+ SDAP_UNAVAIL,
+ SDAP_RETRY,
+ SDAP_ERROR,
+ SDAP_AUTH_SUCCESS,
+ SDAP_AUTH_FAILED,
+ SDAP_AUTH_PW_EXPIRED,
+ SDAP_ACCT_EXPIRED
+};
+
+enum sdap_basic_opt {
+ SDAP_URI = 0,
+ SDAP_SEARCH_BASE,
+ SDAP_DEFAULT_BIND_DN,
+ SDAP_DEFAULT_AUTHTOK_TYPE,
+ SDAP_DEFAULT_AUTHTOK,
+ SDAP_SEARCH_TIMEOUT,
+ SDAP_NETWORK_TIMEOUT,
+ SDAP_OPT_TIMEOUT,
+ SDAP_TLS_REQCERT,
+ SDAP_USER_SEARCH_BASE,
+ SDAP_USER_SEARCH_SCOPE,
+ SDAP_USER_SEARCH_FILTER,
+ SDAP_GROUP_SEARCH_BASE,
+ SDAP_GROUP_SEARCH_SCOPE,
+ SDAP_GROUP_SEARCH_FILTER,
+ SDAP_SCHEMA,
+ SDAP_OFFLINE_TIMEOUT,
+ SDAP_FORCE_UPPER_CASE_REALM,
+ SDAP_ENUM_REFRESH_TIMEOUT,
+ SDAP_CACHE_PURGE_TIMEOUT,
+ SDAP_ENTRY_CACHE_TIMEOUT,
+ SDAP_TLS_CACERT,
+ SDAP_TLS_CACERTDIR,
+ SDAP_ID_TLS,
+ SDAP_SASL_MECH,
+ SDAP_SASL_AUTHID,
+ SDAP_KRB5_KEYTAB,
+ SDAP_KRB5_KINIT,
+ SDAP_KRB5_REALM,
+ SDAP_PWD_POLICY,
+ SDAP_REFERRALS,
+
+ SDAP_OPTS_BASIC /* opts counter */
+};
+
+enum sdap_gen_attrs {
+ SDAP_AT_ENTRY_USN = 0,
+ SDAP_AT_LAST_USN,
+
+ SDAP_AT_GENERAL /* attrs counter */
+};
+
+/* the objectclass must be the first attribute.
+ * Functions depend on this */
+enum sdap_user_attrs {
+ SDAP_OC_USER = 0,
+ SDAP_AT_USER_NAME,
+ SDAP_AT_USER_PWD,
+ SDAP_AT_USER_UID,
+ SDAP_AT_USER_GID,
+ SDAP_AT_USER_GECOS,
+ SDAP_AT_USER_HOME,
+ SDAP_AT_USER_SHELL,
+ SDAP_AT_USER_PRINC,
+ SDAP_AT_USER_FULLNAME,
+ SDAP_AT_USER_MEMBEROF,
+ SDAP_AT_USER_UUID,
+ SDAP_AT_USER_MODSTAMP,
+ SDAP_AT_SP_LSTCHG,
+ SDAP_AT_SP_MIN,
+ SDAP_AT_SP_MAX,
+ SDAP_AT_SP_WARN,
+ SDAP_AT_SP_INACT,
+ SDAP_AT_SP_EXPIRE,
+ SDAP_AT_SP_FLAG,
+ SDAP_AT_KP_LASTCHANGE,
+ SDAP_AT_KP_EXPIRATION,
+ SDAP_AT_PWD_ATTRIBUTE,
+
+ SDAP_OPTS_USER /* attrs counter */
+};
+
+#define SDAP_FIRST_EXTRA_USER_AT SDAP_AT_SP_LSTCHG
+
+/* the objectclass must be the first attribute.
+ * Functions depend on this */
+enum sdap_group_attrs {
+ SDAP_OC_GROUP = 0,
+ SDAP_AT_GROUP_NAME,
+ SDAP_AT_GROUP_PWD,
+ SDAP_AT_GROUP_GID,
+ SDAP_AT_GROUP_MEMBER,
+ SDAP_AT_GROUP_UUID,
+ SDAP_AT_GROUP_MODSTAMP,
+
+ SDAP_OPTS_GROUP /* attrs counter */
+};
+
+struct sdap_attr_map {
+ const char *opt_name;
+ const char *def_name;
+ const char *sys_name;
+ char *name;
+};
+
+struct sdap_options {
+ struct dp_option *basic;
+ struct sdap_attr_map *gen_map;
+ struct sdap_attr_map *user_map;
+ struct sdap_attr_map *group_map;
+
+ /* supported schema types */
+ enum schema_type {
+ SDAP_SCHEMA_RFC2307 = 1, /* memberUid = uid */
+ SDAP_SCHEMA_RFC2307BIS = 2, /* member = dn */
+ SDAP_SCHEMA_IPA_V1 = 3, /* member/memberof */
+ SDAP_SCHEMA_AD = 4 /* AD's member/memberof */
+ } schema_type;
+
+ struct ldb_dn *users_base;
+ struct ldb_dn *groups_base;
+};
+
+int sdap_get_map(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_attr_map *def_map,
+ int num_entries,
+ struct sdap_attr_map **_map);
+
+int sdap_parse_entry(TALLOC_CTX *memctx,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sdap_attr_map *map, int attrs_num,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
+ struct sdap_msg *sm, char **_dn);
+
+errno_t setup_tls_config(struct dp_option *basic_opts);
+
+bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse,
+ const char *sasl_mech);
+
+int build_attrs_from_map(TALLOC_CTX *memctx,
+ struct sdap_attr_map *map,
+ size_t size, const char ***_attrs);
+#endif /* _SDAP_H_ */
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
new file mode 100644
index 00000000..959c08a6
--- /dev/null
+++ b/src/providers/ldap/sdap_async.c
@@ -0,0 +1,1018 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <ctype.h>
+#include "util/util.h"
+#include "providers/ldap/sdap_async_private.h"
+
+#define REALM_SEPARATOR '@'
+#define REPLY_REALLOC_INCREMENT 10
+
+void make_realm_upper_case(const char *upn)
+{
+ char *c;
+
+ c = strchr(upn, REALM_SEPARATOR);
+ if (c == NULL) {
+ DEBUG(9, ("No realm delimiter found in upn [%s].\n", upn));
+ return;
+ }
+
+ while(*(++c) != '\0') {
+ c[0] = toupper(*c);
+ }
+
+ return;
+}
+
+/* ==LDAP-Memory-Handling================================================= */
+
+static int lmsg_destructor(void *mem)
+{
+ ldap_msgfree((LDAPMessage *)mem);
+ return 0;
+}
+
+static int sdap_msg_attach(TALLOC_CTX *memctx, LDAPMessage *msg)
+{
+ void *h;
+
+ if (!msg) return EINVAL;
+
+ h = sss_mem_attach(memctx, msg, lmsg_destructor);
+ if (!h) return ENOMEM;
+
+ return EOK;
+}
+
+/* ==sdap-hanlde-utility-functions======================================== */
+
+static inline void sdap_handle_release(struct sdap_handle *sh);
+static int sdap_handle_destructor(void *mem);
+
+struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx)
+{
+ struct sdap_handle *sh;
+
+ sh = talloc_zero(memctx, struct sdap_handle);
+ if (!sh) return NULL;
+
+ talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor);
+
+ return sh;
+}
+
+static int sdap_handle_destructor(void *mem)
+{
+ struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle);
+
+ sdap_handle_release(sh);
+
+ return 0;
+}
+
+static void sdap_handle_release(struct sdap_handle *sh)
+{
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->ldap));
+
+ if (sh->connected) {
+ struct sdap_op *op;
+
+#ifdef HAVE_LDAP_CONNCB
+ /* remove all related fd events from the event loop */
+ talloc_zfree(sh->conncb->lc_arg);
+#else
+ talloc_zfree(sh->fde);
+#endif
+
+ while (sh->ops) {
+ op = sh->ops;
+ op->callback(op, NULL, EIO, op->data);
+ /* calling the callback may result in freeing the op */
+ /* check if it is still the same or avoid freeing */
+ if (op == sh->ops) talloc_free(op);
+ }
+
+ if (sh->ldap) {
+ ldap_unbind_ext(sh->ldap, NULL, NULL);
+ }
+#ifdef HAVE_LDAP_CONNCB
+ talloc_zfree(sh->conncb);
+#endif
+ sh->connected = false;
+ sh->ldap = NULL;
+ sh->ops = NULL;
+ }
+}
+
+/* ==Parse-Results-And-Handle-Disconnections============================== */
+static void sdap_process_message(struct tevent_context *ev,
+ struct sdap_handle *sh, LDAPMessage *msg);
+static void sdap_process_result(struct tevent_context *ev, void *pvt);
+static void sdap_process_next_reply(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void sdap_ldap_result(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ sdap_process_result(ev, pvt);
+}
+
+static void sdap_ldap_next_result(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ sdap_process_result(ev, pvt);
+}
+
+static void sdap_process_result(struct tevent_context *ev, void *pvt)
+{
+ struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle);
+ struct timeval no_timeout = {0, 0};
+ struct tevent_timer *te;
+ LDAPMessage *msg;
+ int ret;
+
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->ldap));
+
+ if (!sh->connected || !sh->ldap) {
+ DEBUG(2, ("ERROR: LDAP connection is not connected!\n"));
+ return;
+ }
+
+ ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg);
+ if (ret == 0) {
+ /* this almost always means we have reached the end of
+ * the list of received messages */
+ DEBUG(8, ("Trace: ldap_result found nothing!\n"));
+ return;
+ }
+
+ if (ret == -1) {
+ DEBUG(4, ("ldap_result gave -1, something bad happend!\n"));
+ sdap_handle_release(sh);
+ return;
+ }
+
+ /* We don't know if this will be the last result.
+ *
+ * important: we must do this before actually processing the message
+ * because the message processing might even free the sdap_handler
+ * so it must be the last operation.
+ * FIXME: use tevent_immediate/tevent_queues, when avilable */
+ memset(&no_timeout, 0, sizeof(struct timeval));
+
+ te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh);
+ if (!te) {
+ DEBUG(1, ("Failed to add critical timer to fetch next result!\n"));
+ }
+
+ /* now process this message */
+ sdap_process_message(ev, sh, msg);
+}
+
+/* process a messgae calling the right operation callback.
+ * msg is completely taken care of (including freeeing it)
+ * NOTE: this function may even end up freeing the sdap_handle
+ * so sdap_hanbdle must not be used after this function is called
+ */
+static void sdap_process_message(struct tevent_context *ev,
+ struct sdap_handle *sh, LDAPMessage *msg)
+{
+ struct sdap_msg *reply;
+ struct sdap_op *op;
+ int msgid;
+ int msgtype;
+ int ret;
+
+ msgid = ldap_msgid(msg);
+ if (msgid == -1) {
+ DEBUG(2, ("can't fire callback, message id invalid!\n"));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ msgtype = ldap_msgtype(msg);
+
+ for (op = sh->ops; op; op = op->next) {
+ if (op->msgid == msgid) break;
+ }
+
+ if (op == NULL) {
+ DEBUG(2, ("Unmatched msgid, discarding message (type: %0x)\n",
+ msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ /* shouldn't happen */
+ if (op->done) {
+ DEBUG(2, ("Operation [%p] already handled (type: %0x)\n", op, msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ switch (msgtype) {
+ case LDAP_RES_SEARCH_ENTRY:
+ /* go and process entry */
+ break;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* more ops to come with this msgid */
+ /* just ignore */
+ ldap_msgfree(msg);
+ return;
+
+ case LDAP_RES_BIND:
+ case LDAP_RES_SEARCH_RESULT:
+ case LDAP_RES_MODIFY:
+ case LDAP_RES_ADD:
+ case LDAP_RES_DELETE:
+ case LDAP_RES_MODDN:
+ case LDAP_RES_COMPARE:
+ case LDAP_RES_EXTENDED:
+ case LDAP_RES_INTERMEDIATE:
+ /* no more results expected with this msgid */
+ op->done = true;
+ break;
+
+ default:
+ /* unkwon msg type ?? */
+ DEBUG(1, ("Couldn't figure out the msg type! [%0x]\n", msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ reply = talloc_zero(op, struct sdap_msg);
+ if (!reply) {
+ ldap_msgfree(msg);
+ ret = ENOMEM;
+ } else {
+ reply->msg = msg;
+ ret = sdap_msg_attach(reply, msg);
+ if (ret != EOK) {
+ ldap_msgfree(msg);
+ talloc_zfree(reply);
+ }
+ }
+
+ if (op->list) {
+ /* list exist, queue it */
+
+ op->last->next = reply;
+ op->last = reply;
+
+ } else {
+ /* create list, then call callback */
+ op->list = op->last = reply;
+
+ /* must be the last operation as it may end up freeing all memory
+ * including all ops handlers */
+ op->callback(op, reply, ret, op->data);
+ }
+}
+
+static void sdap_unlock_next_reply(struct sdap_op *op)
+{
+ struct timeval tv;
+ struct tevent_timer *te;
+ struct sdap_msg *next_reply;
+
+ if (op->list) {
+ next_reply = op->list->next;
+ /* get rid of the previous reply, it has been processed already */
+ talloc_zfree(op->list);
+ op->list = next_reply;
+ }
+
+ /* if there are still replies to parse, queue a new operation */
+ if (op->list) {
+ /* use a very small timeout, so that fd operations have a chance to be
+ * served while processing a long reply */
+ tv = tevent_timeval_current();
+
+ /* wait 5 microsecond */
+ tv.tv_usec += 5;
+ tv.tv_sec += tv.tv_usec / 1000000;
+ tv.tv_usec = tv.tv_usec % 1000000;
+
+ te = tevent_add_timer(op->ev, op, tv,
+ sdap_process_next_reply, op);
+ if (!te) {
+ DEBUG(1, ("Failed to add critical timer for next reply!\n"));
+ op->callback(op, NULL, EFAULT, op->data);
+ }
+ }
+}
+
+static void sdap_process_next_reply(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_op *op = talloc_get_type(pvt, struct sdap_op);
+
+ op->callback(op, op->list, EOK, op->data);
+}
+
+#ifdef HAVE_LDAP_CONNCB
+int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv,
+ struct sockaddr *addr, struct ldap_conncb *ctx)
+{
+ int ret;
+ ber_socket_t ber_fd;
+ struct fd_event_item *fd_event_item;
+ struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg,
+ struct ldap_cb_data);
+
+ ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd);
+ if (ret == -1) {
+ DEBUG(1, ("ber_sockbuf_ctrl failed.\n"));
+ return EINVAL;
+ }
+ DEBUG(9, ("New LDAP connection to [%s] with fd [%d].\n",
+ ldap_url_desc2str(srv), ber_fd));
+
+ fd_event_item = talloc_zero(cb_data, struct fd_event_item);
+ if (fd_event_item == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd,
+ TEVENT_FD_READ, sdap_ldap_result,
+ cb_data->sh);
+ if (fd_event_item->fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ talloc_free(fd_event_item);
+ return ENOMEM;
+ }
+ fd_event_item->fd = ber_fd;
+
+ DLIST_ADD(cb_data->fd_list, fd_event_item);
+
+ return LDAP_SUCCESS;
+}
+
+void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb,
+ struct ldap_conncb *ctx)
+{
+ int ret;
+ ber_socket_t ber_fd;
+ struct fd_event_item *fd_event_item;
+ struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg,
+ struct ldap_cb_data);
+
+ if (sb == NULL || cb_data == NULL) {
+ return;
+ }
+
+ ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd);
+ if (ret == -1) {
+ DEBUG(1, ("ber_sockbuf_ctrl failed.\n"));
+ return;
+ }
+ DEBUG(9, ("Closing LDAP connection with fd [%d].\n", ber_fd));
+
+ DLIST_FOR_EACH(fd_event_item, cb_data->fd_list) {
+ if (fd_event_item->fd == ber_fd) {
+ break;
+ }
+ }
+ if (fd_event_item == NULL) {
+ DEBUG(1, ("No event for fd [%d] found.\n", ber_fd));
+ return;
+ }
+
+ DLIST_REMOVE(cb_data->fd_list, fd_event_item);
+ talloc_zfree(fd_event_item);
+
+ return;
+}
+
+#else
+
+static int get_fd_from_ldap(LDAP *ldap, int *fd)
+{
+ int ret;
+
+ ret = ldap_get_option(ldap, LDAP_OPT_DESC, fd);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to get fd from ldap!!\n"));
+ *fd = -1;
+ return EIO;
+ }
+
+ return EOK;
+}
+
+int sdap_install_ldap_callbacks(struct sdap_handle *sh,
+ struct tevent_context *ev)
+{
+ int fd;
+ int ret;
+
+ ret = get_fd_from_ldap(sh->ldap, &fd);
+ if (ret) return ret;
+
+ sh->fde = tevent_add_fd(ev, sh, fd, TEVENT_FD_READ, sdap_ldap_result, sh);
+ if (!sh->fde) return ENOMEM;
+
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], fde[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->fde, sh->ldap));
+
+ return EOK;
+}
+
+#endif
+
+
+/* ==LDAP-Operations-Helpers============================================== */
+
+static int sdap_op_destructor(void *mem)
+{
+ struct sdap_op *op = (struct sdap_op *)mem;
+
+ DLIST_REMOVE(op->sh->ops, op);
+
+ if (op->done) return 0;
+
+ /* we don't check the result here, if a message was really abandoned,
+ * hopefully the server will get an abandon.
+ * If the operation was already fully completed, this is going to be
+ * just a noop */
+ ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL);
+
+ return 0;
+}
+
+static void sdap_op_timeout(struct tevent_req *req)
+{
+ struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op);
+
+ /* should never happen, but just in case */
+ if (op->done) {
+ DEBUG(2, ("Timeout happened after op was finished !?\n"));
+ return;
+ }
+
+ /* signal the caller that we have a timeout */
+ op->callback(op, NULL, ETIMEDOUT, op->data);
+}
+
+int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev,
+ struct sdap_handle *sh, int msgid,
+ sdap_op_callback_t *callback, void *data,
+ int timeout, struct sdap_op **_op)
+{
+ struct sdap_op *op;
+
+ op = talloc_zero(memctx, struct sdap_op);
+ if (!op) return ENOMEM;
+
+ op->sh = sh;
+ op->msgid = msgid;
+ op->callback = callback;
+ op->data = data;
+ op->ev = ev;
+
+ /* check if we need to set a timeout */
+ if (timeout) {
+ struct tevent_req *req;
+ struct timeval tv;
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv, timeout, 0);
+
+ /* allocate on op, so when it get freed the timeout is removed */
+ req = tevent_wakeup_send(op, ev, tv);
+ if (!req) {
+ talloc_zfree(op);
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, sdap_op_timeout, op);
+ }
+
+ DLIST_ADD(sh->ops, op);
+
+ talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor);
+
+ *_op = op;
+ return EOK;
+}
+
+/* ==Modify-Password====================================================== */
+
+struct sdap_exop_modify_passwd_state {
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+
+ int result;
+ char *user_error_message;
+};
+
+static void sdap_exop_modify_passwd_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ char *user_dn,
+ char *password,
+ char *new_password)
+{
+ struct tevent_req *req = NULL;
+ struct sdap_exop_modify_passwd_state *state;
+ int ret;
+ BerElement *ber = NULL;
+ struct berval *bv = NULL;
+ int msgid;
+ LDAPControl *request_controls[2];
+
+ req = tevent_req_create(memctx, &state,
+ struct sdap_exop_modify_passwd_state);
+ if (!req) return NULL;
+
+ state->sh = sh;
+ state->user_error_message = NULL;
+
+ ber = ber_alloc_t( LBER_USE_DER );
+ if (ber == NULL) {
+ DEBUG(7, ("ber_alloc_t failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+ user_dn,
+ LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password,
+ LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password);
+ if (ret == -1) {
+ DEBUG(1, ("ber_printf failed.\n"));
+ ber_free(ber, 1);
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = ber_flatten(ber, &bv);
+ ber_free(ber, 1);
+ if (ret == -1) {
+ DEBUG(1, ("ber_flatten failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ 0, NULL, 0, &request_controls[0]);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("sss_ldap_control_create failed.\n"));
+ goto fail;
+ }
+ request_controls[1] = NULL;
+
+ DEBUG(4, ("Executing extended operation\n"));
+
+ ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD,
+ bv, request_controls, NULL, &msgid);
+ ber_bvfree(bv);
+ ldap_control_free(request_controls[0]);
+ if (ret == -1 || msgid == -1) {
+ DEBUG(1, ("ldap_extended_operation failed\n"));
+ goto fail;
+ }
+ DEBUG(8, ("ldap_extended_operation sent, msgid = %d\n", msgid));
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, state->sh, msgid,
+ sdap_exop_modify_passwd_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, EIO);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_exop_modify_passwd_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
+ struct sdap_exop_modify_passwd_state);
+ char *errmsg = NULL;
+ int ret;
+ LDAPControl **response_controls = NULL;
+ int c;
+ ber_int_t pp_grace;
+ ber_int_t pp_expire;
+ LDAPPasswordPolicyError pp_error;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ ret = ldap_parse_result(state->sh->ldap, reply->msg,
+ &state->result, NULL, &errmsg, NULL,
+ &response_controls, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ ret = EIO;
+ goto done;
+ }
+
+ if (response_controls == NULL) {
+ DEBUG(5, ("Server returned no controls.\n"));
+ } else {
+ for (c = 0; response_controls[c] != NULL; c++) {
+ DEBUG(9, ("Server returned control [%s].\n",
+ response_controls[c]->ldctl_oid));
+ if (strcmp(response_controls[c]->ldctl_oid,
+ LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
+ ret = ldap_parse_passwordpolicy_control(state->sh->ldap,
+ response_controls[c],
+ &pp_expire, &pp_grace,
+ &pp_error);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_parse_passwordpolicy_control failed.\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] "
+ "error [%s].\n", pp_expire, pp_grace,
+ ldap_passwordpolicy_err2txt(pp_error)));
+ }
+ }
+ }
+
+ if (state->result != LDAP_SUCCESS) {
+ state->user_error_message = talloc_strdup(state, errmsg);
+ if (state->user_error_message == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ }
+ }
+
+ DEBUG(3, ("ldap_extended_operation result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ ret = LDAP_SUCCESS;
+done:
+ ldap_controls_free(response_controls);
+ ldap_memfree(errmsg);
+
+ if (ret == LDAP_SUCCESS) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+}
+
+int sdap_exop_modify_passwd_recv(struct tevent_req *req,
+ TALLOC_CTX * mem_ctx,
+ enum sdap_result *result,
+ char **user_error_message)
+{
+ struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
+ struct sdap_exop_modify_passwd_state);
+
+ *result = SDAP_ERROR;
+ *user_error_message = talloc_steal(mem_ctx, state->user_error_message);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (state->result == LDAP_SUCCESS) {
+ *result = SDAP_SUCCESS;
+ }
+
+ return EOK;
+}
+
+/* ==Fetch-RootDSE============================================= */
+
+struct sdap_get_rootdse_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+
+ struct sysdb_attrs *rootdse;
+};
+
+static void sdap_get_rootdse_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_rootdse_state *state;
+
+ DEBUG(9, ("Getting rootdse\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sh;
+ state->rootdse = NULL;
+
+ subreq = sdap_get_generic_send(state, ev, opts, sh,
+ "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", NULL, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_rootdse_done, req);
+
+ return req;
+}
+
+static void sdap_get_rootdse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_rootdse_state *state = tevent_req_data(req,
+ struct sdap_get_rootdse_state);
+ struct sysdb_attrs **results;
+ size_t num_results;
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (num_results == 0 || !results) {
+ DEBUG(2, ("No RootDSE for server ?!\n"));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ if (num_results > 1) {
+ DEBUG(2, ("Multiple replies when searching for RootDSE ??\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ state->rootdse = talloc_steal(state, results[0]);
+ talloc_zfree(results);
+
+ DEBUG(9, ("Got rootdse\n"));
+
+ tevent_req_done(req);
+}
+
+int sdap_get_rootdse_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sysdb_attrs **rootdse)
+{
+ struct sdap_get_rootdse_state *state = tevent_req_data(req,
+ struct sdap_get_rootdse_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *rootdse = talloc_steal(memctx, state->rootdse);
+
+ return EOK;
+}
+
+/* ==Generic Search============================================ */
+
+struct sdap_get_generic_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ const char *search_base;
+ int scope;
+ const char *filter;
+ const char **attrs;
+ struct sdap_attr_map *map;
+ int map_num_attrs;
+
+ struct sdap_op *op;
+
+ size_t reply_max;
+ size_t reply_count;
+ struct sysdb_attrs **reply;
+};
+
+static errno_t add_to_reply(struct sdap_get_generic_state *state,
+ struct sysdb_attrs *msg);
+
+static void sdap_get_generic_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *search_base,
+ int scope,
+ const char *filter,
+ const char **attrs,
+ struct sdap_attr_map *map,
+ int map_num_attrs)
+{
+ struct tevent_req *req = NULL;
+ struct sdap_get_generic_state *state = NULL;
+ int lret;
+ int ret;
+ int msgid;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_generic_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sh;
+ state->search_base = search_base;
+ state->scope = scope;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->map = map;
+ state->map_num_attrs = map_num_attrs;
+ state->op = NULL;
+ state->reply_max = 0;
+ state->reply_count = 0;
+ state->reply = NULL;
+
+ DEBUG(6, ("calling ldap_search_ext with [%s][%s].\n", state->filter,
+ state->search_base));
+ if (debug_level >= 7) {
+ int i;
+
+ if (state->attrs) {
+ for (i = 0; state->attrs[i]; i++) {
+ DEBUG(7, ("Requesting attrs: [%s]\n", state->attrs[i]));
+ }
+ }
+ }
+
+ lret = ldap_search_ext(state->sh->ldap, state->search_base,
+ state->scope, state->filter,
+ discard_const(state->attrs),
+ false, NULL, NULL, NULL, 0, &msgid);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(3, ("ldap_search_ext failed: %s\n", ldap_err2string(lret)));
+ ret = EIO;
+ goto fail;
+ }
+ DEBUG(8, ("ldap_search_ext called, msgid = %d\n", msgid));
+
+ ret = sdap_op_add(state, state->ev, state->sh, msgid,
+ sdap_get_generic_done, req,
+ dp_opt_get_int(state->opts->basic,
+ SDAP_SEARCH_TIMEOUT),
+ &state->op);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+
+static void sdap_get_generic_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_get_generic_state *state = tevent_req_data(req,
+ struct sdap_get_generic_state);
+ struct sysdb_attrs *attrs;
+ char *errmsg;
+ int result;
+ int ret;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ switch (ldap_msgtype(reply->msg)) {
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* ignore references for now */
+ talloc_free(reply);
+
+ /* unlock the operation so that we can proceed with the next result */
+ sdap_unlock_next_reply(state->op);
+ break;
+
+ case LDAP_RES_SEARCH_ENTRY:
+ ret = sdap_parse_entry(state, state->sh, reply,
+ state->map, state->map_num_attrs,
+ &attrs, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("sdap_parse_generic_entry failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ret = add_to_reply(state, attrs);
+ if (ret != EOK) {
+ DEBUG(1, ("add_to_reply failed.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_unlock_next_reply(state->op);
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ ret = ldap_parse_result(state->sh->ldap, reply->msg,
+ &result, NULL, &errmsg, NULL, NULL, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ DEBUG(6, ("Search result: %s(%d), %s\n",
+ ldap_err2string(result), result, errmsg));
+
+ tevent_req_done(req);
+ return;
+
+ default:
+ /* what is going on here !? */
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+static errno_t add_to_reply(struct sdap_get_generic_state *state,
+ struct sysdb_attrs *msg)
+{
+ if (state->reply == NULL || state->reply_max == state->reply_count) {
+ state->reply_max += REPLY_REALLOC_INCREMENT;
+ state->reply = talloc_realloc(state, state->reply,
+ struct sysdb_attrs *,
+ state->reply_max);
+ if (state->reply == NULL) {
+ DEBUG(1, ("talloc_realloc failed.\n"));
+ return ENOMEM;
+ }
+ }
+
+ state->reply[state->reply_count++] = talloc_steal(state->reply, msg);
+
+ return EOK;
+}
+
+int sdap_get_generic_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *reply_count,
+ struct sysdb_attrs ***reply)
+{
+ struct sdap_get_generic_state *state = tevent_req_data(req,
+ struct sdap_get_generic_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *reply_count = state->reply_count;
+ *reply = talloc_steal(mem_ctx, state->reply);
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
new file mode 100644
index 00000000..3c52d236
--- /dev/null
+++ b/src/providers/ldap/sdap_async.h
@@ -0,0 +1,126 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@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/>.
+*/
+
+#ifndef _SDAP_ASYNC_H_
+#define _SDAP_ASYNC_H_
+
+#include <talloc.h>
+#include <tevent.h>
+#include "providers/dp_backend.h"
+#include "providers/ldap/sdap.h"
+#include "providers/fail_over.h"
+
+struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ const char *uri,
+ bool use_start_tls);
+int sdap_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh);
+
+struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *wildcard);
+int sdap_get_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp);
+
+struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *wildcard);
+int sdap_get_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp);
+
+struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ int timeout,
+ const char *keytab,
+ const char *principal,
+ const char *realm);
+int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result);
+
+struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ const char *user_dn,
+ const char *authtok_type,
+ struct dp_opt_blob authtok);
+int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result);
+
+struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *name,
+ const char **grp_attrs);
+int sdap_get_initgr_recv(struct tevent_req *req);
+
+struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ char *user_dn,
+ char *password,
+ char *new_password);
+int sdap_exop_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ enum sdap_result *result,
+ char **user_error_msg);
+
+struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct be_ctx *be,
+ struct sdap_service *service,
+ struct sysdb_attrs **rootdse);
+int sdap_cli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **gsh,
+ struct sysdb_attrs **rootdse);
+
+struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *search_base,
+ int scope,
+ const char *filter,
+ const char **attrs,
+ struct sdap_attr_map *map,
+ int map_num_attrs);
+int sdap_get_generic_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, size_t *reply_count,
+ struct sysdb_attrs ***reply_list);
+
+#endif /* _SDAP_ASYNC_H_ */
diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c
new file mode 100644
index 00000000..abebe288
--- /dev/null
+++ b/src/providers/ldap/sdap_async_accounts.c
@@ -0,0 +1,2065 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/sdap_async_private.h"
+
+/* ==Save-User-Entry====================================================== */
+
+struct sdap_save_user_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+ struct sysdb_attrs *attrs;
+ char *timestamp;
+};
+
+static void sdap_save_user_done(struct tevent_req *subreq);
+
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs,
+ bool is_initgr)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_user_state *state;
+ struct ldb_message_element *el;
+ int ret;
+ const char *pwd;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ long int l;
+ uid_t uid;
+ gid_t gid;
+ struct sysdb_attrs *user_attrs;
+ char *upn = NULL;
+ int i;
+ char *val = NULL;
+ int cache_timeout;
+
+ DEBUG(9, ("Save user\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+ state->attrs = attrs;
+ state->timestamp = NULL;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_NAME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ state->name = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_PWD].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) pwd = NULL;
+ else pwd = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_GECOS].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) gecos = NULL;
+ else gecos = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_HOME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) homedir = NULL;
+ else homedir = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_SHELL].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) shell = NULL;
+ else shell = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_UID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no uid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ uid = l;
+
+ /* check that the uid is valid for this domain */
+ if (OUT_OF_ID_RANGE(uid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_GID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no gid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ gid = l;
+
+ /* check that the gid is valid for this domain */
+ if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ user_attrs = sysdb_new_attrs(state);
+ if (user_attrs == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs, SYSDB_ORIG_DN, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original DN is not available for [%s].\n", state->name));
+ } else {
+ DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n",
+ el->values[0].data, state->name));
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_DN,
+ (const char *) el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs, SYSDB_MEMBEROF, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original memberOf is not available for [%s].\n",
+ state->name));
+ } else {
+ DEBUG(7, ("Adding original memberOf attributes to [%s].\n",
+ state->name));
+ for (i = 0; i < el->num_values; i++) {
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF,
+ (const char *) el->values[i].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+ state->name));
+ } else {
+ ret = sysdb_attrs_add_string(user_attrs,
+ opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name,
+ (const char*)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ state->timestamp = talloc_strdup(state,
+ (const char*)el->values[0].data);
+ if (!state->timestamp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("User principle is not available for [%s].\n", state->name));
+ } else {
+ upn = talloc_strdup(user_attrs, (const char*) el->values[0].data);
+ if (!upn) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (dp_opt_get_bool(opts->basic, SDAP_FORCE_UPPER_CASE_REALM)) {
+ make_realm_upper_case(upn);
+ }
+ DEBUG(7, ("Adding user principle [%s] to attributes of [%s].\n",
+ upn, state->name));
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_UPN, upn);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ for (i = SDAP_FIRST_EXTRA_USER_AT; i < SDAP_OPTS_USER; i++) {
+ ret = sysdb_attrs_get_el(state->attrs, opts->user_map[i].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values > 0) {
+ DEBUG(9, ("Adding [%s]=[%s] to user attributes.\n",
+ opts->user_map[i].sys_name,
+ (const char*) el->values[0].data));
+ val = talloc_strdup(user_attrs, (const char*) el->values[0].data);
+ if (val == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ret = sysdb_attrs_add_string(user_attrs,
+ opts->user_map[i].sys_name, val);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ cache_timeout = dp_opt_get_int(opts->basic, SDAP_ENTRY_CACHE_TIMEOUT);
+
+ if (is_initgr) {
+ ret = sysdb_attrs_add_time_t(user_attrs, SYSDB_INITGR_EXPIRE,
+ (cache_timeout ?
+ (time(NULL) + cache_timeout) : 0));
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ DEBUG(6, ("Storing info for user %s\n", state->name));
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->dom, state->name, pwd,
+ uid, gid, gecos, homedir, shell,
+ user_attrs, cache_timeout);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_user_state *state = tevent_req_data(req,
+ struct sdap_save_user_state);
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save user %s\n", state->name));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_user_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_user_state *state = tevent_req_data(req,
+ struct sdap_save_user_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Generic-Function-to-save-multiple-users============================= */
+
+struct sdap_save_users_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+
+ struct sysdb_attrs **users;
+ int count;
+ int cur;
+
+ struct sysdb_handle *handle;
+
+ char *higher_timestamp;
+};
+
+static void sdap_save_users_trans(struct tevent_req *subreq);
+static void sdap_save_users_store(struct tevent_req *req);
+static void sdap_save_users_process(struct tevent_req *subreq);
+struct tevent_req *sdap_save_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sysdb_attrs **users,
+ int num_users)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_users_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->users = users;
+ state->count = num_users;
+ state->cur = 0;
+ state->handle = NULL;
+ state->higher_timestamp = NULL;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sdap_save_users_trans, req);
+
+ return req;
+}
+
+static void sdap_save_users_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_users_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_save_users_store(req);
+}
+
+static void sdap_save_users_store(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_users_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ subreq = sdap_save_user_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->users[state->cur], false);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_users_process, req);
+}
+
+static void sdap_save_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_users_state *state;
+ char *timestamp = NULL;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ ret = sdap_save_user_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store user %d. Ignoring.\n", state->cur));
+ } else {
+ DEBUG(9, ("User %d processed!\n", state->cur));
+ }
+
+ if (timestamp) {
+ if (state->higher_timestamp) {
+ if (strcmp(timestamp, state->higher_timestamp) > 0) {
+ talloc_zfree(state->higher_timestamp);
+ state->higher_timestamp = timestamp;
+ } else {
+ talloc_zfree(timestamp);
+ }
+ } else {
+ state->higher_timestamp = timestamp;
+ }
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ sdap_save_users_store(req);
+ } else {
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static int sdap_save_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_users_state *state = tevent_req_data(req,
+ struct sdap_save_users_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Search-Users-with-filter============================================= */
+
+struct sdap_get_users_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ const char **attrs;
+ const char *filter;
+
+ char *higher_timestamp;
+ struct sysdb_attrs **users;
+ size_t count;
+};
+
+static void sdap_get_users_process(struct tevent_req *subreq);
+static void sdap_get_users_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *filter)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_users_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->dom = dom;
+ state->sh = sh;
+ state->sysdb = sysdb;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->higher_timestamp = NULL;
+ state->users = NULL;
+ state->count = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_USER_SEARCH_BASE),
+ LDAP_SCOPE_SUBTREE,
+ state->filter, state->attrs,
+ state->opts->user_map, SDAP_OPTS_USER);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_users_process, req);
+
+ return req;
+}
+
+static void sdap_get_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state,
+ &state->count, &state->users);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(6, ("Search for users, returned %d results.\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ subreq = sdap_save_users_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->users, state->count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_users_done, req);
+}
+
+static void sdap_get_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+ int ret;
+
+ DEBUG(9, ("Saving %d Users - Done\n", state->count));
+
+ ret = sdap_save_users_recv(subreq, state, &state->higher_timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to store users.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+/* ==Group-Parsing Routines=============================================== */
+
+struct sdap_orig_entry_state {
+ int done;
+};
+
+static void sdap_find_entry_by_origDN_done(struct tevent_req *req)
+{
+ struct sdap_orig_entry_state *state = tevent_req_callback_data(req,
+ struct sdap_orig_entry_state);
+ state->done = 1;
+}
+
+/* WARNING: this is a sync routine for now */
+static int sdap_find_entry_by_origDN(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *orig_dn,
+ char **localdn)
+{
+ struct tevent_req *req;
+ struct sdap_orig_entry_state *state;
+ static const char *attrs[] = { NULL };
+ struct ldb_dn *base_dn;
+ char *filter;
+ struct ldb_message **msgs;
+ size_t num_msgs;
+ int ret;
+
+ state = talloc_zero(memctx, struct sdap_orig_entry_state);
+ if (!state) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ filter = talloc_asprintf(state, "%s=%s", SYSDB_ORIG_DN, orig_dn);
+ if (!filter) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ base_dn = sysdb_domain_dn(sysdb_handle_get_ctx(handle),
+ state, domain->name);
+ if (!base_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ req = sysdb_search_entry_send(state, ev, handle, base_dn,
+ LDB_SCOPE_SUBTREE, filter, attrs);
+ if (!req) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, sdap_find_entry_by_origDN_done, state);
+
+ /* WARNING: SYNC LOOP HERE */
+ tevent_loop_allow_nesting(ev);
+ while (state->done == 0) {
+ tevent_loop_once(ev);
+ }
+
+ ret = sysdb_search_entry_recv(req, state, &num_msgs, &msgs);
+ if (ret) {
+ goto done;
+ }
+ if (num_msgs != 1) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ *localdn = talloc_strdup(memctx, ldb_dn_get_linearized(msgs[0]->dn));
+ if (!*localdn) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_zfree(state);
+ return ret;
+}
+
+static int sdap_fill_memberships(struct sysdb_attrs *group_attrs,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *domain,
+ struct ldb_val *values,
+ int num_values)
+{
+ struct ldb_message_element *el;
+ int i, j;
+ int ret;
+
+ switch (opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ DEBUG(9, ("[RFC2307 Schema]\n"));
+
+ ret = sysdb_attrs_users_from_ldb_vals(group_attrs, SYSDB_MEMBER,
+ domain->name,
+ values, num_values);
+ if (ret) {
+ goto done;
+ }
+
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ DEBUG(9, ("[IPA or AD Schema]\n"));
+
+ ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
+ if (ret) {
+ goto done;
+ }
+
+ /* Just allocate both big enough to contain all members for now */
+ el->values = talloc_realloc(el, el->values, struct ldb_val,
+ el->num_values + num_values);
+ if (!el->values) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0, j = el->num_values; i < num_values; i++) {
+
+ /* sync search entry with this as origDN */
+ ret = sdap_find_entry_by_origDN(el->values, ev,
+ handle, domain,
+ (char *)values[i].data,
+ (char **)&el->values[j].data);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ goto done;
+ }
+
+ DEBUG(7, (" member #%d (%s): not found!\n",
+ i, (char *)values[i].data));
+ } else {
+ DEBUG(7, (" member #%d (%s): [%s]\n",
+ i, (char *)values[i].data,
+ (char *)el->values[j].data));
+
+ el->values[j].length = strlen((char *)el->values[j].data);
+ j++;
+ }
+ }
+ el->num_values = j;
+
+ break;
+
+ default:
+ DEBUG(0, ("FATAL ERROR: Unhandled schema type! (%d)\n",
+ opts->schema_type));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+/* ==Save-Group-Entry===================================================== */
+
+struct sdap_save_group_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+ char *timestamp;
+};
+
+static void sdap_save_group_done(struct tevent_req *subreq);
+
+ /* FIXME: support non legacy */
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs,
+ bool store_members)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_group_state *state;
+ struct ldb_message_element *el;
+ struct sysdb_attrs *group_attrs;
+ long int l;
+ gid_t gid;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+ state->timestamp = NULL;
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_NAME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ state->name = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_GID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no gid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ gid = l;
+
+ /* check that the gid is valid for this domain */
+ if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ group_attrs = sysdb_new_attrs(state);
+ if (!group_attrs) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original DN is not available for [%s].\n", state->name));
+ } else {
+ DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n",
+ el->values[0].data, state->name));
+ ret = sysdb_attrs_add_string(group_attrs, SYSDB_ORIG_DN,
+ (const char *)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+ state->name));
+ } else {
+ ret = sysdb_attrs_add_string(group_attrs,
+ opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name,
+ (const char*)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ state->timestamp = talloc_strdup(state,
+ (const char*)el->values[0].data);
+ if (!state->timestamp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (store_members) {
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
+ if (ret != EOK) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("No members for group [%s]\n", state->name));
+
+ } else {
+ DEBUG(7, ("Adding member users to group [%s]\n", state->name));
+
+ ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom,
+ el->values, el->num_values);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ DEBUG(6, ("Storing info for group %s\n", state->name));
+
+ subreq = sysdb_store_group_send(state, state->ev,
+ state->handle, state->dom,
+ state->name, gid,
+ group_attrs,
+ dp_opt_get_int(opts->basic,
+ SDAP_ENTRY_CACHE_TIMEOUT));
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_group_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_group_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_group_state *state = tevent_req_data(req,
+ struct sdap_save_group_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save group %s [%d]\n", state->name, ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_group_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_group_state *state = tevent_req_data(req,
+ struct sdap_save_group_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if ( timestamp ) {
+ *timestamp = talloc_steal(mem_ctx, state->timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Save-Group-Memebrs=================================================== */
+
+struct sdap_save_grpmem_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+};
+
+static void sdap_save_grpmem_done(struct tevent_req *subreq);
+
+ /* FIXME: support non legacy */
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_grpmem_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_grpmem_state *state;
+ struct ldb_message_element *el;
+ struct sysdb_attrs *group_attrs = NULL;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_grpmem_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+
+ ret = sysdb_attrs_get_string(attrs,
+ opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
+ &state->name);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
+ if (ret != EOK) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("No members for group [%s]\n", state->name));
+
+ } else {
+ DEBUG(7, ("Adding member users to group [%s]\n", state->name));
+
+ group_attrs = sysdb_new_attrs(state);
+ if (!group_attrs) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom,
+ el->values, el->num_values);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ DEBUG(6, ("Storing members for group %s\n", state->name));
+
+ subreq = sysdb_store_group_send(state, state->ev,
+ state->handle, state->dom,
+ state->name, 0,
+ group_attrs,
+ dp_opt_get_int(opts->basic,
+ SDAP_ENTRY_CACHE_TIMEOUT));
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_grpmem_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_grpmem_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_grpmem_state *state = tevent_req_data(req,
+ struct sdap_save_grpmem_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save group members for %s [%d]\n",
+ state->name, ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_grpmem_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Generic-Function-to-save-multiple-groups============================= */
+
+struct sdap_save_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+
+ struct sysdb_attrs **groups;
+ int count;
+ int cur;
+ bool twopass;
+
+ struct sysdb_handle *handle;
+
+ char *higher_timestamp;
+};
+
+static void sdap_save_groups_trans(struct tevent_req *subreq);
+static void sdap_save_groups_save(struct tevent_req *req);
+static void sdap_save_groups_loop(struct tevent_req *subreq);
+static void sdap_save_groups_mem_save(struct tevent_req *req);
+static void sdap_save_groups_mem_loop(struct tevent_req *subreq);
+struct tevent_req *sdap_save_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sysdb_attrs **groups,
+ int num_groups)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_groups_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->groups = groups;
+ state->count = num_groups;
+ state->cur = 0;
+ state->handle = NULL;
+ state->higher_timestamp = NULL;
+
+ switch (opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ state->twopass = false;
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ state->twopass = true;
+ break;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_trans, req);
+
+ return req;
+}
+
+static void sdap_save_groups_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_save_groups_save(req);
+}
+
+static void sdap_save_groups_save(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_groups_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ /* if 2 pass savemembers = false */
+ subreq = sdap_save_group_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->groups[state->cur],
+ (!state->twopass));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_loop, req);
+}
+
+static void sdap_save_groups_loop(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ char *timestamp = NULL;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sdap_save_group_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur));
+ } else {
+ DEBUG(9, ("Group %d processed!\n", state->cur));
+ }
+
+ if (timestamp) {
+ if (state->higher_timestamp) {
+ if (strcmp(timestamp, state->higher_timestamp) > 0) {
+ talloc_zfree(state->higher_timestamp);
+ state->higher_timestamp = timestamp;
+ } else {
+ talloc_zfree(timestamp);
+ }
+ } else {
+ state->higher_timestamp = timestamp;
+ }
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+
+ sdap_save_groups_save(req);
+
+ } else if (state->twopass) {
+
+ state->cur = 0;
+ sdap_save_groups_mem_save(req);
+
+ } else {
+
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static void sdap_save_groups_mem_save(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_groups_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ subreq = sdap_save_grpmem_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->groups[state->cur]);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_mem_loop, req);
+}
+
+static void sdap_save_groups_mem_loop(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sdap_save_grpmem_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+
+ sdap_save_groups_mem_save(req);
+
+ } else {
+
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static int sdap_save_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_groups_state *state = tevent_req_data(req,
+ struct sdap_save_groups_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Search-Groups-with-filter============================================ */
+
+struct sdap_get_groups_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ const char **attrs;
+ const char *filter;
+
+ char *higher_timestamp;
+ struct sysdb_attrs **groups;
+ size_t count;
+};
+
+static void sdap_get_groups_process(struct tevent_req *subreq);
+static void sdap_get_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *filter)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_groups_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->dom = dom;
+ state->sh = sh;
+ state->sysdb = sysdb;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->higher_timestamp = NULL;
+ state->groups = NULL;
+ state->count = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_GROUP_SEARCH_BASE),
+ LDAP_SCOPE_SUBTREE,
+ state->filter, state->attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_groups_process, req);
+
+ return req;
+}
+
+static void sdap_get_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state,
+ &state->count, &state->groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(6, ("Search for groups, returned %d results.\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->groups, state->count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_groups_done, req);
+}
+
+static void sdap_get_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+ int ret;
+
+ DEBUG(9, ("Saving %d Groups - Done\n", state->count));
+
+ ret = sdap_save_groups_recv(subreq, state, &state->higher_timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to store groups.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307-Classic/BIS========= */
+
+struct sdap_initgr_rfc2307_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+};
+
+static void sdap_initgr_rfc2307_process(struct tevent_req *subreq);
+static void sdap_initgr_rfc2307_done(struct tevent_req *subreq);
+struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *dom,
+ struct sdap_handle *sh,
+ const char *base_dn,
+ const char *name,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_initgr_rfc2307_state *state;
+ const char *filter;
+
+ req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->op = NULL;
+
+ filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ opts->group_map[SDAP_AT_GROUP_MEMBER].name,
+ name, opts->group_map[SDAP_OC_GROUP].name);
+ if (!filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts,
+ state->sh, base_dn, LDAP_SCOPE_SUBTREE,
+ filter, grp_attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_rfc2307_process, req);
+
+ return req;
+}
+
+static void sdap_initgr_rfc2307_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_initgr_rfc2307_state *state;
+ struct sysdb_attrs **groups;
+ size_t count;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_initgr_rfc2307_state);
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ groups, count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_rfc2307_done, req);
+}
+
+static void sdap_initgr_rfc2307_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = sdap_save_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_initgr_rfc2307_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)-nested-groups=============== */
+
+struct sdap_initgr_nested_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+
+ const char **grp_attrs;
+
+ char *filter;
+ char **group_dns;
+ int count;
+ int cur;
+
+ struct sdap_op *op;
+
+ struct sysdb_attrs **groups;
+ int groups_cur;
+};
+
+static void sdap_initgr_nested_search(struct tevent_req *subreq);
+static void sdap_initgr_nested_store(struct tevent_req *req);
+static void sdap_initgr_nested_done(struct tevent_req *subreq);
+static struct tevent_req *sdap_initgr_nested_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *dom,
+ struct sdap_handle *sh,
+ struct sysdb_attrs *user,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_initgr_nested_state *state;
+ struct ldb_message_element *el;
+ int i, ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_initgr_nested_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->grp_attrs = grp_attrs;
+ state->op = NULL;
+
+ state->filter = talloc_asprintf(state, "(objectclass=%s)",
+ opts->group_map[SDAP_OC_GROUP].name);
+ if (!state->filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ /* TODO: test rootDSE for deref support and use it if available */
+ /* TODO: or test rootDSE for ASQ support and use it if available */
+
+ ret = sysdb_attrs_get_el(user, SYSDB_MEMBEROF, &el);
+ if (ret || !el || el->num_values == 0) {
+ DEBUG(4, ("User entry lacks original memberof ?\n"));
+ /* user with no groups ? */
+ tevent_req_error(req, ENOENT);
+ tevent_req_post(req, ev);
+ }
+ state->count = el->num_values;
+
+ state->groups = talloc_zero_array(state, struct sysdb_attrs *,
+ state->count + 1);;
+ if (!state->groups) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->groups_cur = 0;
+
+ state->group_dns = talloc_array(state, char *, state->count + 1);
+ if (!state->group_dns) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ for (i = 0; i < state->count; i++) {
+ state->group_dns[i] = talloc_strdup(state->group_dns,
+ (char *)el->values[i].data);
+ if (!state->group_dns[i]) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ }
+ state->group_dns[i] = NULL; /* terminate */
+ state->cur = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ state->group_dns[state->cur],
+ LDAP_SCOPE_BASE,
+ state->filter, state->grp_attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_search, req);
+
+ return req;
+}
+
+static void sdap_initgr_nested_search(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_initgr_nested_state *state;
+ struct sysdb_attrs **groups;
+ size_t count;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_initgr_nested_state);
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count == 1) {
+ state->groups[state->groups_cur] = groups[0];
+ state->groups_cur++;
+ } else {
+ DEBUG(2, ("Search for group %s, returned %d results. Skipping\n",
+ state->group_dns[state->cur], count));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->opts, state->sh,
+ state->group_dns[state->cur],
+ LDAP_SCOPE_BASE,
+ state->filter, state->grp_attrs,
+ state->opts->group_map,
+ SDAP_OPTS_GROUP);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_search, req);
+ } else {
+ sdap_initgr_nested_store(req);
+ }
+}
+
+static void sdap_initgr_nested_store(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_initgr_nested_state *state;
+
+ state = tevent_req_data(req, struct sdap_initgr_nested_state);
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->groups, state->groups_cur);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_done, req);
+}
+
+static void sdap_initgr_nested_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = sdap_save_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_initgr_nested_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)============================= */
+
+struct sdap_get_initgr_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+ const char *name;
+ const char **grp_attrs;
+
+ struct sysdb_attrs *orig_user;
+
+ struct sysdb_handle *handle;
+};
+
+static void sdap_get_initgr_user(struct tevent_req *subreq);
+static void sdap_get_initgr_store(struct tevent_req *subreq);
+static void sdap_get_initgr_commit(struct tevent_req *subreq);
+static void sdap_get_initgr_process(struct tevent_req *subreq);
+static void sdap_get_initgr_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *name,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_initgr_state *state;
+ const char *base_dn;
+ char *filter;
+ const char **attrs;
+ int ret;
+
+ DEBUG(9, ("Retrieving info for initgroups call\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_initgr_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->name = name;
+ state->grp_attrs = grp_attrs;
+ state->orig_user = NULL;
+
+ filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ state->opts->user_map[SDAP_AT_USER_NAME].name,
+ state->name,
+ state->opts->user_map[SDAP_OC_USER].name);
+ if (!filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ base_dn = dp_opt_get_string(state->opts->basic,
+ SDAP_USER_SEARCH_BASE);
+ if (!base_dn) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = build_attrs_from_map(state, state->opts->user_map,
+ SDAP_OPTS_USER, &attrs);
+ if (ret) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->opts, state->sh,
+ base_dn, LDAP_SCOPE_SUBTREE,
+ filter, attrs,
+ state->opts->user_map, SDAP_OPTS_USER);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_user, req);
+
+ return req;
+}
+
+static void sdap_get_initgr_user(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ struct sysdb_attrs **usr_attrs;
+ size_t count;
+ int ret;
+
+ DEBUG(9, ("Receiving info for the user\n"));
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count != 1) {
+ DEBUG(2, ("Expected one user entry and got %d\n", count));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ state->orig_user = usr_attrs[0];
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_store, req);
+}
+
+static void sdap_get_initgr_store(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Storing the user\n"));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_save_user_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->orig_user, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_commit, req);
+}
+
+static void sdap_get_initgr_commit(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Commit change\n"));
+
+ ret = sdap_save_user_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_process, req);
+}
+
+static void sdap_get_initgr_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Process user's groups\n"));
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ subreq = sdap_initgr_rfc2307_send(state, state->ev, state->opts,
+ state->sysdb, state->dom, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_GROUP_SEARCH_BASE),
+ state->name, state->grp_attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_done, req);
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ /* TODO: AD uses a different member/memberof schema
+ * We need an AD specific call that is able to unroll
+ * nested groups by doing extensive recursive searches */
+
+ subreq = sdap_initgr_nested_send(state, state->ev, state->opts,
+ state->sysdb, state->dom, state->sh,
+ state->orig_user, state->grp_attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_done, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+}
+
+static void sdap_get_initgr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Initgroups done\n"));
+
+ switch (state->opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+
+ ret = sdap_initgr_rfc2307_recv(subreq);
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+
+ ret = sdap_initgr_nested_recv(subreq);
+ break;
+
+ default:
+
+ ret = EINVAL;
+ break;
+ }
+
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_initgr_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c
new file mode 100644
index 00000000..18e47d3b
--- /dev/null
+++ b/src/providers/ldap/sdap_async_connection.c
@@ -0,0 +1,1141 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sasl/sasl.h>
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "providers/ldap/sdap_async_private.h"
+
+#define LDAP_X_SSSD_PASSWORD_EXPIRED 0x555D
+
+/* ==Connect-to-LDAP-Server=============================================== */
+
+struct sdap_connect_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+
+ struct sdap_msg *reply;
+ int result;
+};
+
+static void sdap_connect_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ const char *uri,
+ bool use_start_tls)
+{
+ struct tevent_req *req;
+ struct sdap_connect_state *state;
+ struct timeval tv;
+ int ver;
+ int lret;
+ int ret = EOK;
+ int msgid;
+ bool ldap_referrals;
+
+ req = tevent_req_create(memctx, &state, struct sdap_connect_state);
+ if (!req) return NULL;
+
+ state->reply = talloc(state, struct sdap_msg);
+ if (!state->reply) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sdap_handle_create(state);
+ if (!state->sh) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ /* Initialize LDAP handler */
+ lret = ldap_initialize(&state->sh->ldap, uri);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_initialize failed: %s\n", ldap_err2string(lret)));
+ goto fail;
+ }
+
+ /* Force ldap version to 3 */
+ ver = LDAP_VERSION3;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_PROTOCOL_VERSION, &ver);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set ldap version to 3\n"));
+ goto fail;
+ }
+
+ /* Set Network Timeout */
+ tv.tv_sec = dp_opt_get_int(opts->basic, SDAP_NETWORK_TIMEOUT);
+ tv.tv_usec = 0;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set network timeout to %d\n",
+ dp_opt_get_int(opts->basic, SDAP_NETWORK_TIMEOUT)));
+ goto fail;
+ }
+
+ /* Set Default Timeout */
+ tv.tv_sec = dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT);
+ tv.tv_usec = 0;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_TIMEOUT, &tv);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set default timeout to %d\n",
+ dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT)));
+ goto fail;
+ }
+
+ /* Set Referral chasing */
+ ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS);
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_REFERRALS,
+ (ldap_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF));
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set referral chasing to %s\n",
+ (ldap_referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF")));
+ goto fail;
+ }
+
+#ifdef HAVE_LDAP_CONNCB
+ struct ldap_cb_data *cb_data;
+
+ /* add connection callback */
+ state->sh->conncb = talloc_zero(state->sh, struct ldap_conncb);
+ if (state->sh->conncb == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ cb_data = talloc_zero(state->sh->conncb, struct ldap_cb_data);
+ if (cb_data == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ cb_data->sh = state->sh;
+ cb_data->ev = state->ev;
+
+ state->sh->conncb->lc_add = sdap_ldap_connect_callback_add;
+ state->sh->conncb->lc_del = sdap_ldap_connect_callback_del;
+ state->sh->conncb->lc_arg = cb_data;
+
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_CONNECT_CB,
+ state->sh->conncb);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set connection callback\n"));
+ goto fail;
+ }
+#endif
+
+ /* if we do not use start_tls the connection is not really connected yet
+ * just fake an async procedure and leave connection to the bind call */
+ if (!use_start_tls) {
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ DEBUG(4, ("Executing START TLS\n"));
+
+ lret = ldap_start_tls(state->sh->ldap, NULL, NULL, &msgid);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(3, ("ldap_start_tls failed: [%s]", ldap_err2string(ret)));
+ goto fail;
+ }
+
+ state->sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(state->sh, state->ev);
+ if (ret) goto fail;
+#endif
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, state->sh, msgid,
+ sdap_connect_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ if (ret) {
+ tevent_req_error(req, ret);
+ } else {
+ if (lret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_connect_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_connect_state *state = tevent_req_data(req,
+ struct sdap_connect_state);
+ char *errmsg;
+ int ret;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ state->reply = talloc_steal(state, reply);
+
+ ret = ldap_parse_result(state->sh->ldap, state->reply->msg,
+ &state->result, NULL, &errmsg, NULL, NULL, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ DEBUG(3, ("START TLS result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ if (ldap_tls_inplace(state->sh->ldap)) {
+ DEBUG(9, ("SSL/TLS handler already in place.\n"));
+ tevent_req_done(req);
+ return;
+ }
+
+/* FIXME: take care that ldap_install_tls might block */
+ ret = ldap_install_tls(state->sh->ldap);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_install_tls failed: [%d][%s]\n", ret,
+ ldap_err2string(ret)));
+ state->result = ret;
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh)
+{
+ struct sdap_connect_state *state = tevent_req_data(req,
+ struct sdap_connect_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* if tstate shows in progress, it is because
+ * we did not ask to perform tls, just pretend all is fine */
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ return err;
+ }
+ }
+
+ *sh = talloc_steal(memctx, state->sh);
+ if (!*sh) {
+ return ENOMEM;
+ }
+ return EOK;
+}
+
+/* ==Simple-Bind========================================================== */
+
+struct simple_bind_state {
+ struct tevent_context *ev;
+ struct sdap_handle *sh;
+ const char *user_dn;
+ struct berval *pw;
+
+ struct sdap_op *op;
+
+ struct sdap_msg *reply;
+ int result;
+};
+
+static void simple_bind_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *user_dn,
+ struct berval *pw)
+{
+ struct tevent_req *req;
+ struct simple_bind_state *state;
+ int ret = EOK;
+ int msgid;
+ int ldap_err;
+ LDAPControl *request_controls[2];
+
+ req = tevent_req_create(memctx, &state, struct simple_bind_state);
+ if (!req) return NULL;
+
+ state->reply = talloc(state, struct sdap_msg);
+ if (!state->reply) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sh = sh;
+ state->user_dn = user_dn;
+ state->pw = pw;
+
+ ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ 0, NULL, 0, &request_controls[0]);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("sss_ldap_control_create failed.\n"));
+ goto fail;
+ }
+ request_controls[1] = NULL;
+
+ DEBUG(4, ("Executing simple bind as: %s\n", state->user_dn));
+
+ ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE,
+ state->pw, request_controls, NULL, &msgid);
+ ldap_control_free(request_controls[0]);
+ if (ret == -1 || msgid == -1) {
+ ret = ldap_get_option(state->sh->ldap,
+ LDAP_OPT_RESULT_CODE, &ldap_err);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_bind failed (couldn't get ldap error)\n"));
+ ret = LDAP_LOCAL_ERROR;
+ } else {
+ DEBUG(1, ("ldap_bind failed (%d)[%s]\n",
+ ldap_err, ldap_err2string(ldap_err)));
+ ret = ldap_err;
+ }
+ goto fail;
+ }
+ DEBUG(8, ("ldap simple bind sent, msgid = %d\n", msgid));
+
+ if (!sh->connected) {
+ sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(sh, ev);
+ if (ret) goto fail;
+#endif
+ }
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, sh, msgid,
+ simple_bind_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ if (ret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void simple_bind_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct simple_bind_state *state = tevent_req_data(req,
+ struct simple_bind_state);
+ char *errmsg;
+ int ret;
+ LDAPControl **response_controls;
+ int c;
+ ber_int_t pp_grace;
+ ber_int_t pp_expire;
+ LDAPPasswordPolicyError pp_error;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ state->reply = talloc_steal(state, reply);
+
+ ret = ldap_parse_result(state->sh->ldap, state->reply->msg,
+ &state->result, NULL, &errmsg, NULL,
+ &response_controls, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ ret = EIO;
+ goto done;
+ }
+
+ if (response_controls == NULL) {
+ DEBUG(5, ("Server returned no controls.\n"));
+ } else {
+ for (c = 0; response_controls[c] != NULL; c++) {
+ DEBUG(9, ("Server returned control [%s].\n",
+ response_controls[c]->ldctl_oid));
+ if (strcmp(response_controls[c]->ldctl_oid,
+ LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
+ ret = ldap_parse_passwordpolicy_control(state->sh->ldap,
+ response_controls[c],
+ &pp_expire, &pp_grace,
+ &pp_error);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_parse_passwordpolicy_control failed.\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] "
+ "error [%s].\n", pp_expire, pp_grace,
+ ldap_passwordpolicy_err2txt(pp_error)));
+
+ if (state->result == LDAP_SUCCESS &&
+ (pp_error == PP_changeAfterReset || pp_grace > 0)) {
+ DEBUG(4, ("User must set a new password.\n"));
+ state->result = LDAP_X_SSSD_PASSWORD_EXPIRED;
+ }
+ }
+ }
+ }
+
+ DEBUG(3, ("Bind result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ ret = LDAP_SUCCESS;
+done:
+ ldap_controls_free(response_controls);
+
+ if (ret == LDAP_SUCCESS) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+}
+
+static int simple_bind_recv(struct tevent_req *req, int *ldaperr)
+{
+ struct simple_bind_state *state = tevent_req_data(req,
+ struct simple_bind_state);
+
+ *ldaperr = LDAP_OTHER;
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *ldaperr = state->result;
+ return EOK;
+}
+
+/* ==SASL-Bind============================================================ */
+
+struct sasl_bind_state {
+ struct tevent_context *ev;
+ struct sdap_handle *sh;
+
+ const char *sasl_mech;
+ const char *sasl_user;
+ struct berval *sasl_cred;
+
+ int result;
+};
+
+static int sdap_sasl_interact(LDAP *ld, unsigned flags,
+ void *defaults, void *interact);
+
+static struct tevent_req *sasl_bind_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ struct berval *sasl_cred)
+{
+ struct tevent_req *req;
+ struct sasl_bind_state *state;
+ int ret = EOK;
+
+ req = tevent_req_create(memctx, &state, struct sasl_bind_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->sh = sh;
+ state->sasl_mech = sasl_mech;
+ state->sasl_user = sasl_user;
+ state->sasl_cred = sasl_cred;
+
+ DEBUG(4, ("Executing sasl bind mech: %s, user: %s\n",
+ sasl_mech, sasl_user));
+
+ /* FIXME: Warning, this is a sync call!
+ * No async variant exist in openldap libraries yet */
+
+ ret = ldap_sasl_interactive_bind_s(state->sh->ldap, NULL,
+ sasl_mech, NULL, NULL,
+ LDAP_SASL_QUIET,
+ (*sdap_sasl_interact), state);
+ state->result = ret;
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_sasl_bind failed (%d)[%s]\n",
+ ret, ldap_err2string(ret)));
+ goto fail;
+ }
+
+ if (!sh->connected) {
+ sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(sh, ev);
+ if (ret) goto fail;
+#endif
+ }
+
+ tevent_req_post(req, ev);
+ return req;
+
+fail:
+ if (ret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static int sdap_sasl_interact(LDAP *ld, unsigned flags,
+ void *defaults, void *interact)
+{
+ struct sasl_bind_state *state = talloc_get_type(defaults,
+ struct sasl_bind_state);
+ sasl_interact_t *in = (sasl_interact_t *)interact;
+
+ if (!ld) return LDAP_PARAM_ERROR;
+
+ while (in->id != SASL_CB_LIST_END) {
+
+ switch (in->id) {
+ case SASL_CB_GETREALM:
+ case SASL_CB_AUTHNAME:
+ case SASL_CB_PASS:
+ if (in->defresult) {
+ in->result = in->defresult;
+ } else {
+ in->result = "";
+ }
+ in->len = strlen(in->result);
+ break;
+ case SASL_CB_USER:
+ if (state->sasl_user) {
+ in->result = state->sasl_user;
+ } else if (in->defresult) {
+ in->result = in->defresult;
+ } else {
+ in->result = "";
+ }
+ in->len = strlen(in->result);
+ break;
+ case SASL_CB_NOECHOPROMPT:
+ case SASL_CB_ECHOPROMPT:
+ goto fail;
+ }
+
+ in++;
+ }
+
+ return LDAP_SUCCESS;
+
+fail:
+ return LDAP_UNAVAILABLE;
+}
+
+static int sasl_bind_recv(struct tevent_req *req, int *ldaperr)
+{
+ struct sasl_bind_state *state = tevent_req_data(req,
+ struct sasl_bind_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ *ldaperr = LDAP_OTHER;
+ return err;
+ }
+ }
+
+ *ldaperr = state->result;
+ return EOK;
+}
+
+/* ==Perform-Kinit-given-keytab-and-principal============================= */
+
+struct sdap_kinit_state {
+ int result;
+};
+
+static void sdap_kinit_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ int timeout,
+ const char *keytab,
+ const char *principal,
+ const char *realm)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sdap_kinit_state *state;
+ int ret;
+
+ DEBUG(6, ("Attempting kinit (%s, %s, %s)\n", keytab, principal, realm));
+
+ req = tevent_req_create(memctx, &state, struct sdap_kinit_state);
+ if (!req) return NULL;
+
+ state->result = SDAP_AUTH_FAILED;
+
+ if (keytab) {
+ ret = setenv("KRB5_KTNAME", keytab, 1);
+ if (ret == -1) {
+ DEBUG(2, ("Failed to set KRB5_KTNAME to %s\n", keytab));
+ return NULL;
+ }
+ }
+
+ subreq = sdap_get_tgt_send(state, ev, realm, principal, keytab, timeout);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_kinit_done, req);
+
+ return req;
+}
+
+static void sdap_kinit_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_kinit_state *state = tevent_req_data(req,
+ struct sdap_kinit_state);
+
+ int ret;
+ int result;
+ char *ccname = NULL;
+
+ ret = sdap_get_tgt_recv(subreq, state, &result, &ccname);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ state->result = SDAP_AUTH_FAILED;
+ DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (result == EOK) {
+ ret = setenv("KRB5CCNAME", ccname, 1);
+ if (ret == -1) {
+ DEBUG(2, ("Unable to set env. variable KRB5CCNAME!\n"));
+ state->result = SDAP_AUTH_FAILED;
+ tevent_req_error(req, EFAULT);
+ }
+
+ state->result = SDAP_AUTH_SUCCESS;
+ tevent_req_done(req);
+ return;
+ }
+
+ DEBUG(4, ("Could not get TGT: %d [%s]\n", result, strerror(result)));
+ state->result = SDAP_AUTH_FAILED;
+ tevent_req_error(req, EIO);
+}
+
+int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result)
+{
+ struct sdap_kinit_state *state = tevent_req_data(req,
+ struct sdap_kinit_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ *result = SDAP_ERROR;
+ return err;
+ }
+ }
+
+ *result = state->result;
+ return EOK;
+}
+
+
+/* ==Authenticaticate-User-by-DN========================================== */
+
+struct sdap_auth_state {
+ const char *user_dn;
+ struct berval pw;
+
+ int result;
+ bool is_sasl;
+};
+
+static void sdap_auth_done(struct tevent_req *subreq);
+
+/* TODO: handle sasl_cred */
+struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ const char *user_dn,
+ const char *authtok_type,
+ struct dp_opt_blob authtok)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_auth_state *state;
+
+ if (authtok_type != NULL && strcasecmp(authtok_type,"password") != 0) {
+ DEBUG(1,("Authentication token type [%s] is not supported"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct sdap_auth_state);
+ if (!req) return NULL;
+
+ state->user_dn = user_dn;
+ state->pw.bv_val = (char *)authtok.data;
+ state->pw.bv_len = authtok.length;
+
+ if (sasl_mech) {
+ state->is_sasl = true;
+ subreq = sasl_bind_send(state, ev, sh, sasl_mech, sasl_user, NULL);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ state->is_sasl = false;
+ subreq = simple_bind_send(state, ev, sh, user_dn, &state->pw);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ tevent_req_set_callback(subreq, sdap_auth_done, req);
+ return req;
+}
+
+static void sdap_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_auth_state *state = tevent_req_data(req,
+ struct sdap_auth_state);
+ int ret;
+
+ if (state->is_sasl) {
+ ret = sasl_bind_recv(subreq, &state->result);
+ } else {
+ ret = simple_bind_recv(subreq, &state->result);
+ }
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result)
+{
+ struct sdap_auth_state *state = tevent_req_data(req,
+ struct sdap_auth_state);
+
+ *result = SDAP_ERROR;
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ switch (state->result) {
+ case LDAP_SUCCESS:
+ *result = SDAP_AUTH_SUCCESS;
+ break;
+ case LDAP_INVALID_CREDENTIALS:
+ *result = SDAP_AUTH_FAILED;
+ break;
+ case LDAP_X_SSSD_PASSWORD_EXPIRED:
+ *result = SDAP_AUTH_PW_EXPIRED;
+ break;
+ default:
+ break;
+ }
+
+ return EOK;
+}
+
+/* ==Client connect============================================ */
+
+struct sdap_cli_connect_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_service *service;
+
+ bool use_rootdse;
+ struct sysdb_attrs *rootdse;
+
+ struct sdap_handle *sh;
+
+ struct fo_server *srv;
+};
+
+static void sdap_cli_resolve_done(struct tevent_req *subreq);
+static void sdap_cli_connect_done(struct tevent_req *subreq);
+static void sdap_cli_rootdse_step(struct tevent_req *req);
+static void sdap_cli_rootdse_done(struct tevent_req *subreq);
+static void sdap_cli_kinit_step(struct tevent_req *req);
+static void sdap_cli_kinit_done(struct tevent_req *subreq);
+static void sdap_cli_auth_step(struct tevent_req *req);
+static void sdap_cli_auth_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct be_ctx *be,
+ struct sdap_service *service,
+ struct sysdb_attrs **rootdse)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_cli_connect_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_cli_connect_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->service = service;
+ state->srv = NULL;
+
+ if (rootdse) {
+ state->use_rootdse = true;
+ state->rootdse = *rootdse;
+ } else {
+ state->use_rootdse = false;
+ state->rootdse = NULL;
+ }
+
+ /* NOTE: this call may cause service->uri to be refreshed
+ * with a new valid server. Do not use service->uri before */
+ subreq = be_resolve_server_send(state, ev, be, service->name);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_resolve_done, req);
+
+ return req;
+}
+
+static void sdap_cli_resolve_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ int ret;
+
+ ret = be_resolve_server_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ if (ret) {
+ /* all servers have been tried and none
+ * was found good, go offline */
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ subreq = sdap_connect_send(state, state->ev, state->opts,
+ state->service->uri,
+ dp_opt_get_bool(state->opts->basic,
+ SDAP_ID_TLS));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_connect_done, req);
+}
+
+static void sdap_cli_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ const char *sasl_mech;
+ int ret;
+
+ ret = sdap_connect_recv(subreq, state, &state->sh);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->use_rootdse && !state->rootdse) {
+ /* fetch the rootDSE this time */
+ sdap_cli_rootdse_step(req);
+ return;
+ }
+
+ sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH);
+
+ if (sasl_mech && state->use_rootdse) {
+ /* check if server claims to support GSSAPI */
+ if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse,
+ sasl_mech)) {
+ tevent_req_error(req, ENOTSUP);
+ return;
+ }
+ }
+
+ if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) {
+ if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) {
+ sdap_cli_kinit_step(req);
+ return;
+ }
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_rootdse_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_get_rootdse_send(state, state->ev, state->opts, state->sh);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_rootdse_done, req);
+
+ if (!state->sh->connected) {
+ /* this rootdse search is performed before we actually do a bind,
+ * so we need to set up the callbacks or we will never get notified
+ * of a reply */
+ state->sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ int ret;
+
+ ret = sdap_install_ldap_callbacks(state->sh, state->ev);
+ if (ret) {
+ tevent_req_error(req, ret);
+ }
+#endif
+ }
+}
+
+static void sdap_cli_rootdse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ const char *sasl_mech;
+ int ret;
+
+ ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH);
+
+ if (sasl_mech && state->use_rootdse) {
+ /* check if server claims to support GSSAPI */
+ if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse,
+ sasl_mech)) {
+ tevent_req_error(req, ENOTSUP);
+ return;
+ }
+ }
+
+ if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) {
+ if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) {
+ sdap_cli_kinit_step(req);
+ return;
+ }
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_kinit_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_kinit_send(state, state->ev,
+ state->sh,
+ dp_opt_get_int(state->opts->basic,
+ SDAP_OPT_TIMEOUT),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_KRB5_KEYTAB),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_AUTHID),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_KRB5_REALM));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_kinit_done, req);
+}
+
+static void sdap_cli_kinit_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ enum sdap_result result;
+ int ret;
+
+ ret = sdap_kinit_recv(subreq, &result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (result != SDAP_AUTH_SUCCESS) {
+ tevent_req_error(req, EACCES);
+ return;
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_auth_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_auth_send(state,
+ state->ev,
+ state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_MECH),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_AUTHID),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_DEFAULT_BIND_DN),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_DEFAULT_AUTHTOK_TYPE),
+ dp_opt_get_blob(state->opts->basic,
+ SDAP_DEFAULT_AUTHTOK));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_auth_done, req);
+}
+
+static void sdap_cli_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ enum sdap_result result;
+ int ret;
+
+ ret = sdap_auth_recv(subreq, &result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (result != SDAP_AUTH_SUCCESS) {
+ tevent_req_error(req, EACCES);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_cli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **gsh,
+ struct sysdb_attrs **rootdse)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* mark the server as bad if connection failed */
+ if (state->srv) {
+ fo_set_port_status(state->srv, PORT_NOT_WORKING);
+ }
+
+ if (tstate == TEVENT_REQ_USER_ERROR) {
+ return err;
+ }
+ return EIO;
+ } else if (state->srv) {
+ fo_set_port_status(state->srv, PORT_WORKING);
+ }
+
+ if (gsh) {
+ *gsh = talloc_steal(memctx, state->sh);
+ if (!*gsh) {
+ return ENOMEM;
+ }
+ } else {
+ talloc_zfree(state->sh);
+ }
+
+ if (rootdse) {
+ if (state->use_rootdse) {
+ *rootdse = talloc_steal(memctx, state->rootdse);
+ if (!*rootdse) {
+ return ENOMEM;
+ }
+ } else {
+ *rootdse = NULL;
+ }
+ } else {
+ talloc_zfree(rootdse);
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h
new file mode 100644
index 00000000..55f76ed7
--- /dev/null
+++ b/src/providers/ldap/sdap_async_private.h
@@ -0,0 +1,68 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@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/>.
+*/
+
+#ifndef _SDAP_ASYNC_PRIVATE_H_
+#define _SDAP_ASYNC_PRIVATE_H_
+
+#include "config.h"
+#include "providers/ldap/sdap_async.h"
+
+void make_realm_upper_case(const char *upn);
+struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx);
+
+#ifdef HAVE_LDAP_CONNCB
+int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv,
+ struct sockaddr *addr, struct ldap_conncb *ctx);
+void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb,
+ struct ldap_conncb *ctx);
+#else
+int sdap_install_ldap_callbacks(struct sdap_handle *sh,
+ struct tevent_context *ev);
+#endif
+
+int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev,
+ struct sdap_handle *sh, int msgid,
+ sdap_op_callback_t *callback, void *data,
+ int timeout, struct sdap_op **_op);
+
+struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh);
+int sdap_get_rootdse_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sysdb_attrs **rootdse);
+
+/* from sdap_child_helpers.c */
+
+struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ int timeout);
+
+int sdap_get_tgt_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *result,
+ char **ccname);
+
+#endif /* _SDAP_ASYNC_PRIVATE_H_ */
diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c
new file mode 100644
index 00000000..0a95c8a0
--- /dev/null
+++ b/src/providers/ldap/sdap_child_helpers.c
@@ -0,0 +1,462 @@
+/*
+ SSSD
+
+ LDAP Backend Module -- child helpers
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/child_common.h"
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#else
+#define LDAP_CHILD SSSD_LIBEXEC_PATH"/ldap_child"
+#endif
+
+#ifndef LDAP_CHILD_USER
+#define LDAP_CHILD_USER "nobody"
+#endif
+
+struct sdap_child {
+ /* child info */
+ pid_t pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+};
+
+static void sdap_close_fd(int *fd)
+{
+ int ret;
+
+ if (*fd == -1) {
+ DEBUG(6, ("fd already closed\n"));
+ return;
+ }
+
+ ret = close(*fd);
+ if (ret) {
+ ret = errno;
+ DEBUG(2, ("Closing fd %d, return error %d (%s)\n",
+ *fd, ret, strerror(ret)));
+ }
+
+ *fd = -1;
+}
+
+static int sdap_child_destructor(void *ptr)
+{
+ struct sdap_child *child = talloc_get_type(ptr, struct sdap_child);
+
+ child_cleanup(child->read_from_child_fd, child->write_to_child_fd);
+
+ return 0;
+}
+
+static errno_t sdap_fork_child(struct sdap_child *child)
+{
+ int pipefd_to_child[2];
+ int pipefd_from_child[2];
+ pid_t pid;
+ int ret;
+ errno_t err;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ err = exec_child(child,
+ pipefd_to_child, pipefd_from_child,
+ LDAP_CHILD, ldap_child_debug_fd);
+ if (err != EOK) {
+ DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n",
+ err, strerror(err)));
+ return err;
+ }
+ } else if (pid > 0) { /* parent */
+ child->pid = pid;
+ child->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ child->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ fd_nonblocking(child->read_from_child_fd);
+ fd_nonblocking(child->write_to_child_fd);
+
+ } else { /* error */
+ err = errno;
+ DEBUG(1, ("fork failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ return EOK;
+}
+
+static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+
+ buf = talloc(mem_ctx, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ buf->size = 3 * sizeof(uint32_t);
+ if (realm_str) {
+ buf->size += strlen(realm_str);
+ }
+ if (princ_str) {
+ buf->size += strlen(princ_str);
+ }
+ if (keytab_name) {
+ buf->size += strlen(keytab_name);
+ }
+
+ DEBUG(7, ("buffer size: %d\n", buf->size));
+
+ buf->data = talloc_size(buf, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+
+ /* realm */
+ if (realm_str) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(realm_str), rp);
+ COPY_MEM(&buf->data[rp], realm_str, rp, strlen(realm_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ /* principal */
+ if (princ_str) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(princ_str), rp);
+ COPY_MEM(&buf->data[rp], princ_str, rp, strlen(princ_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ /* keytab */
+ if (keytab_name) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab_name), rp);
+ COPY_MEM(&buf->data[rp], keytab_name, rp, strlen(realm_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ *io_buf = buf;
+ return EOK;
+}
+
+static int parse_child_response(TALLOC_CTX *mem_ctx,
+ uint8_t *buf, ssize_t size,
+ int *result, char **ccache)
+{
+ size_t p = 0;
+ uint32_t len;
+ uint32_t res;
+ char *ccn;
+
+ /* operation result code */
+ COPY_UINT32_CHECK(&res, buf + p, p, size);
+
+ /* ccache name size */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ if ((p + len ) > size) return EINVAL;
+
+ ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1));
+ if (ccn == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return ENOMEM;
+ }
+ memcpy(ccn, buf+p, sizeof(char) * (len + 1));
+ ccn[len] = '\0';
+
+ *result = res;
+ *ccache = ccn;
+ return EOK;
+}
+
+/* ==The-public-async-interface============================================*/
+
+struct sdap_get_tgt_state {
+ struct tevent_context *ev;
+ struct sdap_child *child;
+ ssize_t len;
+ uint8_t *buf;
+};
+
+static errno_t set_tgt_child_timeout(struct tevent_req *req,
+ struct tevent_context *ev,
+ int timeout);
+static void sdap_get_tgt_step(struct tevent_req *subreq);
+static void sdap_get_tgt_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ int timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_tgt_state *state;
+ struct io_buffer *buf;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sdap_get_tgt_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ state->child = talloc_zero(state, struct sdap_child);
+ if (!state->child) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->child->read_from_child_fd = -1;
+ state->child->write_to_child_fd = -1;
+ talloc_set_destructor((TALLOC_CTX *)state->child, sdap_child_destructor);
+
+ /* prepare the data to pass to child */
+ ret = create_tgt_req_send_buffer(state,
+ realm_str, princ_str, keytab_name,
+ &buf);
+ if (ret != EOK) {
+ DEBUG(1, ("create_tgt_req_send_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = sdap_fork_child(state->child);
+ if (ret != EOK) {
+ DEBUG(1, ("sdap_fork_child failed.\n"));
+ goto fail;
+ }
+
+ ret = set_tgt_child_timeout(req, ev, timeout);
+ if (ret != EOK) {
+ DEBUG(1, ("activate_child_timeout_handler failed.\n"));
+ goto fail;
+ }
+
+ subreq = write_pipe_send(state, ev, buf->data, buf->size,
+ state->child->write_to_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_get_tgt_step, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_get_tgt_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_close_fd(&state->child->write_to_child_fd);
+
+ subreq = read_pipe_send(state, state->ev,
+ state->child->read_from_child_fd);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_tgt_done, req);
+}
+
+static void sdap_get_tgt_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_close_fd(&state->child->read_from_child_fd);
+
+ tevent_req_done(req);
+}
+
+int sdap_get_tgt_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *result,
+ char **ccname)
+{
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ char *ccn;
+ int res;
+ int ret;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ ret = parse_child_response(mem_ctx, state->buf, state->len, &res, &ccn);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse child response: [%d][%s]\n", ret, strerror(ret)));
+ return ret;
+ }
+
+ DEBUG(6, ("Child responded: %d [%s]\n", res, ccn));
+ *result = res;
+ *ccname = ccn;
+ return EOK;
+}
+
+
+
+static void get_tgt_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ DEBUG(9, ("timeout for tgt child [%d] reached.\n", state->child->pid));
+
+ ret = kill(state->child->pid, SIGKILL);
+ if (ret == -1) {
+ DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+ tevent_req_error(req, ETIMEDOUT);
+}
+
+static errno_t set_tgt_child_timeout(struct tevent_req *req,
+ struct tevent_context *ev,
+ int timeout)
+{
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ DEBUG(6, ("Setting %d seconds timeout for tgt child\n", timeout));
+
+ tv = tevent_timeval_current_ofs(timeout, 0);
+
+ te = tevent_add_timer(ev, req, tv, get_tgt_timeout_handler, req);
+ if (te == NULL) {
+ DEBUG(1, ("tevent_add_timer failed.\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+
+
+/* Setup child logging */
+int setup_child(struct sdap_id_ctx *ctx)
+{
+ int ret;
+ const char *mech;
+ struct tevent_signal *sige;
+ unsigned v;
+ FILE *debug_filep;
+
+ mech = dp_opt_get_string(ctx->opts->basic,
+ SDAP_SASL_MECH);
+ if (!mech) {
+ return EOK;
+ }
+
+ sige = tevent_add_signal(ctx->be->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ return ENOMEM;
+ }
+
+ if (debug_to_file != 0 && ldap_child_debug_fd == -1) {
+ ret = open_debug_file_ex("ldap_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+
+ ldap_child_debug_fd = fileno(debug_filep);
+ if (ldap_child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ return ret;
+ }
+
+ v = fcntl(ldap_child_debug_fd, F_GETFD, 0);
+ fcntl(ldap_child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ return EOK;
+}
diff --git a/src/providers/providers.h b/src/providers/providers.h
new file mode 100644
index 00000000..44e7028a
--- /dev/null
+++ b/src/providers/providers.h
@@ -0,0 +1,24 @@
+/*
+ SSSD
+
+ Data Provider, public header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+int dp_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb);
diff --git a/src/providers/proxy.c b/src/providers/proxy.c
new file mode 100644
index 00000000..12bb25ec
--- /dev/null
+++ b/src/providers/proxy.c
@@ -0,0 +1,2521 @@
+/*
+ SSSD
+
+ Proxy Module
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008-2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <nss.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dlfcn.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/dp_backend.h"
+#include "db/sysdb.h"
+
+struct proxy_nss_ops {
+ enum nss_status (*getpwnam_r)(const char *name, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*setpwent)(void);
+ enum nss_status (*getpwent_r)(struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*endpwent)(void);
+
+ enum nss_status (*getgrnam_r)(const char *name, struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*getgrgid_r)(gid_t gid, struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*setgrent)(void);
+ enum nss_status (*getgrent_r)(struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*endgrent)(void);
+ enum nss_status (*initgroups_dyn)(const char *user, gid_t group,
+ long int *start, long int *size,
+ gid_t **groups, long int limit,
+ int *errnop);
+};
+
+struct proxy_ctx {
+ struct be_ctx *be;
+ int entry_cache_timeout;
+ struct proxy_nss_ops ops;
+};
+
+struct proxy_auth_ctx {
+ struct be_ctx *be;
+ char *pam_target;
+};
+
+struct authtok_conv {
+ uint32_t authtok_size;
+ uint8_t *authtok;
+};
+
+static int proxy_internal_conv(int num_msg, const struct pam_message **msgm,
+ struct pam_response **response,
+ void *appdata_ptr) {
+ int i;
+ struct pam_response *reply;
+ struct authtok_conv *auth_data;
+
+ auth_data = talloc_get_type(appdata_ptr, struct authtok_conv);
+
+ if (num_msg <= 0) return PAM_CONV_ERR;
+
+ reply = (struct pam_response *) calloc(num_msg,
+ sizeof(struct pam_response));
+ if (reply == NULL) return PAM_CONV_ERR;
+
+ for (i=0; i < num_msg; i++) {
+ switch( msgm[i]->msg_style ) {
+ case PAM_PROMPT_ECHO_OFF:
+ DEBUG(4, ("Conversation message: [%s]\n", msgm[i]->msg));
+ reply[i].resp_retcode = 0;
+ reply[i].resp = calloc(auth_data->authtok_size + 1,
+ sizeof(char));
+ if (reply[i].resp == NULL) goto failed;
+ memcpy(reply[i].resp, auth_data->authtok, auth_data->authtok_size);
+
+ break;
+ default:
+ DEBUG(1, ("Conversation style %d not supported.\n",
+ msgm[i]->msg_style));
+ goto failed;
+ }
+ }
+
+ *response = reply;
+ reply = NULL;
+
+ return PAM_SUCCESS;
+
+failed:
+ free(reply);
+ return PAM_CONV_ERR;
+}
+
+static void proxy_pam_handler_cache_done(struct tevent_req *treq);
+static void proxy_reply(struct be_req *req, int dp_err,
+ int error, const char *errstr);
+
+static void proxy_pam_handler(struct be_req *req) {
+ int ret;
+ int pam_status;
+ pam_handle_t *pamh=NULL;
+ struct authtok_conv *auth_data;
+ struct pam_conv conv;
+ struct pam_data *pd;
+ struct proxy_auth_ctx *ctx;;
+ bool cache_auth_data = false;
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ return;
+ default:
+ DEBUG(1, ("Unsupported PAM task.\n"));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ proxy_reply(req, DP_ERR_OK, EINVAL, "Unsupported PAM task");
+ return;
+ }
+
+ conv.conv=proxy_internal_conv;
+ auth_data = talloc_zero(req, struct authtok_conv);
+ conv.appdata_ptr=auth_data;
+
+ ret = pam_start(ctx->pam_target, pd->user, &conv, &pamh);
+ if (ret == PAM_SUCCESS) {
+ DEBUG(1, ("Pam transaction started.\n"));
+ ret = pam_set_item(pamh, PAM_TTY, pd->tty);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_TTY failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ ret = pam_set_item(pamh, PAM_RUSER, pd->ruser);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_RUSER failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ ret = pam_set_item(pamh, PAM_RHOST, pd->rhost);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_RHOST failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ if ((pam_status == PAM_SUCCESS) &&
+ (req->be_ctx->domain->cache_credentials)) {
+ cache_auth_data = true;
+ }
+ break;
+ case SSS_PAM_SETCRED:
+ pam_status=pam_setcred(pamh, 0);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ pam_status=pam_acct_mgmt(pamh, 0);
+ break;
+ case SSS_PAM_OPEN_SESSION:
+ pam_status=pam_open_session(pamh, 0);
+ break;
+ case SSS_PAM_CLOSE_SESSION:
+ pam_status=pam_close_session(pamh, 0);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ if (pd->priv != 1) {
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ if (pam_status != PAM_SUCCESS) break;
+ }
+ auth_data->authtok_size = pd->newauthtok_size;
+ auth_data->authtok = pd->newauthtok;
+ pam_status = pam_chauthtok(pamh, 0);
+ if ((pam_status == PAM_SUCCESS) &&
+ (req->be_ctx->domain->cache_credentials)) {
+ cache_auth_data = true;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (pd->priv != 1) {
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ } else {
+ pam_status = PAM_SUCCESS;
+ }
+ break;
+ default:
+ DEBUG(1, ("unknown PAM call"));
+ pam_status=PAM_ABORT;
+ }
+
+ DEBUG(4, ("Pam result: [%d][%s]\n", pam_status,
+ pam_strerror(pamh, pam_status)));
+
+ if (pam_status == PAM_AUTHINFO_UNAVAIL) {
+ be_mark_offline(req->be_ctx);
+ }
+
+ ret = pam_end(pamh, pam_status);
+ if (ret != PAM_SUCCESS) {
+ pamh=NULL;
+ DEBUG(1, ("Cannot terminate pam transaction.\n"));
+ }
+
+ } else {
+ DEBUG(1, ("Failed to initialize pam transaction.\n"));
+ pam_status = PAM_SYSTEM_ERR;
+ }
+
+ pd->pam_status = pam_status;
+
+ if (cache_auth_data) {
+ struct tevent_req *subreq;
+ char *password;
+
+ password = talloc_size(req, auth_data->authtok_size + 1);
+ if (!password) {
+ /* password caching failures are not fatal errors */
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ }
+ memcpy(password, auth_data->authtok, auth_data->authtok_size);
+ password[auth_data->authtok_size] = '\0';
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ subreq = sysdb_cache_password_send(req, req->be_ctx->ev,
+ req->be_ctx->sysdb, NULL,
+ req->be_ctx->domain,
+ pd->user, password);
+ if (!subreq) {
+ /* password caching failures are not fatal errors */
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ }
+ tevent_req_set_callback(subreq, proxy_pam_handler_cache_done, req);
+ }
+
+ proxy_reply(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_pam_handler_cache_done(struct tevent_req *subreq)
+{
+ struct be_req *req = tevent_req_callback_data(subreq, struct be_req);
+ int ret;
+
+ /* password caching failures are not fatal errors */
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* so we just log it any return */
+ if (ret) {
+ DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
+ ret, strerror(ret)));
+ }
+
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_reply(struct be_req *req, int dp_err,
+ int error, const char *errstr)
+{
+ return req->fn(req, dp_err, error, errstr);
+}
+
+/* =Common-proxy-tevent_req-utils=========================================*/
+
+#define DEFAULT_BUFSIZE 4096
+#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */
+
+struct proxy_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ const char *name;
+
+ struct sysdb_handle *handle;
+ struct passwd *pwd;
+ struct group *grp;
+ uid_t uid;
+ gid_t gid;
+};
+
+static void proxy_default_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int proxy_default_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Getpwnam-wrapper======================================================*/
+
+static void get_pw_name_process(struct tevent_req *subreq);
+static void get_pw_name_remove_done(struct tevent_req *subreq);
+static void get_pw_name_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_pw_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_process, req);
+
+ return req;
+}
+
+static void get_pw_name_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ DEBUG(7, ("Searching user by name (%s)\n", state->name));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.getpwnam_r(state->name, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("User %s not found.\n", state->name));
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User %s found: (%s, %d, %d)\n",
+ state->name, state->pwd->pw_name,
+ state->pwd->pw_uid, state->pwd->pw_gid));
+
+ /* uid=0 or gid=0 are invalid values */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ struct ldb_dn *dn;
+
+ DEBUG(7, ("User %s does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->name));
+
+ dn = sysdb_user_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
+ }
+}
+
+static void get_pw_name_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+static void get_pw_name_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getpwuid-wrapper======================================================*/
+
+static void get_pw_uid_process(struct tevent_req *subreq);
+static void get_pw_uid_remove_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_pw_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ uid_t uid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->uid = uid;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_pw_uid_process, req);
+
+ return req;
+}
+
+static void get_pw_uid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ DEBUG(7, ("Searching user by uid (%d)\n", state->uid));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* always zero out the pwd structure */
+ memset(state->pwd, 0, sizeof(struct passwd));
+
+ status = ctx->ops.getpwuid_r(state->uid, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("User %d not found.\n", state->uid));
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User %d found (%s, %d, %d)\n",
+ state->uid, state->pwd->pw_name,
+ state->pwd->pw_uid, state->pwd->pw_gid));
+
+ /* uid=0 or gid=0 are invalid values */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ DEBUG(7, ("User %d does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->uid));
+
+ subreq = sysdb_delete_user_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->uid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_uid_remove_done, req);
+ }
+}
+
+static void get_pw_uid_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getpwent-wrapper======================================================*/
+
+struct enum_users_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ struct sysdb_handle *handle;
+
+ struct passwd *pwd;
+
+ size_t buflen;
+ char *buffer;
+
+ bool in_transaction;
+};
+
+static void enum_users_process(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_users_state *state;
+ enum nss_status status;
+
+ DEBUG(7, ("Enumerating users\n"));
+
+ req = tevent_req_create(mem_ctx, &state, struct enum_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->handle = NULL;
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->buflen = DEFAULT_BUFSIZE;
+ state->buffer = talloc_size(state, state->buflen);
+ if (!state->buffer) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->in_transaction = false;
+
+ status = ctx->ops.setpwent();
+ if (status != NSS_STATUS_SUCCESS) {
+ tevent_req_error(req, EIO);
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_users_process, req);
+
+ return req;
+
+fail:
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *newbuf;
+ int ret;
+
+ if (!state->in_transaction) {
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ goto fail;
+ }
+ talloc_zfree(subreq);
+
+ state->in_transaction = true;
+ } else {
+ ret = sysdb_store_user_recv(subreq);
+ if (ret) {
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ DEBUG(2, ("Failed to store user. Ignoring.\n"));
+ }
+ talloc_zfree(subreq);
+ }
+
+again:
+ /* always zero out the pwd structure */
+ memset(state->pwd, 0, sizeof(struct passwd));
+
+ /* get entry */
+ status = ctx->ops.getpwent_r(state->pwd,
+ state->buffer, state->buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (state->buflen < MAX_BUF_SIZE) {
+ state->buflen *= 2;
+ }
+ if (state->buflen > MAX_BUF_SIZE) {
+ state->buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ /* we are done here */
+ DEBUG(7, ("Enumeration completed.\n"));
+
+ ctx->ops.endpwent();
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+ return;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User found (%s, %d, %d)\n", state->pwd->pw_name,
+ state->pwd->pw_uid, state->pwd->pw_gid));
+
+ /* uid=0 or gid=0 are invalid values */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->pwd->pw_name));
+
+ goto again; /* skip */
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_users_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getpwent_r failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ ctx->ops.endpwent();
+ tevent_req_error(req, ret);
+}
+
+/* =Getgrnam-wrapper======================================================*/
+
+#define DEBUG_GR_MEM(level, state) \
+ do { \
+ if (debug_level >= level) { \
+ if (!state->grp->gr_mem || !state->grp->gr_mem[0]) { \
+ DEBUG(level, ("Group %s has no members!\n", \
+ state->grp->gr_name)); \
+ } else { \
+ int i = 0; \
+ while (state->grp->gr_mem[i]) { \
+ /* count */ \
+ i++; \
+ } \
+ DEBUG(level, ("Group %s has %d members!\n", \
+ state->grp->gr_name, i)); \
+ } \
+ } \
+ } while(0)
+
+static void get_gr_name_process(struct tevent_req *subreq);
+static void get_gr_name_remove_done(struct tevent_req *subreq);
+static void get_gr_name_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_gr_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_process, req);
+
+ return req;
+}
+
+static void get_gr_name_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ DEBUG(7, ("Searching group by name (%s)\n", state->name));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrnam_r(state->name, state->grp,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (buflen < MAX_BUF_SIZE) {
+ buflen *= 2;
+ }
+ if (buflen > MAX_BUF_SIZE) {
+ buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, buffer, buflen);
+ if (!newbuf) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("Group %s not found.\n", state->name));
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group %s found: (%s, %d)\n", state->name,
+ state->grp->gr_name, state->grp->gr_gid));
+
+ /* gid=0 is an invalid value */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_group = true;
+ break;
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getgrnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_group) {
+ struct ldb_dn *dn;
+
+ DEBUG(7, ("Group %s does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->name));
+
+ dn = sysdb_group_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_remove_done, req);
+ }
+}
+
+static void get_gr_name_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+static void get_gr_name_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getgrgid-wrapper======================================================*/
+
+static void get_gr_gid_process(struct tevent_req *subreq);
+static void get_gr_gid_remove_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_gr_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->gid = gid;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_gr_gid_process, req);
+
+ return req;
+}
+
+static void get_gr_gid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ DEBUG(7, ("Searching group by gid (%d)\n", state->gid));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+again:
+ /* always zero out the group structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrgid_r(state->gid, state->grp,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (buflen < MAX_BUF_SIZE) {
+ buflen *= 2;
+ }
+ if (buflen > MAX_BUF_SIZE) {
+ buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, buffer, buflen);
+ if (!newbuf) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("Group %d not found.\n", state->gid));
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group %d found (%s, %d)\n", state->gid,
+ state->grp->gr_name, state->grp->gr_gid));
+
+ /* gid=0 is an invalid value */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+ delete_group = true;
+ break;
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
+ state->gid, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_group) {
+
+ DEBUG(7, ("Group %d does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->gid));
+
+ subreq = sysdb_delete_group_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->gid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_gid_remove_done, req);
+ }
+}
+
+static void get_gr_gid_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getgrent-wrapper======================================================*/
+
+struct enum_groups_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ struct sysdb_handle *handle;
+
+ struct group *grp;
+
+ size_t buflen;
+ char *buffer;
+
+ bool in_transaction;
+};
+
+static void enum_groups_process(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_groups_state *state;
+ enum nss_status status;
+
+ DEBUG(7, ("Enumerating groups\n"));
+
+ req = tevent_req_create(mem_ctx, &state, struct enum_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->handle = NULL;
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->buflen = DEFAULT_BUFSIZE;
+ state->buffer = talloc_size(state, state->buflen);
+ if (!state->buffer) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->in_transaction = false;
+
+ status = ctx->ops.setgrent();
+ if (status != NSS_STATUS_SUCCESS) {
+ tevent_req_error(req, EIO);
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_groups_process, req);
+
+ return req;
+
+fail:
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ struct sysdb_attrs *members;
+ char *newbuf;
+ int ret;
+
+ if (!state->in_transaction) {
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->in_transaction = true;
+ } else {
+ ret = sysdb_store_group_recv(subreq);
+ if (ret) {
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ DEBUG(2, ("Failed to store group. Ignoring.\n"));
+ }
+ talloc_zfree(subreq);
+ }
+
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ /* get entry */
+ status = ctx->ops.getgrent_r(state->grp,
+ state->buffer, state->buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (state->buflen < MAX_BUF_SIZE) {
+ state->buflen *= 2;
+ }
+ if (state->buflen > MAX_BUF_SIZE) {
+ state->buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ /* we are done here */
+ DEBUG(7, ("Enumeration completed.\n"));
+
+ ctx->ops.endgrent();
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+ return;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group found (%s, %d)\n",
+ state->grp->gr_name, state->grp->gr_gid));
+
+ /* gid=0 is an invalid value */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+
+ goto again; /* skip */
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_groups_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getgrent_r failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ ctx->ops.endgrent();
+ tevent_req_error(req, ret);
+}
+
+
+/* =Initgroups-wrapper====================================================*/
+
+static void get_initgr_process(struct tevent_req *subreq);
+static void get_initgr_groups_process(struct tevent_req *subreq);
+static void get_initgr_groups_done(struct tevent_req *subreq);
+static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t *gids, int num_gids);
+static int get_groups_by_gid_recv(struct tevent_req *req);
+static void get_groups_by_gid_process(struct tevent_req *subreq);
+static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid);
+static int get_group_from_gid_recv(struct tevent_req *req);
+static void get_group_from_gid_send_del_done(struct tevent_req *subreq);
+static void get_group_from_gid_send_add_done(struct tevent_req *subreq);
+
+
+static struct tevent_req *get_initgr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_initgr_process, req);
+
+ return req;
+}
+
+static void get_initgr_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.getpwnam_r(state->name, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ /* uid=0 or gid=0 are invalid values */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_initgr_groups_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ struct ldb_dn *dn;
+
+ dn = sysdb_user_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
+ }
+}
+
+static void get_initgr_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ enum nss_status status;
+ long int limit;
+ long int size;
+ long int num;
+ long int num_gids;
+ gid_t *gids;
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ num_gids = 0;
+ limit = 4096;
+ num = 4096;
+ size = num*sizeof(gid_t);
+ gids = talloc_size(state, size);
+ if (!gids) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->gid = state->pwd->pw_gid;
+
+again:
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.initgroups_dyn(state->name, state->gid, &num_gids,
+ &num, &gids, limit, &ret);
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (size < MAX_BUF_SIZE) {
+ num *= 2;
+ size = num*sizeof(gid_t);
+ }
+ if (size > MAX_BUF_SIZE) {
+ size = MAX_BUF_SIZE;
+ num = size/sizeof(gid_t);
+ }
+ limit = num;
+ gids = talloc_realloc_size(state, gids, size);
+ if (!gids) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ goto again; /* retry with more memory */
+
+ case NSS_STATUS_SUCCESS:
+ DEBUG(4, ("User [%s] appears to be member of %lu groups\n",
+ state->name, num_gids));
+
+ subreq = get_groups_by_gid_send(state, state->ev, state->handle,
+ state->ctx, state->domain,
+ gids, num_gids);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_initgr_groups_done, req);
+ break;
+
+ default:
+ DEBUG(2, ("proxy -> initgroups_dyn failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+static void get_initgr_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = get_groups_by_gid_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+struct get_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct proxy_ctx *ctx;
+ struct sss_domain_info *domain;
+
+ gid_t *gids;
+ int num_gids;
+ int cur_gid;
+};
+
+static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t *gids, int num_gids)
+{
+ struct tevent_req *req, *subreq;
+ struct get_groups_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct get_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ctx = ctx;
+ state->domain = domain;
+ state->gids = gids;
+ state->num_gids = num_gids;
+ state->cur_gid = 0;
+
+ subreq = get_group_from_gid_send(state, ev, handle, ctx, domain, gids[0]);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
+
+ return req;
+}
+
+static void get_groups_by_gid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct get_groups_state *state = tevent_req_data(req,
+ struct get_groups_state);
+ int ret;
+
+ ret = get_group_from_gid_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->cur_gid++;
+ if (state->cur_gid >= state->num_gids) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = get_group_from_gid_send(state,
+ state->ev, state->handle,
+ state->ctx, state->domain,
+ state->gids[state->cur_gid]);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
+}
+
+static int get_groups_by_gid_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ctx = ctx;
+ state->domain = domain;
+ state->gid = gid;
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrgid_r(state->gid, state->grp,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (buflen < MAX_BUF_SIZE) {
+ buflen *= 2;
+ }
+ if (buflen > MAX_BUF_SIZE) {
+ buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, buffer, buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ /* gid=0 is an invalid value */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+ delete_group = true;
+ break;
+ }
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ goto fail;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, get_group_from_gid_send_add_done, req);
+ break;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
+ state->gid, status));
+ ret = EIO;
+ goto fail;
+ }
+
+ if (delete_group) {
+ subreq = sysdb_delete_group_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->gid);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, get_group_from_gid_send_del_done, req);
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void get_group_from_gid_send_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void get_group_from_gid_send_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int get_group_from_gid_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Proxy_Id-Functions====================================================*/
+
+static void proxy_get_account_info_done(struct tevent_req *subreq);
+
+/* TODO: See if we can use async_req code */
+static void proxy_get_account_info(struct be_req *breq)
+{
+ struct tevent_req *subreq;
+ struct be_acct_req *ar;
+ struct proxy_ctx *ctx;
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ uid_t uid;
+ gid_t gid;
+
+ ar = talloc_get_type(breq->req_data, struct be_acct_req);
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct proxy_ctx);
+ ev = breq->be_ctx->ev;
+ sysdb = breq->be_ctx->sysdb;
+ domain = breq->be_ctx->domain;
+
+ if (be_is_offline(breq->be_ctx)) {
+ return proxy_reply(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
+ }
+
+ /* for now we support only core attrs */
+ if (ar->attr_type != BE_ATTR_CORE) {
+ return proxy_reply(breq, DP_ERR_FATAL, EINVAL, "Invalid attr type");
+ }
+
+ switch (ar->entry_type & 0xFFF) {
+ case BE_REQ_USER: /* user */
+ switch (ar->filter_type) {
+ case BE_FILTER_NAME:
+ if (strchr(ar->filter_value, '*')) {
+ subreq = enum_users_send(breq, ev, ctx,
+ sysdb, domain);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ } else {
+ subreq = get_pw_name_send(breq, ev, ctx,
+ sysdb, domain,
+ ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+
+ case BE_FILTER_IDNUM:
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ } else {
+ char *endptr;
+ errno = 0;
+ uid = (uid_t)strtol(ar->filter_value, &endptr, 0);
+ if (errno || *endptr || (ar->filter_value == endptr)) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ }
+ subreq = get_pw_uid_send(breq, ev, ctx,
+ sysdb, domain, uid);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ default:
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ break;
+
+ case BE_REQ_GROUP: /* group */
+ switch (ar->filter_type) {
+ case BE_FILTER_NAME:
+ if (strchr(ar->filter_value, '*')) {
+ subreq = enum_groups_send(breq, ev, ctx,
+ sysdb, domain);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ } else {
+ subreq = get_gr_name_send(breq, ev, ctx,
+ sysdb, domain,
+ ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ case BE_FILTER_IDNUM:
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ } else {
+ char *endptr;
+ errno = 0;
+ gid = (gid_t)strtol(ar->filter_value, &endptr, 0);
+ if (errno || *endptr || (ar->filter_value == endptr)) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ }
+ subreq = get_gr_gid_send(breq, ev, ctx,
+ sysdb, domain, gid);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ default:
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ break;
+
+ case BE_REQ_INITGROUPS: /* init groups for user */
+ if (ar->filter_type != BE_FILTER_NAME) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter value");
+ }
+ if (ctx->ops.initgroups_dyn == NULL) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENODEV, "Initgroups call not supported");
+ }
+ subreq = get_initgr_send(breq, ev, ctx, sysdb,
+ domain, ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+
+ default: /*fail*/
+ break;
+ }
+
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid request type");
+}
+
+static void proxy_get_account_info_done(struct tevent_req *subreq)
+{
+ struct be_req *breq = tevent_req_callback_data(subreq,
+ struct be_req);
+ int ret;
+ ret = proxy_default_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENXIO) {
+ DEBUG(2, ("proxy returned UNAVAIL error, going offline!\n"));
+ be_mark_offline(breq->be_ctx);
+ }
+ proxy_reply(breq, DP_ERR_FATAL, ret, NULL);
+ return;
+ }
+ proxy_reply(breq, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_shutdown(struct be_req *req)
+{
+ /* TODO: Clean up any internal data */
+ req->fn(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_auth_shutdown(struct be_req *req)
+{
+ talloc_free(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data);
+ req->fn(req, DP_ERR_OK, EOK, NULL);
+}
+
+struct bet_ops proxy_id_ops = {
+ .handler = proxy_get_account_info,
+ .finalize = proxy_shutdown
+};
+
+struct bet_ops proxy_auth_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+struct bet_ops proxy_access_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+struct bet_ops proxy_chpass_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+static void *proxy_dlsym(void *handle, const char *functemp, char *libname)
+{
+ char *funcname;
+ void *funcptr;
+
+ funcname = talloc_asprintf(NULL, functemp, libname);
+ if (funcname == NULL) return NULL;
+
+ funcptr = dlsym(handle, funcname);
+ talloc_free(funcname);
+
+ return funcptr;
+}
+
+int sssm_proxy_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ struct proxy_ctx *ctx;
+ char *libname;
+ char *libpath;
+ void *handle;
+ int ret;
+
+ ctx = talloc_zero(bectx, struct proxy_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+
+ ret = confdb_get_int(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT, 600,
+ &ctx->entry_cache_timeout);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_PROXY_LIBNAME, NULL, &libname);
+ if (ret != EOK) goto done;
+ if (libname == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ libpath = talloc_asprintf(ctx, "libnss_%s.so.2", libname);
+ if (!libpath) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ handle = dlopen(libpath, RTLD_NOW);
+ if (!handle) {
+ DEBUG(0, ("Unable to load %s module with path, error: %s\n",
+ libpath, dlerror()));
+ ret = ELIBACC;
+ goto done;
+ }
+
+ ctx->ops.getpwnam_r = proxy_dlsym(handle, "_nss_%s_getpwnam_r", libname);
+ if (!ctx->ops.getpwnam_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getpwuid_r = proxy_dlsym(handle, "_nss_%s_getpwuid_r", libname);
+ if (!ctx->ops.getpwuid_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.setpwent = proxy_dlsym(handle, "_nss_%s_setpwent", libname);
+ if (!ctx->ops.setpwent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getpwent_r = proxy_dlsym(handle, "_nss_%s_getpwent_r", libname);
+ if (!ctx->ops.getpwent_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.endpwent = proxy_dlsym(handle, "_nss_%s_endpwent", libname);
+ if (!ctx->ops.endpwent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrnam_r = proxy_dlsym(handle, "_nss_%s_getgrnam_r", libname);
+ if (!ctx->ops.getgrnam_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrgid_r = proxy_dlsym(handle, "_nss_%s_getgrgid_r", libname);
+ if (!ctx->ops.getgrgid_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.setgrent = proxy_dlsym(handle, "_nss_%s_setgrent", libname);
+ if (!ctx->ops.setgrent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrent_r = proxy_dlsym(handle, "_nss_%s_getgrent_r", libname);
+ if (!ctx->ops.getgrent_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.endgrent = proxy_dlsym(handle, "_nss_%s_endgrent", libname);
+ if (!ctx->ops.endgrent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.initgroups_dyn = proxy_dlsym(handle, "_nss_%s_initgroups_dyn",
+ libname);
+ if (!ctx->ops.initgroups_dyn) {
+ DEBUG(1, ("The '%s' library does not provides the "
+ "_nss_XXX_initgroups_dyn function!\n"
+ "initgroups will be slow as it will require "
+ "full groups enumeration!\n", libname));
+ }
+
+ *ops = &proxy_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_proxy_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ struct proxy_auth_ctx *ctx;
+ int ret;
+
+ ctx = talloc(bectx, struct proxy_auth_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+
+ ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_PROXY_PAM_TARGET, NULL,
+ &ctx->pam_target);
+ if (ret != EOK) goto done;
+ if (!ctx->pam_target) {
+ DEBUG(1, ("Missing option proxy_pam_target.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ *ops = &proxy_auth_ops;
+ *pvt_data = ctx;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_proxy_access_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ int ret;
+ ret = sssm_proxy_auth_init(bectx, ops, pvt_data);
+ *ops = &proxy_access_ops;
+ return ret;
+}
+
+int sssm_proxy_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ int ret;
+ ret = sssm_proxy_auth_init(bectx, ops, pvt_data);
+ *ops = &proxy_chpass_ops;
+ return ret;
+}
diff --git a/src/providers/sssd_be.exports b/src/providers/sssd_be.exports
new file mode 100644
index 00000000..9afa106b
--- /dev/null
+++ b/src/providers/sssd_be.exports
@@ -0,0 +1,4 @@
+{
+ global:
+ *;
+};
diff --git a/src/python/pysss.c b/src/python/pysss.c
new file mode 100644
index 00000000..8011ed67
--- /dev/null
+++ b/src/python/pysss.c
@@ -0,0 +1,937 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <Python.h>
+#include <structmember.h>
+#include <talloc.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+#define TRANSACTION_WAIT(trs, retval) do { \
+ while (!trs->transaction_done) { \
+ tevent_loop_once(trs->self->ev); \
+ } \
+ retval = trs->error; \
+ if (retval) { \
+ PyErr_SetSssError(retval); \
+ goto fail; \
+ } \
+} while(0)
+
+/*
+ * function taken from samba sources tree as of Aug 20 2009,
+ * file source4/lib/ldb/pyldb.c
+ */
+static char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list,
+ const char *paramname)
+{
+ char **ret;
+ int i;
+
+ ret = talloc_array(NULL, char *, PyList_Size(list)+1);
+ for (i = 0; i < PyList_Size(list); i++) {
+ PyObject *item = PyList_GetItem(list, i);
+ if (!PyString_Check(item)) {
+ PyErr_Format(PyExc_TypeError, "%s should be strings", paramname);
+ return NULL;
+ }
+ ret[i] = talloc_strndup(ret, PyString_AsString(item),
+ PyString_Size(item));
+ }
+
+ ret[i] = NULL;
+ return ret;
+}
+
+/*
+ * The sss.local object
+ */
+typedef struct {
+ PyObject_HEAD
+
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct confdb_ctx *confdb;
+
+ struct sss_domain_info *local;
+
+ int lock;
+ int unlock;
+} PySssLocalObject;
+
+/*
+ * The transaction object
+ */
+struct py_sss_transaction {
+ PySssLocalObject *self;
+ struct ops_ctx *ops;
+
+ struct sysdb_handle *handle;
+ bool transaction_done;
+ int error;
+};
+
+/*
+ * Error reporting
+ */
+static void PyErr_SetSssErrorWithMessage(int ret, const char *message)
+{
+ PyObject *exc = Py_BuildValue(discard_const_p(char, "(is)"),
+ ret, message);
+
+ PyErr_SetObject(PyExc_IOError, exc);
+ Py_XDECREF(exc);
+}
+
+static void PyErr_SetSssError(int ret)
+{
+ PyErr_SetSssErrorWithMessage(ret, strerror(ret));
+}
+
+/*
+ * Common init of all methods
+ */
+struct tools_ctx *init_ctx(TALLOC_CTX *mem_ctx,
+ PySssLocalObject *self)
+{
+ struct ops_ctx *octx = NULL;
+ struct tools_ctx *tctx = NULL;
+
+ tctx = talloc_zero(self->mem_ctx, struct tools_ctx);
+ if (tctx == NULL) {
+ return NULL;
+ }
+
+ tctx->ev = self->ev;
+ tctx->confdb = self->confdb;
+ tctx->sysdb = self->sysdb;
+ tctx->local = self->local;
+ /* tctx->nctx is NULL here, which is OK since we don't parse domains
+ * in the python bindings (yet?) */
+
+ octx = talloc_zero(tctx, struct ops_ctx);
+ if (octx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ octx->domain = self->local;
+
+ tctx->octx = octx;
+ return tctx;
+}
+
+/*
+ * Add a user
+ */
+PyDoc_STRVAR(py_sss_useradd__doc__,
+ "Add a user named ``username``.\n\n"
+ ":param username: name of the user\n\n"
+ ":param kwargs: Keyword arguments that customize the operation\n\n"
+ "* useradd can be customized further with keyword arguments:\n"
+ " * ``uid``: The UID of the user\n"
+ " * ``gid``: The GID of the user\n"
+ " * ``gecos``: The comment string\n"
+ " * ``homedir``: Home directory\n"
+ " * ``shell``: Login shell\n"
+ " * ``skel``: Specify an alternative skeleton directory\n"
+ " * ``create_home``: (bool) Force creation of home directory on or off\n"
+ " * ``groups``: List of groups the user is member of\n");
+
+
+static PyObject *py_sss_useradd(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ unsigned long uid = 0;
+ unsigned long gid = 0;
+ const char *gecos = NULL;
+ const char *home = NULL;
+ const char *shell = NULL;
+ const char *skel = NULL;
+ char *username = NULL;
+ int ret;
+ const char * const kwlist[] = { "username", "uid", "gid", "gecos",
+ "homedir", "shell", "skel",
+ "create_home", "groups", NULL };
+ PyObject *py_groups = Py_None;
+ PyObject *py_create_home = Py_None;
+ int create_home = 0;
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ discard_const_p(char, "s|kkssssO!O!"),
+ discard_const_p(char *, kwlist),
+ &username,
+ &uid,
+ &gid,
+ &gecos,
+ &home,
+ &shell,
+ &skel,
+ &PyBool_Type,
+ &py_create_home,
+ &PyList_Type,
+ &py_groups)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (py_groups != Py_None) {
+ tctx->octx->addgroups = PyList_AsStringList(tctx, py_groups, "groups");
+ if (!tctx->octx->addgroups) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ }
+
+ /* user-wise the parameter is only bool - do or don't,
+ * however we must have a third state - undecided, pick default */
+ if (py_create_home == Py_True) {
+ create_home = DO_CREATE_HOME;
+ } else if (py_create_home == Py_False) {
+ create_home = DO_NOT_CREATE_HOME;
+ }
+
+ tctx->octx->name = username;
+ tctx->octx->uid = uid;
+
+ /* fill in defaults */
+ ret = useradd_defaults(tctx,
+ self->confdb,
+ tctx->octx, gecos,
+ home, shell,
+ create_home,
+ skel);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+
+ /* Add the user within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* useradd */
+ ret = useradd(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* Create user's home directory and/or mail spool */
+ if (tctx->octx->create_homedir) {
+ /* We need to know the UID and GID of the user, if
+ * sysdb did assign it automatically, do a lookup */
+ if (tctx->octx->uid == 0 || tctx->octx->gid == 0) {
+ ret = sysdb_getpwnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ tctx->octx->name,
+ tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+ }
+
+ ret = create_homedir(tctx,
+ tctx->octx->skeldir,
+ tctx->octx->home,
+ tctx->octx->name,
+ tctx->octx->uid,
+ tctx->octx->gid,
+ tctx->octx->umask);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+
+ /* failure here should not be fatal */
+ create_mail_spool(tctx,
+ tctx->octx->name,
+ tctx->octx->maildir,
+ tctx->octx->uid,
+ tctx->octx->gid);
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+/*
+ * Delete a user
+ */
+PyDoc_STRVAR(py_sss_userdel__doc__,
+ "Remove the user named ``username``.\n\n"
+ ":param username: Name of user being removed\n"
+ ":param kwargs: Keyword arguments that customize the operation\n\n"
+ "* userdel can be customized further with keyword arguments:\n"
+ " * ``force``: (bool) Force removal of files not owned by the user\n"
+ " * ``remove``: (bool) Toggle removing home directory and mail spool\n");
+
+static PyObject *py_sss_userdel(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ char *username = NULL;
+ int ret;
+ PyObject *py_remove = Py_None;
+ int remove_home = 0;
+ PyObject *py_force = Py_None;
+ const char * const kwlist[] = { "username", "remove", "force", NULL };
+
+ if(!PyArg_ParseTupleAndKeywords(args, kwds,
+ discard_const_p(char, "s|O!O!"),
+ discard_const_p(char *, kwlist),
+ &username,
+ &PyBool_Type,
+ &py_remove,
+ &PyBool_Type,
+ &py_force)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ tctx->octx->name = username;
+
+ if (py_remove == Py_True) {
+ remove_home = DO_REMOVE_HOME;
+ } else if (py_remove == Py_False) {
+ remove_home = DO_NOT_REMOVE_HOME;
+ }
+
+ /*
+ * Fills in defaults for ops_ctx user did not specify.
+ */
+ ret = userdel_defaults(tctx,
+ tctx->confdb,
+ tctx->octx,
+ remove_home);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+
+ if (tctx->octx->remove_homedir) {
+ ret = sysdb_getpwnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ tctx->octx->name,
+ tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+ }
+
+ /* Delete the user within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ ret = userdel(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ if (tctx->octx->remove_homedir) {
+ ret = remove_homedir(tctx,
+ tctx->octx->home,
+ tctx->octx->maildir,
+ tctx->octx->name,
+ tctx->octx->uid,
+ (py_force == Py_True));
+ if (ret != EOK) {
+ PyErr_SetSssError(ret);
+ goto fail;
+ }
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+/*
+ * Modify a user
+ */
+PyDoc_STRVAR(py_sss_usermod__doc__,
+ "Modify a user.\n\n"
+ ":param username: Name of user being modified\n\n"
+ ":param kwargs: Keyword arguments that customize the operation\n\n"
+ "* usermod can be customized further with keyword arguments:\n"
+ " * ``uid``: The UID of the user\n"
+ " * ``gid``: The GID of the user\n"
+ " * ``gecos``: The comment string\n"
+ " * ``homedir``: Home directory\n"
+ " * ``shell``: Login shell\n"
+ " * ``addgroups``: List of groups to add the user to\n"
+ " * ``rmgroups``: List of groups to remove the user from\n"
+ " * ``lock``: Lock or unlock the account\n");
+
+static PyObject *py_sss_usermod(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ int ret;
+ PyObject *py_addgroups = Py_None;
+ PyObject *py_rmgroups = Py_None;
+ unsigned long uid = 0;
+ unsigned long gid = 0;
+ char *gecos = NULL;
+ char *home = NULL;
+ char *shell = NULL;
+ char *username = NULL;
+ unsigned long lock = 0;
+ const char * const kwlist[] = { "username", "uid", "gid", "lock",
+ "gecos", "homedir", "shell",
+ "addgroups", "rmgroups", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ discard_const_p(char, "s|kkksssO!O!"),
+ discard_const_p(char *, kwlist),
+ &username,
+ &uid,
+ &gid,
+ &lock,
+ &gecos,
+ &home,
+ &shell,
+ &PyList_Type,
+ &py_addgroups,
+ &PyList_Type,
+ &py_rmgroups)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (lock && lock != DO_LOCK && lock != DO_UNLOCK) {
+ PyErr_SetString(PyExc_ValueError,
+ "Unkown value for lock parameter");
+ goto fail;
+ }
+
+ if (py_addgroups != Py_None) {
+ tctx->octx->addgroups = PyList_AsStringList(tctx,
+ py_addgroups,
+ "addgroups");
+ if (!tctx->octx->addgroups) {
+ return NULL;
+ }
+ }
+
+ if (py_rmgroups != Py_None) {
+ tctx->octx->rmgroups = PyList_AsStringList(tctx,
+ py_rmgroups,
+ "rmgroups");
+ if (!tctx->octx->rmgroups) {
+ return NULL;
+ }
+ }
+
+ tctx->octx->name = username;
+ tctx->octx->uid = uid;
+ tctx->octx->gid = gid;
+ tctx->octx->gecos = gecos;
+ tctx->octx->home = home;
+ tctx->octx->shell = shell;
+ tctx->octx->lock = lock;
+
+ /* Modify the user within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* usermod */
+ ret = usermod(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+/*
+ * Add a group
+ */
+PyDoc_STRVAR(py_sss_groupadd__doc__,
+ "Add a group.\n\n"
+ ":param groupname: Name of group being added\n\n"
+ ":param kwargs: Keyword arguments ro customize the operation\n\n"
+ "* groupmod can be customized further with keyword arguments:\n"
+ " * ``gid``: The GID of the group\n");
+
+static PyObject *py_sss_groupadd(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ char *groupname;
+ unsigned long gid = 0;
+ int ret;
+ const char * const kwlist[] = { "groupname", "gid", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ discard_const_p(char, "s|k"),
+ discard_const_p(char *, kwlist),
+ &groupname,
+ &gid)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ tctx->octx->name = groupname;
+ tctx->octx->gid = gid;
+
+ /* Add the group within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* groupadd */
+ ret = groupadd(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+/*
+ * Delete a group
+ */
+PyDoc_STRVAR(py_sss_groupdel__doc__,
+ "Remove a group.\n\n"
+ ":param groupname: Name of group being removed\n");
+
+static PyObject *py_sss_groupdel(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ char *groupname = NULL;
+ int ret;
+
+ if(!PyArg_ParseTuple(args, discard_const_p(char, "s"), &groupname)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ tctx->octx->name = groupname;
+
+ /* Remove the group within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* groupdel */
+ ret = groupdel(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+/*
+ * Modify a group
+ */
+PyDoc_STRVAR(py_sss_groupmod__doc__,
+"Modify a group.\n\n"
+":param groupname: Name of group being modified\n\n"
+":param kwargs: Keyword arguments ro customize the operation\n\n"
+"* groupmod can be customized further with keyword arguments:\n"
+" * ``gid``: The GID of the group\n\n"
+" * ``addgroups``: Groups to add the group to\n\n"
+" * ``rmgroups``: Groups to remove the group from\n\n");
+
+static PyObject *py_sss_groupmod(PySssLocalObject *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct tools_ctx *tctx = NULL;
+ int ret;
+ PyObject *py_addgroups = Py_None;
+ PyObject *py_rmgroups = Py_None;
+ unsigned long gid = 0;
+ char *groupname = NULL;
+ const char * const kwlist[] = { "groupname", "gid", "addgroups",
+ "rmgroups", NULL };
+
+ /* parse arguments */
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ discard_const_p(char, "s|kO!O!"),
+ discard_const_p(char *, kwlist),
+ &groupname,
+ &gid,
+ &PyList_Type,
+ &py_addgroups,
+ &PyList_Type,
+ &py_rmgroups)) {
+ goto fail;
+ }
+
+ tctx = init_ctx(self->mem_ctx, self);
+ if (!tctx) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (py_addgroups != Py_None) {
+ tctx->octx->addgroups = PyList_AsStringList(tctx,
+ py_addgroups,
+ "addgroups");
+ if (!tctx->octx->addgroups) {
+ return NULL;
+ }
+ }
+
+ if (py_rmgroups != Py_None) {
+ tctx->octx->rmgroups = PyList_AsStringList(tctx,
+ py_rmgroups,
+ "rmgroups");
+ if (!tctx->octx->rmgroups) {
+ return NULL;
+ }
+ }
+
+ tctx->octx->name = groupname;
+ tctx->octx->gid = gid;
+
+ /* Modify the group within a transaction */
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ /* groupmod */
+ ret = groupmod(tctx, self->ev,
+ self->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ end_transaction(tctx);
+ if (tctx->error) {
+ PyErr_SetSssError(tctx->error);
+ goto fail;
+ }
+
+ talloc_zfree(tctx);
+ Py_RETURN_NONE;
+
+fail:
+ talloc_zfree(tctx);
+ return NULL;
+}
+
+
+/*** python plumbing begins here ***/
+
+/*
+ * The sss.local destructor
+ */
+static void PySssLocalObject_dealloc(PySssLocalObject *self)
+{
+ talloc_free(self->mem_ctx);
+ self->ob_type->tp_free((PyObject*) self);
+}
+
+/*
+ * The sss.local constructor
+ */
+static PyObject *PySssLocalObject_new(PyTypeObject *type,
+ PyObject *args,
+ PyObject *kwds)
+{
+ TALLOC_CTX *mem_ctx;
+ PySssLocalObject *self;
+ char *confdb_path;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ self = (PySssLocalObject *) type->tp_alloc(type, 0);
+ if (self == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ self->mem_ctx = mem_ctx;
+
+ self->ev = tevent_context_init(mem_ctx);
+ if (self->ev == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(EIO, "Cannot create event context");
+ return NULL;
+ }
+
+ confdb_path = talloc_asprintf(self->mem_ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (confdb_path == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ /* Connect to the conf db */
+ ret = confdb_init(self->mem_ctx, &self->confdb, confdb_path);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret,
+ "Could not initialize connection to the confdb\n");
+ return NULL;
+ }
+
+ ret = confdb_get_domain(self->confdb, "local", &self->local);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret, "Cannot get local domain");
+ return NULL;
+ }
+
+ /* open 'local' sysdb at default path */
+ ret = sysdb_domain_init(self->mem_ctx, self->ev, self->local, DB_PATH, &self->sysdb);
+ if (ret != EOK) {
+ talloc_free(mem_ctx);
+ PyErr_SetSssErrorWithMessage(ret,
+ "Could not initialize connection to the sysdb\n");
+ return NULL;
+ }
+
+ self->lock = DO_LOCK;
+ self->unlock = DO_UNLOCK;
+
+ return (PyObject *) self;
+}
+
+/*
+ * sss.local object methods
+ */
+static PyMethodDef sss_local_methods[] = {
+ { "useradd", (PyCFunction) py_sss_useradd,
+ METH_KEYWORDS, py_sss_useradd__doc__
+ },
+ { "userdel", (PyCFunction) py_sss_userdel,
+ METH_KEYWORDS, py_sss_userdel__doc__
+ },
+ { "usermod", (PyCFunction) py_sss_usermod,
+ METH_KEYWORDS, py_sss_usermod__doc__
+ },
+ { "groupadd", (PyCFunction) py_sss_groupadd,
+ METH_KEYWORDS, py_sss_groupadd__doc__
+ },
+ { "groupdel", (PyCFunction) py_sss_groupdel,
+ METH_KEYWORDS, py_sss_groupdel__doc__
+ },
+ { "groupmod", (PyCFunction) py_sss_groupmod,
+ METH_KEYWORDS, py_sss_groupmod__doc__
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+static PyMemberDef sss_members[] = {
+ { discard_const_p(char, "lock"), T_INT,
+ offsetof(PySssLocalObject, lock), RO },
+ { discard_const_p(char, "unlock"), T_INT,
+ offsetof(PySssLocalObject, unlock), RO },
+ {NULL} /* Sentinel */
+};
+
+/*
+ * sss.local object properties
+ */
+static PyTypeObject pysss_local_type = {
+ PyObject_HEAD_INIT(NULL)
+ .tp_name = "sss.local",
+ .tp_basicsize = sizeof(PySssLocalObject),
+ .tp_new = PySssLocalObject_new,
+ .tp_dealloc = (destructor) PySssLocalObject_dealloc,
+ .tp_methods = sss_local_methods,
+ .tp_members = sss_members,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "SSS DB manipulation",
+};
+
+/*
+ * Module methods
+ */
+static PyMethodDef module_methods[] = {
+ {NULL} /* Sentinel */
+};
+
+/*
+ * Module initialization
+ */
+PyMODINIT_FUNC
+initpysss(void)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&pysss_local_type) < 0)
+ return;
+
+ m = Py_InitModule(discard_const_p(char, "pysss"), module_methods);
+ if (m == NULL)
+ return;
+
+ Py_INCREF(&pysss_local_type);
+ PyModule_AddObject(m, discard_const_p(char, "local"), (PyObject *)&pysss_local_type);
+}
+
diff --git a/src/resolv/ares/ares_data.c b/src/resolv/ares/ares_data.c
new file mode 100644
index 00000000..1cccaa55
--- /dev/null
+++ b/src/resolv/ares/ares_data.c
@@ -0,0 +1,140 @@
+/* $Id: ares_data.c,v 1.2 2009-11-20 09:06:33 yangtse Exp $ */
+
+/* Copyright (C) 2009 by Daniel Stenberg
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "ares.h"
+#include "ares_data.h"
+
+/*
+** ares_free_data() - c-ares external API function.
+**
+** This function must be used by the application to free data memory that
+** has been internally allocated by some c-ares function and for which a
+** pointer has already been returned to the calling application. The list
+** of c-ares functions returning pointers that must be free'ed using this
+** function is:
+**
+** ares_parse_srv_reply()
+** ares_parse_txt_reply()
+*/
+
+void _ares_free_data(void *dataptr)
+{
+ struct ares_data *ptr;
+
+ if (!dataptr)
+ return;
+
+ ptr = (void *)((char *)dataptr - offsetof(struct ares_data, data));
+
+ if (ptr->mark != ARES_DATATYPE_MARK)
+ return;
+
+ switch (ptr->type)
+ {
+ case ARES_DATATYPE_SRV_REPLY:
+
+ if (ptr->data.srv_reply.next)
+ _ares_free_data(ptr->data.srv_reply.next);
+ if (ptr->data.srv_reply.host)
+ free(ptr->data.srv_reply.host);
+ break;
+
+ case ARES_DATATYPE_TXT_REPLY:
+
+ if (ptr->data.txt_reply.next)
+ _ares_free_data(ptr->data.txt_reply.next);
+ if (ptr->data.txt_reply.txt)
+ free(ptr->data.txt_reply.txt);
+ break;
+
+ default:
+ return;
+ }
+
+ free(ptr);
+}
+
+
+/*
+** ares_malloc_data() - c-ares internal helper function.
+**
+** This function allocates memory for a c-ares private ares_data struct
+** for the specified ares_datatype, initializes c-ares private fields
+** and zero initializes those which later might be used from the public
+** API. It returns an interior pointer which can be passed by c-ares
+** functions to the calling application, and that must be free'ed using
+** c-ares external API function ares_free_data().
+*/
+
+void *_ares_malloc_data(ares_datatype type)
+{
+ struct ares_data *ptr;
+
+ ptr = malloc(sizeof(struct ares_data));
+ if (!ptr)
+ return NULL;
+
+ switch (type)
+ {
+ case ARES_DATATYPE_SRV_REPLY:
+ ptr->data.srv_reply.next = NULL;
+ ptr->data.srv_reply.host = NULL;
+ ptr->data.srv_reply.priority = 0;
+ ptr->data.srv_reply.weight = 0;
+ ptr->data.srv_reply.port = 0;
+ break;
+
+ case ARES_DATATYPE_TXT_REPLY:
+ ptr->data.txt_reply.next = NULL;
+ ptr->data.txt_reply.txt = NULL;
+ ptr->data.txt_reply.length = 0;
+ break;
+
+ default:
+ free(ptr);
+ return NULL;
+ }
+
+ ptr->mark = ARES_DATATYPE_MARK;
+ ptr->type = type;
+
+ return &ptr->data;
+}
+
+
+/*
+** ares_get_datatype() - c-ares internal helper function.
+**
+** This function returns the ares_datatype of the data stored in a
+** private ares_data struct when given the public API pointer.
+*/
+
+ares_datatype ares_get_datatype(void * dataptr)
+{
+ struct ares_data *ptr;
+
+ ptr = (void *)((char *)dataptr - offsetof(struct ares_data, data));
+
+ if (ptr->mark == ARES_DATATYPE_MARK)
+ return ptr->type;
+
+ return ARES_DATATYPE_UNKNOWN;
+}
diff --git a/src/resolv/ares/ares_data.h b/src/resolv/ares/ares_data.h
new file mode 100644
index 00000000..d3606314
--- /dev/null
+++ b/src/resolv/ares/ares_data.h
@@ -0,0 +1,68 @@
+/* $Id: ares_data.h,v 1.2 2009-11-23 12:03:33 yangtse Exp $ */
+
+/* Copyright (C) 2009 by Daniel Stenberg
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+#ifndef HAVE_ARES_DATA
+#include "resolv/ares/ares_parse_txt_reply.h"
+#include "resolv/ares/ares_parse_srv_reply.h"
+#endif /* HAVE_ARES_DATA */
+
+typedef enum {
+ ARES_DATATYPE_UNKNOWN = 1, /* unknown data type - introduced in 1.7.0 */
+ ARES_DATATYPE_SRV_REPLY, /* struct ares_srv_reply - introduced in 1.7.0 */
+ ARES_DATATYPE_TXT_REPLY, /* struct ares_txt_reply - introduced in 1.7.0 */
+#if 0
+ ARES_DATATYPE_ADDR6TTL, /* struct ares_addrttl */
+ ARES_DATATYPE_ADDRTTL, /* struct ares_addr6ttl */
+ ARES_DATATYPE_HOSTENT, /* struct hostent */
+ ARES_DATATYPE_OPTIONS, /* struct ares_options */
+#endif
+ ARES_DATATYPE_LAST /* not used - introduced in 1.7.0 */
+} ares_datatype;
+
+#define ARES_DATATYPE_MARK 0xbead
+
+/*
+ * ares_data struct definition is internal to c-ares and shall not
+ * be exposed by the public API in order to allow future changes
+ * and extensions to it without breaking ABI. This will be used
+ * internally by c-ares as the container of multiple types of data
+ * dynamically allocated for which a reference will be returned
+ * to the calling application.
+ *
+ * c-ares API functions returning a pointer to c-ares internally
+ * allocated data will actually be returning an interior pointer
+ * into this ares_data struct.
+ *
+ * All this is 'invisible' to the calling application, the only
+ * requirement is that this kind of data must be free'ed by the
+ * calling application using ares_free_data() with the pointer
+ * it has received from a previous c-ares function call.
+ */
+
+struct ares_data {
+ ares_datatype type; /* Actual data type identifier. */
+ unsigned int mark; /* Private ares_data signature. */
+ union {
+ struct ares_txt_reply txt_reply;
+ struct ares_srv_reply srv_reply;
+ } data;
+};
+
+void *_ares_malloc_data(ares_datatype type);
+void _ares_free_data(void *dataptr);
+
+ares_datatype ares_get_datatype(void * dataptr);
diff --git a/src/resolv/ares/ares_dns.h b/src/resolv/ares/ares_dns.h
new file mode 100644
index 00000000..c0a9dda6
--- /dev/null
+++ b/src/resolv/ares/ares_dns.h
@@ -0,0 +1,91 @@
+/* $Id: ares_dns.h,v 1.8 2007-02-16 14:22:08 yangtse Exp $ */
+
+/* Copyright 1998 by the Massachusetts Institute of Technology.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+#ifndef ARES__DNS_H
+#define ARES__DNS_H
+
+#define DNS__16BIT(p) (((p)[0] << 8) | (p)[1])
+#define DNS__32BIT(p) (((p)[0] << 24) | ((p)[1] << 16) | \
+ ((p)[2] << 8) | (p)[3])
+
+#define DNS__SET16BIT(p, v) (((p)[0] = (unsigned char)(((v) >> 8) & 0xff)), \
+ ((p)[1] = (unsigned char)((v) & 0xff)))
+#define DNS__SET32BIT(p, v) (((p)[0] = (unsigned char)(((v) >> 24) & 0xff)), \
+ ((p)[1] = (unsigned char)(((v) >> 16) & 0xff)), \
+ ((p)[2] = (unsigned char)(((v) >> 8) & 0xff)), \
+ ((p)[3] = (unsigned char)((v) & 0xff)))
+
+#if 0
+/* we cannot use this approach on systems where we can't access 16/32 bit
+ data on un-aligned addresses */
+#define DNS__16BIT(p) ntohs(*(unsigned short*)(p))
+#define DNS__32BIT(p) ntohl(*(unsigned long*)(p))
+#define DNS__SET16BIT(p, v) *(unsigned short*)(p) = htons(v)
+#define DNS__SET32BIT(p, v) *(unsigned long*)(p) = htonl(v)
+#endif
+
+/* Macros for parsing a DNS header */
+#define DNS_HEADER_QID(h) DNS__16BIT(h)
+#define DNS_HEADER_QR(h) (((h)[2] >> 7) & 0x1)
+#define DNS_HEADER_OPCODE(h) (((h)[2] >> 3) & 0xf)
+#define DNS_HEADER_AA(h) (((h)[2] >> 2) & 0x1)
+#define DNS_HEADER_TC(h) (((h)[2] >> 1) & 0x1)
+#define DNS_HEADER_RD(h) ((h)[2] & 0x1)
+#define DNS_HEADER_RA(h) (((h)[3] >> 7) & 0x1)
+#define DNS_HEADER_Z(h) (((h)[3] >> 4) & 0x7)
+#define DNS_HEADER_RCODE(h) ((h)[3] & 0xf)
+#define DNS_HEADER_QDCOUNT(h) DNS__16BIT((h) + 4)
+#define DNS_HEADER_ANCOUNT(h) DNS__16BIT((h) + 6)
+#define DNS_HEADER_NSCOUNT(h) DNS__16BIT((h) + 8)
+#define DNS_HEADER_ARCOUNT(h) DNS__16BIT((h) + 10)
+
+/* Macros for constructing a DNS header */
+#define DNS_HEADER_SET_QID(h, v) DNS__SET16BIT(h, v)
+#define DNS_HEADER_SET_QR(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 7))
+#define DNS_HEADER_SET_OPCODE(h, v) ((h)[2] |= (unsigned char)(((v) & 0xf) << 3))
+#define DNS_HEADER_SET_AA(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 2))
+#define DNS_HEADER_SET_TC(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 1))
+#define DNS_HEADER_SET_RD(h, v) ((h)[2] |= (unsigned char)((v) & 0x1))
+#define DNS_HEADER_SET_RA(h, v) ((h)[3] |= (unsigned char)(((v) & 0x1) << 7))
+#define DNS_HEADER_SET_Z(h, v) ((h)[3] |= (unsigned char)(((v) & 0x7) << 4))
+#define DNS_HEADER_SET_RCODE(h, v) ((h)[3] |= (unsigned char)((v) & 0xf))
+#define DNS_HEADER_SET_QDCOUNT(h, v) DNS__SET16BIT((h) + 4, v)
+#define DNS_HEADER_SET_ANCOUNT(h, v) DNS__SET16BIT((h) + 6, v)
+#define DNS_HEADER_SET_NSCOUNT(h, v) DNS__SET16BIT((h) + 8, v)
+#define DNS_HEADER_SET_ARCOUNT(h, v) DNS__SET16BIT((h) + 10, v)
+
+/* Macros for parsing the fixed part of a DNS question */
+#define DNS_QUESTION_TYPE(q) DNS__16BIT(q)
+#define DNS_QUESTION_CLASS(q) DNS__16BIT((q) + 2)
+
+/* Macros for constructing the fixed part of a DNS question */
+#define DNS_QUESTION_SET_TYPE(q, v) DNS__SET16BIT(q, v)
+#define DNS_QUESTION_SET_CLASS(q, v) DNS__SET16BIT((q) + 2, v)
+
+/* Macros for parsing the fixed part of a DNS resource record */
+#define DNS_RR_TYPE(r) DNS__16BIT(r)
+#define DNS_RR_CLASS(r) DNS__16BIT((r) + 2)
+#define DNS_RR_TTL(r) DNS__32BIT((r) + 4)
+#define DNS_RR_LEN(r) DNS__16BIT((r) + 8)
+
+/* Macros for constructing the fixed part of a DNS resource record */
+#define DNS_RR_SET_TYPE(r) DNS__SET16BIT(r, v)
+#define DNS_RR_SET_CLASS(r) DNS__SET16BIT((r) + 2, v)
+#define DNS_RR_SET_TTL(r) DNS__SET32BIT((r) + 4, v)
+#define DNS_RR_SET_LEN(r) DNS__SET16BIT((r) + 8, v)
+
+#endif /* ARES__DNS_H */
diff --git a/src/resolv/ares/ares_parse_srv_reply.c b/src/resolv/ares/ares_parse_srv_reply.c
new file mode 100644
index 00000000..086c4dba
--- /dev/null
+++ b/src/resolv/ares/ares_parse_srv_reply.c
@@ -0,0 +1,183 @@
+/*
+ SSSD
+
+ Async resolver - SRV records parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This code is based on other c-ares parsing licensed as follows:
+
+ * Copyright 1998 by the Massachusetts Institute of Technology.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ares.h"
+/* this drags in some private macros c-ares uses */
+#include "ares_dns.h"
+#include "ares_data.h"
+
+#include "ares_parse_srv_reply.h"
+
+int _ares_parse_srv_reply (const unsigned char *abuf, int alen,
+ struct ares_srv_reply **srv_out)
+{
+ unsigned int qdcount, ancount, i;
+ const unsigned char *aptr, *vptr;
+ int status, rr_type, rr_class, rr_len;
+ long len;
+ char *hostname = NULL, *rr_name = NULL;
+ struct ares_srv_reply *srv_head = NULL;
+ struct ares_srv_reply *srv_last = NULL;
+ struct ares_srv_reply *srv_curr;
+
+ /* Set *srv_out to NULL for all failure cases. */
+ *srv_out = NULL;
+
+ /* Give up if abuf doesn't have room for a header. */
+ if (alen < HFIXEDSZ)
+ return ARES_EBADRESP;
+
+ /* Fetch the question and answer count from the header. */
+ qdcount = DNS_HEADER_QDCOUNT (abuf);
+ ancount = DNS_HEADER_ANCOUNT (abuf);
+ if (qdcount != 1)
+ return ARES_EBADRESP;
+ if (ancount == 0)
+ return ARES_ENODATA;
+
+ /* Expand the name from the question, and skip past the question. */
+ aptr = abuf + HFIXEDSZ;
+ status = ares_expand_name (aptr, abuf, alen, &hostname, &len);
+ if (status != ARES_SUCCESS)
+ return status;
+
+ if (aptr + len + QFIXEDSZ > abuf + alen)
+ {
+ free (hostname);
+ return ARES_EBADRESP;
+ }
+ aptr += len + QFIXEDSZ;
+
+ /* Examine each answer resource record (RR) in turn. */
+ for (i = 0; i < (int) ancount; i++)
+ {
+ /* Decode the RR up to the data field. */
+ status = ares_expand_name (aptr, abuf, alen, &rr_name, &len);
+ if (status != ARES_SUCCESS)
+ {
+ break;
+ }
+ aptr += len;
+ if (aptr + RRFIXEDSZ > abuf + alen)
+ {
+ status = ARES_EBADRESP;
+ break;
+ }
+ rr_type = DNS_RR_TYPE (aptr);
+ rr_class = DNS_RR_CLASS (aptr);
+ rr_len = DNS_RR_LEN (aptr);
+ aptr += RRFIXEDSZ;
+
+ /* Check if we are really looking at a SRV record */
+ if (rr_class == C_IN && rr_type == T_SRV)
+ {
+ /* parse the SRV record itself */
+ if (rr_len < 6)
+ {
+ status = ARES_EBADRESP;
+ break;
+ }
+
+ /* Allocate storage for this SRV answer appending it to the list */
+ srv_curr = _ares_malloc_data(ARES_DATATYPE_SRV_REPLY);
+ if (!srv_curr)
+ {
+ status = ARES_ENOMEM;
+ break;
+ }
+ if (srv_last)
+ {
+ srv_last->next = srv_curr;
+ }
+ else
+ {
+ srv_head = srv_curr;
+ }
+ srv_last = srv_curr;
+
+ vptr = aptr;
+ srv_curr->priority = ntohs (*((const unsigned short *)vptr));
+ vptr += sizeof(const unsigned short);
+ srv_curr->weight = ntohs (*((const unsigned short *)vptr));
+ vptr += sizeof(const unsigned short);
+ srv_curr->port = ntohs (*((const unsigned short *)vptr));
+ vptr += sizeof(const unsigned short);
+
+ status = ares_expand_name (vptr, abuf, alen, &srv_curr->host, &len);
+ if (status != ARES_SUCCESS)
+ break;
+ }
+
+ /* Don't lose memory in the next iteration */
+ free(rr_name);
+ rr_name = NULL;
+
+ /* Move on to the next record */
+ aptr += rr_len;
+ }
+
+ if (hostname)
+ free (hostname);
+ if (rr_name)
+ free (rr_name);
+
+ /* clean up on error */
+ if (status != ARES_SUCCESS)
+ {
+ if (srv_head)
+ _ares_free_data (srv_head);
+ return status;
+ }
+
+ /* everything looks fine, return the data */
+ *srv_out = srv_head;
+
+ return ARES_SUCCESS;
+}
diff --git a/src/resolv/ares/ares_parse_srv_reply.h b/src/resolv/ares/ares_parse_srv_reply.h
new file mode 100644
index 00000000..29c6e08d
--- /dev/null
+++ b/src/resolv/ares/ares_parse_srv_reply.h
@@ -0,0 +1,35 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __ARES_PARSE_SRV_REPLY_H__
+#define __ARES_PARSE_SRV_REPLY_H__
+
+struct ares_srv_reply {
+ struct ares_srv_reply *next;
+ char *host;
+ unsigned short priority;
+ unsigned short weight;
+ unsigned short port;
+};
+
+int _ares_parse_srv_reply (const unsigned char *abuf, int alen,
+ struct ares_srv_reply **srv_out);
+
+#endif /* __ARES_PARSE_SRV_REPLY_H__ */
diff --git a/src/resolv/ares/ares_parse_txt_reply.c b/src/resolv/ares/ares_parse_txt_reply.c
new file mode 100644
index 00000000..d710e8f9
--- /dev/null
+++ b/src/resolv/ares/ares_parse_txt_reply.c
@@ -0,0 +1,204 @@
+/*
+ SSSD
+
+ Async resolver - TXT records parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This code is based on other c-ares parsing licensed as follows:
+
+ * Copyright 1998 by the Massachusetts Institute of Technology.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ares.h"
+/* this drags in some private macros c-ares uses */
+#include "ares_dns.h"
+#include "ares_data.h"
+
+#include "ares_parse_txt_reply.h"
+
+int _ares_parse_txt_reply (const unsigned char *abuf, int alen,
+ struct ares_txt_reply **txt_out)
+{
+ size_t substr_len, str_len;
+ unsigned int qdcount, ancount, i;
+ const unsigned char *aptr;
+ const unsigned char *strptr;
+ int status, rr_type, rr_class, rr_len;
+ long len;
+ char *hostname = NULL, *rr_name = NULL;
+ struct ares_txt_reply *txt_head = NULL;
+ struct ares_txt_reply *txt_last = NULL;
+ struct ares_txt_reply *txt_curr;
+
+ /* Set *txt_out to NULL for all failure cases. */
+ *txt_out = NULL;
+
+ /* Give up if abuf doesn't have room for a header. */
+ if (alen < HFIXEDSZ)
+ return ARES_EBADRESP;
+
+ /* Fetch the question and answer count from the header. */
+ qdcount = DNS_HEADER_QDCOUNT(abuf);
+ ancount = DNS_HEADER_ANCOUNT(abuf);
+ if (qdcount != 1)
+ return ARES_EBADRESP;
+ if (ancount == 0)
+ return ARES_ENODATA;
+
+ /* Expand the name from the question, and skip past the question. */
+ aptr = abuf + HFIXEDSZ;
+ status = ares_expand_name(aptr, abuf, alen, &hostname, &len);
+ if (status != ARES_SUCCESS)
+ return status;
+
+ if (aptr + len + QFIXEDSZ > abuf + alen)
+ {
+ free (hostname);
+ return ARES_EBADRESP;
+ }
+ aptr += len + QFIXEDSZ;
+
+ /* Examine each answer resource record (RR) in turn. */
+ for (i = 0; i < (int) ancount; i++)
+ {
+ /* Decode the RR up to the data field. */
+ status = ares_expand_name(aptr, abuf, alen, &rr_name, &len);
+ if (status != ARES_SUCCESS)
+ {
+ break;
+ }
+ aptr += len;
+ if (aptr + RRFIXEDSZ > abuf + alen)
+ {
+ status = ARES_EBADRESP;
+ break;
+ }
+ rr_type = DNS_RR_TYPE(aptr);
+ rr_class = DNS_RR_CLASS(aptr);
+ rr_len = DNS_RR_LEN(aptr);
+ aptr += RRFIXEDSZ;
+
+ /* Check if we are really looking at a TXT record */
+ if (rr_class == C_IN && rr_type == T_TXT)
+ {
+ /* Allocate storage for this TXT answer appending it to the list */
+ txt_curr = _ares_malloc_data(ARES_DATATYPE_TXT_REPLY);
+ if (!txt_curr)
+ {
+ status = ARES_ENOMEM;
+ break;
+ }
+ if (txt_last)
+ {
+ txt_last->next = txt_curr;
+ }
+ else
+ {
+ txt_head = txt_curr;
+ }
+ txt_last = txt_curr;
+
+ /*
+ * There may be multiple substrings in a single TXT record. Each
+ * substring may be up to 255 characters in length, with a
+ * "length byte" indicating the size of the substring payload.
+ * RDATA contains both the length-bytes and payloads of all
+ * substrings contained therein.
+ */
+
+ /* Compute total length to allow a single memory allocation */
+ strptr = aptr;
+ while (strptr < (aptr + rr_len))
+ {
+ substr_len = (unsigned char)*strptr;
+ txt_curr->length += substr_len;
+ strptr += substr_len + 1;
+ }
+
+ /* Including null byte */
+ txt_curr->txt = malloc (txt_curr->length + 1);
+ if (txt_curr->txt == NULL)
+ {
+ status = ARES_ENOMEM;
+ break;
+ }
+
+ /* Step through the list of substrings, concatenating them */
+ str_len = 0;
+ strptr = aptr;
+ while (strptr < (aptr + rr_len))
+ {
+ substr_len = (unsigned char)*strptr;
+ strptr++;
+ memcpy ((char *) txt_curr->txt + str_len, strptr, substr_len);
+ str_len += substr_len;
+ strptr += substr_len;
+ }
+ /* Make sure we NULL-terminate */
+ *((char *) txt_curr->txt + txt_curr->length) = '\0';
+ }
+
+ /* Don't lose memory in the next iteration */
+ free(rr_name);
+ rr_name = NULL;
+
+ /* Move on to the next record */
+ aptr += rr_len;
+ }
+
+ if (hostname)
+ free (hostname);
+ if (rr_name)
+ free (rr_name);
+
+ /* clean up on error */
+ if (status != ARES_SUCCESS)
+ {
+ if (txt_head)
+ _ares_free_data (txt_head);
+ return status;
+ }
+
+ /* everything looks fine, return the data */
+ *txt_out = txt_head;
+
+ return ARES_SUCCESS;
+}
diff --git a/src/resolv/ares/ares_parse_txt_reply.h b/src/resolv/ares/ares_parse_txt_reply.h
new file mode 100644
index 00000000..216e2c0d
--- /dev/null
+++ b/src/resolv/ares/ares_parse_txt_reply.h
@@ -0,0 +1,33 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __ARES_PARSE_TXT_REPLY_H__
+#define __ARES_PARSE_TXT_REPLY_H__
+
+struct ares_txt_reply {
+ struct ares_txt_reply *next;
+ unsigned char *txt;
+ size_t length; /* length excludes null termination */
+};
+
+int _ares_parse_txt_reply(const unsigned char* abuf, int alen,
+ struct ares_txt_reply **txt_out);
+
+#endif /* __ARES_PARSE_TXT_REPLY_H__ */
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
new file mode 100644
index 00000000..70d8d11e
--- /dev/null
+++ b/src/resolv/async_resolv.c
@@ -0,0 +1,1062 @@
+/*
+ SSSD
+
+ Async resolver
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ares.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "resolv/async_resolv.h"
+#include "util/dlinklist.h"
+#include "util/util.h"
+
+#ifndef HAVE_ARES_DATA
+#define ares_parse_srv_reply(abuf, alen, srv_out) \
+ _ares_parse_srv_reply(abuf, alen, srv_out)
+#define ares_parse_txt_reply(abuf, alen, txt_out) \
+ _ares_parse_txt_reply(abuf, alen, txt_out)
+#define ares_free_data(dataptr) \
+ _ares_free_data(dataptr)
+#define ares_malloc_data(data) \
+ _ares_malloc_data(data)
+#endif /* HAVE_ARES_DATA */
+
+struct fd_watch {
+ struct fd_watch *prev;
+ struct fd_watch *next;
+
+ int fd;
+ struct resolv_ctx *ctx;
+ struct tevent_fd *fde;
+};
+
+struct resolv_ctx {
+ /* Contexts are linked so we can keep track of them and re-create
+ * the ares channels in all of them at once if we need to. */
+ struct resolv_ctx *prev;
+ struct resolv_ctx *next;
+
+ struct tevent_context *ev_ctx;
+ ares_channel channel;
+
+ /* List of file descriptors that are watched by tevent. */
+ struct fd_watch *fds;
+
+ /* Time in milliseconds before canceling a DNS request */
+ int timeout;
+
+ /* The timeout watcher periodically calls ares_process_fd() to check
+ * if our pending requests didn't timeout. */
+ int pending_requests;
+ struct tevent_timer *timeout_watcher;
+};
+
+struct resolv_ctx *context_list;
+
+static int
+return_code(int ares_code)
+{
+ switch (ares_code) {
+ case ARES_SUCCESS:
+ return EOK;
+ case ARES_ENOMEM:
+ return ENOMEM;
+ case ARES_EFILE:
+ default:
+ return EIO;
+ }
+}
+
+const char *
+resolv_strerror(int ares_code)
+{
+ return ares_strerror(ares_code);
+}
+
+static int
+fd_watch_destructor(struct fd_watch *f)
+{
+ DLIST_REMOVE(f->ctx->fds, f);
+ f->fd = -1;
+
+ return 0;
+}
+
+static void
+fd_input_available(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *data)
+{
+ struct fd_watch *watch = talloc_get_type(data, struct fd_watch);
+
+ if (watch->ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return;
+ }
+
+ if (flags & TEVENT_FD_READ) {
+ ares_process_fd(watch->ctx->channel, watch->fd, ARES_SOCKET_BAD);
+ }
+ if (flags & TEVENT_FD_WRITE) {
+ ares_process_fd(watch->ctx->channel, ARES_SOCKET_BAD, watch->fd);
+ }
+}
+
+static void
+check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data);
+
+static void
+add_timeout_timer(struct tevent_context *ev, struct resolv_ctx *ctx)
+{
+ struct timeval tv = { 0 };
+ struct timeval *tvp;
+
+ tvp = ares_timeout(ctx->channel, NULL, &tv);
+
+ if (tvp == NULL) {
+ tvp = &tv;
+ }
+
+ /* Enforce a minimum of 1 second. */
+ if (tvp->tv_sec < 1) {
+ tv = tevent_timeval_current_ofs(1, 0);
+ } else {
+ tv = tevent_timeval_current_ofs(tvp->tv_sec, tvp->tv_usec);
+ }
+
+ ctx->timeout_watcher = tevent_add_timer(ev, ctx, tv, check_fd_timeouts,
+ ctx);
+ if (ctx->timeout_watcher == NULL) {
+ DEBUG(1, ("Out of memory\n"));
+ }
+}
+
+static void
+check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data)
+{
+ struct resolv_ctx *ctx = talloc_get_type(private_data, struct resolv_ctx);
+
+ DEBUG(9, ("Checking for DNS timeouts\n"));
+
+ /* NULLify the timeout_watcher so we don't
+ * free it in the _done() function if it
+ * gets called. Now that we're already in
+ * the handler, tevent will take care of
+ * freeing it when it returns.
+ */
+ ctx->timeout_watcher = NULL;
+
+ ares_process_fd(ctx->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+
+ if (ctx->pending_requests > 0) {
+ add_timeout_timer(ev, ctx);
+ }
+}
+
+static void
+schedule_timeout_watcher(struct tevent_context *ev, struct resolv_ctx *ctx)
+{
+ ctx->pending_requests++;
+ if (ctx->timeout_watcher) {
+ return;
+ }
+
+ DEBUG(9, ("Scheduling DNS timeout watcher\n"));
+ add_timeout_timer(ev, ctx);
+}
+
+static void
+unschedule_timeout_watcher(struct resolv_ctx *ctx)
+{
+ if (ctx->pending_requests <= 0) {
+ DEBUG(1, ("Pending DNS requests mismatch\n"));
+ return;
+ }
+
+ ctx->pending_requests--;
+ if (ctx->pending_requests == 0) {
+ DEBUG(9, ("Unscheduling DNS timeout watcher\n"));
+ talloc_zfree(ctx->timeout_watcher);
+ }
+}
+
+static void fd_event_add(struct resolv_ctx *ctx, int s, int flags);
+static void fd_event_close(struct resolv_ctx *ctx, int s);
+
+/*
+ * When ares is ready to read or write to a file descriptor, it will
+ * call this callback. If both read and write are 0, it means that ares
+ * will soon close the socket. We are mainly using this function to register
+ * new file descriptors with tevent.
+ */
+static void
+fd_event(void *data, int s, int fd_read, int fd_write)
+{
+ struct resolv_ctx *ctx = talloc_get_type(data, struct resolv_ctx);
+ struct fd_watch *watch;
+ int flags;
+
+ /* The socket is about to get closed. */
+ if (fd_read == 0 && fd_write == 0) {
+ fd_event_close(ctx, s);
+ return;
+ }
+
+ flags = fd_read ? TEVENT_FD_READ : 0;
+ flags |= fd_write ? TEVENT_FD_WRITE : 0;
+
+ /* Are we already watching this file descriptor? */
+ watch = ctx->fds;
+ while (watch) {
+ if (watch->fd == s) {
+ tevent_fd_set_flags(watch->fde, flags);
+ return;
+ }
+ watch = watch->next;
+ }
+
+ fd_event_add(ctx, s, flags);
+}
+
+static void
+fd_event_add(struct resolv_ctx *ctx, int s, int flags)
+{
+ struct fd_watch *watch;
+
+ /* The file descriptor is new, register it with tevent. */
+ watch = talloc(ctx, struct fd_watch);
+ if (watch == NULL) {
+ DEBUG(1, ("Out of memory allocating fd_watch structure\n"));
+ return;
+ }
+ talloc_set_destructor(watch, fd_watch_destructor);
+
+ watch->fd = s;
+ watch->ctx = ctx;
+
+ watch->fde = tevent_add_fd(ctx->ev_ctx, watch, s, flags,
+ fd_input_available, watch);
+ if (watch->fde == NULL) {
+ DEBUG(1, ("tevent_add_fd() failed\n"));
+ talloc_free(watch);
+ return;
+ }
+ DLIST_ADD(ctx->fds, watch);
+}
+
+static void
+fd_event_close(struct resolv_ctx *ctx, int s)
+{
+ struct fd_watch *watch;
+
+ /* Remove the socket from list */
+ watch = ctx->fds;
+ while (watch) {
+ if (watch->fd == s) {
+ talloc_free(watch);
+ return;
+ }
+ watch = watch->next;
+ }
+}
+
+static int
+resolv_ctx_destructor(struct resolv_ctx *ctx)
+{
+ ares_channel channel;
+
+ DLIST_REMOVE(context_list, ctx);
+
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Ares channel already destroyed?\n"));
+ return -1;
+ }
+
+ /* Set ctx->channel to NULL first, so that callbacks that get
+ * ARES_EDESTRUCTION won't retry. */
+ channel = ctx->channel;
+ ctx->channel = NULL;
+ ares_destroy(channel);
+
+ return 0;
+}
+
+static int
+recreate_ares_channel(struct resolv_ctx *ctx)
+{
+ int ret;
+ ares_channel new_channel;
+ ares_channel old_channel;
+ struct ares_options options;
+
+ DEBUG(4, ("Initializing new c-ares channel\n"));
+ /* FIXME: the options would contain
+ * the nameservers to contact, the domains
+ * to search, timeout... => get from confdb
+ */
+ options.sock_state_cb = fd_event;
+ options.sock_state_cb_data = ctx;
+ options.timeout = ctx->timeout * 1000;
+ options.tries = 1;
+ ret = ares_init_options(&new_channel, &options,
+ ARES_OPT_SOCK_STATE_CB |
+ ARES_OPT_TIMEOUTMS |
+ ARES_OPT_TRIES);
+ if (ret != ARES_SUCCESS) {
+ DEBUG(1, ("Failed to initialize ares channel: %s\n",
+ resolv_strerror(ret)));
+ return return_code(ret);
+ }
+
+ old_channel = ctx->channel;
+ ctx->channel = new_channel;
+ if (old_channel != NULL) {
+ DEBUG(4, ("Destroying the old c-ares channel\n"));
+ ares_destroy(old_channel);
+ }
+
+ return EOK;
+}
+
+int
+resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx,
+ int timeout, struct resolv_ctx **ctxp)
+{
+ int ret;
+ struct resolv_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct resolv_ctx);
+ if (ctx == NULL)
+ return ENOMEM;
+
+ ctx->ev_ctx = ev_ctx;
+ ctx->timeout = timeout;
+
+ ret = recreate_ares_channel(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ DLIST_ADD(context_list, ctx);
+ talloc_set_destructor(ctx, resolv_ctx_destructor);
+
+ *ctxp = ctx;
+ return EOK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+void
+resolv_reread_configuration(void)
+{
+ struct resolv_ctx *ctx;
+
+ DEBUG(4, ("Recreating all c-ares channels\n"));
+ DLIST_FOR_EACH(ctx, context_list) {
+ recreate_ares_channel(ctx);
+ }
+}
+
+struct hostent *
+resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src)
+{
+ struct hostent *ret;
+ int len;
+ int i;
+
+ ret = talloc_zero(mem_ctx, struct hostent);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (src->h_name != NULL) {
+ ret->h_name = talloc_strdup(ret, src->h_name);
+ if (ret->h_name == NULL) {
+ goto fail;
+ }
+ }
+ if (src->h_aliases != NULL) {
+ for (len = 0; src->h_aliases[len] != NULL; len++);
+ ret->h_aliases = talloc_size(ret, sizeof(char *) * (len + 1));
+ if (ret->h_aliases == NULL) {
+ goto fail;
+ }
+ for (i = 0; i < len; i++) {
+ ret->h_aliases[i] = talloc_strdup(ret->h_aliases, src->h_aliases[i]);
+ if (ret->h_aliases[i] == NULL) {
+ goto fail;
+ }
+ }
+ ret->h_aliases[len] = NULL;
+ }
+ ret->h_addrtype = src->h_addrtype;
+ ret->h_length = src->h_length;
+ if (src->h_addr_list != NULL) {
+ for (len = 0; src->h_addr_list[len] != NULL; len++);
+ ret->h_addr_list = talloc_size(ret, sizeof(char *) * (len + 1));
+ if (ret->h_addr_list == NULL) {
+ goto fail;
+ }
+ for (i = 0; i < len; i++) {
+ ret->h_addr_list[i] = talloc_memdup(ret->h_addr_list,
+ src->h_addr_list[i],
+ ret->h_length);
+ if (ret->h_addr_list[i] == NULL) {
+ goto fail;
+ }
+ }
+ ret->h_addr_list[len] = NULL;
+ }
+
+ return ret;
+
+fail:
+ talloc_free(ret);
+ return NULL;
+}
+
+/*******************************************************************
+ * Get host by name. *
+ *******************************************************************/
+
+struct gethostbyname_state {
+ struct resolv_ctx *resolv_ctx;
+ /* Part of the query. */
+ const char *name;
+ int family;
+ /* These are returned by ares. The hostent struct will be freed
+ * when the user callback returns. */
+ struct hostent *hostent;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+ares_gethostbyname_wakeup(struct tevent_req *req);
+
+struct tevent_req *
+resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct gethostbyname_state *state;
+ struct timeval tv = { 0, 0 };
+
+ DEBUG(4, ("Trying to resolve A record of '%s'\n", name));
+
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct gethostbyname_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv_ctx = ctx;
+ state->name = name;
+ state->family = AF_INET;
+ state->hostent = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+
+ /* We need to have a wrapper around ares_gethostbyname(), because
+ * ares_gethostbyname() can in some cases call it's callback immediately.
+ * This would not let our caller to set a callback for req. */
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(1, ("Failed to add critical timer to run next operation!\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ares_gethostbyname_wakeup, req);
+ schedule_timeout_watcher(ev, ctx);
+
+ return req;
+}
+
+static void
+resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent);
+
+static void
+resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent)
+{
+ struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ ares_gethostbyname(state->resolv_ctx->channel, state->name,
+ state->family, resolv_gethostbyname_done, req);
+ return;
+ }
+
+ unschedule_timeout_watcher(state->resolv_ctx);
+
+ if (hostent != NULL) {
+ state->hostent = resolv_copy_hostent(req, hostent);
+ if (state->hostent == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ } else {
+ state->hostent = NULL;
+ }
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ if (status == ARES_ENOTFOUND || status == ARES_ENODATA) {
+ /* IPv4 failure. Try IPv6 */
+ state->family = AF_INET6;
+ state->retrying = 0;
+ state->timeouts = 0;
+ DEBUG(4, ("Trying to resolve AAAA record of '%s'\n",
+ state->name));
+ ares_gethostbyname(state->resolv_ctx->channel, state->name,
+ state->family, resolv_gethostbyname6_done,
+ req);
+ return;
+ }
+
+ /* Any other error indicates a server error,
+ * so don't bother trying again
+ */
+ tevent_req_error(req, return_code(status));
+ }
+ else {
+ tevent_req_done(req);
+ }
+}
+
+static void
+resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent)
+{
+ struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION) {
+ state->retrying = 1;
+ ares_gethostbyname(state->resolv_ctx->channel, state->name,
+ state->family, resolv_gethostbyname6_done, req);
+ return;
+ }
+
+ if (hostent != NULL) {
+ state->hostent = resolv_copy_hostent(req, hostent);
+ if (state->hostent == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ } else {
+ state->hostent = NULL;
+ }
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ tevent_req_error(req, return_code(status));
+ }
+ else {
+ tevent_req_done(req);
+ }
+}
+
+int
+resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *status, int *timeouts,
+ struct hostent **hostent)
+{
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+
+ /* Fill in even in case of error as status contains the
+ * c-ares return code */
+ if (status) {
+ *status = state->status;
+ }
+ if (timeouts) {
+ *timeouts = state->timeouts;
+ }
+ if (hostent) {
+ *hostent = talloc_steal(mem_ctx, state->hostent);
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+ares_gethostbyname_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct gethostbyname_state *state = tevent_req_data(req,
+ struct gethostbyname_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ares_gethostbyname(state->resolv_ctx->channel, state->name,
+ state->family, resolv_gethostbyname_done, req);
+}
+
+/* SRV and TXT parsing is not used anywhere in the code yet, so we disable it
+ * for now
+ */
+#ifdef BUILD_TXT_SRV
+
+/*
+ * A simple helper function that will take an array of struct ares_srv_reply that
+ * was allocated by malloc() in c-ares and copies it using talloc. The old one
+ * is freed and the talloc one is put into 'reply_list' instead.
+ */
+static int
+rewrite_talloc_srv_reply(TALLOC_CTX *mem_ctx, struct ares_srv_reply **reply_list)
+{
+ struct ares_srv_reply *ptr = NULL;
+ struct ares_srv_reply *new_list = NULL;
+ struct ares_srv_reply *old_list = *reply_list;
+
+ /* Nothing to do, but not an error */
+ if (!old_list) {
+ return EOK;
+ }
+
+ /* Copy the linked list */
+ while (old_list) {
+ /* Special case for the first node */
+ if (!new_list) {
+ new_list = talloc_zero(mem_ctx, struct ares_srv_reply);
+ if (new_list == NULL) {
+ ares_free_data(*reply_list);
+ return ENOMEM;
+ }
+ ptr = new_list;
+ } else {
+ ptr->next = talloc_zero(new_list, struct ares_srv_reply);
+ if (ptr->next == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = ptr->next;
+ }
+
+ ptr->weight = old_list->weight;
+ ptr->priority = old_list->priority;
+ ptr->port = old_list->port;
+ ptr->host = talloc_strdup(ptr, old_list->host);
+ if (ptr->host == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+
+ old_list = old_list->next;
+ }
+
+ /* Free the old one (uses malloc). */
+ ares_free_data(*reply_list);
+
+ /* And now put our own new_list in place. */
+ *reply_list = new_list;
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get SRV record *
+ *******************************************************************/
+
+struct getsrv_state {
+ struct resolv_ctx *resolv_ctx;
+ /* the SRV query - for example _ldap._tcp.example.com */
+ const char *query;
+
+ /* parsed data returned by ares */
+ struct ares_srv_reply *reply_list;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+ares_getsrv_wakeup(struct tevent_req *subreq);
+
+struct tevent_req *
+resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *query)
+{
+ struct tevent_req *req, *subreq;
+ struct getsrv_state *state;
+ struct timeval tv = { 0, 0 };
+
+ DEBUG(4, ("Trying to resolve SRV record of '%s'\n", query));
+
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct getsrv_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv_ctx = ctx;
+ state->query = query;
+ state->reply_list = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(1, ("Failed to add critical timer to run next operation!\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ares_getsrv_wakeup, req);
+ schedule_timeout_watcher(ev, ctx);
+
+ return req;
+}
+
+static void
+resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
+ struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
+ int ret;
+ struct ares_srv_reply *reply_list;
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_srv, resolv_getsrv_done, req);
+ return;
+ }
+
+ unschedule_timeout_watcher(state->resolv_ctx);
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ tevent_req_error(req, return_code(status));
+ ret = return_code(status);
+ goto fail;
+ }
+
+ ret = ares_parse_srv_reply(abuf, alen, &reply_list);
+ if (status != ARES_SUCCESS) {
+ DEBUG(2, ("SRV record parsing failed: %d: %s\n", ret, ares_strerror(ret)));
+ ret = return_code(ret);
+ goto fail;
+ }
+ ret = rewrite_talloc_srv_reply(req, &reply_list);
+ if (ret != EOK) {
+ goto fail;
+ }
+ state->reply_list = reply_list;
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ state->reply_list = NULL;
+ tevent_req_error(req, ret);
+}
+
+int
+resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
+ int *timeouts, struct ares_srv_reply **reply_list)
+{
+ struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = talloc_steal(mem_ctx, state->reply_list);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+ares_getsrv_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct getsrv_state *state = tevent_req_data(req,
+ struct getsrv_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_srv, resolv_getsrv_done, req);
+}
+
+/*
+ * A simple helper function that will take an array of struct txt_reply that
+ * was allocated by malloc() in c-ares and copies it using talloc. The old one
+ * is freed and the talloc one is put into 'reply_list' instead.
+ */
+static int
+rewrite_talloc_txt_reply(TALLOC_CTX *mem_ctx, struct ares_txt_reply **reply_list)
+{
+ struct ares_txt_reply *ptr = NULL;
+ struct ares_txt_reply *new_list = NULL;
+ struct ares_txt_reply *old_list = *reply_list;
+
+ /* Nothing to do, but not an error */
+ if (!old_list) {
+ return EOK;
+ }
+
+ /* Copy the linked list */
+ while (old_list) {
+
+ /* Special case for the first node */
+ if (!new_list) {
+ new_list = talloc_zero(mem_ctx, struct ares_txt_reply);
+ if (new_list == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = new_list;
+ } else {
+ ptr->next = talloc_zero(new_list, struct ares_txt_reply);
+ if (ptr->next == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = ptr->next;
+ }
+
+ ptr->length = old_list->length;
+ ptr->txt = talloc_memdup(ptr, old_list->txt,
+ old_list->length);
+ if (ptr->txt == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+
+ old_list = old_list->next;
+ }
+
+ ares_free_data(*reply_list);
+
+ /* And now put our own new_list in place. */
+ *reply_list = new_list;
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get TXT record *
+ *******************************************************************/
+
+struct gettxt_state {
+ struct resolv_ctx *resolv_ctx;
+ /* the TXT query */
+ const char *query;
+
+ /* parsed data returned by ares */
+ struct ares_txt_reply *reply_list;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+ares_gettxt_wakeup(struct tevent_req *subreq);
+
+struct tevent_req *
+resolv_gettxt_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *query)
+{
+ struct tevent_req *req, *subreq;
+ struct gettxt_state *state;
+ struct timeval tv = { 0, 0 };
+
+ DEBUG(4, ("Trying to resolve TXT record of '%s'\n", query));
+
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct gettxt_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv_ctx = ctx;
+ state->query = query;
+ state->reply_list = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(1, ("Failed to add critical timer to run next operation!\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ares_gettxt_wakeup, req);
+ schedule_timeout_watcher(ev, ctx);
+
+ return req;
+}
+
+static void
+resolv_gettxt_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
+ struct gettxt_state *state = tevent_req_data(req, struct gettxt_state);
+ int ret;
+ struct ares_txt_reply *reply_list;
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_txt, resolv_gettxt_done, req);
+ return;
+ }
+
+ unschedule_timeout_watcher(state->resolv_ctx);
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ ret = return_code(status);
+ goto fail;
+ }
+
+ ret = ares_parse_txt_reply(abuf, alen, &reply_list);
+ if (status != ARES_SUCCESS) {
+ DEBUG(2, ("TXT record parsing failed: %d: %s\n", ret, ares_strerror(ret)));
+ ret = return_code(ret);
+ goto fail;
+ }
+ ret = rewrite_talloc_txt_reply(req, &reply_list);
+ if (ret != EOK) {
+ goto fail;
+ }
+ state->reply_list = reply_list;
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ state->reply_list = NULL;
+ tevent_req_error(req, ret);
+}
+
+int
+resolv_gettxt_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
+ int *timeouts, struct ares_txt_reply **reply_list)
+{
+ struct gettxt_state *state = tevent_req_data(req, struct gettxt_state);
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = talloc_steal(mem_ctx, state->reply_list);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+ares_gettxt_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct gettxt_state *state = tevent_req_data(req,
+ struct gettxt_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_txt, resolv_gettxt_done, req);
+}
+
+#endif
diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h
new file mode 100644
index 00000000..2ba6449b
--- /dev/null
+++ b/src/resolv/async_resolv.h
@@ -0,0 +1,95 @@
+/*
+ SSSD
+
+ Async resolver header
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __ASYNC_RESOLV_H__
+#define __ASYNC_RESOLV_H__
+
+#include <netdb.h>
+#include <ares.h>
+
+#include "config.h"
+
+#ifndef HAVE_ARES_DATA
+#include "resolv/ares/ares_parse_srv_reply.h"
+#include "resolv/ares/ares_parse_txt_reply.h"
+#include "resolv/ares/ares_data.h"
+#endif /* HAVE_ARES_DATA */
+
+/*
+ * An opaque structure which holds context for a module using the async
+ * resolver. Is should be used as a "local-global" variable - in sssd,
+ * every backend should have its own.
+
+ * Do NOT free the context until there are any pending resolv_ calls
+ */
+struct resolv_ctx;
+
+int resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx,
+ int timeout, struct resolv_ctx **ctxp);
+
+void resolv_reread_configuration(void);
+
+const char *resolv_strerror(int ares_code);
+
+struct hostent *resolv_copy_hostent(TALLOC_CTX *mem_ctx,
+ struct hostent *src);
+
+/** Get host by name **/
+struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *name);
+
+int resolv_gethostbyname_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *status,
+ int *timeouts,
+ struct hostent **hostent);
+
+/** Get SRV record **/
+struct tevent_req *resolv_getsrv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *query);
+
+int resolv_getsrv_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct ares_srv_reply **reply_list);
+
+/** Get TXT record **/
+struct tevent_req *resolv_gettxt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *query);
+
+int resolv_gettxt_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct ares_txt_reply **reply_list);
+
+#endif /* __ASYNC_RESOLV_H__ */
diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
new file mode 100644
index 00000000..ea6ba583
--- /dev/null
+++ b/src/responder/common/responder.h
@@ -0,0 +1,152 @@
+/*
+ SSSD
+
+ SSS Client Responder, header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SSS_RESPONDER_H__
+#define __SSS_RESPONDER_H__
+
+#include <stdint.h>
+#include <sys/un.h>
+#include <pcre.h>
+#include "config.h"
+#include "talloc.h"
+#include "tevent.h"
+#include "ldb.h"
+#include "dhash.h"
+#include "sbus/sssd_dbus.h"
+#include "sss_client/sss_cli.h"
+
+extern hash_table_t *dp_requests;
+
+/* if there is a provider other than the special local */
+#define NEED_CHECK_PROVIDER(provider) \
+ (provider != NULL && strcmp(provider, "local") != 0)
+
+/* needed until nsssrv.h is updated */
+struct cli_request {
+
+ /* original request from the wire */
+ struct sss_packet *in;
+
+ /* reply data */
+ struct sss_packet *out;
+};
+
+struct cli_protocol_version {
+ uint32_t version;
+ const char *date;
+ const char *description;
+};
+
+struct be_conn {
+ struct be_conn *next;
+ struct be_conn *prev;
+
+ const char *cli_name;
+ struct sss_domain_info *domain;
+
+ char *sbus_address;
+ struct sbus_interface *intf;
+ struct sbus_connection *conn;
+};
+
+struct resp_ctx {
+ struct tevent_context *ev;
+ struct tevent_fd *lfde;
+ int lfd;
+ struct tevent_fd *priv_lfde;
+ int priv_lfd;
+ struct confdb_ctx *cdb;
+ const char *sock_name;
+ const char *priv_sock_name;
+
+ struct sbus_connection *mon_conn;
+ struct be_conn *be_conns;
+
+ struct sss_domain_info *domains;
+ struct sysdb_ctx_list *db_list;
+
+ struct sss_cmd_table *sss_cmds;
+ const char *sss_pipe_name;
+ const char *confdb_service_path;
+
+ struct sss_names_ctx *names;
+
+ void *pvt_ctx;
+};
+
+struct cli_ctx {
+ struct tevent_context *ev;
+ struct resp_ctx *rctx;
+ int cfd;
+ struct tevent_fd *cfde;
+ struct sockaddr_un addr;
+ struct cli_request *creq;
+ struct cli_protocol_version *cli_protocol_version;
+ int priv;
+};
+
+struct sss_cmd_table {
+ enum sss_cli_command cmd;
+ int (*fn)(struct cli_ctx *cctx);
+};
+
+/* responder_common.c */
+int sss_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb,
+ struct sss_cmd_table sss_cmds[],
+ const char *sss_pipe_name,
+ const char *sss_priv_pipe_name,
+ const char *confdb_service_path,
+ const char *svc_name,
+ uint16_t svc_version,
+ struct sbus_interface *monitor_intf,
+ const char *cli_name,
+ struct sbus_interface *dp_intf,
+ struct resp_ctx **responder_ctx);
+
+int sss_parse_name(TALLOC_CTX *memctx,
+ struct sss_names_ctx *snctx,
+ const char *orig, char **domain, char **name);
+
+int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain,
+ struct be_conn **_conn);
+
+/* responder_cmd.c */
+int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds);
+void sss_cmd_done(struct cli_ctx *cctx, void *freectx);
+int sss_cmd_get_version(struct cli_ctx *cctx);
+struct cli_protocol_version *register_cli_protocol_version(void);
+
+#define SSS_DP_USER 1
+#define SSS_DP_GROUP 2
+#define SSS_DP_INITGROUPS 3
+
+typedef void (*sss_dp_callback_t)(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx,
+ sss_dp_callback_t callback, void *callback_ctx,
+ int timeout, const char *domain,
+ bool fast_reply, int type,
+ const char *opt_name, uint32_t opt_id);
+
+#endif /* __SSS_RESPONDER_H__ */
diff --git a/src/responder/common/responder_cmd.c b/src/responder/common/responder_cmd.c
new file mode 100644
index 00000000..cd989030
--- /dev/null
+++ b/src/responder/common/responder_cmd.c
@@ -0,0 +1,103 @@
+/*
+ SSSD
+
+ SSS Client Responder, command parser
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <errno.h>
+#include "util/util.h"
+#include "responder/common/responder.h"
+#include "responder/common/responder_packet.h"
+
+
+void sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+ /* now that the packet is in place, unlock queue
+ * making the event writable */
+ TEVENT_FD_WRITEABLE(cctx->cfde);
+
+ /* free all request related data through the talloc hierarchy */
+ talloc_free(freectx);
+}
+
+int sss_cmd_get_version(struct cli_ctx *cctx)
+{
+ uint8_t *req_body;
+ size_t req_blen;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ uint32_t client_version;
+ int i;
+ static struct cli_protocol_version *cli_protocol_version = NULL;
+
+ cctx->cli_protocol_version = NULL;
+
+ if (cli_protocol_version == NULL) {
+ cli_protocol_version = register_cli_protocol_version();
+ }
+
+ if (cli_protocol_version != NULL) {
+ cctx->cli_protocol_version = &cli_protocol_version[0];
+
+ sss_packet_get_body(cctx->creq->in, &req_body, &req_blen);
+ if (req_blen == sizeof(uint32_t)) {
+ memcpy(&client_version, req_body, sizeof(uint32_t));
+ DEBUG(5, ("Received client version [%d].\n", client_version));
+
+ i=0;
+ while(cli_protocol_version[i].version>0) {
+ if (cli_protocol_version[i].version == client_version) {
+ cctx->cli_protocol_version = &cli_protocol_version[i];
+ break;
+ }
+ i++;
+ }
+ }
+ }
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ return ret;
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = cctx->cli_protocol_version!=NULL ?
+ cctx->cli_protocol_version->version : 0;
+ DEBUG(5, ("Offered version [%d].\n", ((uint32_t *)body)[0]));
+
+ sss_cmd_done(cctx, NULL);
+ return EOK;
+}
+
+int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds)
+{
+ enum sss_cli_command cmd;
+ int i;
+
+ cmd = sss_packet_get_cmd(cctx->creq->in);
+
+ for (i = 0; sss_cmds[i].cmd != SSS_CLI_NULL; i++) {
+ if (cmd == sss_cmds[i].cmd) {
+ return sss_cmds[i].fn(cctx);
+ }
+ }
+
+ return EINVAL;
+}
diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c
new file mode 100644
index 00000000..37bbcb30
--- /dev/null
+++ b/src/responder/common/responder_common.c
@@ -0,0 +1,589 @@
+/*
+ SSSD
+
+ Common Responder methods
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+#include "popt.h"
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "confdb/confdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "responder/common/responder.h"
+#include "responder/common/responder_packet.h"
+#include "providers/data_provider.h"
+#include "monitor/monitor_interfaces.h"
+#include "sbus/sbus_client.h"
+
+static void set_nonblocking(int fd)
+{
+ unsigned v;
+ v = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, v | O_NONBLOCK);
+}
+
+static void set_close_on_exec(int fd)
+{
+ unsigned v;
+ v = fcntl(fd, F_GETFD, 0);
+ fcntl(fd, F_SETFD, v | FD_CLOEXEC);
+}
+
+static int client_destructor(struct cli_ctx *ctx)
+{
+ if (ctx->cfd > 0) close(ctx->cfd);
+ return 0;
+}
+
+static void client_send(struct tevent_context *ev, struct cli_ctx *cctx)
+{
+ int ret;
+
+ ret = sss_packet_send(cctx->creq->out, cctx->cfd);
+ if (ret == EAGAIN) {
+ /* not all data was sent, loop again */
+ return;
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to read request, aborting client!\n"));
+ talloc_free(cctx);
+ return;
+ }
+
+ /* ok all sent */
+ TEVENT_FD_NOT_WRITEABLE(cctx->cfde);
+ TEVENT_FD_READABLE(cctx->cfde);
+ talloc_free(cctx->creq);
+ cctx->creq = NULL;
+ return;
+}
+
+static void client_recv(struct tevent_context *ev, struct cli_ctx *cctx)
+{
+ int ret;
+
+ if (!cctx->creq) {
+ cctx->creq = talloc_zero(cctx, struct cli_request);
+ if (!cctx->creq) {
+ DEBUG(0, ("Failed to alloc request, aborting client!\n"));
+ talloc_free(cctx);
+ return;
+ }
+ }
+
+ if (!cctx->creq->in) {
+ ret = sss_packet_new(cctx->creq, SSS_PACKET_MAX_RECV_SIZE,
+ 0, &cctx->creq->in);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to alloc request, aborting client!\n"));
+ talloc_free(cctx);
+ return;
+ }
+ }
+
+ ret = sss_packet_recv(cctx->creq->in, cctx->cfd);
+ switch (ret) {
+ case EOK:
+ /* do not read anymore */
+ TEVENT_FD_NOT_READABLE(cctx->cfde);
+ /* execute command */
+ ret = sss_cmd_execute(cctx, cctx->rctx->sss_cmds);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to execute request, aborting client!\n"));
+ talloc_free(cctx);
+ }
+ /* past this point cctx can be freed at any time by callbacks
+ * in case of error, do not use it */
+ return;
+
+ case EAGAIN:
+ /* need to read still some data, loop again */
+ break;
+
+ case EINVAL:
+ DEBUG(6, ("Invalid data from client, closing connection!\n"));
+ talloc_free(cctx);
+ break;
+
+ case ENODATA:
+ DEBUG(5, ("Client disconnected!\n"));
+ talloc_free(cctx);
+ break;
+
+ default:
+ DEBUG(6, ("Failed to read request, aborting client!\n"));
+ talloc_free(cctx);
+ }
+
+ return;
+}
+
+static void client_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *ptr)
+{
+ struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx);
+
+ if (flags & TEVENT_FD_READ) {
+ client_recv(ev, cctx);
+ return;
+ }
+ if (flags & TEVENT_FD_WRITE) {
+ client_send(ev, cctx);
+ return;
+ }
+}
+
+/* TODO: this is a copy of accept_fd_handler, maybe both can be put into on
+ * handler. */
+static void accept_priv_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *ptr)
+{
+ /* accept and attach new event handler */
+ struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx);
+ struct cli_ctx *cctx;
+ socklen_t len;
+ struct stat stat_buf;
+ int ret;
+
+ ret = stat(rctx->priv_sock_name, &stat_buf);
+ if (ret == -1) {
+ DEBUG(1, ("stat on privileged pipe failed: [%d][%s].\n", errno,
+ strerror(errno)));
+ return;
+ }
+
+ if ( ! (stat_buf.st_uid == 0 && stat_buf.st_gid == 0 &&
+ (stat_buf.st_mode&(S_IFSOCK|S_IRUSR|S_IWUSR)) == stat_buf.st_mode)) {
+ DEBUG(1, ("privileged pipe has an illegal status.\n"));
+/* TODO: what is the best response to this condition? Terminate? */
+ return;
+ }
+
+
+ cctx = talloc_zero(rctx, struct cli_ctx);
+ if (!cctx) {
+ struct sockaddr_un addr;
+ int fd;
+ DEBUG(0, ("Out of memory trying to setup client context on privileged pipe!\n"));
+ /* accept and close to signal the client we have a problem */
+ memset(&addr, 0, sizeof(addr));
+ len = sizeof(addr);
+ fd = accept(rctx->priv_lfd, (struct sockaddr *)&addr, &len);
+ if (fd == -1) {
+ return;
+ }
+ close(fd);
+ return;
+ }
+
+ len = sizeof(cctx->addr);
+ cctx->cfd = accept(rctx->priv_lfd, (struct sockaddr *)&cctx->addr, &len);
+ if (cctx->cfd == -1) {
+ DEBUG(1, ("Accept failed [%s]", strerror(errno)));
+ talloc_free(cctx);
+ return;
+ }
+
+ cctx->priv = 1;
+
+ cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd,
+ TEVENT_FD_READ, client_fd_handler, cctx);
+ if (!cctx->cfde) {
+ close(cctx->cfd);
+ talloc_free(cctx);
+ DEBUG(2, ("Failed to queue client handler on privileged pipe\n"));
+ }
+
+ cctx->ev = ev;
+ cctx->rctx = rctx;
+
+ talloc_set_destructor(cctx, client_destructor);
+
+ DEBUG(4, ("Client connected to privileged pipe!\n"));
+
+ return;
+}
+
+static void accept_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *ptr)
+{
+ /* accept and attach new event handler */
+ struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx);
+ struct cli_ctx *cctx;
+ socklen_t len;
+
+ cctx = talloc_zero(rctx, struct cli_ctx);
+ if (!cctx) {
+ struct sockaddr_un addr;
+ int fd;
+ DEBUG(0, ("Out of memory trying to setup client context!\n"));
+ /* accept and close to signal the client we have a problem */
+ memset(&addr, 0, sizeof(addr));
+ len = sizeof(addr);
+ fd = accept(rctx->lfd, (struct sockaddr *)&addr, &len);
+ if (fd == -1) {
+ return;
+ }
+ close(fd);
+ return;
+ }
+
+ len = sizeof(cctx->addr);
+ cctx->cfd = accept(rctx->lfd, (struct sockaddr *)&cctx->addr, &len);
+ if (cctx->cfd == -1) {
+ DEBUG(1, ("Accept failed [%s]", strerror(errno)));
+ talloc_free(cctx);
+ return;
+ }
+
+ cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd,
+ TEVENT_FD_READ, client_fd_handler, cctx);
+ if (!cctx->cfde) {
+ close(cctx->cfd);
+ talloc_free(cctx);
+ DEBUG(2, ("Failed to queue client handler\n"));
+ }
+
+ cctx->ev = ev;
+ cctx->rctx = rctx;
+
+ talloc_set_destructor(cctx, client_destructor);
+
+ DEBUG(4, ("Client connected!\n"));
+
+ return;
+}
+
+static int sss_monitor_init(struct resp_ctx *rctx,
+ struct sbus_interface *intf,
+ const char *svc_name,
+ uint16_t svc_version)
+{
+ char *sbus_address;
+ int ret;
+
+ /* Set up SBUS connection to the monitor */
+ ret = monitor_get_sbus_address(rctx, &sbus_address);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not locate monitor address.\n"));
+ return ret;
+ }
+
+ ret = sbus_client_init(rctx, rctx->ev, sbus_address,
+ intf, &rctx->mon_conn,
+ NULL, NULL);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to connect to monitor services.\n"));
+ return ret;
+ }
+
+ /* Identify ourselves to the monitor */
+ ret = monitor_common_send_id(rctx->mon_conn, svc_name, svc_version);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to identify to the monitor!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int sss_dp_init(struct resp_ctx *rctx,
+ struct sbus_interface *intf,
+ const char *cli_name,
+ struct sss_domain_info *domain)
+{
+ struct be_conn *be_conn;
+ int ret;
+
+ be_conn = talloc_zero(rctx, struct be_conn);
+ if (!be_conn) return ENOMEM;
+
+ be_conn->cli_name = cli_name;
+ be_conn->domain = domain;
+ be_conn->intf = intf;
+
+ /* Set up SBUS connection to the monitor */
+ ret = dp_get_sbus_address(be_conn, &be_conn->sbus_address, domain->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not locate DP address.\n"));
+ return ret;
+ }
+ ret = sbus_client_init(rctx, rctx->ev,
+ be_conn->sbus_address,
+ intf, &be_conn->conn,
+ NULL, NULL);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to connect to monitor services.\n"));
+ return ret;
+ }
+
+ DLIST_ADD_END(rctx->be_conns, be_conn, struct be_conn *);
+
+ /* Identify ourselves to the DP */
+ ret = dp_common_send_id(be_conn->conn,
+ DATA_PROVIDER_VERSION,
+ cli_name, domain->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to identify to the DP!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* create a unix socket and listen to it */
+static int set_unix_socket(struct resp_ctx *rctx)
+{
+ struct sockaddr_un addr;
+
+/* for future use */
+#if 0
+ char *default_pipe;
+ int ret;
+
+ default_pipe = talloc_asprintf(rctx, "%s/%s", PIPE_PATH,
+ rctx->sss_pipe_name);
+ if (!default_pipe) {
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(rctx->cdb, rctx,
+ rctx->confdb_socket_path, "unixSocket",
+ default_pipe, &rctx->sock_name);
+ if (ret != EOK) {
+ talloc_free(default_pipe);
+ return ret;
+ }
+ talloc_free(default_pipe);
+
+ default_pipe = talloc_asprintf(rctx, "%s/private/%s", PIPE_PATH,
+ rctx->sss_pipe_name);
+ if (!default_pipe) {
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(rctx->cdb, rctx,
+ rctx->confdb_socket_path, "privUnixSocket",
+ default_pipe, &rctx->priv_sock_name);
+ if (ret != EOK) {
+ talloc_free(default_pipe);
+ return ret;
+ }
+ talloc_free(default_pipe);
+#endif
+
+ if (rctx->sock_name != NULL ) {
+ rctx->lfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (rctx->lfd == -1) {
+ return EIO;
+ }
+
+ /* Set the umask so that permissions are set right on the socket.
+ * It must be readable and writable by anybody on the system. */
+ umask(0111);
+
+ set_nonblocking(rctx->lfd);
+ set_close_on_exec(rctx->lfd);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, rctx->sock_name, sizeof(addr.sun_path));
+
+ /* make sure we have no old sockets around */
+ unlink(rctx->sock_name);
+
+ if (bind(rctx->lfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ DEBUG(0,("Unable to bind on socket '%s'\n", rctx->sock_name));
+ goto failed;
+ }
+ if (listen(rctx->lfd, 10) != 0) {
+ DEBUG(0,("Unable to listen on socket '%s'\n", rctx->sock_name));
+ goto failed;
+ }
+
+ rctx->lfde = tevent_add_fd(rctx->ev, rctx, rctx->lfd,
+ TEVENT_FD_READ, accept_fd_handler, rctx);
+ if (!rctx->lfde) {
+ DEBUG(0, ("Failed to queue handler on pipe\n"));
+ goto failed;
+ }
+ }
+
+ if (rctx->priv_sock_name != NULL ) {
+ /* create privileged pipe */
+ rctx->priv_lfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (rctx->priv_lfd == -1) {
+ close(rctx->lfd);
+ return EIO;
+ }
+
+ umask(0177);
+
+ set_nonblocking(rctx->priv_lfd);
+ set_close_on_exec(rctx->priv_lfd);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, rctx->priv_sock_name, sizeof(addr.sun_path));
+
+ unlink(rctx->priv_sock_name);
+
+ if (bind(rctx->priv_lfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ DEBUG(0,("Unable to bind on socket '%s'\n", rctx->priv_sock_name));
+ goto failed;
+ }
+ if (listen(rctx->priv_lfd, 10) != 0) {
+ DEBUG(0,("Unable to listen on socket '%s'\n", rctx->priv_sock_name));
+ goto failed;
+ }
+
+ rctx->priv_lfde = tevent_add_fd(rctx->ev, rctx, rctx->priv_lfd,
+ TEVENT_FD_READ, accept_priv_fd_handler, rctx);
+ if (!rctx->priv_lfde) {
+ DEBUG(0, ("Failed to queue handler on privileged pipe\n"));
+ goto failed;
+ }
+ }
+
+ /* we want default permissions on created files to be very strict,
+ so set our umask to 0177 */
+ umask(0177);
+ return EOK;
+
+failed:
+ /* we want default permissions on created files to be very strict,
+ so set our umask to 0177 */
+ umask(0177);
+ close(rctx->lfd);
+ close(rctx->priv_lfd);
+ return EIO;
+}
+
+int sss_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb,
+ struct sss_cmd_table sss_cmds[],
+ const char *sss_pipe_name,
+ const char *sss_priv_pipe_name,
+ const char *confdb_service_path,
+ const char *svc_name,
+ uint16_t svc_version,
+ struct sbus_interface *monitor_intf,
+ const char *cli_name,
+ struct sbus_interface *dp_intf,
+ struct resp_ctx **responder_ctx)
+{
+ struct resp_ctx *rctx;
+ struct sss_domain_info *dom;
+ int ret;
+
+ rctx = talloc_zero(mem_ctx, struct resp_ctx);
+ if (!rctx) {
+ DEBUG(0, ("fatal error initializing resp_ctx\n"));
+ return ENOMEM;
+ }
+ rctx->ev = ev;
+ rctx->cdb = cdb;
+ rctx->sss_cmds = sss_cmds;
+ rctx->sock_name = sss_pipe_name;
+ rctx->priv_sock_name = sss_priv_pipe_name;
+ rctx->confdb_service_path = confdb_service_path;
+
+ ret = confdb_get_domains(rctx->cdb, &rctx->domains);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up domain map\n"));
+ return ret;
+ }
+
+ ret = sss_monitor_init(rctx, monitor_intf, svc_name, svc_version);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up message bus\n"));
+ return ret;
+ }
+
+ for (dom = rctx->domains; dom; dom = dom->next) {
+
+ /* skip local domain, it doesn't have a backend */
+ if (strcasecmp(dom->provider, "local") == 0) {
+ continue;
+ }
+
+ ret = sss_dp_init(rctx, dp_intf, cli_name, dom);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up backend connector\n"));
+ return ret;
+ }
+ }
+
+ ret = sysdb_init(rctx, ev, cdb, NULL, false, &rctx->db_list);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing resp_ctx\n"));
+ return ret;
+ }
+
+ ret = sss_names_init(rctx, rctx->cdb, &rctx->names);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing regex data\n"));
+ return ret;
+ }
+
+ /* after all initializations we are ready to listen on our socket */
+ ret = set_unix_socket(rctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing socket\n"));
+ return ret;
+ }
+
+ DEBUG(1, ("Responder Initialization complete\n"));
+
+ *responder_ctx = rctx;
+ return EOK;
+}
+
+int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain,
+ struct be_conn **_conn)
+{
+ struct be_conn *iter;
+
+ if (!rctx->be_conns) return ENOENT;
+
+ for (iter = rctx->be_conns; iter; iter = iter->next) {
+ if (strcasecmp(domain, iter->domain->name) == 0) break;
+ }
+
+ if (!iter) return ENOENT;
+
+ *_conn = iter;
+
+ return EOK;
+}
+
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
new file mode 100644
index 00000000..782befb1
--- /dev/null
+++ b/src/responder/common/responder_dp.c
@@ -0,0 +1,590 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/time.h>
+#include <time.h>
+#include "util/util.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/responder.h"
+#include "providers/data_provider.h"
+#include "sbus/sbus_client.h"
+
+hash_table_t *dp_requests = NULL;
+
+struct sss_dp_req;
+
+struct sss_dp_callback {
+ struct sss_dp_callback *prev;
+ struct sss_dp_callback *next;
+
+ struct sss_dp_req *sdp_req;
+
+ sss_dp_callback_t callback;
+ void *callback_ctx;
+};
+
+struct sss_dp_req {
+ struct tevent_context *ev;
+ DBusPendingCall *pending_reply;
+
+ char *key;
+
+ struct tevent_timer *tev;
+ struct sss_dp_callback *cb_list;
+
+ dbus_uint16_t err_maj;
+ dbus_uint32_t err_min;
+ char *err_msg;
+};
+
+static int sss_dp_callback_destructor(void *ptr)
+{
+ struct sss_dp_callback *cb = talloc_get_type(ptr, struct sss_dp_callback);
+
+ DLIST_REMOVE(cb->sdp_req->cb_list, cb);
+
+ return EOK;
+}
+
+static int sss_dp_req_destructor(void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+ struct sss_dp_callback *cb, *next;
+ hash_key_t key;
+
+ /* Cancel Dbus pending reply if still pending */
+ if (sdp_req->pending_reply) {
+ dbus_pending_call_cancel(sdp_req->pending_reply);
+ sdp_req->pending_reply = NULL;
+ }
+
+ /* Destroy the hash entry */
+ key.type = HASH_KEY_STRING;
+ key.str = sdp_req->key;
+ int hret = hash_delete(dp_requests, &key);
+ if (hret != HASH_SUCCESS) {
+ /* This should never happen */
+ DEBUG(0, ("Could not clear entry from request queue\n"));
+ }
+
+ /* Free any remaining callback */
+ if (sdp_req->err_maj == DP_ERR_OK) {
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = EIO;
+ sdp_req->err_msg = discard_const_p(char, "Internal Error");
+ }
+
+ cb = sdp_req->cb_list;
+ while (cb) {
+ cb->callback(sdp_req->err_maj,
+ sdp_req->err_min,
+ sdp_req->err_msg,
+ cb->callback_ctx);
+ next = cb->next;
+ talloc_free(cb);
+ cb = next;
+ }
+
+ return 0;
+}
+
+static void sdp_req_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = ETIMEDOUT;
+ sdp_req->err_msg = discard_const_p(char, "Timed out");
+
+ /* steal te on NULL because it will be freed as soon as the handler
+ * returns. Causing a double free if we don't, as te is allocated on
+ * sdp_req and we are just going to free it */
+ talloc_steal(NULL, te);
+
+ talloc_free(sdp_req);
+}
+
+static int sss_dp_get_reply(DBusPendingCall *pending,
+ dbus_uint16_t *err_maj,
+ dbus_uint32_t *err_min,
+ char **err_msg);
+
+static void sss_dp_invoke_callback(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct sss_dp_req *sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+ struct sss_dp_callback *cb;
+ struct timeval tv;
+ struct tevent_timer *tev;
+
+ cb = sdp_req->cb_list;
+ /* Remove the callback from the list, the caller may free it, within the
+ * callback. */
+ talloc_set_destructor((TALLOC_CTX *)cb, NULL);
+ DLIST_REMOVE(sdp_req->cb_list, cb);
+
+ cb->callback(sdp_req->err_maj,
+ sdp_req->err_min,
+ sdp_req->err_msg,
+ cb->callback_ctx);
+
+ /* Call the next callback if needed */
+ if (sdp_req->cb_list != NULL) {
+ tv = tevent_timeval_current();
+ tev = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sss_dp_invoke_callback, sdp_req);
+ if (!te) {
+ /* Out of memory or other serious error */
+ goto done;
+ }
+
+ return;
+ }
+
+ /* No more callbacks to invoke. Destroy the request */
+done:
+ /* steal te on NULL because it will be freed as soon as the handler
+ * returns. Causing a double free if we don't, as te is allocated on
+ * sdp_req and we are just going to free it */
+ talloc_steal(NULL, te);
+
+ talloc_zfree(sdp_req);
+}
+
+static void sss_dp_send_acct_callback(DBusPendingCall *pending, void *ptr)
+{
+ int ret;
+ struct sss_dp_req *sdp_req;
+ struct sss_dp_callback *cb;
+ struct timeval tv;
+ struct tevent_timer *te;
+
+ sdp_req = talloc_get_type(ptr, struct sss_dp_req);
+
+ /* prevent trying to cancel a reply that we already received */
+ sdp_req->pending_reply = NULL;
+
+ ret = sss_dp_get_reply(pending,
+ &sdp_req->err_maj,
+ &sdp_req->err_min,
+ &sdp_req->err_msg);
+ if (ret != EOK) {
+ if (ret == ETIME) {
+ sdp_req->err_maj = DP_ERR_TIMEOUT;
+ sdp_req->err_min = ret;
+ sdp_req->err_msg = talloc_strdup(sdp_req, "Request timed out");
+ }
+ else {
+ sdp_req->err_maj = DP_ERR_FATAL;
+ sdp_req->err_min = ret;
+ sdp_req->err_msg =
+ talloc_strdup(sdp_req,
+ "Failed to get reply from Data Provider");
+ }
+ }
+
+ /* Check whether we need to issue any callbacks */
+ cb = sdp_req->cb_list;
+ if (sdp_req->cb_list == NULL) {
+ if (cb == NULL) {
+ /* No callbacks to invoke. Destroy the hash entry */
+ talloc_zfree(sdp_req);
+ return;
+ }
+ }
+
+ /* Queue up all callbacks */
+ tv = tevent_timeval_current();
+ te = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sss_dp_invoke_callback, sdp_req);
+ if (!te) {
+ /* Out of memory or other serious error */
+ goto error;
+ }
+
+ return;
+
+error:
+ talloc_zfree(sdp_req);
+}
+
+static int sss_dp_send_acct_req_create(struct resp_ctx *rctx,
+ TALLOC_CTX *callback_memctx,
+ const char *domain,
+ uint32_t be_type,
+ char *filter,
+ int timeout,
+ sss_dp_callback_t callback,
+ void *callback_ctx,
+ struct sss_dp_req **ndp);
+
+int sss_dp_send_acct_req(struct resp_ctx *rctx, TALLOC_CTX *callback_memctx,
+ sss_dp_callback_t callback, void *callback_ctx,
+ int timeout, const char *domain,
+ bool fast_reply, int type,
+ const char *opt_name, uint32_t opt_id)
+{
+ int ret, hret;
+ uint32_t be_type;
+ char *filter;
+ hash_key_t key;
+ hash_value_t value;
+ TALLOC_CTX *tmp_ctx;
+ struct timeval tv;
+ struct sss_dp_req *sdp_req = NULL;
+ struct sss_dp_callback *cb;
+
+ /* either, or, not both */
+ if (opt_name && opt_id) {
+ return EINVAL;
+ }
+
+ if (!domain) {
+ return EINVAL;
+ }
+
+ switch (type) {
+ case SSS_DP_USER:
+ be_type = BE_REQ_USER;
+ break;
+ case SSS_DP_GROUP:
+ be_type = BE_REQ_GROUP;
+ break;
+ case SSS_DP_INITGROUPS:
+ be_type = BE_REQ_INITGROUPS;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ if (fast_reply) {
+ be_type |= BE_REQ_FAST;
+ }
+
+ if (dp_requests == NULL) {
+ /* Create a hash table to handle queued update requests */
+ ret = hash_create(10, &dp_requests, NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ fprintf(stderr, "cannot create hash table (%s)\n", hash_error_string(ret));
+ return EIO;
+ }
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ key.type = HASH_KEY_STRING;
+ key.str = NULL;
+
+ if (opt_name) {
+ filter = talloc_asprintf(tmp_ctx, "name=%s", opt_name);
+ key.str = talloc_asprintf(tmp_ctx, "%d%s@%s", type, opt_name, domain);
+ } else if (opt_id) {
+ filter = talloc_asprintf(tmp_ctx, "idnumber=%u", opt_id);
+ key.str = talloc_asprintf(tmp_ctx, "%d%d@%s", type, opt_id, domain);
+ } else {
+ filter = talloc_strdup(tmp_ctx, "name=*");
+ key.str = talloc_asprintf(tmp_ctx, "%d*@%s", type, domain);
+ }
+ if (!filter || !key.str) {
+ talloc_zfree(tmp_ctx);
+ return ENOMEM;
+ }
+
+ /* Check whether there's already a request in progress */
+ hret = hash_lookup(dp_requests, &key, &value);
+ switch (hret) {
+ case HASH_SUCCESS:
+ /* Request already in progress
+ * Add an additional callback if needed and return
+ */
+ DEBUG(2, ("Identical request in progress\n"));
+
+ if (callback) {
+ /* We have a new request asking for a callback */
+ sdp_req = talloc_get_type(value.ptr, struct sss_dp_req);
+ if (!sdp_req) {
+ DEBUG(0, ("Could not retrieve DP request context\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ cb = talloc_zero(callback_memctx, struct sss_dp_callback);
+ if (!cb) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cb->callback = callback;
+ cb->callback_ctx = callback_ctx;
+ cb->sdp_req = sdp_req;
+
+ DLIST_ADD_END(sdp_req->cb_list, cb, struct sss_dp_callback *);
+ talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor);
+ }
+
+ ret = EOK;
+ break;
+
+ case HASH_ERROR_KEY_NOT_FOUND:
+ /* No such request in progress
+ * Create a new request
+ */
+ ret = sss_dp_send_acct_req_create(rctx, callback_memctx, domain,
+ be_type, filter, timeout,
+ callback, callback_ctx,
+ &sdp_req);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ value.type = HASH_VALUE_PTR;
+ value.ptr = sdp_req;
+ hret = hash_enter(dp_requests, &key, &value);
+ if (hret != HASH_SUCCESS) {
+ DEBUG(0, ("Could not store request query (%s)",
+ hash_error_string(hret)));
+ talloc_zfree(sdp_req);
+ ret = EIO;
+ goto done;
+ }
+
+ sdp_req->key = talloc_strdup(sdp_req, key.str);
+
+ tv = tevent_timeval_current_ofs(timeout, 0);
+ sdp_req->tev = tevent_add_timer(sdp_req->ev, sdp_req, tv,
+ sdp_req_timeout, sdp_req);
+ if (!sdp_req->tev) {
+ DEBUG(0, ("Out of Memory!?"));
+ talloc_zfree(sdp_req);
+ ret = ENOMEM;
+ goto done;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *)sdp_req, sss_dp_req_destructor);
+
+ ret = EOK;
+ break;
+
+ default:
+ DEBUG(0,("Could not query request list (%s)\n",
+ hash_error_string(hret)));
+ talloc_zfree(sdp_req);
+ ret = EIO;
+ }
+
+done:
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
+static int sss_dp_send_acct_req_create(struct resp_ctx *rctx,
+ TALLOC_CTX *callback_memctx,
+ const char *domain,
+ uint32_t be_type,
+ char *filter,
+ int timeout,
+ sss_dp_callback_t callback,
+ void *callback_ctx,
+ struct sss_dp_req **ndp)
+{
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ DBusPendingCall *pending_reply;
+ dbus_bool_t dbret;
+ struct sss_dp_callback *cb;
+ struct sss_dp_req *sdp_req;
+ uint32_t attrs = BE_ATTR_CORE;
+ struct be_conn *be_conn;
+ int ret;
+
+ /* double check dp_ctx has actually been initialized.
+ * in some pathological cases it may happen that nss starts up before
+ * dp connection code is actually able to establish a connection.
+ */
+ ret = sss_dp_get_domain_conn(rctx, domain, &be_conn);
+ if (ret != EOK) {
+ DEBUG(1, ("The Data Provider connection for %s is not available!"
+ " This maybe a bug, it shouldn't happen!\n", domain));
+ return EIO;
+ }
+ dbus_conn = sbus_get_connection(be_conn->conn);
+
+ /* create the message */
+ msg = dbus_message_new_method_call(NULL,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_GETACCTINFO);
+ if (msg == NULL) {
+ DEBUG(0,("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ DEBUG(4, ("Sending request for [%s][%u][%d][%s]\n",
+ domain, be_type, attrs, filter));
+
+ dbret = dbus_message_append_args(msg,
+ DBUS_TYPE_UINT32, &be_type,
+ DBUS_TYPE_UINT32, &attrs,
+ DBUS_TYPE_STRING, &filter,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1,("Failed to build message\n"));
+ return EIO;
+ }
+
+ dbret = dbus_connection_send_with_reply(dbus_conn, msg,
+ &pending_reply, timeout);
+ if (!dbret || pending_reply == NULL) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ sdp_req = talloc_zero(rctx, struct sss_dp_req);
+ if (!sdp_req) {
+ dbus_message_unref(msg);
+ return ENOMEM;
+ }
+ sdp_req->ev = rctx->ev;
+ sdp_req->pending_reply = pending_reply;
+
+ if (callback) {
+ cb = talloc_zero(callback_memctx, struct sss_dp_callback);
+ if (!cb) {
+ dbus_message_unref(msg);
+ talloc_zfree(sdp_req);
+ return ENOMEM;
+ }
+ cb->callback = callback;
+ cb->callback_ctx = callback_ctx;
+ cb->sdp_req = sdp_req;
+
+ DLIST_ADD(sdp_req->cb_list, cb);
+ talloc_set_destructor((TALLOC_CTX *)cb, sss_dp_callback_destructor);
+ }
+
+ /* Set up the reply handler */
+ dbret = dbus_pending_call_set_notify(pending_reply,
+ sss_dp_send_acct_callback,
+ sdp_req, NULL);
+ if (!dbret) {
+ DEBUG(0, ("Could not queue up pending request!"));
+ talloc_zfree(sdp_req);
+ dbus_pending_call_cancel(pending_reply);
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ dbus_message_unref(msg);
+
+ *ndp = sdp_req;
+
+ return EOK;
+}
+
+static int sss_dp_get_reply(DBusPendingCall *pending,
+ dbus_uint16_t *err_maj,
+ dbus_uint32_t *err_min,
+ char **err_msg)
+{
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t ret;
+ int type;
+ int err = EOK;
+
+ dbus_error_init(&dbus_error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n"));
+
+ /* FIXME: Destroy this connection ? */
+ err = EIO;
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dbus_message_get_args(reply, &dbus_error,
+ DBUS_TYPE_UINT16, err_maj,
+ DBUS_TYPE_UINT32, err_min,
+ DBUS_TYPE_STRING, err_msg,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1,("Failed to parse message\n"));
+ /* FIXME: Destroy this connection ? */
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ err = EIO;
+ goto done;
+ }
+
+ DEBUG(4, ("Got reply (%u, %u, %s) from Data Provider\n",
+ (unsigned int)*err_maj, (unsigned int)*err_min, *err_msg));
+
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+ if (strcmp(dbus_message_get_error_name(reply),
+ DBUS_ERROR_NO_REPLY) == 0) {
+ err = ETIME;
+ goto done;
+ }
+ DEBUG(0,("The Data Provider returned an error [%s]\n",
+ dbus_message_get_error_name(reply)));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+
+ /* FIXME: Destroy this connection ? */
+ err = EIO;
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+
+ return err;
+}
+
diff --git a/src/responder/common/responder_packet.c b/src/responder/common/responder_packet.c
new file mode 100644
index 00000000..6e581a3c
--- /dev/null
+++ b/src/responder/common/responder_packet.c
@@ -0,0 +1,253 @@
+/*
+ SSSD
+
+ SSS Client Responder, command parser
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <errno.h>
+#include "talloc.h"
+#include "util/util.h"
+#include "responder/common/responder_packet.h"
+
+#define SSSSRV_PACKET_MEM_SIZE 512
+
+struct sss_packet {
+ size_t memsize;
+ uint8_t *buffer;
+
+ /* header */
+ uint32_t *len;
+ uint32_t *cmd;
+ uint32_t *status;
+ uint32_t *reserved;
+
+ uint8_t *body;
+
+ /* io pointer */
+ size_t iop;
+};
+
+/*
+ * Allocate a new packet structure
+ *
+ * - if size is defined use it otherwise the default packet will be
+ * SSSSRV_PACKET_MEM_SIZE bytes.
+ */
+int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size,
+ enum sss_cli_command cmd,
+ struct sss_packet **rpacket)
+{
+ struct sss_packet *packet;
+
+ packet = talloc(mem_ctx, struct sss_packet);
+ if (!packet) return ENOMEM;
+
+ if (size) {
+ int n = (size + SSS_NSS_HEADER_SIZE) % SSSSRV_PACKET_MEM_SIZE;
+ packet->memsize = (n + 1) * SSSSRV_PACKET_MEM_SIZE;
+ } else {
+ packet->memsize = SSSSRV_PACKET_MEM_SIZE;
+ }
+
+ packet->buffer = talloc_size(packet, packet->memsize);
+ if (!packet->buffer) {
+ talloc_free(packet);
+ return ENOMEM;
+ }
+ memset(packet->buffer, 0, SSS_NSS_HEADER_SIZE);
+
+ packet->len = &((uint32_t *)packet->buffer)[0];
+ packet->cmd = &((uint32_t *)packet->buffer)[1];
+ packet->status = &((uint32_t *)packet->buffer)[2];
+ packet->reserved = &((uint32_t *)packet->buffer)[3];
+ packet->body = (uint8_t *)&((uint32_t *)packet->buffer)[4];
+
+ *(packet->len) = size + SSS_NSS_HEADER_SIZE;
+ *(packet->cmd) = cmd;
+
+ packet->iop = 0;
+
+ *rpacket = packet;
+
+ return EOK;
+}
+
+/* grows a packet size only in SSSSRV_PACKET_MEM_SIZE chunks */
+int sss_packet_grow(struct sss_packet *packet, size_t size)
+{
+ size_t totlen, len;
+ uint8_t *newmem;
+
+ if (size == 0) {
+ return EOK;
+ }
+
+ totlen = packet->memsize;
+ len = *packet->len + size;
+
+ /* make sure we do not overflow */
+ if (totlen < len) {
+ int n = len % SSSSRV_PACKET_MEM_SIZE + 1;
+ totlen += n * SSSSRV_PACKET_MEM_SIZE;
+ if (totlen < len) {
+ return EINVAL;
+ }
+ }
+
+ if (totlen > packet->memsize) {
+ newmem = talloc_realloc_size(packet, packet->buffer, totlen);
+ if (!newmem) {
+ return ENOMEM;
+ }
+
+ packet->memsize = totlen;
+
+ /* re-set pointers if realloc had to move memory */
+ if (newmem != packet->buffer) {
+ packet->buffer = newmem;
+ packet->len = &((uint32_t *)packet->buffer)[0];
+ packet->cmd = &((uint32_t *)packet->buffer)[1];
+ packet->status = &((uint32_t *)packet->buffer)[2];
+ packet->reserved = &((uint32_t *)packet->buffer)[3];
+ packet->body = (uint8_t *)&((uint32_t *)packet->buffer)[4];
+ }
+ }
+
+ *(packet->len) += size;
+
+ return 0;
+}
+
+/* reclaim backet previously resrved space in the packet
+ * usually done in functione recovering from not fatal erros */
+int sss_packet_shrink(struct sss_packet *packet, size_t size)
+{
+ size_t newlen;
+
+ if (size > *(packet->len)) return EINVAL;
+
+ newlen = *(packet->len) - size;
+ if (newlen < SSS_NSS_HEADER_SIZE) return EINVAL;
+
+ *(packet->len) = newlen;
+ return 0;
+}
+
+int sss_packet_set_size(struct sss_packet *packet, size_t size)
+{
+ size_t newlen;
+
+ newlen = SSS_NSS_HEADER_SIZE + size;
+
+ /* make sure we do not overflow */
+ if (packet->memsize < newlen) return EINVAL;
+
+ *(packet->len) = newlen;
+
+ return 0;
+}
+
+int sss_packet_recv(struct sss_packet *packet, int fd)
+{
+ size_t rb;
+ size_t len;
+ void *buf;
+
+ buf = packet->buffer + packet->iop;
+ if (packet->iop > 4) len = *packet->len - packet->iop;
+ else len = packet->memsize - packet->iop;
+
+ /* check for wrapping */
+ if (len > packet->memsize) {
+ return EINVAL;
+ }
+
+ errno = 0;
+ rb = recv(fd, buf, len, 0);
+
+ if (rb == -1 && errno == EAGAIN) {
+ return EAGAIN;
+ }
+
+ if (rb == 0) {
+ return ENODATA;
+ }
+
+ if (*packet->len > packet->memsize) {
+ return EINVAL;
+ }
+
+ packet->iop += rb;
+ if (packet->iop < 4) {
+ return EAGAIN;
+ }
+
+ if (packet->iop < *packet->len) {
+ return EAGAIN;
+ }
+
+ return EOK;
+}
+
+int sss_packet_send(struct sss_packet *packet, int fd)
+{
+ size_t rb;
+ size_t len;
+ void *buf;
+
+ buf = packet->buffer + packet->iop;
+ len = *packet->len - packet->iop;
+
+ errno = 0;
+ rb = send(fd, buf, len, 0);
+
+ if (rb == -1 && errno == EAGAIN) {
+ return EAGAIN;
+ }
+
+ if (rb == 0) {
+ return EIO;
+ }
+
+ packet->iop += rb;
+
+ if (packet->iop < *packet->len) {
+ return EAGAIN;
+ }
+
+ return EOK;
+}
+
+enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet)
+{
+ return (enum sss_cli_command)(*packet->cmd);
+}
+
+void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen)
+{
+ *body = packet->body;
+ *blen = *packet->len - SSS_NSS_HEADER_SIZE;
+}
+
+void sss_packet_set_error(struct sss_packet *packet, int error)
+{
+ *(packet->status) = error;
+}
diff --git a/src/responder/common/responder_packet.h b/src/responder/common/responder_packet.h
new file mode 100644
index 00000000..2bfdc8a3
--- /dev/null
+++ b/src/responder/common/responder_packet.h
@@ -0,0 +1,43 @@
+/*
+ SSSD
+
+ SSS Client Responder, header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SSSSRV_PACKET_H__
+#define __SSSSRV_PACKET_H__
+
+#include "sss_client/sss_cli.h"
+
+#define SSS_PACKET_MAX_RECV_SIZE 1024
+
+struct sss_packet;
+
+int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size,
+ enum sss_cli_command cmd,
+ struct sss_packet **rpacket);
+int sss_packet_grow(struct sss_packet *packet, size_t size);
+int sss_packet_shrink(struct sss_packet *packet, size_t size);
+int sss_packet_set_size(struct sss_packet *packet, size_t size);
+int sss_packet_recv(struct sss_packet *packet, int fd);
+int sss_packet_send(struct sss_packet *packet, int fd);
+enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet);
+void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen);
+void sss_packet_set_error(struct sss_packet *packet, int error);
+
+#endif /* __SSSSRV_PACKET_H__ */
diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c
new file mode 100644
index 00000000..7de346f0
--- /dev/null
+++ b/src/responder/nss/nsssrv.c
@@ -0,0 +1,367 @@
+/*
+ SSSD
+
+ NSS Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "popt.h"
+#include "util/util.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_nc.h"
+#include "db/sysdb.h"
+#include "confdb/confdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "responder/common/responder_packet.h"
+#include "providers/data_provider.h"
+#include "monitor/monitor_interfaces.h"
+#include "sbus/sbus_client.h"
+
+#define SSS_NSS_PIPE_NAME "nss"
+
+#define DEFAULT_PWFIELD "*"
+
+static int service_reload(DBusMessage *message, struct sbus_connection *conn);
+
+struct sbus_method monitor_nss_methods[] = {
+ { MON_CLI_METHOD_PING, monitor_common_pong },
+ { MON_CLI_METHOD_RELOAD, service_reload },
+ { MON_CLI_METHOD_RES_INIT, monitor_common_res_init },
+ { NULL, NULL }
+};
+
+struct sbus_interface monitor_nss_interface = {
+ MONITOR_INTERFACE,
+ MONITOR_PATH,
+ SBUS_DEFAULT_VTABLE,
+ monitor_nss_methods,
+ NULL
+};
+
+static int service_reload(DBusMessage *message, struct sbus_connection *conn)
+{
+ /* Monitor calls this function when we need to reload
+ * our configuration information. Perform whatever steps
+ * are needed to update the configuration objects.
+ */
+
+ /* Send an empty reply to acknowledge receipt */
+ return monitor_common_pong(message, conn);
+}
+
+static int nss_get_config(struct nss_ctx *nctx,
+ struct resp_ctx *rctx,
+ struct confdb_ctx *cdb)
+{
+ TALLOC_CTX *tmpctx;
+ struct sss_domain_info *dom;
+ char *domain, *name;
+ char **filter_list;
+ int ret, i;
+
+ tmpctx = talloc_new(nctx);
+ if (!tmpctx) return ENOMEM;
+
+ ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_ENUM_CACHE_TIMEOUT, 120,
+ &nctx->enum_cache_timeout);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_ENTRY_NEG_TIMEOUT, 15,
+ &nctx->neg_timeout);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_bool(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_FILTER_USERS_IN_GROUPS, true,
+ &nctx->filter_users_in_groups);
+ if (ret != EOK) goto done;
+
+
+ ret = confdb_get_int(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE, 0,
+ &nctx->cache_refresh_percent);
+ if (ret != EOK) goto done;
+ if (nctx->cache_refresh_percent < 0 ||
+ nctx->cache_refresh_percent > 99) {
+ DEBUG(0,("Configuration error: entry_cache_nowait_percentage is"
+ "invalid. Disabling feature.\n"));
+ nctx->cache_refresh_percent = 0;
+ }
+
+ ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_FILTER_USERS, &filter_list);
+ if (ret == ENOENT) {
+ filter_list = talloc_array(tmpctx, char *, 2);
+ filter_list[0] = talloc_strdup(tmpctx, "root");
+ filter_list[1] = NULL;
+ if (!filter_list || !filter_list[0]) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = EOK;
+ }
+ else if (ret != EOK) goto done;
+
+ for (i = 0; (filter_list && filter_list[i]); i++) {
+ ret = sss_parse_name(tmpctx, nctx->rctx->names,
+ filter_list[i], &domain, &name);
+ if (ret != EOK) {
+ DEBUG(1, ("Invalid name in filterUsers list: [%s] (%d)\n",
+ filter_list[i], ret));
+ continue;
+ }
+ if (domain) {
+ ret = nss_ncache_set_user(nctx->ncache, true, domain, name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to store permanent user filter for [%s]"
+ " (%d [%s])\n", filter_list[i],
+ ret, strerror(ret)));
+ continue;
+ }
+ } else {
+ for (dom = rctx->domains; dom; dom = dom->next) {
+ ret = nss_ncache_set_user(nctx->ncache, true, dom->name, name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to store permanent user filter for"
+ " [%s:%s] (%d [%s])\n",
+ dom->name, filter_list[i],
+ ret, strerror(ret)));
+ continue;
+ }
+ }
+ }
+ }
+
+ ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_FILTER_GROUPS, &filter_list);
+ if (ret == ENOENT) {
+ filter_list = talloc_array(tmpctx, char *, 2);
+ filter_list[0] = talloc_strdup(tmpctx, "root");
+ filter_list[1] = NULL;
+ if (!filter_list || !filter_list[0]) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = EOK;
+ }
+ else if (ret != EOK) goto done;
+
+ for (i = 0; (filter_list && filter_list[i]); i++) {
+ ret = sss_parse_name(tmpctx, nctx->rctx->names,
+ filter_list[i], &domain, &name);
+ if (ret != EOK) {
+ DEBUG(1, ("Invalid name in filterGroups list: [%s] (%d)\n",
+ filter_list[i], ret));
+ continue;
+ }
+ if (domain) {
+ ret = nss_ncache_set_group(nctx->ncache, true, domain, name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to store permanent group filter for"
+ " [%s] (%d [%s])\n", filter_list[i],
+ ret, strerror(ret)));
+ continue;
+ }
+ } else {
+ for (dom = rctx->domains; dom; dom = dom->next) {
+ ret = nss_ncache_set_group(nctx->ncache, true, dom->name, name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to store permanent group filter for"
+ " [%s:%s] (%d [%s])\n",
+ dom->name, filter_list[i],
+ ret, strerror(ret)));
+ continue;
+ }
+ }
+ }
+ }
+
+ ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY,
+ CONFDB_NSS_PWFIELD, DEFAULT_PWFIELD,
+ &nctx->pwfield);
+ if (ret != EOK) goto done;
+
+ ret = 0;
+done:
+ talloc_free(tmpctx);
+ return ret;
+}
+
+static struct sbus_method nss_dp_methods[] = {
+ { NULL, NULL }
+};
+
+struct sbus_interface nss_dp_interface = {
+ DP_INTERFACE,
+ DP_PATH,
+ SBUS_DEFAULT_VTABLE,
+ nss_dp_methods,
+ NULL
+};
+
+
+static void nss_dp_reconnect_init(struct sbus_connection *conn,
+ int status, void *pvt)
+{
+ struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn);
+ int ret;
+
+ /* Did we reconnect successfully? */
+ if (status == SBUS_RECONNECT_SUCCESS) {
+ DEBUG(1, ("Reconnected to the Data Provider.\n"));
+
+ /* Identify ourselves to the data provider */
+ ret = dp_common_send_id(be_conn->conn,
+ DATA_PROVIDER_VERSION,
+ "NSS", be_conn->domain->name);
+ /* all fine */
+ if (ret == EOK) return;
+ }
+
+ /* Failed to reconnect */
+ DEBUG(0, ("Could not reconnect to %s provider.\n",
+ be_conn->domain->name));
+
+ /* FIXME: kill the frontend and let the monitor restart it ? */
+ /* nss_shutdown(rctx); */
+}
+
+int nss_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb)
+{
+ struct sss_cmd_table *nss_cmds;
+ struct be_conn *iter;
+ struct nss_ctx *nctx;
+ int ret, max_retries;
+
+ nctx = talloc_zero(mem_ctx, struct nss_ctx);
+ if (!nctx) {
+ DEBUG(0, ("fatal error initializing nss_ctx\n"));
+ return ENOMEM;
+ }
+
+ ret = nss_ncache_init(nctx, &nctx->ncache);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing negative cache\n"));
+ return ret;
+ }
+
+ nss_cmds = get_nss_cmds();
+
+ ret = sss_process_init(nctx, ev, cdb,
+ nss_cmds,
+ SSS_NSS_SOCKET_NAME, NULL,
+ CONFDB_NSS_CONF_ENTRY,
+ NSS_SBUS_SERVICE_NAME,
+ NSS_SBUS_SERVICE_VERSION,
+ &monitor_nss_interface,
+ "NSS", &nss_dp_interface,
+ &nctx->rctx);
+ if (ret != EOK) {
+ return ret;
+ }
+ nctx->rctx->pvt_ctx = nctx;
+
+ ret = nss_get_config(nctx, nctx->rctx, cdb);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error getting nss config\n"));
+ return ret;
+ }
+
+ /* Enable automatic reconnection to the Data Provider */
+ ret = confdb_get_int(nctx->rctx->cdb, nctx->rctx,
+ CONFDB_NSS_CONF_ENTRY,
+ CONFDB_SERVICE_RECON_RETRIES,
+ 3, &max_retries);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to set up automatic reconnection\n"));
+ return ret;
+ }
+
+ for (iter = nctx->rctx->be_conns; iter; iter = iter->next) {
+ sbus_reconnect_init(iter->conn, max_retries,
+ nss_dp_reconnect_init, iter);
+ }
+
+ DEBUG(1, ("NSS Initialization complete\n"));
+
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ struct main_context *main_ctx;
+ int ret;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+
+ poptFreeContext(pc);
+
+ /* set up things like debug, signals, daemonization, etc... */
+ debug_log_file = "sssd_nss";
+
+ ret = server_setup("sssd[nss]", 0, CONFDB_NSS_CONF_ENTRY, &main_ctx);
+ if (ret != EOK) return 2;
+
+ ret = die_if_parent_died();
+ if (ret != EOK) {
+ /* This is not fatal, don't return */
+ DEBUG(2, ("Could not set up to exit when parent process does\n"));
+ }
+
+ ret = nss_process_init(main_ctx,
+ main_ctx->event_ctx,
+ main_ctx->confdb_ctx);
+ if (ret != EOK) return 3;
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ return 0;
+}
+
diff --git a/src/responder/nss/nsssrv.h b/src/responder/nss/nsssrv.h
new file mode 100644
index 00000000..a6c66183
--- /dev/null
+++ b/src/responder/nss/nsssrv.h
@@ -0,0 +1,70 @@
+/*
+ SSSD
+
+ NSS Responder, header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __NSSSRV_H__
+#define __NSSSRV_H__
+
+#include <stdint.h>
+#include <sys/un.h>
+#include "config.h"
+#include "talloc.h"
+#include "tevent.h"
+#include "ldb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/responder.h"
+#include "responder/nss/nsssrv_nc.h"
+
+#define NSS_SBUS_SERVICE_VERSION 0x0001
+#define NSS_SBUS_SERVICE_NAME "nss"
+
+#define NSS_PACKET_MAX_RECV_SIZE 1024
+
+struct getent_ctx;
+
+struct nss_ctx {
+ struct resp_ctx *rctx;
+
+ int neg_timeout;
+ struct nss_nc_ctx *ncache;
+
+ int cache_refresh_percent;
+
+ int enum_cache_timeout;
+ time_t last_user_enum;
+ time_t last_group_enum;
+
+ struct getent_ctx *pctx;
+ struct getent_ctx *gctx;
+
+ bool filter_users_in_groups;
+
+ char *pwfield;
+};
+
+struct nss_packet;
+
+int nss_cmd_execute(struct cli_ctx *cctx);
+
+struct sss_cmd_table *get_nss_cmds(void);
+
+#endif /* __NSSSRV_H__ */
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
new file mode 100644
index 00000000..46d4a236
--- /dev/null
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -0,0 +1,3182 @@
+/*
+ SSSD
+
+ NSS Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "responder/nss/nsssrv.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include <time.h>
+
+struct nss_cmd_ctx {
+ struct cli_ctx *cctx;
+ char *name;
+ uint32_t id;
+
+ bool immediate;
+ bool check_next;
+ bool enum_cached;
+};
+
+struct dom_ctx {
+ struct sss_domain_info *domain;
+ struct ldb_result *res;
+ int cur;
+};
+
+struct getent_ctx {
+ struct dom_ctx *doms;
+ int num;
+ int cur;
+};
+
+struct nss_dom_ctx {
+ struct nss_cmd_ctx *cmdctx;
+ struct sss_domain_info *domain;
+
+ bool check_provider;
+
+ /* cache results */
+ struct ldb_result *res;
+};
+
+static int nss_cmd_send_error(struct nss_cmd_ctx *cmdctx, int err)
+{
+ struct cli_ctx *cctx = cmdctx->cctx;
+ int ret;
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ sss_packet_set_error(cctx->creq->out, err);
+ return EOK;
+}
+
+#define NSS_CMD_FATAL_ERROR(cctx) do { \
+ DEBUG(1,("Fatal error, killing connection!")); \
+ talloc_free(cctx); \
+ return; \
+} while(0)
+
+#define NSS_CMD_FATAL_ERROR_CODE(cctx, ret) do { \
+ DEBUG(1,("Fatal error, killing connection!")); \
+ talloc_free(cctx); \
+ return ret; \
+} while(0)
+
+static struct sss_domain_info *nss_get_dom(struct sss_domain_info *doms,
+ const char *domain)
+{
+ struct sss_domain_info *dom;
+
+ for (dom = doms; dom; dom = dom->next) {
+ if (strcasecmp(dom->name, domain) == 0) break;
+ }
+ if (!dom) DEBUG(2, ("Unknown domain [%s]!\n", domain));
+
+ return dom;
+}
+
+static int fill_empty(struct sss_packet *packet)
+{
+ uint8_t *body;
+ size_t blen;
+ int ret;
+
+ ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
+ if (ret != EOK) return ret;
+
+ sss_packet_get_body(packet, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* num results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+
+ return EOK;
+}
+
+/****************************************************************************
+ * PASSWD db related functions
+ ***************************************************************************/
+
+static int fill_pwent(struct sss_packet *packet,
+ struct sss_domain_info *dom,
+ struct nss_ctx *nctx,
+ bool filter_users,
+ struct ldb_message **msgs,
+ int count)
+{
+ struct ldb_message *msg;
+ uint8_t *body;
+ const char *name;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ uint32_t uid;
+ uint32_t gid;
+ size_t rsize, rp, blen;
+ size_t s1, s2, s3, s4, s5;
+ size_t dom_len = 0;
+ int delim = 1;
+ int i, ret, num, t;
+ bool add_domain = dom->fqnames;
+ const char *domain = dom->name;
+ const char *namefmt = nctx->rctx->names->fq_fmt;
+ bool packet_initialized = false;
+ int ncret;
+
+ if (add_domain) dom_len = strlen(domain);
+
+ rp = 2*sizeof(uint32_t);
+
+ num = 0;
+ for (i = 0; i < count; i++) {
+ msg = msgs[i];
+
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+ gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+
+ if (!name || !uid || !gid) {
+ DEBUG(1, ("Incomplete user object for %s[%llu]! Skipping\n",
+ name?name:"<NULL>", (unsigned long long int)uid));
+ continue;
+ }
+
+ if (filter_users) {
+ ncret = nss_ncache_check_user(nctx->ncache,
+ nctx->neg_timeout,
+ domain, name);
+ if (ncret == EEXIST) {
+ DEBUG(4, ("User [%s@%s] filtered out! (negative cache)\n",
+ name, domain));
+ continue;
+ }
+ }
+
+ if (!packet_initialized) {
+ /* first 2 fields (len and reserved), filled up later */
+ ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
+ if (ret != EOK) return ret;
+ packet_initialized = true;
+ }
+
+ gecos = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL);
+ homedir = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL);
+ shell = ldb_msg_find_attr_as_string(msg, SYSDB_SHELL, NULL);
+
+ if (!gecos) gecos = "";
+ if (!homedir) homedir = "/";
+ if (!shell) shell = "";
+
+ s1 = strlen(name) + 1;
+ s2 = strlen(gecos) + 1;
+ s3 = strlen(homedir) + 1;
+ s4 = strlen(shell) + 1;
+ s5 = strlen(nctx->pwfield) + 1;
+ if (add_domain) s1 += delim + dom_len;
+
+ rsize = 2*sizeof(uint32_t) +s1 + s2 + s3 + s4 + s5;
+
+ ret = sss_packet_grow(packet, rsize);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+
+ ((uint32_t *)(&body[rp]))[0] = uid;
+ ((uint32_t *)(&body[rp]))[1] = gid;
+ rp += 2*sizeof(uint32_t);
+
+ if (add_domain) {
+ ret = snprintf((char *)&body[rp], s1, namefmt, name, domain);
+ if (ret >= s1) {
+ /* need more space, got creative with the print format ? */
+ t = ret - s1 + 1;
+ ret = sss_packet_grow(packet, t);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ delim += t;
+ s1 += t;
+ sss_packet_get_body(packet, &body, &blen);
+
+ /* retry */
+ ret = snprintf((char *)&body[rp], s1, namefmt, name, domain);
+ }
+
+ if (ret != s1-1) {
+ DEBUG(1, ("Failed to generate a fully qualified name for user "
+ "[%s] in [%s]! Skipping user.\n", name, domain));
+ continue;
+ }
+ } else {
+ memcpy(&body[rp], name, s1);
+ }
+ rp += s1;
+
+ memcpy(&body[rp], nctx->pwfield, s5);
+ rp += s5;
+ memcpy(&body[rp], gecos, s2);
+ rp += s2;
+ memcpy(&body[rp], homedir, s3);
+ rp += s3;
+ memcpy(&body[rp], shell, s4);
+ rp += s4;
+
+ num++;
+ }
+
+done:
+ /* if there are no results just return ENOENT,
+ * let the caller decide if this is the last packet or not */
+ if (!packet_initialized) return ENOENT;
+
+ sss_packet_get_body(packet, &body, &blen);
+ ((uint32_t *)body)[0] = num; /* num results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+
+ return EOK;
+}
+
+static errno_t check_cache(struct nss_dom_ctx *dctx,
+ struct nss_ctx *nctx,
+ struct ldb_result *res,
+ int req_type,
+ const char *opt_name,
+ uint32_t opt_id,
+ sss_dp_callback_t callback)
+{
+ errno_t ret;
+ int timeout;
+ time_t now;
+ uint64_t lastUpdate;
+ uint64_t cacheExpire = 0;
+ uint64_t midpoint_refresh;
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ bool off_band_update = false;
+
+ /* when searching for a user, more than one reply is a db error */
+ if ((req_type == SSS_DP_USER) && (res->count > 1)) {
+ DEBUG(1, ("getpwXXX call returned more than one result!"
+ " DB Corrupted?\n"));
+ ret = nss_cmd_send_error(cmdctx, ENOENT);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR_CODE(cctx, ENOENT);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return ENOENT;
+ }
+
+ /* if we have any reply let's check cache validity */
+ if (res->count > 0) {
+
+ now = time(NULL);
+
+ lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0],
+ SYSDB_LAST_UPDATE, 0);
+ if (req_type == SSS_DP_INITGROUPS) {
+ cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
+ SYSDB_INITGR_EXPIRE, 1);
+ }
+ if (cacheExpire == 0) {
+ cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
+ SYSDB_CACHE_EXPIRE, 0);
+ }
+
+ midpoint_refresh = 0;
+ if(nctx->cache_refresh_percent) {
+ midpoint_refresh = lastUpdate +
+ (cacheExpire - lastUpdate)*nctx->cache_refresh_percent/100;
+ if (midpoint_refresh - lastUpdate < 10) {
+ /* If the percentage results in an expiration
+ * less than ten seconds after the lastUpdate time,
+ * that's too often we will simply set it to 10s
+ */
+ midpoint_refresh = lastUpdate+10;
+ }
+ }
+
+ if (cacheExpire > now) {
+ /* cache still valid */
+
+ if (midpoint_refresh && midpoint_refresh < now) {
+ /* We're past the the cache refresh timeout
+ * We'll return the value from the cache, but we'll also
+ * queue the cache entry for update out-of-band.
+ */
+ DEBUG(6, ("Performing midpoint cache update on [%s]\n",
+ opt_name));
+ off_band_update = true;
+ }
+ else {
+
+ /* Cache is still valid. Just return it. */
+ return EOK;
+ }
+ }
+ }
+
+ if (off_band_update) {
+
+ timeout = SSS_CLI_SOCKET_TIMEOUT/2;
+
+ /* No callback required
+ * This was an out-of-band update. We'll return EOK
+ * so the calling function can return the cached entry
+ * immediately.
+ */
+ ret = sss_dp_send_acct_req(cctx->rctx, NULL, NULL, NULL,
+ timeout, dctx->domain->name,
+ true, req_type,
+ opt_name, opt_id);
+ if (ret != EOK) {
+ DEBUG(3, ("Failed to dispatch request: %d(%s)\n",
+ ret, strerror(ret)));
+ } else {
+
+ DEBUG(3, ("Updating cache out-of-band\n"));
+ }
+
+ } else {
+ /* This is a cache miss. Or the cache is expired.
+ * We need to get the updated user information before returning it.
+ */
+
+ /* dont loop forever :-) */
+ dctx->check_provider = false;
+ timeout = SSS_CLI_SOCKET_TIMEOUT/2;
+
+ /* keep around current data in case backend is offline */
+ if (res->count) {
+ dctx->res = talloc_steal(dctx, res);
+ }
+
+ ret = sss_dp_send_acct_req(cctx->rctx, cmdctx,
+ callback, dctx, timeout,
+ dctx->domain->name,
+ true, req_type,
+ opt_name, opt_id);
+ if (ret != EOK) {
+ DEBUG(3, ("Failed to dispatch request: %d(%s)\n",
+ ret, strerror(ret)));
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR_CODE(cctx, EIO);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return EIO;
+ }
+
+ return EAGAIN;
+ }
+
+ return EOK;
+}
+
+static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_getpwnam_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *dom;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ bool neghit = false;
+ int ncret;
+ int ret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, status);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ if (dctx->check_provider) {
+ ret = check_cache(dctx, nctx, res,
+ SSS_DP_USER, cmdctx->name, 0,
+ nss_cmd_getpwnam_dp_callback);
+ if (ret != EOK) {
+ /* Anything but EOK means we should reenter the mainloop
+ * because we may be refreshing the cache
+ */
+ return;
+ }
+ }
+
+ switch (res->count) {
+ case 0:
+ if (cmdctx->check_next) {
+
+ ret = EOK;
+
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = dctx->domain->next; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ ncret = nss_ncache_check_user(nctx->ncache,
+ nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ if (neghit) {
+ DEBUG(2, ("User [%s] does not exist! (negative cache)\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+ if (dom == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ if (dctx->res) talloc_free(res);
+ dctx->res = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getpwnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getpwnam_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+
+ DEBUG(2, ("No results for getpwnam call\n"));
+
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_user(nctx->ncache, false,
+ dctx->domain->name, cmdctx->name);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ break;
+
+ case 1:
+ DEBUG(6, ("Returning info for user [%s]\n", cmdctx->name));
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = fill_pwent(cctx->creq->out,
+ dctx->domain,
+ nctx, false,
+ res->msgs, res->count);
+ if (ret == ENOENT) {
+ ret = fill_empty(cctx->creq->out);
+ }
+ sss_packet_set_error(cctx->creq->out, ret);
+
+ break;
+
+ default:
+ DEBUG(1, ("getpwnam call returned more than one result !?!\n"));
+ ret = nss_cmd_send_error(cmdctx, ENOENT);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_getpwnam_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+
+ if (!dctx->res) {
+ /* return 0 results */
+ dctx->res = talloc_zero(dctx, struct ldb_result);
+ if (!dctx->res) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ nss_cmd_getpwnam_callback(dctx, LDB_SUCCESS, dctx->res);
+ return;
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getpwnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getpwnam_callback, dctx);
+
+done:
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache! (%d [%s])\n",
+ ret, strerror(ret)));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_getpwnam(struct cli_ctx *cctx)
+{
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ const char *rawname;
+ char *domname;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int ncret;
+ bool neghit = false;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* get user name to query */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+
+ /* if not terminated fail */
+ if (body[blen -1] != '\0') {
+ ret = EINVAL;
+ goto done;
+ }
+ rawname = (const char *)body;
+
+ domname = NULL;
+ ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname,
+ &domname, &cmdctx->name);
+ if (ret != EOK) {
+ DEBUG(2, ("Invalid name received [%s]\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ DEBUG(4, ("Requesting info for [%s] from [%s]\n",
+ cmdctx->name, domname?domname:"<ALL>"));
+
+ if (domname) {
+ dctx->domain = nss_get_dom(cctx->rctx->domains, domname);
+ if (!dctx->domain) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
+ dctx->domain->name, cmdctx->name);
+ if (ncret == EEXIST) {
+ neghit = true;
+ }
+ }
+ else {
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ dctx->domain = dom;
+ }
+ if (neghit) {
+ DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+ if (dctx->domain == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+ if (!domname) {
+ /* this is a multidomain search */
+ cmdctx->check_next = true;
+ }
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_getpwnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getpwnam_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_getpwuid_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ bool neghit = false;
+ int ret;
+ int ncret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, status);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ if (dctx->check_provider) {
+ ret = check_cache(dctx, nctx, res,
+ SSS_DP_USER, NULL, cmdctx->id,
+ nss_cmd_getpwuid_dp_callback);
+ if (ret != EOK) {
+ /* Anything but EOK means we should reenter the mainloop
+ * because we may be refreshing the cache
+ */
+ return;
+ }
+ }
+
+ switch (res->count) {
+ case 0:
+ if (cmdctx->check_next) {
+
+ ret = EOK;
+
+ dom = dctx->domain->next;
+ ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout,
+ cmdctx->id);
+ if (ncret == EEXIST) {
+ DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n",
+ (unsigned long)cmdctx->id));
+ ret = ENOENT;
+ }
+ if (dom == NULL) {
+ DEBUG(0, ("No matching domain found for [%lu], fail!\n",
+ (unsigned long)cmdctx->id));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ if (dctx->res) talloc_free(res);
+ dctx->res = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getpwuid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getpwuid_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+
+ DEBUG(2, ("No results for getpwuid call\n"));
+
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_uid(nctx->ncache, false, cmdctx->id);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ break;
+
+ case 1:
+ DEBUG(6, ("Returning info for user [%u]\n", (unsigned)cmdctx->id));
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+
+ ret = fill_pwent(cctx->creq->out,
+ dctx->domain,
+ nctx, true,
+ res->msgs, res->count);
+ if (ret == ENOENT) {
+ ret = fill_empty(cctx->creq->out);
+ }
+ sss_packet_set_error(cctx->creq->out, ret);
+
+ break;
+
+ default:
+ DEBUG(1, ("getpwnam call returned more than one result !?!\n"));
+ ret = nss_cmd_send_error(cmdctx, ENOENT);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_getpwuid_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+
+ if (!dctx->res) {
+ /* return 0 results */
+ dctx->res = talloc_zero(dctx, struct ldb_result);
+ if (!dctx->res) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ nss_cmd_getpwuid_callback(dctx, LDB_SUCCESS, dctx->res);
+ return;
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getpwuid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getpwuid_callback, dctx);
+
+done:
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_getpwuid(struct cli_ctx *cctx)
+{
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int ncret;
+
+ ret = ENOENT;
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* get uid to query */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+
+ if (blen != sizeof(uint32_t)) {
+ ret = EINVAL;
+ goto done;
+ }
+ cmdctx->id = *((uint32_t *)body);
+
+ /* this is a multidomain search */
+ cmdctx->check_next = true;
+
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_uid(nctx->ncache, nctx->neg_timeout,
+ cmdctx->id);
+ if (ncret == EEXIST) {
+ DEBUG(3, ("Uid [%lu] does not exist! (negative cache)\n",
+ (unsigned long)cmdctx->id));
+ continue;
+ }
+
+ /* check that the uid is valid for this domain */
+ if ((dom->id_min && (cmdctx->id < dom->id_min)) ||
+ (dom->id_max && (cmdctx->id > dom->id_max))) {
+ DEBUG(4, ("Uid [%lu] does not exist in domain [%s]! "
+ "(id out of range)\n",
+ (unsigned long)cmdctx->id, dom->name));
+ continue;
+ }
+
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+ DEBUG(4, ("Requesting info for [%lu@%s]\n",
+ cmdctx->id, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_getpwuid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getpwuid_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+
+ break;
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* to keep it simple at this stage we are retrieving the
+ * full enumeration again for each request for each process
+ * and we also block on setpwent() for the full time needed
+ * to retrieve the data. And endpwent() frees all the data.
+ * Next steps are:
+ * - use an nsssrv wide cache with data already structured
+ * so that it can be immediately returned (see nscd way)
+ * - use mutexes so that setpwent() can return immediately
+ * even if the data is still being fetched
+ * - make getpwent() wait on the mutex
+ */
+static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx);
+
+static void nss_cmd_setpw_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_setpwent_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct getent_ctx *pctx;
+ struct nss_ctx *nctx;
+ int timeout;
+ int ret;
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, ENOENT);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ pctx = nctx->pctx;
+ if (pctx == NULL) {
+ pctx = talloc_zero(nctx, struct getent_ctx);
+ if (!pctx) {
+ ret = nss_cmd_send_error(cmdctx, ENOMEM);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+ nctx->pctx = pctx;
+ }
+
+ pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1);
+ if (!pctx->doms) {
+ talloc_free(pctx);
+ nctx->pctx = NULL;
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+
+ pctx->doms[pctx->num].domain = dctx->domain;
+ pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res);
+ pctx->doms[pctx->num].cur = 0;
+
+ pctx->num++;
+
+ /* do not reply until all domain searches are done */
+ for (dom = dctx->domain->next; dom; dom = dom->next) {
+ if (dom->enumerate != 0) break;
+ }
+ dctx->domain = dom;
+
+ if (dctx->domain != NULL) {
+ if (cmdctx->enum_cached) {
+ dctx->check_provider = false;
+ } else {
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ }
+
+ if (dctx->check_provider) {
+ timeout = SSS_CLI_SOCKET_TIMEOUT;
+ ret = sss_dp_send_acct_req(cctx->rctx, cmdctx,
+ nss_cmd_setpw_dp_callback, dctx,
+ timeout, dom->name, true,
+ SSS_DP_USER, NULL, 0);
+ } else {
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_enumpwent(dctx, sysdb,
+ dctx->domain, NULL,
+ nss_cmd_setpwent_callback, dctx);
+ }
+ if (ret != EOK) {
+ /* FIXME: shutdown ? */
+ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n",
+ dom->name));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return;
+ }
+
+ /* set cache mark */
+ nctx->last_user_enum = time(NULL);
+
+ if (cmdctx->immediate) {
+ /* this was a getpwent call w/o setpwent,
+ * return immediately one result */
+ ret = nss_cmd_getpwent_immediate(cmdctx);
+ if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx);
+ return;
+ }
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_setpw_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_enumpwent(cmdctx, sysdb,
+ dctx->domain, NULL,
+ nss_cmd_setpwent_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_setpwent_ext(struct cli_ctx *cctx, bool immediate)
+{
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct nss_ctx *nctx;
+ time_t now = time(NULL);
+ int timeout;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+
+ DEBUG(4, ("Requesting info for all users\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ talloc_free(nctx->pctx);
+ nctx->pctx = NULL;
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+ cmdctx->immediate = immediate;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* do not query backends if we have a recent enumeration */
+ if (nctx->enum_cache_timeout) {
+ if (nctx->last_user_enum +
+ nctx->enum_cache_timeout > now) {
+ cmdctx->enum_cached = true;
+ }
+ }
+
+ /* check if enumeration is enabled in any domain */
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+ if (dom->enumerate != 0) break;
+ }
+ dctx->domain = dom;
+
+ if (dctx->domain == NULL) {
+ DEBUG(2, ("Enumeration disabled on all domains!\n"));
+ ret = ENOENT;
+ goto done;
+ }
+
+ if (cmdctx->enum_cached) {
+ dctx->check_provider = false;
+ } else {
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ }
+
+ if (dctx->check_provider) {
+ timeout = SSS_CLI_SOCKET_TIMEOUT;
+ ret = sss_dp_send_acct_req(cctx->rctx, cmdctx,
+ nss_cmd_setpw_dp_callback, dctx,
+ timeout, dom->name, true,
+ SSS_DP_USER, NULL, 0);
+ } else {
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_enumpwent(dctx, sysdb,
+ dctx->domain, NULL,
+ nss_cmd_setpwent_callback, dctx);
+ }
+ if (ret != EOK) {
+ /* FIXME: shutdown ? */
+ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n",
+ dom->name));
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ if (cmdctx->immediate) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ else {
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int nss_cmd_setpwent(struct cli_ctx *cctx)
+{
+ return nss_cmd_setpwent_ext(cctx, false);
+}
+
+
+static int nss_cmd_retpwent(struct cli_ctx *cctx, int num)
+{
+ struct nss_ctx *nctx;
+ struct getent_ctx *pctx;
+ struct ldb_message **msgs = NULL;
+ struct dom_ctx *pdom = NULL;
+ int n = 0;
+ int ret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ pctx = nctx->pctx;
+
+retry:
+ if (pctx->cur >= pctx->num) goto none;
+
+ pdom = &pctx->doms[pctx->cur];
+
+ n = pdom->res->count - pdom->cur;
+ if (n == 0 && (pctx->cur+1 < pctx->num)) {
+ pctx->cur++;
+ pdom = &pctx->doms[pctx->cur];
+ n = pdom->res->count - pdom->cur;
+ }
+
+ if (!n) goto none;
+
+ if (n > num) n = num;
+
+ msgs = &(pdom->res->msgs[pdom->cur]);
+ pdom->cur += n;
+
+ ret = fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, n);
+ if (ret == ENOENT) goto retry;
+ return ret;
+
+none:
+ return fill_empty(cctx->creq->out);
+}
+
+/* used only if a process calls getpwent() without first calling setpwent()
+ */
+static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx)
+{
+ struct cli_ctx *cctx = cmdctx->cctx;
+ uint8_t *body;
+ size_t blen;
+ uint32_t num;
+ int ret;
+
+ /* get max num of entries to return in one call */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+ if (blen != sizeof(uint32_t)) {
+ return EINVAL;
+ }
+ num = *((uint32_t *)body);
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = nss_cmd_retpwent(cctx, num);
+
+ sss_packet_set_error(cctx->creq->out, ret);
+ sss_cmd_done(cctx, cmdctx);
+
+ return EOK;
+}
+
+static int nss_cmd_getpwent(struct cli_ctx *cctx)
+{
+ struct nss_ctx *nctx;
+ struct nss_cmd_ctx *cmdctx;
+
+ DEBUG(4, ("Requesting info for all accounts\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ /* see if we need to trigger an implicit setpwent() */
+ if (nctx->pctx == NULL) {
+ nctx->pctx = talloc_zero(nctx, struct getent_ctx);
+ if (!nctx->pctx) return ENOMEM;
+
+ return nss_cmd_setpwent_ext(cctx, true);
+ }
+
+ cmdctx = talloc(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ return nss_cmd_getpwent_immediate(cmdctx);
+}
+
+static int nss_cmd_endpwent(struct cli_ctx *cctx)
+{
+ struct nss_ctx *nctx;
+ int ret;
+
+ DEBUG(4, ("Terminating request info for all accounts\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+
+ if (nctx->pctx == NULL) goto done;
+
+ /* free results and reset */
+ talloc_free(nctx->pctx);
+ nctx->pctx = NULL;
+
+done:
+ sss_cmd_done(cctx, NULL);
+ return EOK;
+}
+
+/****************************************************************************
+ * GROUP db related functions
+ ***************************************************************************/
+
+#define GID_ROFFSET 0
+#define MNUM_ROFFSET sizeof(uint32_t)
+#define STRS_ROFFSET 2*sizeof(uint32_t)
+
+static int fill_grent(struct sss_packet *packet,
+ struct sss_domain_info *dom,
+ struct nss_ctx *nctx,
+ bool filter_groups,
+ struct ldb_message **msgs,
+ int max, int *count)
+{
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ uint8_t *body;
+ size_t blen;
+ uint32_t gid;
+ const char *name;
+ size_t nsize;
+ size_t delim;
+ size_t dom_len;
+ size_t pwlen;
+ int i = 0;
+ int j = 0;
+ int ret, num, memnum;
+ size_t rzero, rsize;
+ bool add_domain = dom->fqnames;
+ const char *domain = dom->name;
+ const char *namefmt = nctx->rctx->names->fq_fmt;
+
+ if (add_domain) {
+ delim = 1;
+ dom_len = strlen(domain);
+ } else {
+ delim = 0;
+ dom_len = 0;
+ }
+
+ num = 0;
+ pwlen = strlen(nctx->pwfield) + 1;
+
+ /* first 2 fields (len and reserved), filled up later */
+ ret = sss_packet_grow(packet, 2*sizeof(uint32_t));
+ if (ret != EOK) {
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+ rzero = 2*sizeof(uint32_t);
+ rsize = 0;
+
+ for (i = 0; i < *count; i++) {
+ msg = msgs[i];
+
+ /* new group */
+ if (!ldb_msg_check_string_attribute(msg, "objectClass",
+ SYSDB_GROUP_CLASS)) {
+ DEBUG(1, ("Wrong object (%s) found on stack!\n",
+ ldb_dn_get_linearized(msg->dn)));
+ continue;
+ }
+
+ /* if we reached the max allowed entries, simply return */
+ if (num >= max) {
+ goto done;
+ }
+
+ /* new result starts at end of previous result */
+ rzero += rsize;
+ rsize = 0;
+
+ /* find group name/gid */
+ name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+ gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0);
+ if (!name || !gid) {
+ DEBUG(1, ("Incomplete group object for %s[%llu]! Skipping\n",
+ name?name:"<NULL>", (unsigned long long int)gid));
+ continue;
+ }
+
+ if (filter_groups) {
+ ret = nss_ncache_check_group(nctx->ncache,
+ nctx->neg_timeout, domain, name);
+ if (ret == EEXIST) {
+ DEBUG(4, ("Group [%s@%s] filtered out! (negative cache)\n",
+ name, domain));
+ continue;
+ }
+ }
+
+ nsize = strlen(name) + 1; /* includes terminating \0 */
+ if (add_domain) nsize += delim + dom_len;
+
+ /* fill in gid and name and set pointer for number of members */
+ rsize = STRS_ROFFSET + nsize + pwlen; /* name\0x\0 */
+
+ ret = sss_packet_grow(packet, rsize);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+
+ /* 0-3: 32bit number gid */
+ ((uint32_t *)(&body[rzero+GID_ROFFSET]))[0] = gid;
+
+ /* 4-7: 32bit unsigned number of members */
+ ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = 0;
+
+ /* 8-X: sequence of strings (name, passwd, mem..) */
+ if (add_domain) {
+ ret = snprintf((char *)&body[rzero+STRS_ROFFSET],
+ nsize, namefmt, name, domain);
+ if (ret >= nsize) {
+ /* need more space, got creative with the print format ? */
+ int t = ret - nsize + 1;
+ ret = sss_packet_grow(packet, t);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+ rsize += t;
+ delim += t;
+ nsize += t;
+
+ /* retry */
+ ret = snprintf((char *)&body[rzero+STRS_ROFFSET],
+ nsize, namefmt, name, domain);
+ }
+
+ if (ret != nsize-1) {
+ DEBUG(1, ("Failed to generate a fully qualified name for"
+ " group [%s] in [%s]! Skipping\n", name, domain));
+ /* reclaim space */
+ ret = sss_packet_shrink(packet, rsize);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ rsize = 0;
+ continue;
+ }
+ } else {
+ memcpy(&body[rzero+STRS_ROFFSET], name, nsize);
+ }
+
+ /* group passwd field */
+ memcpy(&body[rzero + rsize -pwlen], nctx->pwfield, pwlen);
+
+ el = ldb_msg_find_element(msg, SYSDB_MEMBERUID);
+ if (el) {
+ memnum = 0;
+
+ for (j = 0; j < el->num_values; j++) {
+ name = (const char *)el->values[j].data;
+
+ if (nctx->filter_users_in_groups) {
+ ret = nss_ncache_check_user(nctx->ncache,
+ nctx->neg_timeout,
+ domain, name);
+ if (ret == EEXIST) {
+ DEBUG(6, ("Group [%s] member [%s@%s] filtered out!"
+ " (negative cache)\n",
+ (char *)&body[rzero+STRS_ROFFSET],
+ name, domain));
+ continue;
+ }
+ }
+
+ nsize = strlen(name) + 1; /* includes terminating \0 */
+ if (add_domain) nsize += delim + dom_len;
+
+ ret = sss_packet_grow(packet, nsize);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+
+ if (add_domain) {
+ ret = snprintf((char *)&body[rzero + rsize],
+ nsize, namefmt, name, domain);
+ if (ret >= nsize) {
+ /* need more space,
+ * got creative with the print format ? */
+ int t = ret - nsize + 1;
+ ret = sss_packet_grow(packet, t);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+ delim += t;
+ nsize += t;
+
+ /* retry */
+ ret = snprintf((char *)&body[rzero + rsize],
+ nsize, namefmt, name, domain);
+ }
+
+ if (ret != nsize-1) {
+ DEBUG(1, ("Failed to generate a fully qualified name"
+ " for member [%s@%s] of group [%s]!"
+ " Skipping\n", name, domain,
+ (char *)&body[rzero+STRS_ROFFSET]));
+ /* reclaim space */
+ ret = sss_packet_shrink(packet, nsize);
+ if (ret != EOK) {
+ num = 0;
+ goto done;
+ }
+ continue;
+ }
+
+ } else {
+ memcpy(&body[rzero + rsize], name, nsize);
+ }
+
+ rsize += nsize;
+
+ memnum++;
+ }
+
+ if (memnum) {
+ /* set num of members */
+ ((uint32_t *)(&body[rzero+MNUM_ROFFSET]))[0] = memnum;
+ }
+ }
+
+ num++;
+ continue;
+ }
+
+done:
+ *count = i;
+
+ if (num == 0) {
+ /* if num is 0 most probably something went wrong,
+ * reset packet and return ENOENT */
+ ret = sss_packet_set_size(packet, 0);
+ if (ret != EOK) return ret;
+ return ENOENT;
+ }
+
+ ((uint32_t *)body)[0] = num; /* num results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+
+ return EOK;
+}
+
+static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_getgrnam_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ bool neghit = false;
+ int ncret;
+ int i, ret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, status);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ if (dctx->check_provider) {
+ ret = check_cache(dctx, nctx, res,
+ SSS_DP_GROUP, cmdctx->name, 0,
+ nss_cmd_getgrnam_dp_callback);
+ if (ret != EOK) {
+ /* Anything but EOK means we should reenter the mainloop
+ * because we may be refreshing the cache
+ */
+ return;
+ }
+ }
+
+ switch (res->count) {
+ case 0:
+ if (cmdctx->check_next) {
+
+ ret = EOK;
+
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = dctx->domain->next; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ ncret = nss_ncache_check_group(nctx->ncache,
+ nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ if (neghit) {
+ DEBUG(2, ("Group [%s] does not exist! (negative cache)\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+ if (dom == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ if (dctx->res) talloc_free(res);
+ dctx->res = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getgrnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getgrnam_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+
+
+ DEBUG(2, ("No results for getgrnam call\n"));
+
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_group(nctx->ncache, false,
+ dctx->domain->name, cmdctx->name);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ break;
+
+ default:
+
+ DEBUG(6, ("Returning info for group [%s]\n", cmdctx->name));
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ i = res->count;
+ ret = fill_grent(cctx->creq->out,
+ dctx->domain,
+ nctx, false,
+ res->msgs, 1, &i);
+ if (ret == ENOENT) {
+ ret = fill_empty(cctx->creq->out);
+ }
+ sss_packet_set_error(cctx->creq->out, ret);
+ }
+
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_getgrnam_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+
+ if (!dctx->res) {
+ /* return 0 results */
+ dctx->res = talloc_zero(dctx, struct ldb_result);
+ if (!dctx->res) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ nss_cmd_getgrnam_callback(dctx, LDB_SUCCESS, dctx->res);
+ return;
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getgrnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getgrnam_callback, dctx);
+
+done:
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache! (%d [%s])\n",
+ ret, strerror(ret)));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_getgrnam(struct cli_ctx *cctx)
+{
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ const char *rawname;
+ char *domname;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int ncret;
+ bool neghit = false;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* get user name to query */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+
+ /* if not terminated fail */
+ if (body[blen -1] != '\0') {
+ ret = EINVAL;
+ goto done;
+ }
+ rawname = (const char *)body;
+
+ domname = NULL;
+ ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname,
+ &domname, &cmdctx->name);
+ if (ret != EOK) {
+ DEBUG(2, ("Invalid name received [%s]\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ DEBUG(4, ("Requesting info for [%s] from [%s]\n",
+ cmdctx->name, domname?domname:"<ALL>"));
+
+ if (domname) {
+ dctx->domain = nss_get_dom(cctx->rctx->domains, domname);
+ if (!dctx->domain) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout,
+ dctx->domain->name, cmdctx->name);
+ if (ncret == EEXIST) {
+ neghit = true;
+ }
+ }
+ else {
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_group(nctx->ncache, nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ dctx->domain = dom;
+ }
+ if (neghit) {
+ DEBUG(2, ("Group [%s] does not exist! (negative cache)\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+ if (dctx->domain == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+ if (!domname) {
+ /* this is a multidomain search */
+ cmdctx->check_next = true;
+ }
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_getgrnam(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getgrnam_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_getgrgid_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ bool neghit = false;
+ int i, ret;
+ int ncret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, status);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ if (dctx->check_provider) {
+ ret = check_cache(dctx, nctx, res,
+ SSS_DP_GROUP, NULL, cmdctx->id,
+ nss_cmd_getgrgid_dp_callback);
+ if (ret != EOK) {
+ /* Anything but EOK means we should reenter the mainloop
+ * because we may be refreshing the cache
+ */
+ return;
+ }
+ }
+
+ switch (res->count) {
+ case 0:
+ if (cmdctx->check_next) {
+
+ ret = EOK;
+
+ dom = dctx->domain->next;
+
+ ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout,
+ cmdctx->id);
+ if (ncret == EEXIST) {
+ DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n",
+ (unsigned long)cmdctx->id));
+ ret = ENOENT;
+ }
+ if (dom == NULL) {
+ DEBUG(0, ("No matching domain found for [%lu], fail!\n",
+ (unsigned long)cmdctx->id));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ if (dctx->res) talloc_free(res);
+ dctx->res = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getgrgid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getgrgid_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+
+ DEBUG(2, ("No results for getgrgid call\n"));
+
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_gid(nctx->ncache, false, cmdctx->id);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ break;
+
+ default:
+
+ DEBUG(6, ("Returning info for group [%u]\n", (unsigned)cmdctx->id));
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ i = res->count;
+ ret = fill_grent(cctx->creq->out,
+ dctx->domain,
+ nctx, true,
+ res->msgs, 1, &i);
+ if (ret == ENOENT) {
+ ret = fill_empty(cctx->creq->out);
+ }
+ sss_packet_set_error(cctx->creq->out, ret);
+ }
+
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_getgrgid_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+
+ if (!dctx->res) {
+ /* return 0 results */
+ dctx->res = talloc_zero(dctx, struct ldb_result);
+ if (!dctx->res) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ nss_cmd_getgrgid_callback(dctx, LDB_SUCCESS, dctx->res);
+ return;
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_getgrgid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getgrgid_callback, dctx);
+
+done:
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_getgrgid(struct cli_ctx *cctx)
+{
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int ncret;
+
+ ret = ENOENT;
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* get uid to query */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+
+ if (blen != sizeof(uint32_t)) {
+ ret = EINVAL;
+ goto done;
+ }
+ cmdctx->id = *((uint32_t *)body);
+
+ /* this is a multidomain search */
+ cmdctx->check_next = true;
+
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_gid(nctx->ncache, nctx->neg_timeout,
+ cmdctx->id);
+ if (ncret == EEXIST) {
+ DEBUG(3, ("Gid [%lu] does not exist! (negative cache)\n",
+ (unsigned long)cmdctx->id));
+ continue;
+ }
+
+ /* check that the uid is valid for this domain */
+ if ((dom->id_min && (cmdctx->id < dom->id_min)) ||
+ (dom->id_max && (cmdctx->id > dom->id_max))) {
+ DEBUG(4, ("Gid [%lu] does not exist in domain [%s]! "
+ "(id out of range)\n",
+ (unsigned long)cmdctx->id, dom->name));
+ continue;
+ }
+
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+ DEBUG(4, ("Requesting info for [%lu@%s]\n",
+ cmdctx->id, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_getgrgid(cmdctx, sysdb,
+ dctx->domain, cmdctx->id,
+ nss_cmd_getgrgid_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+
+ break;
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* to keep it simple at this stage we are retrieving the
+ * full enumeration again for each request for each process
+ * and we also block on setgrent() for the full time needed
+ * to retrieve the data. And endgrent() frees all the data.
+ * Next steps are:
+ * - use and nsssrv wide cache with data already structured
+ * so that it can be immediately returned (see nscd way)
+ * - use mutexes so that setgrent() can return immediately
+ * even if the data is still being fetched
+ * - make getgrent() wait on the mutex
+ */
+static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx);
+
+static void nss_cmd_setgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_setgrent_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct getent_ctx *gctx;
+ struct nss_ctx *nctx;
+ int timeout;
+ int ret;
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, ENOENT);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ gctx = nctx->gctx;
+ if (gctx == NULL) {
+ gctx = talloc_zero(nctx, struct getent_ctx);
+ if (!gctx) {
+ ret = nss_cmd_send_error(cmdctx, ENOMEM);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+ nctx->gctx = gctx;
+ }
+
+ gctx->doms = talloc_realloc(gctx, gctx->doms, struct dom_ctx, gctx->num +1);
+ if (!gctx->doms) NSS_CMD_FATAL_ERROR(cctx);
+
+ gctx->doms[gctx->num].domain = dctx->domain;
+ gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res);
+ gctx->doms[gctx->num].cur = 0;
+
+ gctx->num++;
+
+ /* do not reply until all domain searches are done */
+ for (dom = dctx->domain->next; dom; dom = dom->next) {
+ if (dom->enumerate != 0) break;
+ }
+ dctx->domain = dom;
+
+ if (dctx->domain != NULL) {
+ if (cmdctx->enum_cached) {
+ dctx->check_provider = false;
+ } else {
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ }
+
+ if (dctx->check_provider) {
+ timeout = SSS_CLI_SOCKET_TIMEOUT;
+ ret = sss_dp_send_acct_req(cctx->rctx, cmdctx,
+ nss_cmd_setgr_dp_callback, dctx,
+ timeout, dom->name, true,
+ SSS_DP_GROUP, NULL, 0);
+ } else {
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_enumgrent(dctx, sysdb,
+ dctx->domain,
+ nss_cmd_setgrent_callback, dctx);
+ }
+ if (ret != EOK) {
+ /* FIXME: shutdown ? */
+ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n",
+ dom->name));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return;
+ }
+
+ /* set cache mark */
+ nctx->last_group_enum = time(NULL);
+
+ if (cmdctx->immediate) {
+ /* this was a getgrent call w/o setgrent,
+ * return immediately one result */
+ ret = nss_cmd_getgrent_immediate(cmdctx);
+ if (ret != EOK) NSS_CMD_FATAL_ERROR(cctx);
+ return;
+ }
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_setgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_enumgrent(dctx, sysdb,
+ dctx->domain,
+ nss_cmd_setgrent_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+static int nss_cmd_setgrent_ext(struct cli_ctx *cctx, bool immediate)
+{
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct nss_ctx *nctx;
+ time_t now = time(NULL);
+ int timeout;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+
+ DEBUG(4, ("Requesting info for all groups\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ talloc_free(nctx->gctx);
+ nctx->gctx = NULL;
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+ cmdctx->immediate = immediate;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* do not query backends if we have a recent enumeration */
+ if (nctx->enum_cache_timeout) {
+ if (nctx->last_group_enum +
+ nctx->enum_cache_timeout > now) {
+ cmdctx->enum_cached = true;
+ }
+ }
+
+ /* check if enumeration is enabled in any domain */
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+ if (dom->enumerate != 0) break;
+ }
+ dctx->domain = dom;
+
+ if (dctx->domain == NULL) {
+ DEBUG(2, ("Enumeration disabled on all domains!\n"));
+ ret = ENOENT;
+ goto done;
+ }
+
+ if (cmdctx->enum_cached) {
+ dctx->check_provider = false;
+ } else {
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ }
+
+ if (dctx->check_provider) {
+ timeout = SSS_CLI_SOCKET_TIMEOUT;
+ ret = sss_dp_send_acct_req(cctx->rctx, cmdctx,
+ nss_cmd_setgr_dp_callback, dctx,
+ timeout, dom->name, true,
+ SSS_DP_GROUP, NULL, 0);
+ } else {
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_enumgrent(dctx, sysdb,
+ dctx->domain,
+ nss_cmd_setgrent_callback, dctx);
+ }
+ if (ret != EOK) {
+ /* FIXME: shutdown ? */
+ DEBUG(1, ("Failed to send enumeration request for domain [%s]!\n",
+ dom->name));
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ if (cmdctx->immediate) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ else {
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int nss_cmd_setgrent(struct cli_ctx *cctx)
+{
+ return nss_cmd_setgrent_ext(cctx, false);
+}
+
+static int nss_cmd_retgrent(struct cli_ctx *cctx, int num)
+{
+ struct nss_ctx *nctx;
+ struct getent_ctx *gctx;
+ struct ldb_message **msgs = NULL;
+ struct dom_ctx *gdom = NULL;
+ int n = 0;
+ int ret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+ gctx = nctx->gctx;
+
+ do {
+ if (gctx->cur >= gctx->num) goto none;
+
+ gdom = &gctx->doms[gctx->cur];
+
+ n = gdom->res->count - gdom->cur;
+ if (n == 0 && (gctx->cur+1 < gctx->num)) {
+ gctx->cur++;
+ gdom = &gctx->doms[gctx->cur];
+ n = gdom->res->count - gdom->cur;
+ }
+
+ if (!n) goto none;
+
+ msgs = &(gdom->res->msgs[gdom->cur]);
+
+ ret = fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, num, &n);
+
+ gdom->cur += n;
+
+ } while(ret == ENOENT);
+
+ return ret;
+
+none:
+ return fill_empty(cctx->creq->out);
+}
+
+/* used only if a process calls getpwent() without first calling setpwent()
+ */
+static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx)
+{
+ struct cli_ctx *cctx = cmdctx->cctx;
+ uint8_t *body;
+ size_t blen;
+ uint32_t num;
+ int ret;
+
+ /* get max num of entries to return in one call */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+ if (blen != sizeof(uint32_t)) {
+ return EINVAL;
+ }
+ num = *((uint32_t *)body);
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = nss_cmd_retgrent(cctx, num);
+
+ sss_packet_set_error(cctx->creq->out, ret);
+ sss_cmd_done(cctx, cmdctx);
+
+ return EOK;
+}
+
+static int nss_cmd_getgrent(struct cli_ctx *cctx)
+{
+ struct nss_ctx *nctx;
+ struct nss_cmd_ctx *cmdctx;
+
+ DEBUG(4, ("Requesting info for all groups\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ /* see if we need to trigger an implicit setpwent() */
+ if (nctx->gctx == NULL) {
+ nctx->gctx = talloc_zero(nctx, struct getent_ctx);
+ if (!nctx->gctx) return ENOMEM;
+
+ return nss_cmd_setgrent_ext(cctx, true);
+ }
+
+ cmdctx = talloc(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ return nss_cmd_getgrent_immediate(cmdctx);
+}
+
+static int nss_cmd_endgrent(struct cli_ctx *cctx)
+{
+ struct nss_ctx *nctx;
+ int ret;
+
+ DEBUG(4, ("Terminating request info for all groups\n"));
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ /* create response packet */
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+
+ if (nctx->gctx == NULL) goto done;
+
+ /* free results and reset */
+ talloc_free(nctx->gctx);
+ nctx->gctx = NULL;
+
+done:
+ sss_cmd_done(cctx, NULL);
+ return EOK;
+}
+
+static int fill_initgr(struct sss_packet *packet, struct ldb_result *res)
+{
+ uint8_t *body;
+ size_t blen;
+ gid_t gid;
+ int ret, i, num;
+
+ if (res->count == 0) {
+ return ENOENT;
+ }
+
+ /* one less, the first one is the user entry */
+ num = res->count -1;
+
+ ret = sss_packet_grow(packet, (2 + res->count) * sizeof(uint32_t));
+ if (ret != EOK) {
+ return ret;
+ }
+ sss_packet_get_body(packet, &body, &blen);
+
+ /* skip first entry, it's the user entry */
+ for (i = 0; i < num; i++) {
+ gid = ldb_msg_find_attr_as_uint64(res->msgs[i + 1], SYSDB_GIDNUM, 0);
+ if (!gid) {
+ DEBUG(1, ("Incomplete group object for initgroups! Aborting\n"));
+ return EFAULT;
+ }
+ ((uint32_t *)body)[2 + i] = gid;
+ }
+
+ ((uint32_t *)body)[0] = num; /* num results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+
+ return EOK;
+}
+
+static void nss_cmd_getinitgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+
+static void nss_cmd_getinitgr_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ uint8_t *body;
+ size_t blen;
+ bool neghit = false;
+ int ncret;
+ int ret;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ if (status != LDB_SUCCESS) {
+ ret = nss_cmd_send_error(cmdctx, status);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ return;
+ }
+
+ if (dctx->check_provider) {
+ ret = check_cache(dctx, nctx, res,
+ SSS_DP_INITGROUPS, cmdctx->name, 0,
+ nss_cmd_getinitgr_dp_callback);
+ if (ret != EOK) {
+ /* Anything but EOK means we should reenter the mainloop
+ * because we may be refreshing the cache
+ */
+ return;
+ }
+ }
+
+ switch (res->count) {
+ case 0:
+ if (cmdctx->check_next) {
+
+ ret = EOK;
+
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = dctx->domain->next; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ ncret = nss_ncache_check_user(nctx->ncache,
+ nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ if (neghit) {
+ DEBUG(2, ("User [%s] does not exist! (negative cache)\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+ if (dom == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ dctx->domain = dom;
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+ if (dctx->res) talloc_free(res);
+ dctx->res = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_initgroups(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getinitgr_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+
+ DEBUG(2, ("No results for initgroups call\n"));
+
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_user(nctx->ncache, false,
+ dctx->domain->name, cmdctx->name);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ break;
+
+ default:
+
+ DEBUG(6, ("Returning initgr for user [%s]\n", cmdctx->name));
+
+ ret = sss_packet_new(cctx->creq, 0,
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = fill_initgr(cctx->creq->out, res);
+ if (ret == ENOENT) {
+ ret = fill_empty(cctx->creq->out);
+ }
+ sss_packet_set_error(cctx->creq->out, ret);
+ }
+
+ sss_cmd_done(cctx, cmdctx);
+}
+
+static void nss_cmd_getinitgr_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx);
+ struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+ struct cli_ctx *cctx = cmdctx->cctx;
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n"
+ "Will try to return what we have in cache\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+
+ if (!dctx->res) {
+ /* return 0 results */
+ dctx->res = talloc_zero(dctx, struct ldb_result);
+ if (!dctx->res) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ nss_cmd_getinitgr_callback(dctx, LDB_SUCCESS, dctx->res);
+ return;
+ }
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ ret = sysdb_initgroups(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getinitgr_callback, dctx);
+
+done:
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+
+ ret = nss_cmd_send_error(cmdctx, ret);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ sss_cmd_done(cctx, cmdctx);
+ }
+}
+
+/* for now, if we are online, try to always query the backend */
+static int nss_cmd_initgroups(struct cli_ctx *cctx)
+{
+ struct nss_cmd_ctx *cmdctx;
+ struct nss_dom_ctx *dctx;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct nss_ctx *nctx;
+ const char *rawname;
+ char *domname;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int ncret;
+ bool neghit = false;
+
+ nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+ cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+ if (!cmdctx) {
+ return ENOMEM;
+ }
+ cmdctx->cctx = cctx;
+
+ dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+ if (!dctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ dctx->cmdctx = cmdctx;
+
+ /* get user name to query */
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+
+ /* if not terminated fail */
+ if (body[blen -1] != '\0') {
+ ret = EINVAL;
+ goto done;
+ }
+ rawname = (const char *)body;
+
+ domname = NULL;
+ ret = sss_parse_name(cmdctx, cctx->rctx->names, rawname,
+ &domname, &cmdctx->name);
+ if (ret != EOK) {
+ DEBUG(2, ("Invalid name received [%s]\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ DEBUG(4, ("Requesting info for [%s] from [%s]\n",
+ cmdctx->name, domname ? : "<ALL>"));
+
+ if (domname) {
+ dctx->domain = nss_get_dom(cctx->rctx->domains, domname);
+ if (!dctx->domain) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
+ domname, cmdctx->name);
+ if (ncret == EEXIST) {
+ neghit = true;
+ }
+ }
+ else {
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+ /* verify this user has not yet been negatively cached,
+ * or has been permanently filtered */
+ ncret = nss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+ }
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ dctx->domain = dom;
+ }
+ if (neghit) {
+ DEBUG(2, ("User [%s] does not exist! (negative cache)\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+ if (dctx->domain == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n", rawname));
+ ret = ENOENT;
+ goto done;
+ }
+
+ dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+ if (!domname) {
+ /* this is a multidomain search */
+ cmdctx->check_next = true;
+ }
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ cmdctx->name, dctx->domain->name));
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ dctx->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ ret = EFAULT;
+ goto done;
+ }
+ ret = sysdb_initgroups(cmdctx, sysdb,
+ dctx->domain, cmdctx->name,
+ nss_cmd_getinitgr_callback, dctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+
+done:
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ /* we do not have any entry to return */
+ ret = sss_packet_new(cctx->creq, 2*sizeof(uint32_t),
+ sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret == EOK) {
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ ((uint32_t *)body)[0] = 0; /* 0 results */
+ ((uint32_t *)body)[1] = 0; /* reserved */
+ }
+ }
+ if (ret != EOK) {
+ ret = nss_cmd_send_error(cmdctx, ret);
+ }
+ if (ret == EOK) {
+ sss_cmd_done(cctx, cmdctx);
+ }
+ return ret;
+ }
+
+ return EOK;
+}
+
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+ static struct cli_protocol_version nss_cli_protocol_version[] = {
+ {1, "2008-09-05", "initial version, \\0 terminated strings"},
+ {0, NULL, NULL}
+ };
+
+ return nss_cli_protocol_version;
+}
+
+static struct sss_cmd_table nss_cmds[] = {
+ {SSS_GET_VERSION, sss_cmd_get_version},
+ {SSS_NSS_GETPWNAM, nss_cmd_getpwnam},
+ {SSS_NSS_GETPWUID, nss_cmd_getpwuid},
+ {SSS_NSS_SETPWENT, nss_cmd_setpwent},
+ {SSS_NSS_GETPWENT, nss_cmd_getpwent},
+ {SSS_NSS_ENDPWENT, nss_cmd_endpwent},
+ {SSS_NSS_GETGRNAM, nss_cmd_getgrnam},
+ {SSS_NSS_GETGRGID, nss_cmd_getgrgid},
+ {SSS_NSS_SETGRENT, nss_cmd_setgrent},
+ {SSS_NSS_GETGRENT, nss_cmd_getgrent},
+ {SSS_NSS_ENDGRENT, nss_cmd_endgrent},
+ {SSS_NSS_INITGR, nss_cmd_initgroups},
+ {SSS_CLI_NULL, NULL}
+};
+
+struct sss_cmd_table *get_nss_cmds(void) {
+ return nss_cmds;
+}
+
+int nss_cmd_execute(struct cli_ctx *cctx)
+{
+ enum sss_cli_command cmd;
+ int i;
+
+ cmd = sss_packet_get_cmd(cctx->creq->in);
+
+ for (i = 0; nss_cmds[i].cmd != SSS_CLI_NULL; i++) {
+ if (cmd == nss_cmds[i].cmd) {
+ return nss_cmds[i].fn(cctx);
+ }
+ }
+
+ return EINVAL;
+}
+
diff --git a/src/responder/nss/nsssrv_nc.c b/src/responder/nss/nsssrv_nc.c
new file mode 100644
index 00000000..1fa7d612
--- /dev/null
+++ b/src/responder/nss/nsssrv_nc.c
@@ -0,0 +1,321 @@
+/*
+ SSSD
+
+ NSS Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include <fcntl.h>
+#include <time.h>
+#include "tdb.h"
+
+#define NC_ENTRY_PREFIX "NCE/"
+#define NC_USER_PREFIX NC_ENTRY_PREFIX"USER"
+#define NC_GROUP_PREFIX NC_ENTRY_PREFIX"GROUP"
+#define NC_UID_PREFIX NC_ENTRY_PREFIX"UID"
+#define NC_GID_PREFIX NC_ENTRY_PREFIX"GID"
+
+struct nss_nc_ctx {
+ struct tdb_context *tdb;
+};
+
+static int string_to_tdb_data(char *str, TDB_DATA *ret)
+{
+ if (!str || !ret) return EINVAL;
+
+ ret->dptr = (uint8_t *)str;
+ ret->dsize = strlen(str)+1;
+
+ return EOK;
+}
+
+int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx)
+{
+ struct nss_nc_ctx *ctx;
+
+ ctx = talloc_zero(memctx, struct nss_nc_ctx);
+ if (!ctx) return ENOMEM;
+
+ errno = 0;
+ /* open a memory only tdb with default hash size */
+ ctx->tdb = tdb_open("memcache", 0, TDB_INTERNAL, O_RDWR|O_CREAT, 0);
+ if (!ctx->tdb) return errno;
+
+ *_ctx = ctx;
+ return EOK;
+};
+
+static int nss_ncache_check_str(struct nss_nc_ctx *ctx, char *str, int ttl)
+{
+ TDB_DATA key;
+ TDB_DATA data;
+ unsigned long long int timestamp;
+ bool expired = false;
+ char *ep;
+ int ret;
+
+ ret = string_to_tdb_data(str, &key);
+ if (ret != EOK) goto done;
+
+ data = tdb_fetch(ctx->tdb, key);
+
+ if (!data.dptr) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ if (ttl == -1) {
+ /* a negative ttl means: never expires */
+ ret = EEXIST;
+ goto done;
+ }
+
+ errno = 0;
+ timestamp = strtoull((const char *)data.dptr, &ep, 0);
+ if (errno != 0 || *ep != '\0') {
+ /* Malformed entry, remove it and return no entry */
+ expired = true;
+ goto done;
+ }
+
+ if (timestamp == 0) {
+ /* a 0 timestamp means this is a permanent entry */
+ ret = EEXIST;
+ goto done;
+ }
+
+ if (timestamp + ttl > time(NULL)) {
+ /* still valid */
+ ret = EEXIST;
+ goto done;
+ }
+
+ expired = true;
+
+done:
+ if (expired) {
+ /* expired, remove and return no entry */
+ tdb_delete(ctx->tdb, key);
+ ret = ENOENT;
+ }
+
+ return ret;
+}
+
+static int nss_ncache_set_str(struct nss_nc_ctx *ctx,
+ char *str, bool permanent)
+{
+ TDB_DATA key;
+ TDB_DATA data;
+ char *timest;
+ int ret;
+
+ ret = string_to_tdb_data(str, &key);
+ if (ret != EOK) return ret;
+
+ if (permanent) {
+ timest = talloc_strdup(ctx, "0");
+ } else {
+ timest = talloc_asprintf(ctx, "%llu",
+ (unsigned long long int)time(NULL));
+ }
+ if (!timest) return ENOMEM;
+
+ ret = string_to_tdb_data(timest, &data);
+ if (ret != EOK) goto done;
+
+ ret = tdb_store(ctx->tdb, key, data, TDB_REPLACE);
+ if (ret != 0) {
+ DEBUG(1, ("Negative cache failed to set entry: [%s]",
+ tdb_errorstr(ctx->tdb)));
+ ret = EFAULT;
+ }
+
+done:
+ talloc_free(timest);
+ return ret;
+}
+
+int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl,
+ const char *domain, const char *name)
+{
+ char *str;
+ int ret;
+
+ if (!name || !*name) return EINVAL;
+
+ str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_check_str(ctx, str, ttl);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl,
+ const char *domain, const char *name)
+{
+ char *str;
+ int ret;
+
+ if (!name || !*name) return EINVAL;
+
+ str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_check_str(ctx, str, ttl);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid)
+{
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_check_str(ctx, str, ttl);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid)
+{
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_check_str(ctx, str, ttl);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent,
+ const char *domain, const char *name)
+{
+ char *str;
+ int ret;
+
+ if (!name || !*name) return EINVAL;
+
+ str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_set_str(ctx, str, permanent);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent,
+ const char *domain, const char *name)
+{
+ char *str;
+ int ret;
+
+ if (!name || !*name) return EINVAL;
+
+ str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_set_str(ctx, str, permanent);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid)
+{
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(ctx, "%s/%u", NC_UID_PREFIX, uid);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_set_str(ctx, str, permanent);
+
+ talloc_free(str);
+ return ret;
+}
+
+int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid)
+{
+ char *str;
+ int ret;
+
+ str = talloc_asprintf(ctx, "%s/%u", NC_GID_PREFIX, gid);
+ if (!str) return ENOMEM;
+
+ ret = nss_ncache_set_str(ctx, str, permanent);
+
+ talloc_free(str);
+ return ret;
+}
+
+static int delete_permanent(struct tdb_context *tdb,
+ TDB_DATA key, TDB_DATA data, void *state)
+{
+ unsigned long long int timestamp;
+ bool remove_key = false;
+ char *ep;
+
+ if (strncmp((char *)key.dptr,
+ NC_ENTRY_PREFIX, sizeof(NC_ENTRY_PREFIX)) != 0) {
+ /* not interested in this key */
+ return 0;
+ }
+
+ errno = 0;
+ timestamp = strtoull((const char *)data.dptr, &ep, 0);
+ if (errno != 0 || *ep != '\0') {
+ /* Malformed entry, remove it */
+ remove_key = true;
+ goto done;
+ }
+
+ if (timestamp == 0) {
+ /* a 0 timestamp means this is a permanent entry */
+ remove_key = true;
+ }
+
+done:
+ if (remove_key) {
+ return tdb_delete(tdb, key);
+ }
+
+ return 0;
+}
+
+int nss_ncache_reset_permament(struct nss_nc_ctx *ctx)
+{
+ int ret;
+
+ ret = tdb_traverse(ctx->tdb, delete_permanent, NULL);
+ if (ret < 0)
+ return EIO;
+
+ return EOK;
+}
diff --git a/src/responder/nss/nsssrv_nc.h b/src/responder/nss/nsssrv_nc.h
new file mode 100644
index 00000000..c0fa197c
--- /dev/null
+++ b/src/responder/nss/nsssrv_nc.h
@@ -0,0 +1,51 @@
+/*
+ SSSD
+
+ NSS Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NSS_NEG_CACHE_H_
+#define _NSS_NEG_CACHE_H_
+
+struct nss_nc_ctx;
+
+/* init the in memory negative cache */
+int nss_ncache_init(TALLOC_CTX *memctx, struct nss_nc_ctx **_ctx);
+
+/* check if the user is expired according to the passed in time to live */
+int nss_ncache_check_user(struct nss_nc_ctx *ctx, int ttl,
+ const char *domain, const char *name);
+int nss_ncache_check_group(struct nss_nc_ctx *ctx, int ttl,
+ const char *domain, const char *name);
+int nss_ncache_check_uid(struct nss_nc_ctx *ctx, int ttl, uid_t uid);
+int nss_ncache_check_gid(struct nss_nc_ctx *ctx, int ttl, gid_t gid);
+
+/* add a new neg-cache entry setting the timestamp to "now" unless
+ * "permanent" is set to true, in which case the timestamps is set to 0
+ * and the negative cache never expires (used to permanently filter out
+ * users and groups) */
+int nss_ncache_set_user(struct nss_nc_ctx *ctx, bool permanent,
+ const char *domain, const char *name);
+int nss_ncache_set_group(struct nss_nc_ctx *ctx, bool permanent,
+ const char *domain, const char *name);
+int nss_ncache_set_uid(struct nss_nc_ctx *ctx, bool permanent, uid_t uid);
+int nss_ncache_set_gid(struct nss_nc_ctx *ctx, bool permanent, gid_t gid);
+
+int nss_ncache_reset_permament(struct nss_nc_ctx *ctx);
+
+#endif /* _NSS_NEG_CACHE_H_ */
diff --git a/src/responder/pam/pam_LOCAL_domain.c b/src/responder/pam/pam_LOCAL_domain.c
new file mode 100644
index 00000000..34f0c8dd
--- /dev/null
+++ b/src/responder/pam/pam_LOCAL_domain.c
@@ -0,0 +1,476 @@
+/*
+ SSSD
+
+ PAM e credentials
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <time.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "util/sha512crypt.h"
+#include "providers/data_provider.h"
+#include "responder/pam/pamsrv.h"
+
+
+#define NULL_CHECK_OR_JUMP(var, msg, ret, err, label) do { \
+ if (var == NULL) { \
+ DEBUG(1, (msg)); \
+ ret = (err); \
+ goto label; \
+ } \
+} while(0)
+
+#define NEQ_CHECK_OR_JUMP(var, val, msg, ret, err, label) do { \
+ if (var != (val)) { \
+ DEBUG(1, (msg)); \
+ ret = (err); \
+ goto label; \
+ } \
+} while(0)
+
+
+struct LOCAL_request {
+ struct tevent_context *ev;
+ struct sysdb_ctx *dbctx;
+ struct sysdb_attrs *mod_attrs;
+ struct sysdb_handle *handle;
+
+ struct ldb_result *res;
+ int error;
+
+ struct pam_auth_req *preq;
+};
+
+static void prepare_reply(struct LOCAL_request *lreq)
+{
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
+
+ if (lreq->error != EOK && pd->pam_status == PAM_SUCCESS)
+ pd->pam_status = PAM_SYSTEM_ERR;
+
+ lreq->preq->callback(lreq->preq);
+}
+
+static void set_user_attr_done(struct tevent_req *req)
+{
+ struct LOCAL_request *lreq;
+ int ret;
+
+ lreq = tevent_req_callback_data(req, struct LOCAL_request);
+
+ ret = sysdb_transaction_commit_recv(req);
+ if (ret) {
+ DEBUG(2, ("set_user_attr failed.\n"));
+ lreq->error =ret;
+ }
+
+ prepare_reply(lreq);
+}
+
+static void set_user_attr_req_done(struct tevent_req *subreq);
+static void set_user_attr_req(struct tevent_req *req)
+{
+ struct LOCAL_request *lreq = tevent_req_callback_data(req,
+ struct LOCAL_request);
+ struct tevent_req *subreq;
+ int ret;
+
+ DEBUG(4, ("entering set_user_attr_req\n"));
+
+ ret = sysdb_transaction_recv(req, lreq, &lreq->handle);
+ if (ret) {
+ lreq->error = ret;
+ return prepare_reply(lreq);
+ }
+
+ subreq = sysdb_set_user_attr_send(lreq, lreq->ev, lreq->handle,
+ lreq->preq->domain,
+ lreq->preq->pd->user,
+ lreq->mod_attrs, SYSDB_MOD_REP);
+ if (!subreq) {
+ /* cancel transaction */
+ talloc_zfree(lreq->handle);
+ lreq->error = ret;
+ return prepare_reply(lreq);
+ }
+ tevent_req_set_callback(subreq, set_user_attr_req_done, lreq);
+}
+
+static void set_user_attr_req_done(struct tevent_req *subreq)
+{
+ struct LOCAL_request *lreq = tevent_req_callback_data(subreq,
+ struct LOCAL_request);
+ struct tevent_req *req;
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+
+ DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", ret, strerror(ret)));
+
+ if (ret) {
+ lreq->error = ret;
+ goto fail;
+ }
+
+ req = sysdb_transaction_commit_send(lreq, lreq->ev, lreq->handle);
+ if (!req) {
+ lreq->error = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(req, set_user_attr_done, lreq);
+
+ return;
+
+fail:
+ DEBUG(2, ("set_user_attr failed.\n"));
+
+ /* cancel transaction */
+ talloc_zfree(lreq->handle);
+
+ prepare_reply(lreq);
+}
+
+static void do_successful_login(struct LOCAL_request *lreq)
+{
+ struct tevent_req *req;
+ int ret;
+
+ lreq->mod_attrs = sysdb_new_attrs(lreq);
+ NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"),
+ lreq->error, ENOMEM, done);
+
+ ret = sysdb_attrs_add_long(lreq->mod_attrs,
+ SYSDB_LAST_LOGIN, (long)time(NULL));
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"),
+ lreq->error, ret, done);
+
+ ret = sysdb_attrs_add_long(lreq->mod_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0L);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"),
+ lreq->error, ret, done);
+
+ req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx);
+ if (!req) {
+ lreq->error = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, set_user_attr_req, lreq);
+
+ return;
+
+done:
+
+ prepare_reply(lreq);
+}
+
+static void do_failed_login(struct LOCAL_request *lreq)
+{
+ struct tevent_req *req;
+ int ret;
+ int failedLoginAttempts;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
+ pd->pam_status = PAM_AUTH_ERR;
+/* TODO: maybe add more inteligent delay calculation */
+ pd->response_delay = 3;
+
+ lreq->mod_attrs = sysdb_new_attrs(lreq);
+ NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"),
+ lreq->error, ENOMEM, done);
+
+ ret = sysdb_attrs_add_long(lreq->mod_attrs,
+ SYSDB_LAST_FAILED_LOGIN, (long)time(NULL));
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"),
+ lreq->error, ret, done);
+
+ failedLoginAttempts = ldb_msg_find_attr_as_int(lreq->res->msgs[0],
+ SYSDB_FAILED_LOGIN_ATTEMPTS,
+ 0);
+ failedLoginAttempts++;
+
+ ret = sysdb_attrs_add_long(lreq->mod_attrs,
+ SYSDB_FAILED_LOGIN_ATTEMPTS,
+ (long)failedLoginAttempts);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"),
+ lreq->error, ret, done);
+
+ req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx);
+ if (!req) {
+ lreq->error = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, set_user_attr_req, lreq);
+
+ return;
+
+done:
+
+ prepare_reply(lreq);
+}
+
+static void do_pam_acct_mgmt(struct LOCAL_request *lreq)
+{
+ const char *disabled;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
+
+ disabled = ldb_msg_find_attr_as_string(lreq->res->msgs[0],
+ SYSDB_DISABLED, NULL);
+ if ((disabled != NULL) &&
+ (strncasecmp(disabled, "false",5) != 0) &&
+ (strncasecmp(disabled, "no",2) != 0) ) {
+ pd->pam_status = PAM_PERM_DENIED;
+ }
+
+ prepare_reply(lreq);
+}
+
+static void do_pam_chauthtok(struct LOCAL_request *lreq)
+{
+ struct tevent_req *req;
+ int ret;
+ char *newauthtok;
+ char *salt;
+ char *new_hash;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
+
+ newauthtok = talloc_strndup(lreq, (char *) pd->newauthtok,
+ pd->newauthtok_size);
+ NULL_CHECK_OR_JUMP(newauthtok, ("talloc_strndup failed.\n"), lreq->error,
+ ENOMEM, done);
+ memset(pd->newauthtok, 0, pd->newauthtok_size);
+
+ if (strlen(newauthtok) == 0) {
+ /* TODO: should we allow null passwords via a config option ? */
+ DEBUG(1, ("Empty passwords are not allowed!"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = s3crypt_gen_salt(lreq, &salt);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("Salt generation failed.\n"),
+ lreq->error, ret, done);
+ DEBUG(4, ("Using salt [%s]\n", salt));
+
+ ret = s3crypt_sha512(lreq, newauthtok, salt, &new_hash);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("Hash generation failed.\n"),
+ lreq->error, ret, done);
+ DEBUG(4, ("New hash [%s]\n", new_hash));
+ memset(newauthtok, 0, pd->newauthtok_size);
+
+ lreq->mod_attrs = sysdb_new_attrs(lreq);
+ NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"),
+ lreq->error, ENOMEM, done);
+
+ ret = sysdb_attrs_add_string(lreq->mod_attrs, SYSDB_PWD, new_hash);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_string failed.\n"),
+ lreq->error, ret, done);
+
+ ret = sysdb_attrs_add_long(lreq->mod_attrs,
+ "lastPasswordChange", (long)time(NULL));
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"),
+ lreq->error, ret, done);
+
+ req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx);
+ if (!req) {
+ lreq->error = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, set_user_attr_req, lreq);
+
+ return;
+done:
+
+ prepare_reply(lreq);
+}
+
+static void local_handler_callback(void *pvt, int ldb_status,
+ struct ldb_result *res)
+{
+ struct LOCAL_request *lreq;
+ const char *username = NULL;
+ const char *password = NULL;
+ char *newauthtok = NULL;
+ char *new_hash = NULL;
+ char *authtok = NULL;
+ struct pam_data *pd;
+ int ret;
+
+ lreq = talloc_get_type(pvt, struct LOCAL_request);
+ pd = lreq->preq->pd;
+
+ DEBUG(4, ("pam_handler_callback called with ldb_status [%d].\n",
+ ldb_status));
+
+ NEQ_CHECK_OR_JUMP(ldb_status, LDB_SUCCESS, ("ldb search failed.\n"),
+ lreq->error, sysdb_error_to_errno(ldb_status), done);
+
+
+ if (res->count < 1) {
+ DEBUG(4, ("No user found with filter ["SYSDB_PWNAM_FILTER"]\n",
+ pd->user));
+ pd->pam_status = PAM_USER_UNKNOWN;
+ goto done;
+ } else if (res->count > 1) {
+ DEBUG(4, ("More than one object found with filter ["SYSDB_PWNAM_FILTER"]\n"));
+ lreq->error = EFAULT;
+ goto done;
+ }
+
+ username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL);
+ if (strcmp(username, pd->user) != 0) {
+ DEBUG(1, ("Expected username [%s] get [%s].\n", pd->user, username));
+ lreq->error = EINVAL;
+ goto done;
+ }
+
+ lreq->res = res;
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if ((pd->cmd == SSS_PAM_CHAUTHTOK ||
+ pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) &&
+ lreq->preq->cctx->priv == 1) {
+/* TODO: maybe this is a candiate for an explicit audit message. */
+ DEBUG(4, ("allowing root to reset a password.\n"));
+ break;
+ }
+ authtok = talloc_strndup(lreq, (char *) pd->authtok,
+ pd->authtok_size);
+ NULL_CHECK_OR_JUMP(authtok, ("talloc_strndup failed.\n"),
+ lreq->error, ENOMEM, done);
+ memset(pd->authtok, 0, pd->authtok_size);
+
+ password = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_PWD, NULL);
+ NULL_CHECK_OR_JUMP(password, ("No password stored.\n"),
+ lreq->error, LDB_ERR_NO_SUCH_ATTRIBUTE, done);
+ DEBUG(4, ("user: [%s], password hash: [%s]\n", username, password));
+
+ ret = s3crypt_sha512(lreq, authtok, password, &new_hash);
+ memset(authtok, 0, pd->authtok_size);
+ NEQ_CHECK_OR_JUMP(ret, EOK, ("nss_sha512_crypt failed.\n"),
+ lreq->error, ret, done);
+
+ DEBUG(4, ("user: [%s], new hash: [%s]\n", username, new_hash));
+
+ if (strcmp(new_hash, password) != 0) {
+ DEBUG(1, ("Passwords do not match.\n"));
+ do_failed_login(lreq);
+ return;
+ }
+
+ break;
+ }
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ do_successful_login(lreq);
+ return;
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ do_pam_chauthtok(lreq);
+ return;
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ do_pam_acct_mgmt(lreq);
+ return;
+ break;
+ case SSS_PAM_SETCRED:
+ break;
+ case SSS_PAM_OPEN_SESSION:
+ break;
+ case SSS_PAM_CLOSE_SESSION:
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ break;
+ default:
+ lreq->error = EINVAL;
+ DEBUG(1, ("Unknown PAM task [%d].\n"));
+ }
+
+done:
+ if (pd->authtok != NULL)
+ memset(pd->authtok, 0, pd->authtok_size);
+ if (authtok != NULL)
+ memset(authtok, 0, pd->authtok_size);
+ if (pd->newauthtok != NULL)
+ memset(pd->newauthtok, 0, pd->newauthtok_size);
+ if (newauthtok != NULL)
+ memset(newauthtok, 0, pd->newauthtok_size);
+
+ prepare_reply(lreq);
+}
+
+int LOCAL_pam_handler(struct pam_auth_req *preq)
+{
+ int ret;
+ struct LOCAL_request *lreq;
+
+ static const char *attrs[] = {SYSDB_NAME,
+ SYSDB_PWD,
+ SYSDB_DISABLED,
+ SYSDB_LAST_LOGIN,
+ "lastPasswordChange",
+ "accountExpires",
+ SYSDB_FAILED_LOGIN_ATTEMPTS,
+ "passwordHint",
+ "passwordHistory",
+ SYSDB_LAST_FAILED_LOGIN,
+ NULL};
+
+ DEBUG(4, ("LOCAL pam handler.\n"));
+
+ lreq = talloc_zero(preq, struct LOCAL_request);
+ if (!lreq) {
+ return ENOMEM;
+ }
+
+ ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list,
+ preq->domain, &lreq->dbctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ talloc_free(lreq);
+ return ret;
+ }
+ lreq->ev = preq->cctx->ev;
+ lreq->preq = preq;
+
+ preq->pd->pam_status = PAM_SUCCESS;
+
+ ret = sysdb_get_user_attr(lreq, lreq->dbctx,
+ preq->domain, preq->pd->user, attrs,
+ local_handler_callback, lreq);
+
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_get_user_attr failed.\n"));
+ talloc_free(lreq);
+ return ret;
+ }
+
+ return EOK;
+}
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
new file mode 100644
index 00000000..84b13dc4
--- /dev/null
+++ b/src/responder/pam/pamsrv.c
@@ -0,0 +1,224 @@
+/*
+ SSSD
+
+ PAM Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "popt.h"
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "confdb/confdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "responder/common/responder_packet.h"
+#include "providers/data_provider.h"
+#include "monitor/monitor_interfaces.h"
+#include "sbus/sbus_client.h"
+#include "responder/pam/pamsrv.h"
+
+#define SSS_PAM_SBUS_SERVICE_VERSION 0x0001
+#define SSS_PAM_SBUS_SERVICE_NAME "pam"
+
+static int service_reload(DBusMessage *message, struct sbus_connection *conn);
+
+struct sbus_method monitor_pam_methods[] = {
+ { MON_CLI_METHOD_PING, monitor_common_pong },
+ { MON_CLI_METHOD_RELOAD, service_reload },
+ { MON_CLI_METHOD_RES_INIT, monitor_common_res_init },
+ { NULL, NULL }
+};
+
+struct sbus_interface monitor_pam_interface = {
+ MONITOR_INTERFACE,
+ MONITOR_PATH,
+ SBUS_DEFAULT_VTABLE,
+ monitor_pam_methods,
+ NULL
+};
+
+static int service_reload(DBusMessage *message, struct sbus_connection *conn) {
+ /* Monitor calls this function when we need to reload
+ * our configuration information. Perform whatever steps
+ * are needed to update the configuration objects.
+ */
+
+ /* Send an empty reply to acknowledge receipt */
+ return monitor_common_pong(message, conn);
+}
+
+static struct sbus_method pam_dp_methods[] = {
+ { NULL, NULL }
+};
+
+struct sbus_interface pam_dp_interface = {
+ DP_INTERFACE,
+ DP_PATH,
+ SBUS_DEFAULT_VTABLE,
+ pam_dp_methods,
+ NULL
+};
+
+
+static void pam_dp_reconnect_init(struct sbus_connection *conn, int status, void *pvt)
+{
+ struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn);
+ int ret;
+
+ /* Did we reconnect successfully? */
+ if (status == SBUS_RECONNECT_SUCCESS) {
+ DEBUG(1, ("Reconnected to the Data Provider.\n"));
+
+ /* Identify ourselves to the data provider */
+ ret = dp_common_send_id(be_conn->conn,
+ DATA_PROVIDER_VERSION,
+ "PAM", be_conn->domain->name);
+ /* all fine */
+ if (ret == EOK) return;
+ }
+
+ /* Handle failure */
+ DEBUG(0, ("Could not reconnect to %s provider.\n",
+ be_conn->domain->name));
+
+ /* FIXME: kill the frontend and let the monitor restart it ? */
+ /* pam_shutdown(rctx); */
+}
+
+static errno_t pam_get_config(struct pam_ctx *pctx,
+ struct resp_ctx *rctx,
+ struct confdb_ctx *cdb)
+{
+ int ret = EOK;
+ ret = confdb_get_int(cdb, pctx, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_CRED_TIMEOUT, 0,
+ &pctx->cred_expiration);
+ return ret;
+}
+
+static int pam_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb)
+{
+ struct sss_cmd_table *pam_cmds;
+ struct be_conn *iter;
+ struct pam_ctx *pctx;
+ int ret, max_retries;
+
+ pctx = talloc_zero(mem_ctx, struct pam_ctx);
+ if (!pctx) {
+ return ENOMEM;
+ }
+
+ pam_cmds = get_pam_cmds();
+ ret = sss_process_init(pctx, ev, cdb,
+ pam_cmds,
+ SSS_PAM_SOCKET_NAME,
+ SSS_PAM_PRIV_SOCKET_NAME,
+ CONFDB_PAM_CONF_ENTRY,
+ SSS_PAM_SBUS_SERVICE_NAME,
+ SSS_PAM_SBUS_SERVICE_VERSION,
+ &monitor_pam_interface,
+ "PAM", &pam_dp_interface,
+ &pctx->rctx);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ pctx->rctx->pvt_ctx = pctx;
+ ret = pam_get_config(pctx, pctx->rctx, pctx->rctx->cdb);
+
+ /* Enable automatic reconnection to the Data Provider */
+
+ /* FIXME: "retries" is too generic, either get it from a global config
+ * or specify these retries are about the sbus connections to DP */
+ ret = confdb_get_int(pctx->rctx->cdb, pctx->rctx, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to set up automatic reconnection\n"));
+ return ret;
+ }
+
+ for (iter = pctx->rctx->be_conns; iter; iter = iter->next) {
+ sbus_reconnect_init(iter->conn, max_retries,
+ pam_dp_reconnect_init, iter);
+ }
+
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ struct main_context *main_ctx;
+ int ret;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+
+ poptFreeContext(pc);
+
+ /* set up things like debug, signals, daemonization, etc... */
+ debug_log_file = "sssd_pam";
+
+ ret = server_setup("sssd[pam]", 0, CONFDB_PAM_CONF_ENTRY, &main_ctx);
+ if (ret != EOK) return 2;
+
+ ret = die_if_parent_died();
+ if (ret != EOK) {
+ /* This is not fatal, don't return */
+ DEBUG(2, ("Could not set up to exit when parent process does\n"));
+ }
+
+ ret = pam_process_init(main_ctx,
+ main_ctx->event_ctx,
+ main_ctx->confdb_ctx);
+ if (ret != EOK) return 3;
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ return 0;
+}
+
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
new file mode 100644
index 00000000..60f9c66a
--- /dev/null
+++ b/src/responder/pam/pamsrv.h
@@ -0,0 +1,57 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __PAMSRV_H__
+#define __PAMSRV_H__
+
+#include <security/pam_appl.h>
+#include "util/util.h"
+#include "sbus/sssd_dbus.h"
+#include "responder/common/responder.h"
+
+struct pam_auth_req;
+
+typedef void (pam_dp_callback_t)(struct pam_auth_req *preq);
+
+struct pam_ctx {
+ int cred_expiration;
+ struct resp_ctx *rctx;
+};
+
+struct pam_auth_req {
+ struct cli_ctx *cctx;
+ struct sss_domain_info *domain;
+
+ struct pam_data *pd;
+
+ pam_dp_callback_t *callback;
+
+ bool check_provider;
+ void *data;
+};
+
+struct sss_cmd_table *get_pam_cmds(void);
+
+int pam_dp_send_req(struct pam_auth_req *preq, int timeout);
+
+int LOCAL_pam_handler(struct pam_auth_req *preq);
+
+#endif /* __PAMSRV_H__ */
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
new file mode 100644
index 00000000..37aad829
--- /dev/null
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -0,0 +1,1181 @@
+/*
+ SSSD
+
+ PAM Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <time.h>
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "confdb/confdb.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/responder.h"
+#include "providers/data_provider.h"
+#include "responder/pam/pamsrv.h"
+#include "db/sysdb.h"
+
+static void pam_reply(struct pam_auth_req *preq);
+
+static int extract_authtok(uint32_t *type, uint32_t *size, uint8_t **tok, uint8_t *body, size_t blen, size_t *c) {
+ uint32_t data_size;
+
+ if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL;
+
+ memcpy(&data_size, &body[*c], sizeof(uint32_t));
+ *c += sizeof(uint32_t);
+ if (data_size < sizeof(uint32_t) || (*c)+(data_size) > blen) return EINVAL;
+ *size = data_size - sizeof(uint32_t);
+
+ memcpy(type, &body[*c], sizeof(uint32_t));
+ *c += sizeof(uint32_t);
+
+ *tok = body+(*c);
+
+ *c += (*size);
+
+ return EOK;
+}
+
+static int extract_string(char **var, uint8_t *body, size_t blen, size_t *c) {
+ uint32_t size;
+ uint8_t *str;
+
+ if (blen-(*c) < sizeof(uint32_t)+1) return EINVAL;
+
+ memcpy(&size, &body[*c], sizeof(uint32_t));
+ *c += sizeof(uint32_t);
+ if (*c+size > blen) return EINVAL;
+
+ str = body+(*c);
+
+ if (str[size-1]!='\0') return EINVAL;
+
+ *c += size;
+
+ *var = (char *) str;
+
+ return EOK;
+}
+
+static int extract_uint32_t(uint32_t *var, uint8_t *body, size_t blen, size_t *c) {
+ uint32_t size;
+
+ if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL;
+
+ memcpy(&size, &body[*c], sizeof(uint32_t));
+ *c += sizeof(uint32_t);
+
+ memcpy(var, &body[*c], sizeof(uint32_t));
+ *c += sizeof(uint32_t);
+
+ return EOK;
+}
+
+static int pam_parse_in_data_v2(struct sss_names_ctx *snctx,
+ struct pam_data *pd,
+ uint8_t *body, size_t blen)
+{
+ size_t c;
+ uint32_t type;
+ uint32_t size;
+ char *pam_user;
+ int ret;
+ uint32_t terminator = SSS_END_OF_PAM_REQUEST;
+
+ if (blen < 4*sizeof(uint32_t)+2 ||
+ ((uint32_t *)body)[0] != SSS_START_OF_PAM_REQUEST ||
+ memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) {
+ DEBUG(1, ("Received data is invalid.\n"));
+ return EINVAL;
+ }
+
+ c = sizeof(uint32_t);
+ do {
+ memcpy(&type, &body[c], sizeof(uint32_t));
+ c += sizeof(uint32_t);
+ if (c > blen) return EINVAL;
+
+ switch(type) {
+ case SSS_PAM_ITEM_USER:
+ ret = extract_string(&pam_user, body, blen, &c);
+ if (ret != EOK) return ret;
+
+ ret = sss_parse_name(pd, snctx, pam_user,
+ &pd->domain, &pd->user);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_SERVICE:
+ ret = extract_string(&pd->service, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_TTY:
+ ret = extract_string(&pd->tty, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_RUSER:
+ ret = extract_string(&pd->ruser, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_RHOST:
+ ret = extract_string(&pd->rhost, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_CLI_PID:
+ ret = extract_uint32_t(&pd->cli_pid,
+ body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_AUTHTOK:
+ ret = extract_authtok(&pd->authtok_type, &pd->authtok_size,
+ &pd->authtok, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_NEWAUTHTOK:
+ ret = extract_authtok(&pd->newauthtok_type,
+ &pd->newauthtok_size,
+ &pd->newauthtok, body, blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_END_OF_PAM_REQUEST:
+ if (c != blen) return EINVAL;
+ break;
+ default:
+ DEBUG(1,("Ignoring unknown data type [%d].\n", type));
+ size = ((uint32_t *)&body[c])[0];
+ c += size+sizeof(uint32_t);
+ }
+ } while(c < blen);
+
+ if (pd->user == NULL || *pd->user == '\0') return EINVAL;
+
+ DEBUG_PAM_DATA(4, pd);
+
+ return EOK;
+
+}
+
+static int pam_parse_in_data_v3(struct sss_names_ctx *snctx,
+ struct pam_data *pd,
+ uint8_t *body, size_t blen)
+{
+ int ret;
+
+ ret = pam_parse_in_data_v2(snctx, pd, body, blen);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_parse_in_data_v2 failed.\n"));
+ return ret;
+ }
+
+ if (pd->cli_pid == 0) {
+ DEBUG(1, ("Missing client PID.\n"));
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+static int pam_parse_in_data(struct sss_names_ctx *snctx,
+ struct pam_data *pd,
+ uint8_t *body, size_t blen)
+{
+ int start;
+ int end;
+ int last;
+ int ret;
+
+ last = blen - 1;
+ end = 0;
+
+ /* user name */
+ for (start = end; end < last; end++) if (body[end] == '\0') break;
+ if (body[end++] != '\0') return EINVAL;
+
+ ret = sss_parse_name(pd, snctx, (char *)&body[start], &pd->domain, &pd->user);
+ if (ret != EOK) return ret;
+
+ for (start = end; end < last; end++) if (body[end] == '\0') break;
+ if (body[end++] != '\0') return EINVAL;
+ pd->service = (char *) &body[start];
+
+ for (start = end; end < last; end++) if (body[end] == '\0') break;
+ if (body[end++] != '\0') return EINVAL;
+ pd->tty = (char *) &body[start];
+
+ for (start = end; end < last; end++) if (body[end] == '\0') break;
+ if (body[end++] != '\0') return EINVAL;
+ pd->ruser = (char *) &body[start];
+
+ for (start = end; end < last; end++) if (body[end] == '\0') break;
+ if (body[end++] != '\0') return EINVAL;
+ pd->rhost = (char *) &body[start];
+
+ start = end;
+ pd->authtok_type = (int) body[start];
+
+ start += sizeof(uint32_t);
+ pd->authtok_size = (int) body[start];
+
+ start += sizeof(uint32_t);
+ end = start + pd->authtok_size;
+ if (pd->authtok_size == 0) {
+ pd->authtok = NULL;
+ } else {
+ if (end <= blen) {
+ pd->authtok = (uint8_t *) &body[start];
+ } else {
+ DEBUG(1, ("Invalid authtok size: %d\n", pd->authtok_size));
+ return EINVAL;
+ }
+ }
+
+ start = end;
+ pd->newauthtok_type = (int) body[start];
+
+ start += sizeof(uint32_t);
+ pd->newauthtok_size = (int) body[start];
+
+ start += sizeof(uint32_t);
+ end = start + pd->newauthtok_size;
+
+ if (pd->newauthtok_size == 0) {
+ pd->newauthtok = NULL;
+ } else {
+ if (end <= blen) {
+ pd->newauthtok = (uint8_t *) &body[start];
+ } else {
+ DEBUG(1, ("Invalid newauthtok size: %d\n", pd->newauthtok_size));
+ return EINVAL;
+ }
+ }
+
+ DEBUG_PAM_DATA(4, pd);
+
+ return EOK;
+}
+
+/*=Save-Last-Login-State===================================================*/
+
+struct set_last_login_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *dbctx;
+
+ struct sss_domain_info *dom;
+ const char *username;
+ struct sysdb_attrs *attrs;
+
+ struct sysdb_handle *handle;
+
+ struct ldb_result *res;
+};
+
+static void set_last_login_trans_done(struct tevent_req *subreq);
+static void set_last_login_attrs_done(struct tevent_req *subreq);
+static void set_last_login_done(struct tevent_req *subreq);
+
+static struct tevent_req *set_last_login_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *dbctx,
+ struct sss_domain_info *dom,
+ const char *username,
+ struct sysdb_attrs *attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct set_last_login_state *state;
+
+ req = tevent_req_create(memctx, &state, struct set_last_login_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->dbctx = dbctx;
+ state->dom = dom;
+ state->username = username;
+ state->attrs = attrs;
+ state->handle = NULL;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->dbctx);
+ if (!subreq) {
+ talloc_free(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, set_last_login_trans_done, req);
+
+ return req;
+}
+
+static void set_last_login_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct set_last_login_state *state = tevent_req_data(req,
+ struct set_last_login_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("Unable to acquire sysdb transaction lock\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->dom, state->username,
+ state->attrs, SYSDB_MOD_REP);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, set_last_login_attrs_done, req);
+}
+
+static void set_last_login_attrs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct set_last_login_state *state = tevent_req_data(req,
+ struct set_last_login_state);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, set_last_login_done, req);
+}
+
+static void set_last_login_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ if (ret != EOK) {
+ DEBUG(2, ("set_last_login failed.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int set_last_login_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/*=========================================================================*/
+
+
+static void set_last_login_reply(struct tevent_req *req);
+
+static errno_t set_last_login(struct pam_auth_req *preq)
+{
+ struct tevent_req *req;
+ struct sysdb_ctx *dbctx;
+ struct sysdb_attrs *attrs;
+ errno_t ret;
+
+ attrs = sysdb_new_attrs(preq);
+ if (!attrs) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL));
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, preq->domain,
+ &dbctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb context not found for this domain!\n"));
+ goto fail;
+ }
+
+ req = set_last_login_send(preq, preq->cctx->ev, dbctx,
+ preq->domain, preq->pd->user, attrs);
+ if (!req) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(req, set_last_login_reply, preq);
+
+ return EOK;
+
+fail:
+ return ret;
+}
+
+static void set_last_login_reply(struct tevent_req *req)
+{
+ struct pam_auth_req *preq = tevent_req_callback_data(req,
+ struct pam_auth_req);
+ int ret;
+
+ ret = set_last_login_recv(req);
+ if (ret != EOK) {
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ } else {
+ preq->pd->last_auth_saved = true;
+ }
+
+ preq->callback(preq);
+}
+
+static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct pam_auth_req *preq;
+
+ DEBUG(4, ("pam_reply_delay get called.\n"));
+
+ preq = talloc_get_type(pvt, struct pam_auth_req);
+
+ pam_reply(preq);
+}
+
+static void pam_cache_auth_done(struct tevent_req *req);
+
+static void pam_reply(struct pam_auth_req *preq)
+{
+ struct cli_ctx *cctx;
+ uint8_t *body;
+ size_t blen;
+ int ret;
+ int32_t resp_c;
+ int32_t resp_size;
+ struct response_data *resp;
+ int p;
+ struct timeval tv;
+ struct tevent_timer *te;
+ struct pam_data *pd;
+ struct tevent_req *req;
+ struct sysdb_ctx *sysdb;
+ struct pam_ctx *pctx;
+ uint32_t user_info_type;
+
+ pd = preq->pd;
+ cctx = preq->cctx;
+
+ DEBUG(4, ("pam_reply get called.\n"));
+
+ if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) {
+ switch(pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ if ((preq->domain != NULL) &&
+ (preq->domain->cache_credentials == true) &&
+ (pd->offline_auth == false)) {
+
+ /* do auth with offline credentials */
+ pd->offline_auth = true;
+
+ ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list,
+ preq->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for "
+ "domain [%s]!\n", preq->domain->name));
+ goto done;
+ }
+
+ pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx,
+ struct pam_ctx);
+
+ req = sysdb_cache_auth_send(preq, preq->cctx->ev, sysdb,
+ preq->domain, pd->user,
+ pd->authtok, pd->authtok_size,
+ pctx->rctx->cdb);
+ if (req == NULL) {
+ DEBUG(1, ("Failed to setup offline auth.\n"));
+ /* this error is not fatal, continue */
+ } else {
+ tevent_req_set_callback(req, pam_cache_auth_done, preq);
+ return;
+ }
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ case SSS_PAM_CHAUTHTOK:
+ DEBUG(5, ("Password change not possible while offline.\n"));
+ pd->pam_status = PAM_AUTHTOK_ERR;
+ user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS;
+ pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t),
+ (const uint8_t *) &user_info_type);
+ break;
+/* TODO: we need the pam session cookie here to make sure that cached
+ * authentication was successful */
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ DEBUG(2, ("Assuming offline authentication setting status for "
+ "pam call %d to PAM_SUCCESS.\n", pd->cmd));
+ pd->pam_status = PAM_SUCCESS;
+ break;
+ default:
+ DEBUG(1, ("Unknown PAM call [%d].\n", pd->cmd));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ }
+ }
+
+ if (pd->response_delay > 0) {
+ ret = gettimeofday(&tv, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("gettimeofday failed [%d][%s].\n",
+ errno, strerror(errno)));
+ goto done;
+ }
+ tv.tv_sec += pd->response_delay;
+ tv.tv_usec = 0;
+ pd->response_delay = 0;
+
+ te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq);
+ if (te == NULL) {
+ DEBUG(1, ("Failed to add event pam_reply_delay.\n"));
+ goto done;
+ }
+
+ return;
+ }
+
+ /* If this was a successful login, save the lastLogin time */
+ if (pd->cmd == SSS_PAM_AUTHENTICATE &&
+ pd->pam_status == PAM_SUCCESS &&
+ preq->domain->cache_credentials &&
+ !pd->offline_auth &&
+ !pd->last_auth_saved &&
+ NEED_CHECK_PROVIDER(preq->domain->provider)) {
+ ret = set_last_login(preq);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ return;
+ }
+
+ ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in),
+ &cctx->creq->out);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (pd->domain != NULL) {
+ pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1,
+ (uint8_t *) pd->domain);
+ }
+
+ resp_c = 0;
+ resp_size = 0;
+ resp = pd->resp_list;
+ while(resp != NULL) {
+ resp_c++;
+ resp_size += resp->len;
+ resp = resp->next;
+ }
+
+ ret = sss_packet_grow(cctx->creq->out, sizeof(int32_t) +
+ sizeof(int32_t) +
+ resp_c * 2* sizeof(int32_t) +
+ resp_size);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ sss_packet_get_body(cctx->creq->out, &body, &blen);
+ DEBUG(4, ("blen: %d\n", blen));
+ p = 0;
+
+ memcpy(&body[p], &pd->pam_status, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+ memcpy(&body[p], &resp_c, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+ resp = pd->resp_list;
+ while(resp != NULL) {
+ memcpy(&body[p], &resp->type, sizeof(int32_t));
+ p += sizeof(int32_t);
+ memcpy(&body[p], &resp->len, sizeof(int32_t));
+ p += sizeof(int32_t);
+ memcpy(&body[p], resp->data, resp->len);
+ p += resp->len;
+
+ resp = resp->next;
+ }
+
+done:
+ sss_cmd_done(cctx, preq);
+}
+
+static void pam_cache_auth_done(struct tevent_req *req)
+{
+ int ret;
+ struct pam_auth_req *preq = tevent_req_callback_data(req,
+ struct pam_auth_req);
+ uint32_t resp_type;
+ size_t resp_len;
+ uint8_t *resp;
+ time_t expire_date = 0;
+ time_t delayed_until = -1;
+ long long dummy;
+
+ ret = sysdb_cache_auth_recv(req, &expire_date, &delayed_until);
+ talloc_zfree(req);
+
+ switch (ret) {
+ case EOK:
+ preq->pd->pam_status = PAM_SUCCESS;
+
+ resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH;
+ resp_len = sizeof(uint32_t) + sizeof(long long);
+ resp = talloc_size(preq->pd, resp_len);
+ if (resp == NULL) {
+ DEBUG(1, ("talloc_size failed, cannot prepare user info.\n"));
+ } else {
+ memcpy(resp, &resp_type, sizeof(uint32_t));
+ dummy = (long long) expire_date;
+ memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long));
+ ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len,
+ (const uint8_t *) resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ }
+ break;
+ case ENOENT:
+ preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case EINVAL:
+ preq->pd->pam_status = PAM_AUTH_ERR;
+ break;
+ case EACCES:
+ preq->pd->pam_status = PAM_PERM_DENIED;
+ if (delayed_until >= 0) {
+ resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED;
+ resp_len = sizeof(uint32_t) + sizeof(long long);
+ resp = talloc_size(preq->pd, resp_len);
+ if (resp == NULL) {
+ DEBUG(1, ("talloc_size failed, cannot prepare user info.\n"));
+ } else {
+ memcpy(resp, &resp_type, sizeof(uint32_t));
+ dummy = (long long) delayed_until;
+ memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long));
+ ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len,
+ (const uint8_t *) resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ }
+ }
+ break;
+ default:
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+ pam_reply(preq);
+ return;
+}
+
+static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr);
+static void pam_check_user_callback(void *ptr, int status,
+ struct ldb_result *res);
+static void pam_dom_forwarder(struct pam_auth_req *preq);
+
+/* TODO: we should probably return some sort of cookie that is set in the
+ * PAM_ENVIRONMENT, so that we can save performing some calls and cache
+ * data. */
+
+static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
+{
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ struct pam_auth_req *preq;
+ struct pam_data *pd;
+ uint8_t *body;
+ size_t blen;
+ int timeout;
+ int ret;
+ uint32_t terminator = SSS_END_OF_PAM_REQUEST;
+ preq = talloc_zero(cctx, struct pam_auth_req);
+ if (!preq) {
+ return ENOMEM;
+ }
+ preq->cctx = cctx;
+
+ preq->pd = talloc_zero(preq, struct pam_data);
+ if (!preq->pd) {
+ talloc_free(preq);
+ return ENOMEM;
+ }
+ pd = preq->pd;
+
+ sss_packet_get_body(cctx->creq->in, &body, &blen);
+ if (blen >= sizeof(uint32_t) &&
+ memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) {
+ DEBUG(1, ("Received data not terminated.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ pd->cmd = pam_cmd;
+ pd->priv = cctx->priv;
+
+ switch (cctx->cli_protocol_version->version) {
+ case 1:
+ ret = pam_parse_in_data(cctx->rctx->names, pd, body, blen);
+ break;
+ case 2:
+ ret = pam_parse_in_data_v2(cctx->rctx->names, pd, body, blen);
+ break;
+ case 3:
+ ret = pam_parse_in_data_v3(cctx->rctx->names, pd, body, blen);
+ break;
+ default:
+ DEBUG(1, ("Illegal protocol version [%d].\n",
+ cctx->cli_protocol_version->version));
+ ret = EINVAL;
+ }
+ if (ret != EOK) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* now check user is valid */
+ if (pd->domain) {
+ for (dom = cctx->rctx->domains; dom; dom = dom->next) {
+ if (strcasecmp(dom->name, pd->domain) == 0) break;
+ }
+ if (!dom) {
+ ret = ENOENT;
+ goto done;
+ }
+ preq->domain = dom;
+ }
+ else {
+ for (dom = preq->cctx->rctx->domains; dom; dom = dom->next) {
+ if (dom->fqnames) continue;
+
+/* FIXME: need to support negative cache */
+#if HAVE_NEG_CACHE
+ ncret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+#endif
+ break;
+ }
+ if (!dom) {
+ ret = ENOENT;
+ goto done;
+ }
+ preq->domain = dom;
+ }
+
+ if (preq->domain->provider == NULL) {
+ DEBUG(1, ("Domain [%s] has no auth provider.\n", preq->domain->name));
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* When auth is requested always search the provider first,
+ * do not rely on cached data unless the provider is completely
+ * offline */
+ if (NEED_CHECK_PROVIDER(preq->domain->provider) &&
+ (pam_cmd == SSS_PAM_AUTHENTICATE || pam_cmd == SSS_PAM_SETCRED)) {
+
+ /* no need to re-check later on */
+ preq->check_provider = false;
+ timeout = SSS_CLI_SOCKET_TIMEOUT/2;
+
+ ret = sss_dp_send_acct_req(preq->cctx->rctx, preq,
+ pam_check_user_dp_callback, preq,
+ timeout, preq->domain->name,
+ false, SSS_DP_INITGROUPS,
+ preq->pd->user, 0);
+ }
+ else {
+ preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider);
+
+ ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+ preq->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ goto done;
+ }
+ ret = sysdb_getpwnam(preq, sysdb,
+ preq->domain, preq->pd->user,
+ pam_check_user_callback, preq);
+ }
+
+done:
+ if (ret != EOK) {
+ switch (ret) {
+ case ENOENT:
+ pd->pam_status = PAM_USER_UNKNOWN;
+ default:
+ pd->pam_status = PAM_SYSTEM_ERR;
+ }
+ pam_reply(preq);
+ }
+ return EOK;
+}
+
+static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min,
+ const char *err_msg, void *ptr)
+{
+ struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req);
+ struct sysdb_ctx *sysdb;
+ int ret;
+
+ if (err_maj) {
+ DEBUG(2, ("Unable to get information from Data Provider\n"
+ "Error: %u, %u, %s\n",
+ (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+ }
+
+ /* always try to see if we have the user in cache even if the provider
+ * returned an error */
+ ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list,
+ preq->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ goto done;
+ }
+ ret = sysdb_getpwnam(preq, sysdb,
+ preq->domain, preq->pd->user,
+ pam_check_user_callback, preq);
+
+done:
+ if (ret != EOK) {
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+}
+
+static void pam_check_user_callback(void *ptr, int status,
+ struct ldb_result *res)
+{
+ struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req);
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ uint64_t cacheExpire;
+ bool call_provider = false;
+ time_t timeout;
+ int ret;
+
+ if (status != LDB_SUCCESS) {
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ return;
+ }
+
+ timeout = SSS_CLI_SOCKET_TIMEOUT/2;
+
+ if (preq->check_provider) {
+ switch (res->count) {
+ case 0:
+ call_provider = true;
+ break;
+
+ case 1:
+ cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
+ SYSDB_CACHE_EXPIRE, 0);
+ if (cacheExpire < time(NULL)) {
+ call_provider = true;
+ }
+ break;
+
+ default:
+ DEBUG(1, ("check user call returned more than one result !?!\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ return;
+ }
+ }
+
+ if (call_provider) {
+
+ /* dont loop forever :-) */
+ preq->check_provider = false;
+
+ /* keep around current data in case backend is offline */
+ if (res->count) {
+ preq->data = talloc_steal(preq, res);
+ }
+
+ ret = sss_dp_send_acct_req(preq->cctx->rctx, preq,
+ pam_check_user_dp_callback, preq,
+ timeout, preq->domain->name,
+ false, SSS_DP_USER,
+ preq->pd->user, 0);
+ if (ret != EOK) {
+ DEBUG(3, ("Failed to dispatch request: %d(%s)\n",
+ ret, strerror(ret)));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ if (!preq->pd->domain) {
+ /* search next as the domain was unknown */
+
+ ret = EOK;
+
+ /* skip domains that require FQnames or have negative caches */
+ for (dom = preq->domain->next; dom; dom = dom->next) {
+
+ if (dom->fqnames) continue;
+
+#if HAVE_NEG_CACHE
+ ncret = nss_ncache_check_user(nctx->ncache,
+ nctx->neg_timeout,
+ dom->name, cmdctx->name);
+ if (ncret == ENOENT) break;
+
+ neghit = true;
+#endif
+ break;
+ }
+#if HAVE_NEG_CACHE
+ /* reset neghit if we still have a domain to check */
+ if (dom) neghit = false;
+
+ if (neghit) {
+ DEBUG(2, ("User [%s] does not exist! (negative cache)\n",
+ cmdctx->name));
+ ret = ENOENT;
+ }
+#endif
+ if (dom == NULL) {
+ DEBUG(2, ("No matching domain found for [%s], fail!\n",
+ preq->pd->user));
+ ret = ENOENT;
+ }
+
+ if (ret == EOK) {
+ preq->domain = dom;
+ preq->data = NULL;
+
+ DEBUG(4, ("Requesting info for [%s@%s]\n",
+ preq->pd->user, preq->domain->name));
+
+ /* When auth is requested always search the provider first,
+ * do not rely on cached data unless the provider is
+ * completely offline */
+ if (NEED_CHECK_PROVIDER(preq->domain->provider) &&
+ (preq->pd->cmd == SSS_PAM_AUTHENTICATE ||
+ preq->pd->cmd == SSS_PAM_SETCRED)) {
+
+ /* no need to re-check later on */
+ preq->check_provider = false;
+
+ ret = sss_dp_send_acct_req(preq->cctx->rctx, preq,
+ pam_check_user_dp_callback,
+ preq, timeout,
+ preq->domain->name,
+ false, SSS_DP_USER,
+ preq->pd->user, 0);
+ }
+ else {
+ preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider);
+
+ ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list,
+ preq->domain, &sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ return;
+ }
+ ret = sysdb_getpwnam(preq, sysdb,
+ preq->domain, preq->pd->user,
+ pam_check_user_callback, preq);
+ }
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to make request to our cache!\n"));
+ }
+ }
+
+ /* we made another call, end here */
+ if (ret == EOK) return;
+ }
+ else {
+ ret = ENOENT;
+ }
+
+ DEBUG(2, ("No results for check user call\n"));
+
+#if HAVE_NEG_CACHE
+ /* set negative cache only if not result of cache check */
+ if (!neghit) {
+ ret = nss_ncache_set_user(nctx->ncache, false,
+ dctx->domain->name, cmdctx->name);
+ if (ret != EOK) {
+ NSS_CMD_FATAL_ERROR(cctx);
+ }
+ }
+#endif
+
+ if (ret != EOK) {
+ if (ret == ENOENT) {
+ preq->pd->pam_status = PAM_USER_UNKNOWN;
+ } else {
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+ pam_reply(preq);
+ return;
+ }
+ break;
+
+ case 1:
+
+ /* BINGO */
+ preq->pd->pw_uid =
+ ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_UIDNUM, -1);
+ if (preq->pd->pw_uid == -1) {
+ DEBUG(1, ("Failed to find uid for user [%s] in domain [%s].\n",
+ preq->pd->user, preq->pd->domain));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+
+ preq->pd->gr_gid =
+ ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_GIDNUM, -1);
+ if (preq->pd->gr_gid == -1) {
+ DEBUG(1, ("Failed to find gid for user [%s] in domain [%s].\n",
+ preq->pd->user, preq->pd->domain));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+
+ pam_dom_forwarder(preq);
+ return;
+
+ default:
+ DEBUG(1, ("check user call returned more than one result !?!\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+}
+
+static void pam_dom_forwarder(struct pam_auth_req *preq)
+{
+ int ret;
+
+ if (!preq->pd->domain) {
+ preq->pd->domain = preq->domain->name;
+ }
+
+ if (!NEED_CHECK_PROVIDER(preq->domain->provider)) {
+ preq->callback = pam_reply;
+ ret = LOCAL_pam_handler(preq);
+ }
+ else {
+ preq->callback = pam_reply;
+ ret = pam_dp_send_req(preq, SSS_CLI_SOCKET_TIMEOUT/2);
+ DEBUG(4, ("pam_dp_send_req returned %d\n", ret));
+ }
+
+ if (ret != EOK) {
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ pam_reply(preq);
+ }
+}
+
+static int pam_cmd_authenticate(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_authenticate\n"));
+ return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE);
+}
+
+static int pam_cmd_setcred(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_setcred\n"));
+ return pam_forwarder(cctx, SSS_PAM_SETCRED);
+}
+
+static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_acct_mgmt\n"));
+ return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT);
+}
+
+static int pam_cmd_open_session(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_open_session\n"));
+ return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION);
+}
+
+static int pam_cmd_close_session(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_close_session\n"));
+ return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION);
+}
+
+static int pam_cmd_chauthtok(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_chauthtok\n"));
+ return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK);
+}
+
+static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) {
+ DEBUG(4, ("entering pam_cmd_chauthtok_prelim\n"));
+ return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM);
+}
+
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+ static struct cli_protocol_version pam_cli_protocol_version[] = {
+ {3, "2009-09-14", "make cli_pid mandatory"},
+ {2, "2009-05-12", "new format <type><size><data>"},
+ {1, "2008-09-05", "initial version, \\0 terminated strings"},
+ {0, NULL, NULL}
+ };
+
+ return pam_cli_protocol_version;
+}
+
+struct sss_cmd_table *get_pam_cmds(void)
+{
+ static struct sss_cmd_table sss_cmds[] = {
+ {SSS_GET_VERSION, sss_cmd_get_version},
+ {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate},
+ {SSS_PAM_SETCRED, pam_cmd_setcred},
+ {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt},
+ {SSS_PAM_OPEN_SESSION, pam_cmd_open_session},
+ {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session},
+ {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok},
+ {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim},
+ {SSS_CLI_NULL, NULL}
+ };
+
+ return sss_cmds;
+}
diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c
new file mode 100644
index 00000000..071d09b8
--- /dev/null
+++ b/src/responder/pam/pamsrv_dp.c
@@ -0,0 +1,142 @@
+/*
+ SSSD
+
+ NSS Responder - Data Provider Interfaces
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/time.h>
+#include <time.h>
+
+#include <talloc.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "responder/common/responder_packet.h"
+#include "providers/data_provider.h"
+#include "sbus/sbus_client.h"
+#include "responder/pam/pamsrv.h"
+
+static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr)
+{
+ DBusError dbus_error;
+ DBusMessage* msg;
+ int ret;
+ int type;
+ struct pam_auth_req *preq;
+
+ preq = talloc_get_type(ptr, struct pam_auth_req);
+
+ dbus_error_init(&dbus_error);
+
+ dbus_pending_call_block(pending);
+ msg = dbus_pending_call_steal_reply(pending);
+ if (msg == NULL) {
+ DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+
+ type = dbus_message_get_type(msg);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dp_unpack_pam_response(msg, preq->pd, &dbus_error);
+ if (!ret) {
+ DEBUG(0, ("Failed to parse reply.\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ DEBUG(4, ("received: [%d][%s]\n", preq->pd->pam_status, preq->pd->domain));
+ break;
+ case DBUS_MESSAGE_TYPE_ERROR:
+ DEBUG(0, ("Reply error.\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ break;
+ default:
+ DEBUG(0, ("Default... what now?.\n"));
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(msg);
+ preq->callback(preq);
+}
+
+int pam_dp_send_req(struct pam_auth_req *preq, int timeout)
+{
+ struct pam_data *pd = preq->pd;
+ struct be_conn *be_conn;
+ DBusMessage *msg;
+ DBusPendingCall *pending_reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t ret;
+ int res;
+
+ /* double check dp_ctx has actually been initialized.
+ * in some pathological cases it may happen that nss starts up before
+ * dp connection code is actually able to establish a connection.
+ */
+ res = sss_dp_get_domain_conn(preq->cctx->rctx,
+ preq->domain->name, &be_conn);
+ if (res != EOK) {
+ DEBUG(1, ("The Data Provider connection for %s is not available!"
+ " This maybe a bug, it shouldn't happen!\n", preq->domain));
+ return EIO;
+ }
+ dbus_conn = sbus_get_connection(be_conn->conn);
+
+ msg = dbus_message_new_method_call(NULL,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_PAMHANDLER);
+ if (msg == NULL) {
+ DEBUG(0,("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+
+ DEBUG(4, ("Sending request with the following data:\n"));
+ DEBUG_PAM_DATA(4, pd);
+
+ ret = dp_pack_pam_request(msg, pd);
+ if (!ret) {
+ DEBUG(1,("Failed to build message\n"));
+ return EIO;
+ }
+
+ ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, timeout);
+ if (!ret || pending_reply == NULL) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ dbus_pending_call_set_notify(pending_reply,
+ pam_dp_process_reply, preq, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
diff --git a/src/sbus/sbus_client.c b/src/sbus/sbus_client.c
new file mode 100644
index 00000000..df5c0712
--- /dev/null
+++ b/src/sbus/sbus_client.c
@@ -0,0 +1,57 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "talloc.h"
+#include "sbus_client.h"
+
+int sbus_client_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *server_address,
+ struct sbus_interface *intf,
+ struct sbus_connection **_conn,
+ sbus_conn_destructor_fn destructor,
+ void *conn_pvt_data)
+{
+ struct sbus_connection *conn = NULL;
+ int ret;
+
+ /* Validate input */
+ if (server_address == NULL) {
+ return EINVAL;
+ }
+
+ ret = sbus_new_connection(mem_ctx, ev, server_address, intf, &conn);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ /* Set connection destructor and private data */
+ sbus_conn_set_destructor(conn, destructor);
+ sbus_conn_set_private_data(conn, conn_pvt_data);
+
+ *_conn = conn;
+ return EOK;
+
+fail:
+ talloc_free(conn);
+ return ret;
+}
diff --git a/src/sbus/sbus_client.h b/src/sbus/sbus_client.h
new file mode 100644
index 00000000..742f8a10
--- /dev/null
+++ b/src/sbus/sbus_client.h
@@ -0,0 +1,36 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SBUS_CLIENT_H_
+#define SBUS_CLIENT_H_
+
+#include "tevent.h"
+#include "sbus/sssd_dbus.h"
+
+int sbus_client_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *server_address,
+ struct sbus_interface *intf,
+ struct sbus_connection **_conn,
+ sbus_conn_destructor_fn destructor,
+ void *conn_pvt_data);
+
+#endif /* SBUS_CLIENT_H_ */
diff --git a/src/sbus/sssd_dbus.h b/src/sbus/sssd_dbus.h
new file mode 100644
index 00000000..ac02c444
--- /dev/null
+++ b/src/sbus/sssd_dbus.h
@@ -0,0 +1,153 @@
+/*
+ SSSD
+
+ SSSD - D-BUS interface
+
+ Copyright (C) Stephen Gallagher 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SSSD_DBUS_H_
+#define _SSSD_DBUS_H_
+
+struct sbus_connection;
+
+#include "dbus/dbus.h"
+
+typedef int (*sbus_msg_handler_fn)(DBusMessage *, struct sbus_connection *);
+
+/*
+ * sbus_conn_destructor_fn
+ * Function to be called when a connection is finalized
+ */
+typedef int (*sbus_conn_destructor_fn)(void *);
+
+typedef void (*sbus_conn_reconn_callback_fn)(struct sbus_connection *, int, void *);
+
+/*
+ * sbus_server_conn_init_fn
+ * Set up function for connection-specific activities
+ * This function should define the sbus_conn_destructor_fn
+ * for this connection at a minimum
+ */
+typedef int (*sbus_server_conn_init_fn)(struct sbus_connection *, void *);
+
+enum {
+ SBUS_CONN_TYPE_PRIVATE = 1,
+ SBUS_CONN_TYPE_SHARED
+};
+
+enum {
+ SBUS_RECONNECT_SUCCESS = 1,
+ SBUS_RECONNECT_EXCEEDED_RETRIES,
+ SBUS_RECONNECT_ERROR
+};
+
+/* Special interface and method for D-BUS introspection */
+#define DBUS_INTROSPECT_INTERFACE "org.freedesktop.DBus.Introspectable"
+#define DBUS_INTROSPECT_METHOD "Introspect"
+
+#define SBUS_DEFAULT_VTABLE { NULL, sbus_message_handler, NULL, NULL, NULL, NULL }
+
+struct sbus_method {
+ const char *method;
+ sbus_msg_handler_fn fn;
+};
+
+struct sbus_interface {
+ const char *interface;
+ const char *path;
+ DBusObjectPathVTable vtable;
+ struct sbus_method *methods;
+ sbus_msg_handler_fn introspect_fn;
+};
+
+/* Server Functions */
+int sbus_new_server(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *address,
+ struct sbus_interface *intf,
+ struct sbus_connection **server,
+ sbus_server_conn_init_fn init_fn, void *init_pvt_data);
+
+/* Connection Functions */
+
+/* sbus_new_connection
+ * Use this function when connecting a new process to
+ * the standard SSSD interface.
+ * This will connect to the address specified and then
+ * call sbus_add_connection to integrate with the main
+ * loop.
+ */
+int sbus_new_connection(TALLOC_CTX *ctx,
+ struct tevent_context *ev,
+ const char *address,
+ struct sbus_interface *intf,
+ struct sbus_connection **conn);
+
+/* sbus_add_connection
+ * Integrates a D-BUS connection with the TEvent main
+ * loop. Use this function when you already have a
+ * DBusConnection object (for example from dbus_bus_get)
+ * Connection type can be either:
+ * SBUS_CONN_TYPE_PRIVATE: Used only from within a D-BUS
+ * server such as the Monitor in the
+ * new_connection_callback
+ * SBUS_CONN_TYPE_SHARED: Used for all D-BUS client
+ * connections, including those retrieved from
+ * dbus_bus_get
+ */
+int sbus_init_connection(TALLOC_CTX *ctx,
+ struct tevent_context *ev,
+ DBusConnection *dbus_conn,
+ struct sbus_interface *intf,
+ int connection_type,
+ struct sbus_connection **_conn);
+
+void sbus_conn_set_destructor(struct sbus_connection *conn,
+ sbus_conn_destructor_fn destructor);
+
+int sbus_default_connection_destructor(void *ctx);
+
+DBusConnection *sbus_get_connection(struct sbus_connection *conn);
+void sbus_disconnect(struct sbus_connection *conn);
+void sbus_conn_set_private_data(struct sbus_connection *conn, void *pvt_data);
+void *sbus_conn_get_private_data(struct sbus_connection *conn);
+int sbus_conn_add_interface(struct sbus_connection *conn,
+ struct sbus_interface *intf);
+bool sbus_conn_disconnecting(struct sbus_connection *conn);
+
+/* max_retries < 0: retry forever
+ * max_retries = 0: never retry (why are you calling this function?)
+ * max_retries > 0: obvious
+ */
+void sbus_reconnect_init(struct sbus_connection *conn,
+ int max_retries,
+ sbus_conn_reconn_callback_fn callback,
+ void *pvt);
+
+/* Default message handler
+ * Should be usable for most cases */
+DBusHandlerResult sbus_message_handler(DBusConnection *conn,
+ DBusMessage *message,
+ void *user_data);
+
+void sbus_conn_send_reply(struct sbus_connection *conn,
+ DBusMessage *reply);
+
+int sbus_is_dbus_fixed_type(int dbus_type);
+int sbus_is_dbus_string_type(int dbus_type);
+size_t sbus_get_dbus_type_size(int dbus_type);
+#endif /* _SSSD_DBUS_H_*/
diff --git a/src/sbus/sssd_dbus_common.c b/src/sbus/sssd_dbus_common.c
new file mode 100644
index 00000000..d446632d
--- /dev/null
+++ b/src/sbus/sssd_dbus_common.c
@@ -0,0 +1,444 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/time.h>
+#include "tevent.h"
+#include "dbus/dbus.h"
+#include "util/util.h"
+#include "sbus/sssd_dbus.h"
+#include "sbus/sssd_dbus_private.h"
+
+/* =Watches=============================================================== */
+
+/* DBUS may ask us to add a watch to a file descriptor that already had a watch
+ * associated. Need to check if that's the case */
+static struct sbus_watch_ctx *fd_to_watch(struct sbus_watch_ctx *list, int fd)
+{
+ struct sbus_watch_ctx *watch_iter;
+
+ watch_iter = list;
+ while (watch_iter != NULL) {
+ if (watch_iter->fd == fd) {
+ return watch_iter;
+ }
+
+ watch_iter = watch_iter->next;
+ }
+
+ return NULL;
+}
+
+static int watch_destructor(void *mem)
+{
+ struct sbus_watch_ctx *watch;
+
+ watch = talloc_get_type(mem, struct sbus_watch_ctx);
+ DLIST_REMOVE(watch->conn->watch_list, watch);
+
+ return 0;
+}
+
+/*
+ * watch_handler
+ * Callback for D-BUS to handle messages on a file-descriptor
+ */
+static void sbus_watch_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *data)
+{
+ struct sbus_watch_ctx *watch = talloc_get_type(data,
+ struct sbus_watch_ctx);
+ enum dbus_conn_type type;
+ union dbus_conn_pointer dbus_p;
+
+ /* conn may get freed inside a handle, save the data we need for later */
+ type = watch->conn->type;
+ dbus_p = watch->conn->dbus;
+
+ /* Take a reference while handling watch */
+ if (type == SBUS_SERVER) {
+ dbus_server_ref(dbus_p.server);
+ } else {
+ dbus_connection_ref(dbus_p.conn);
+ }
+
+ /* Fire if readable */
+ if (flags & TEVENT_FD_READ) {
+ if (watch->dbus_read_watch) {
+ dbus_watch_handle(watch->dbus_read_watch, DBUS_WATCH_READABLE);
+ }
+ }
+
+ /* Fire if writeable */
+ if (flags & TEVENT_FD_WRITE) {
+ if (watch->dbus_write_watch) {
+ dbus_watch_handle(watch->dbus_write_watch, DBUS_WATCH_WRITABLE);
+ }
+ }
+
+ /* Release reference once done */
+ if (type == SBUS_SERVER) {
+ dbus_server_unref(dbus_p.server);
+ } else {
+ dbus_connection_unref(dbus_p.conn);
+ }
+}
+
+/*
+ * add_watch
+ * Set up hooks into the libevents mainloop for
+ * D-BUS to add file descriptor-based events
+ */
+dbus_bool_t sbus_add_watch(DBusWatch *dbus_watch, void *data)
+{
+ unsigned int flags;
+ uint16_t event_flags;
+ struct sbus_connection *conn;
+ struct sbus_watch_ctx *watch;
+ dbus_bool_t enabled;
+ int fd;
+
+ conn = talloc_get_type(data, struct sbus_connection);
+
+#ifdef HAVE_DBUS_WATCH_GET_UNIX_FD
+ fd = dbus_watch_get_unix_fd(dbus_watch);
+#else
+ fd = dbus_watch_get_fd(dbus_watch);
+#endif
+
+ watch = fd_to_watch(conn->watch_list, fd);
+ if (!watch) {
+ /* does not exist, allocate new one */
+ watch = talloc_zero(conn, struct sbus_watch_ctx);
+ if (!watch) {
+ DEBUG(0, ("Out of Memory!\n"));
+ return FALSE;
+ }
+ watch->conn = conn;
+ watch->fd = fd;
+ }
+
+ enabled = dbus_watch_get_enabled(dbus_watch);
+ flags = dbus_watch_get_flags(dbus_watch);
+
+ /* Save the event to the watch object so it can be found later */
+ if (flags & DBUS_WATCH_READABLE) {
+ watch->dbus_read_watch = dbus_watch;
+ }
+ if (flags & DBUS_WATCH_WRITABLE) {
+ watch->dbus_write_watch = dbus_watch;
+ }
+ dbus_watch_set_data(dbus_watch, watch, NULL);
+
+ if (watch->fde) {
+ /* pre-existing event, just toggle flags */
+ sbus_toggle_watch(dbus_watch, data);
+ return TRUE;
+ }
+
+ event_flags = 0;
+ if (enabled) {
+ if (flags & DBUS_WATCH_READABLE) {
+ event_flags |= TEVENT_FD_READ;
+ }
+ if (flags & DBUS_WATCH_WRITABLE) {
+ event_flags |= TEVENT_FD_WRITE;
+ }
+ }
+
+ /* Add the file descriptor to the event loop */
+ watch->fde = tevent_add_fd(conn->ev,
+ watch, fd, event_flags,
+ sbus_watch_handler, watch);
+ if (!watch->fde) {
+ DEBUG(0, ("Failed to set up fd event!\n"));
+ talloc_zfree(watch);
+ return FALSE;
+ }
+
+ DLIST_ADD(conn->watch_list, watch);
+ talloc_set_destructor((TALLOC_CTX *)watch, watch_destructor);
+
+ DEBUG(8, ("%p/%p (%d), %s/%s (%s)\n",
+ watch, dbus_watch, fd,
+ ((flags & DBUS_WATCH_READABLE)?"R":"-"),
+ ((flags & DBUS_WATCH_WRITABLE)?"W":"-"),
+ enabled?"enabled":"disabled"));
+
+ return TRUE;
+}
+
+/*
+ * toggle_watch
+ * Hook for D-BUS to toggle the enabled/disabled state of
+ * an event in the mainloop
+ */
+void sbus_toggle_watch(DBusWatch *dbus_watch, void *data)
+{
+ struct sbus_watch_ctx *watch;
+ unsigned int flags;
+ dbus_bool_t enabled;
+ void *watch_data;
+ int fd;
+
+ enabled = dbus_watch_get_enabled(dbus_watch);
+ flags = dbus_watch_get_flags(dbus_watch);
+
+ watch_data = dbus_watch_get_data(dbus_watch);
+ watch = talloc_get_type(watch_data, struct sbus_watch_ctx);
+ if (!watch) {
+ DEBUG(2, ("[%p] does not carry watch context?!\n", dbus_watch));
+ /* abort ? */
+ return;
+ }
+
+ if (enabled) {
+ if (flags & DBUS_WATCH_READABLE) {
+ TEVENT_FD_READABLE(watch->fde);
+ }
+ if (flags & DBUS_WATCH_WRITABLE) {
+ TEVENT_FD_WRITEABLE(watch->fde);
+ }
+ } else {
+ if (flags & DBUS_WATCH_READABLE) {
+ TEVENT_FD_NOT_READABLE(watch->fde);
+ }
+ if (flags & DBUS_WATCH_WRITABLE) {
+ TEVENT_FD_NOT_WRITEABLE(watch->fde);
+ }
+ }
+
+ if (debug_level >= 8) {
+#ifdef HAVE_DBUS_WATCH_GET_UNIX_FD
+ fd = dbus_watch_get_unix_fd(dbus_watch);
+#else
+ fd = dbus_watch_get_fd(dbus_watch);
+#endif
+ }
+ DEBUG(8, ("%p/%p (%d), %s/%s (%s)\n",
+ watch, dbus_watch, fd,
+ ((flags & DBUS_WATCH_READABLE)?"R":"-"),
+ ((flags & DBUS_WATCH_WRITABLE)?"W":"-"),
+ enabled?"enabled":"disabled"));
+}
+
+/*
+ * sbus_remove_watch
+ * Hook for D-BUS to remove file descriptor-based events
+ * from the libevents mainloop
+ */
+void sbus_remove_watch(DBusWatch *dbus_watch, void *data)
+{
+ struct sbus_watch_ctx *watch;
+ void *watch_data;
+
+ watch_data = dbus_watch_get_data(dbus_watch);
+ watch = talloc_get_type(watch_data, struct sbus_watch_ctx);
+
+ DEBUG(8, ("%p/%p\n", watch, dbus_watch));
+
+ if (!watch) {
+ DEBUG(2, ("DBUS trying to remove unknown watch!\n"));
+ return;
+ }
+
+ /* remove dbus watch data */
+ dbus_watch_set_data(dbus_watch, NULL, NULL);
+
+ /* check which watch to remove, or free if none left */
+ if (watch->dbus_read_watch == dbus_watch) {
+ watch->dbus_read_watch = NULL;
+ }
+ if (watch->dbus_write_watch == dbus_watch) {
+ watch->dbus_write_watch = NULL;
+ }
+ if (!watch->dbus_read_watch && !watch->dbus_write_watch) {
+ talloc_free(watch);
+ }
+}
+
+/* =Timeouts============================================================== */
+
+static struct timeval _get_interval_tv(int interval) {
+ struct timeval tv;
+ struct timeval rightnow;
+
+ gettimeofday(&rightnow,NULL);
+
+ tv.tv_sec = interval / 1000 + rightnow.tv_sec;
+ tv.tv_usec = (interval % 1000) * 1000 + rightnow.tv_usec;
+ return tv;
+}
+
+/*
+ * timeout_handler
+ * Callback for D-BUS to handle timed events
+ */
+static void sbus_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *data)
+{
+ struct sbus_timeout_ctx *timeout;
+ timeout = talloc_get_type(data, struct sbus_timeout_ctx);
+
+ dbus_timeout_handle(timeout->dbus_timeout);
+}
+
+/*
+ * add_timeout
+ * Hook for D-BUS to add time-based events to the mainloop
+ */
+dbus_bool_t sbus_add_timeout(DBusTimeout *dbus_timeout, void *data)
+{
+ struct sbus_connection *conn;
+ struct sbus_timeout_ctx *timeout;
+ struct timeval tv;
+
+ DEBUG(8, ("%p\n", dbus_timeout));
+
+ if (!dbus_timeout_get_enabled(dbus_timeout)) {
+ return TRUE;
+ }
+
+ conn = talloc_get_type(data, struct sbus_connection);
+
+ timeout = talloc_zero(conn, struct sbus_timeout_ctx);
+ if (!timeout) {
+ DEBUG(0, ("Out of Memory!\n"));
+ return FALSE;
+ }
+ timeout->dbus_timeout = dbus_timeout;
+
+ tv = _get_interval_tv(dbus_timeout_get_interval(dbus_timeout));
+ timeout->te = tevent_add_timer(conn->ev, timeout, tv,
+ sbus_timeout_handler, timeout);
+ if (!timeout->te) {
+ DEBUG(0, ("Failed to set up timeout event!\n"));
+ return FALSE;
+ }
+
+ /* Save the event to the watch object so it can be removed later */
+ dbus_timeout_set_data(timeout->dbus_timeout, timeout, NULL);
+
+ return TRUE;
+}
+
+/*
+ * sbus_toggle_timeout
+ * Hook for D-BUS to toggle the enabled/disabled state of a mainloop
+ * event
+ */
+void sbus_toggle_timeout(DBusTimeout *dbus_timeout, void *data)
+{
+ DEBUG(8, ("%p\n", dbus_timeout));
+
+ if (dbus_timeout_get_enabled(dbus_timeout)) {
+ sbus_add_timeout(dbus_timeout, data);
+ } else {
+ sbus_remove_timeout(dbus_timeout, data);
+ }
+}
+
+/*
+ * sbus_remove_timeout
+ * Hook for D-BUS to remove time-based events from the mainloop
+ */
+void sbus_remove_timeout(DBusTimeout *dbus_timeout, void *data)
+{
+ void *timeout;
+
+ DEBUG(8, ("%p\n", dbus_timeout));
+
+ timeout = dbus_timeout_get_data(dbus_timeout);
+
+ /* remove dbus timeout data */
+ dbus_timeout_set_data(dbus_timeout, NULL, NULL);
+
+ /* Freeing the event object will remove it from the event loop */
+ talloc_free(timeout);
+
+}
+
+/* =Helpers=============================================================== */
+
+int sbus_is_dbus_fixed_type(int dbus_type)
+{
+ switch (dbus_type) {
+ case DBUS_TYPE_BYTE:
+ case DBUS_TYPE_BOOLEAN:
+ case DBUS_TYPE_INT16:
+ case DBUS_TYPE_UINT16:
+ case DBUS_TYPE_INT32:
+ case DBUS_TYPE_UINT32:
+ case DBUS_TYPE_INT64:
+ case DBUS_TYPE_UINT64:
+ case DBUS_TYPE_DOUBLE:
+ return true;
+ }
+ return false;
+}
+
+int sbus_is_dbus_string_type(int dbus_type)
+{
+ switch(dbus_type) {
+ case DBUS_TYPE_STRING:
+ case DBUS_TYPE_OBJECT_PATH:
+ case DBUS_TYPE_SIGNATURE:
+ return true;
+ }
+ return false;
+}
+
+size_t sbus_get_dbus_type_size(int dbus_type)
+{
+ size_t ret;
+
+ switch(dbus_type) {
+ /* 1-byte types */
+ case DBUS_TYPE_BYTE:
+ ret = 1;
+ break;
+
+ /* 2-byte types */
+ case DBUS_TYPE_INT16:
+ case DBUS_TYPE_UINT16:
+ ret = 2;
+ break;
+
+ /* 4-byte types */
+ case DBUS_TYPE_BOOLEAN:
+ case DBUS_TYPE_INT32:
+ case DBUS_TYPE_UINT32:
+ ret = 4;
+ break;
+
+ /* 8-byte types */
+ case DBUS_TYPE_INT64:
+ case DBUS_TYPE_UINT64:
+ case DBUS_TYPE_DOUBLE:
+ ret = 8;
+ break;
+
+ default:
+ ret = 0;
+ }
+ return ret;
+}
diff --git a/src/sbus/sssd_dbus_connection.c b/src/sbus/sssd_dbus_connection.c
new file mode 100644
index 00000000..38ccc6ab
--- /dev/null
+++ b/src/sbus/sssd_dbus_connection.c
@@ -0,0 +1,692 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/time.h>
+#include "util/util.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "sbus/sssd_dbus_private.h"
+
+/* Types */
+struct dbus_ctx_list;
+
+struct sbus_interface_p {
+ struct sbus_interface_p *prev, *next;
+ struct sbus_connection *conn;
+ struct sbus_interface *intf;
+};
+
+static bool path_in_interface_list(struct sbus_interface_p *list,
+ const char *path);
+static void sbus_unreg_object_paths(struct sbus_connection *conn);
+
+static int sbus_auto_reconnect(struct sbus_connection *conn);
+
+static void sbus_dispatch(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *data)
+{
+ struct tevent_timer *new_event;
+ struct sbus_connection *conn;
+ DBusConnection *dbus_conn;
+ int ret;
+
+ if (data == NULL) return;
+
+ conn = talloc_get_type(data, struct sbus_connection);
+
+ dbus_conn = conn->dbus.conn;
+ DEBUG(9, ("dbus conn: %lX\n", dbus_conn));
+
+ if (conn->retries > 0) {
+ DEBUG(6, ("SBUS is reconnecting. Deferring.\n"));
+ /* Currently trying to reconnect, defer dispatch */
+ new_event = tevent_add_timer(ev, conn, tv, sbus_dispatch, conn);
+ if (new_event == NULL) {
+ DEBUG(0,("Could not defer dispatch!\n"));
+ }
+ return;
+ }
+
+ if ((!dbus_connection_get_is_connected(dbus_conn)) &&
+ (conn->max_retries != 0)) {
+ /* Attempt to reconnect automatically */
+ ret = sbus_auto_reconnect(conn);
+ if (ret == EOK) {
+ DEBUG(1, ("Performing auto-reconnect\n"));
+ return;
+ }
+
+ DEBUG(0, ("Cannot start auto-reconnection.\n"));
+ conn->reconnect_callback(conn,
+ SBUS_RECONNECT_ERROR,
+ conn->reconnect_pvt);
+ return;
+ }
+
+ if ((conn->disconnect) ||
+ (!dbus_connection_get_is_connected(dbus_conn))) {
+ DEBUG(3,("Connection is not open for dispatching.\n"));
+ /*
+ * Free the connection object.
+ * This will invoke the destructor for the connection
+ */
+ talloc_free(conn);
+ conn = NULL;
+ return;
+ }
+
+ /* Dispatch only once each time through the mainloop to avoid
+ * starving other features
+ */
+ ret = dbus_connection_get_dispatch_status(dbus_conn);
+ if (ret != DBUS_DISPATCH_COMPLETE) {
+ DEBUG(9,("Dispatching.\n"));
+ dbus_connection_dispatch(dbus_conn);
+ }
+
+ /* If other dispatches are waiting, queue up the dispatch function
+ * for the next loop.
+ */
+ ret = dbus_connection_get_dispatch_status(dbus_conn);
+ if (ret != DBUS_DISPATCH_COMPLETE) {
+ new_event = tevent_add_timer(ev, conn, tv, sbus_dispatch, conn);
+ if (new_event == NULL) {
+ DEBUG(2,("Could not add dispatch event!\n"));
+
+ /* TODO: Calling exit here is bad */
+ exit(1);
+ }
+ }
+}
+
+/* dbus_connection_wakeup_main
+ * D-BUS makes a callback to the wakeup_main function when
+ * it has data available for dispatching.
+ * In order to avoid blocking, this function will create a now()
+ * timed event to perform the dispatch during the next iteration
+ * through the mainloop
+ */
+static void sbus_conn_wakeup_main(void *data)
+{
+ struct sbus_connection *conn;
+ struct timeval tv;
+ struct tevent_timer *te;
+
+ conn = talloc_get_type(data, struct sbus_connection);
+
+ tv = tevent_timeval_current();
+
+ /* D-BUS calls this function when it is time to do a dispatch */
+ te = tevent_add_timer(conn->ev, conn, tv, sbus_dispatch, conn);
+ if (te == NULL) {
+ DEBUG(2,("Could not add dispatch event!\n"));
+ /* TODO: Calling exit here is bad */
+ exit(1);
+ }
+}
+
+static int sbus_conn_set_fns(struct sbus_connection *conn);
+
+/*
+ * integrate_connection_with_event_loop
+ * Set up a D-BUS connection to use the libevents mainloop
+ * for handling file descriptor and timed events
+ */
+int sbus_init_connection(TALLOC_CTX *ctx,
+ struct tevent_context *ev,
+ DBusConnection *dbus_conn,
+ struct sbus_interface *intf,
+ int connection_type,
+ struct sbus_connection **_conn)
+{
+ struct sbus_connection *conn;
+ int ret;
+
+ DEBUG(5,("Adding connection %lX\n", dbus_conn));
+ conn = talloc_zero(ctx, struct sbus_connection);
+
+ conn->ev = ev;
+ conn->type = SBUS_CONNECTION;
+ conn->dbus.conn = dbus_conn;
+ conn->connection_type = connection_type;
+
+ ret = sbus_conn_add_interface(conn, intf);
+ if (ret != EOK) {
+ talloc_free(conn);
+ return ret;
+ }
+
+ ret = sbus_conn_set_fns(conn);
+ if (ret != EOK) {
+ talloc_free(conn);
+ return ret;
+ }
+
+ *_conn = conn;
+ return ret;
+}
+
+static int sbus_conn_set_fns(struct sbus_connection *conn)
+{
+ dbus_bool_t dbret;
+
+ /*
+ * Set the default destructor
+ * Connections can override this with
+ * sbus_conn_set_destructor
+ */
+ sbus_conn_set_destructor(conn, NULL);
+
+ /* Set up DBusWatch functions */
+ dbret = dbus_connection_set_watch_functions(conn->dbus.conn,
+ sbus_add_watch,
+ sbus_remove_watch,
+ sbus_toggle_watch,
+ conn, NULL);
+ if (!dbret) {
+ DEBUG(2,("Error setting up D-BUS connection watch functions\n"));
+ return EIO;
+ }
+
+ /* Set up DBusTimeout functions */
+ dbret = dbus_connection_set_timeout_functions(conn->dbus.conn,
+ sbus_add_timeout,
+ sbus_remove_timeout,
+ sbus_toggle_timeout,
+ conn, NULL);
+ if (!dbret) {
+ DEBUG(2,("Error setting up D-BUS server timeout functions\n"));
+ /* FIXME: free resources ? */
+ return EIO;
+ }
+
+ /* Set up dispatch handler */
+ dbus_connection_set_wakeup_main_function(conn->dbus.conn,
+ sbus_conn_wakeup_main,
+ conn, NULL);
+
+ /* Set up any method_contexts passed in */
+
+ /* Attempt to dispatch immediately in case of opportunistic
+ * services connecting before the handlers were all up.
+ * If there are no messages to be dispatched, this will do
+ * nothing.
+ */
+ sbus_conn_wakeup_main(conn);
+
+ return EOK;
+}
+
+int sbus_new_connection(TALLOC_CTX *ctx, struct tevent_context *ev,
+ const char *address, struct sbus_interface *intf,
+ struct sbus_connection **_conn)
+{
+ struct sbus_connection *conn;
+ DBusConnection *dbus_conn;
+ DBusError dbus_error;
+ int ret;
+
+ dbus_error_init(&dbus_error);
+
+ /* Open a shared D-BUS connection to the address */
+ dbus_conn = dbus_connection_open(address, &dbus_error);
+ if (!dbus_conn) {
+ DEBUG(1, ("Failed to open connection: name=%s, message=%s\n",
+ dbus_error.name, dbus_error.message));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ return EIO;
+ }
+
+ ret = sbus_init_connection(ctx, ev, dbus_conn, intf,
+ SBUS_CONN_TYPE_SHARED, &conn);
+ if (ret != EOK) {
+ /* FIXME: release resources */
+ }
+
+ /* Store the address for later reconnection */
+ conn->address = talloc_strdup(conn, address);
+
+ dbus_connection_set_exit_on_disconnect(conn->dbus.conn, FALSE);
+
+ *_conn = conn;
+ return ret;
+}
+
+/*
+ * sbus_conn_set_destructor
+ * Configures a callback to clean up this connection when it
+ * is finalized.
+ * @param conn The sbus_connection created
+ * when this connection was established
+ * @param destructor The destructor function that should be
+ * called when the connection is finalized. If passed NULL,
+ * this will reset the connection to the default destructor.
+ */
+void sbus_conn_set_destructor(struct sbus_connection *conn,
+ sbus_conn_destructor_fn destructor)
+{
+ if (!conn) return;
+
+ conn->destructor = destructor;
+ /* TODO: Should we try to handle the talloc_destructor too? */
+}
+
+int sbus_default_connection_destructor(void *ctx)
+{
+ struct sbus_connection *conn;
+ conn = talloc_get_type(ctx, struct sbus_connection);
+
+ DEBUG(5, ("Invoking default destructor on connection %lX\n",
+ conn->dbus.conn));
+ if (conn->connection_type == SBUS_CONN_TYPE_PRIVATE) {
+ /* Private connections must be closed explicitly */
+ dbus_connection_close(conn->dbus.conn);
+ }
+ else if (conn->connection_type == SBUS_CONN_TYPE_SHARED) {
+ /* Shared connections are destroyed when their last reference is removed */
+ }
+ else {
+ /* Critical Error! */
+ DEBUG(1,("Critical Error, connection_type is neither shared nor private!\n"));
+ return -1;
+ }
+
+ /* Remove object path */
+ /* TODO: Remove object paths */
+
+ dbus_connection_unref(conn->dbus.conn);
+ return 0;
+}
+
+/*
+ * sbus_get_connection
+ * Utility function to retreive the DBusConnection object
+ * from a sbus_connection
+ */
+DBusConnection *sbus_get_connection(struct sbus_connection *conn)
+{
+ return conn->dbus.conn;
+}
+
+void sbus_disconnect (struct sbus_connection *conn)
+{
+ if (conn == NULL) {
+ return;
+ }
+
+ DEBUG(5,("Disconnecting %lX\n", conn->dbus.conn));
+
+ /*******************************
+ * Referencing conn->dbus.conn */
+ dbus_connection_ref(conn->dbus.conn);
+
+ conn->disconnect = 1;
+
+ /* Invoke the custom destructor, if it exists */
+ if (conn->destructor) {
+ conn->destructor(conn);
+ }
+
+ /* Unregister object paths */
+ sbus_unreg_object_paths(conn);
+
+ /* Disable watch functions */
+ dbus_connection_set_watch_functions(conn->dbus.conn,
+ NULL, NULL, NULL,
+ NULL, NULL);
+ /* Disable timeout functions */
+ dbus_connection_set_timeout_functions(conn->dbus.conn,
+ NULL, NULL, NULL,
+ NULL, NULL);
+
+ /* Disable dispatch status function */
+ dbus_connection_set_dispatch_status_function(conn->dbus.conn,
+ NULL, NULL, NULL);
+
+ /* Disable wakeup main function */
+ dbus_connection_set_wakeup_main_function(conn->dbus.conn,
+ NULL, NULL, NULL);
+
+ /* Finalize the connection */
+ sbus_default_connection_destructor(conn);
+
+ dbus_connection_unref(conn->dbus.conn);
+ /* Unreferenced conn->dbus_conn *
+ ******************************/
+
+ DEBUG(5,("Disconnected %lX\n", conn->dbus.conn));
+}
+
+static int sbus_reply_internal_error(DBusMessage *message,
+ struct sbus_connection *conn) {
+ DBusMessage *reply = dbus_message_new_error(message, DBUS_ERROR_IO_ERROR,
+ "Internal Error");
+ if (reply) {
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* messsage_handler
+ * Receive messages and process them
+ */
+DBusHandlerResult sbus_message_handler(DBusConnection *dbus_conn,
+ DBusMessage *message,
+ void *user_data)
+{
+ struct sbus_interface_p *intf_p;
+ const char *method;
+ const char *path;
+ const char *msg_interface;
+ DBusMessage *reply = NULL;
+ int i, ret;
+ int found;
+
+ if (!user_data) {
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ intf_p = talloc_get_type(user_data, struct sbus_interface_p);
+
+ method = dbus_message_get_member(message);
+ DEBUG(9, ("Received SBUS method [%s]\n", method));
+ path = dbus_message_get_path(message);
+ msg_interface = dbus_message_get_interface(message);
+
+ if (!method || !path || !msg_interface)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ /* Validate the D-BUS path */
+ if (strcmp(path, intf_p->intf->path) != 0)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ /* Validate the method interface */
+ if (strcmp(msg_interface, intf_p->intf->interface) == 0) {
+ found = 0;
+ for (i = 0; intf_p->intf->methods[i].method != NULL; i++) {
+ if (strcmp(method, intf_p->intf->methods[i].method) == 0) {
+ found = 1;
+ ret = intf_p->intf->methods[i].fn(message, intf_p->conn);
+ if (ret != EOK) {
+ return sbus_reply_internal_error(message, intf_p->conn);
+ }
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Reply DBUS_ERROR_UNKNOWN_METHOD */
+ DEBUG(1, ("No matching method found for %s.\n", method));
+ reply = dbus_message_new_error(message, DBUS_ERROR_UNKNOWN_METHOD, NULL);
+ sbus_conn_send_reply(intf_p->conn, reply);
+ dbus_message_unref(reply);
+ }
+ }
+ else {
+ /* Special case: check for Introspection request
+ * This is usually only useful for system bus connections
+ */
+ if (strcmp(msg_interface, DBUS_INTROSPECT_INTERFACE) == 0 &&
+ strcmp(method, DBUS_INTROSPECT_METHOD) == 0)
+ {
+ if (intf_p->intf->introspect_fn) {
+ /* If we have been asked for introspection data and we have
+ * an introspection function registered, user that.
+ */
+ ret = intf_p->intf->introspect_fn(message, intf_p->conn);
+ if (ret != EOK) {
+ return sbus_reply_internal_error(message, intf_p->conn);
+ }
+ }
+ }
+ else
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+/* Adds a new D-BUS path message handler to the connection
+ * Note: this must be a unique path.
+ */
+int sbus_conn_add_interface(struct sbus_connection *conn,
+ struct sbus_interface *intf)
+{
+ struct sbus_interface_p *intf_p;
+ dbus_bool_t dbret;
+ const char *path;
+
+ if (!conn || !intf || !intf->vtable.message_function) {
+ return EINVAL;
+ }
+
+ path = intf->path;
+
+ if (path_in_interface_list(conn->intf_list, path)) {
+ DEBUG(0, ("Cannot add method context with identical path.\n"));
+ return EINVAL;
+ }
+
+ intf_p = talloc_zero(conn, struct sbus_interface_p);
+ if (!intf_p) {
+ return ENOMEM;
+ }
+ intf_p->conn = conn;
+ intf_p->intf = intf;
+
+ DLIST_ADD(conn->intf_list, intf_p);
+
+ dbret = dbus_connection_register_object_path(conn->dbus.conn,
+ path, &intf->vtable, intf_p);
+ if (!dbret) {
+ DEBUG(0, ("Could not register object path to the connection.\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static bool path_in_interface_list(struct sbus_interface_p *list,
+ const char *path)
+{
+ struct sbus_interface_p *iter;
+
+ if (!list || !path) {
+ return false;
+ }
+
+ iter = list;
+ while (iter != NULL) {
+ if (strcmp(iter->intf->path, path) == 0) {
+ return true;
+ }
+ iter = iter->next;
+ }
+
+ return false;
+}
+
+static void sbus_unreg_object_paths(struct sbus_connection *conn)
+{
+ struct sbus_interface_p *iter = conn->intf_list;
+
+ while (iter != NULL) {
+ dbus_connection_unregister_object_path(conn->dbus.conn,
+ iter->intf->path);
+ iter = iter->next;
+ }
+}
+
+void sbus_conn_set_private_data(struct sbus_connection *conn, void *pvt_data)
+{
+ conn->pvt_data = pvt_data;
+}
+
+void *sbus_conn_get_private_data(struct sbus_connection *conn)
+{
+ return conn->pvt_data;
+}
+
+static void sbus_reconnect(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *data)
+{
+ struct sbus_connection *conn;
+ struct sbus_interface_p *iter;
+ DBusError dbus_error;
+ dbus_bool_t dbret;
+ int ret;
+
+ conn = talloc_get_type(data, struct sbus_connection);
+ dbus_error_init(&dbus_error);
+
+ DEBUG(3, ("Making reconnection attempt %d to [%s]\n",
+ conn->retries, conn->address));
+ conn->dbus.conn = dbus_connection_open(conn->address, &dbus_error);
+ if (conn->dbus.conn) {
+ /* We successfully reconnected. Set up mainloop integration. */
+ DEBUG(3, ("Reconnected to [%s]\n", conn->address));
+ ret = sbus_conn_set_fns(conn);
+ if (ret != EOK) {
+ dbus_connection_unref(conn->dbus.conn);
+ goto failed;
+ }
+
+ /* Re-register object paths */
+ iter = conn->intf_list;
+ while (iter) {
+ dbret = dbus_connection_register_object_path(conn->dbus.conn,
+ iter->intf->path,
+ &iter->intf->vtable,
+ iter);
+ if (!dbret) {
+ DEBUG(0, ("Could not register object path.\n"));
+ dbus_connection_unref(conn->dbus.conn);
+ goto failed;
+ }
+ iter = iter->next;
+ }
+
+ /* Reset retries to 0 to resume dispatch processing */
+ conn->retries = 0;
+
+ /* Notify the owner of this connection that the
+ * reconnection was successful
+ */
+ conn->reconnect_callback(conn,
+ SBUS_RECONNECT_SUCCESS,
+ conn->reconnect_pvt);
+ return;
+ }
+
+failed:
+ /* Reconnection failed, try again in a few seconds */
+ DEBUG(1, ("Failed to open connection: name=%s, message=%s\n",
+ dbus_error.name, dbus_error.message));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+
+ conn->retries++;
+
+ /* Check if we've passed our last chance or if we've lost track of
+ * our retry count somehow
+ */
+ if ((conn->retries > conn->max_retries) || (conn->retries <= 0)) {
+ conn->reconnect_callback(conn,
+ SBUS_RECONNECT_EXCEEDED_RETRIES,
+ conn->reconnect_pvt);
+ }
+
+ if (conn->retries == 2) {
+ /* Wait 3 seconds before the second reconnect attempt */
+ tv.tv_sec += 3;
+ }
+ else if (conn->retries == 3) {
+ /* Wait 10 seconds before the third reconnect attempt */
+ tv.tv_sec += 10;
+ }
+ else {
+ /* Wait 30 seconds before all subsequent reconnect attempts */
+ tv.tv_sec += 30;
+ }
+
+ te = tevent_add_timer(conn->ev, conn, tv, sbus_reconnect, conn);
+ if (!te) {
+ conn->reconnect_callback(conn,
+ SBUS_RECONNECT_ERROR,
+ conn->reconnect_pvt);
+ }
+}
+
+/* This function will free and recreate the sbus_connection,
+ * calling functions need to be aware of this (and whether
+ * they have attached a talloc destructor to the
+ * sbus_connection.
+ */
+static int sbus_auto_reconnect(struct sbus_connection *conn)
+{
+ struct tevent_timer *te = NULL;
+ struct timeval tv;
+
+ conn->retries++;
+ if (conn->retries >= conn->max_retries) {
+ /* Return EIO (to tell the calling process it
+ * needs to create a new connection from scratch
+ */
+ return EIO;
+ }
+
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += 1; /* Wait 1 second before the first reconnect attempt */
+ te = tevent_add_timer(conn->ev, conn, tv, sbus_reconnect, conn);
+ if (!te) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+/* Max retries */
+void sbus_reconnect_init(struct sbus_connection *conn,
+ int max_retries,
+ sbus_conn_reconn_callback_fn callback,
+ void *pvt)
+{
+ if (max_retries < 0 || callback == NULL) return;
+
+ conn->retries = 0;
+ conn->max_retries = max_retries;
+ conn->reconnect_callback = callback;
+ conn->reconnect_pvt = pvt;
+}
+
+bool sbus_conn_disconnecting(struct sbus_connection *conn)
+{
+ if (conn->disconnect == 1) return true;
+ return false;
+}
+
+void sbus_conn_send_reply(struct sbus_connection *conn, DBusMessage *reply)
+{
+ dbus_connection_send(conn->dbus.conn, reply, NULL);
+}
+
diff --git a/src/sbus/sssd_dbus_private.h b/src/sbus/sssd_dbus_private.h
new file mode 100644
index 00000000..78a2fe5c
--- /dev/null
+++ b/src/sbus/sssd_dbus_private.h
@@ -0,0 +1,98 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef _SSSD_DBUS_PRIVATE_H_
+#define _SSSD_DBUS_PRIVATE_H_
+
+union dbus_conn_pointer {
+ DBusServer *server;
+ DBusConnection *conn;
+};
+enum dbus_conn_type {
+ SBUS_SERVER,
+ SBUS_CONNECTION
+};
+
+struct sbus_interface_p;
+struct sbus_watch_ctx;
+
+struct sbus_connection {
+ struct tevent_context *ev;
+
+ enum dbus_conn_type type;
+ union dbus_conn_pointer dbus;
+
+ char *address;
+ int connection_type;
+ int disconnect;
+
+ sbus_conn_destructor_fn destructor;
+ void *pvt_data; /* Private data for this connection */
+
+ /* dbus tables and handlers */
+ struct sbus_interface_p *intf_list;
+
+ /* reconnect settings */
+ int retries;
+ int max_retries;
+ sbus_conn_reconn_callback_fn reconnect_callback;
+ /* Private data needed to reinit after reconnection */
+ void *reconnect_pvt;
+
+ /* server related stuff */
+ struct sbus_interface *server_intf;
+ sbus_server_conn_init_fn srv_init_fn;
+ void *srv_init_data;
+
+ /* watches list */
+ struct sbus_watch_ctx *watch_list;
+};
+
+/* =Watches=============================================================== */
+
+struct sbus_watch_ctx {
+ struct sbus_watch_ctx *prev, *next;
+
+ struct sbus_connection *conn;
+
+ struct tevent_fd *fde;
+ int fd;
+
+ DBusWatch *dbus_read_watch;
+ DBusWatch *dbus_write_watch;
+};
+
+dbus_bool_t sbus_add_watch(DBusWatch *watch, void *data);
+void sbus_toggle_watch(DBusWatch *watch, void *data);
+void sbus_remove_watch(DBusWatch *watch, void *data);
+
+/* =Timeouts============================================================== */
+
+struct sbus_timeout_ctx {
+ DBusTimeout *dbus_timeout;
+ struct tevent_timer *te;
+};
+
+dbus_bool_t sbus_add_timeout(DBusTimeout *dbus_timeout, void *data);
+void sbus_toggle_timeout(DBusTimeout *dbus_timeout, void *data);
+void sbus_remove_timeout(DBusTimeout *dbus_timeout, void *data);
+
+#endif /* _SSSD_DBUS_PRIVATE_H_ */
diff --git a/src/sbus/sssd_dbus_server.c b/src/sbus/sssd_dbus_server.c
new file mode 100644
index 00000000..a859cbab
--- /dev/null
+++ b/src/sbus/sssd_dbus_server.c
@@ -0,0 +1,171 @@
+/*
+ SSSD
+
+ Service monitor - D-BUS features
+
+ Copyright (C) Stephen Gallagher 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <sys/time.h>
+#include "tevent.h"
+#include "util/util.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "sbus/sssd_dbus_private.h"
+
+static int sbus_server_destructor(void *ctx);
+
+/*
+ * new_connection_callback
+ * Actions to be run upon each new client connection
+ * Must either perform dbus_connection_ref() on the
+ * new connection or else close the connection with
+ * dbus_connection_close()
+ */
+static void sbus_server_init_new_connection(DBusServer *dbus_server,
+ DBusConnection *dbus_conn,
+ void *data)
+{
+ struct sbus_connection *server;
+ struct sbus_connection *conn;
+ int ret;
+
+ DEBUG(5,("Entering.\n"));
+ server = talloc_get_type(data, struct sbus_connection);
+ if (!server) {
+ return;
+ }
+
+ DEBUG(5,("Adding connection %p.\n", dbus_conn));
+ ret = sbus_init_connection(server, server->ev,
+ dbus_conn, server->server_intf,
+ SBUS_CONN_TYPE_PRIVATE, &conn);
+ if (ret != 0) {
+ dbus_connection_close(dbus_conn);
+ DEBUG(5,("Closing connection (failed setup)"));
+ return;
+ }
+
+ dbus_connection_ref(dbus_conn);
+
+ DEBUG(5,("Got a connection\n"));
+
+ /*
+ * Initialize connection-specific features
+ * This may set a more detailed destructor, but
+ * the default destructor will always be chained
+ * to handle connection cleanup.
+ * This function (or its callbacks) should also
+ * set up connection-specific methods.
+ */
+ ret = server->srv_init_fn(conn, server->srv_init_data);
+ if (ret != EOK) {
+ DEBUG(1,("Initialization failed!\n"));
+ dbus_connection_close(dbus_conn);
+ talloc_zfree(conn);
+ }
+}
+
+/*
+ * dbus_new_server
+ * Set up a D-BUS server, integrate with the event loop
+ * for handling file descriptor and timed events
+ */
+int sbus_new_server(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *address,
+ struct sbus_interface *intf,
+ struct sbus_connection **_server,
+ sbus_server_conn_init_fn init_fn, void *init_pvt_data)
+{
+ struct sbus_connection *server;
+ DBusServer *dbus_server;
+ DBusError dbus_error;
+ dbus_bool_t dbret;
+ char *tmp;
+
+ *_server = NULL;
+
+ /* Set up D-BUS server */
+ dbus_error_init(&dbus_error);
+ dbus_server = dbus_server_listen(address, &dbus_error);
+ if (!dbus_server) {
+ DEBUG(1,("dbus_server_listen failed! (name=%s, message=%s)\n",
+ dbus_error.name, dbus_error.message));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ return EIO;
+ }
+
+ tmp = dbus_server_get_address(dbus_server);
+ DEBUG(3, ("D-BUS Server listening on %s\n", tmp));
+ free(tmp);
+
+ server = talloc_zero(mem_ctx, struct sbus_connection);
+ if (!server) {
+ return ENOMEM;
+ }
+
+ server->ev = ev;
+ server->type = SBUS_SERVER;
+ server->dbus.server = dbus_server;
+ server->server_intf = intf;
+ server->srv_init_fn = init_fn;
+ server->srv_init_data = init_pvt_data;
+
+ talloc_set_destructor((TALLOC_CTX *)server, sbus_server_destructor);
+
+ /* Set up D-BUS new connection handler */
+ dbus_server_set_new_connection_function(server->dbus.server,
+ sbus_server_init_new_connection,
+ server, NULL);
+
+ /* Set up DBusWatch functions */
+ dbret = dbus_server_set_watch_functions(server->dbus.server,
+ sbus_add_watch,
+ sbus_remove_watch,
+ sbus_toggle_watch,
+ server, NULL);
+ if (!dbret) {
+ DEBUG(4, ("Error setting up D-BUS server watch functions"));
+ talloc_free(server);
+ return EIO;
+ }
+
+ /* Set up DBusTimeout functions */
+ dbret = dbus_server_set_timeout_functions(server->dbus.server,
+ sbus_add_timeout,
+ sbus_remove_timeout,
+ sbus_toggle_timeout,
+ server, NULL);
+ if (!dbret) {
+ DEBUG(4,("Error setting up D-BUS server timeout functions"));
+ dbus_server_set_watch_functions(server->dbus.server,
+ NULL, NULL, NULL, NULL, NULL);
+ talloc_free(server);
+ return EIO;
+ }
+
+ *_server = server;
+ return EOK;
+}
+
+static int sbus_server_destructor(void *ctx)
+{
+ struct sbus_connection *server;
+
+ server = talloc_get_type(ctx, struct sbus_connection);
+ dbus_server_disconnect(server->dbus.server);
+ return 0;
+}
diff --git a/src/sss_client/common.c b/src/sss_client/common.c
new file mode 100644
index 00000000..6732c24f
--- /dev/null
+++ b/src/sss_client/common.c
@@ -0,0 +1,669 @@
+/*
+ * System Security Services Daemon. NSS client interface
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * Winbind derived code:
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Andrew Tridgell 2000
+ * Copyright (C) Andrew Bartlett 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <nss.h>
+#include <security/pam_modules.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <poll.h>
+#include "sss_cli.h"
+
+/* common functions */
+
+int sss_cli_sd = -1; /* the sss client socket descriptor */
+
+static void sss_cli_close_socket(void)
+{
+ if (sss_cli_sd != -1) {
+ close(sss_cli_sd);
+ sss_cli_sd = -1;
+ }
+}
+
+/* Requests:
+ *
+ * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
+ * byte 4-7: 32bit unsigned with command code
+ * byte 8-11: 32bit unsigned (reserved)
+ * byte 12-15: 32bit unsigned (reserved)
+ * byte 16-X: (optional) request structure associated to the command code used
+ */
+static enum nss_status sss_nss_send_req(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ int *errnop)
+{
+ uint32_t header[4];
+ size_t datasent;
+
+ header[0] = SSS_NSS_HEADER_SIZE + (rd?rd->len:0);
+ header[1] = cmd;
+ header[2] = 0;
+ header[3] = 0;
+
+ datasent = 0;
+
+ while (datasent < header[0]) {
+ struct pollfd pfd;
+ int rdsent;
+ int res, error;
+
+ *errnop = 0;
+ pfd.fd = sss_cli_sd;
+ pfd.events = POLLOUT;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+ *errnop = EPIPE;
+ }
+ if (!(pfd.revents & POLLOUT)) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one avail ?? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop) {
+ sss_cli_close_socket();
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (datasent < SSS_NSS_HEADER_SIZE) {
+ res = write(sss_cli_sd,
+ (char *)header + datasent,
+ SSS_NSS_HEADER_SIZE - datasent);
+ } else {
+ rdsent = datasent - SSS_NSS_HEADER_SIZE;
+ res = write(sss_cli_sd,
+ (const char *)rd->data + rdsent,
+ rd->len - rdsent);
+ }
+
+ if ((res == -1) || (res == 0)) {
+
+ /* Write failed */
+ sss_cli_close_socket();
+ *errnop = errno;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ datasent += res;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+/* Replies:
+ *
+ * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
+ * byte 4-7: 32bit unsigned with command code
+ * byte 8-11: 32bit unsigned with the request status (server errno)
+ * byte 12-15: 32bit unsigned (reserved)
+ * byte 16-X: (optional) reply structure associated to the command code used
+ */
+
+static enum nss_status sss_nss_recv_rep(enum sss_cli_command cmd,
+ uint8_t **buf, int *len,
+ int *errnop)
+{
+ uint32_t header[4];
+ size_t datarecv;
+
+ header[0] = SSS_NSS_HEADER_SIZE; /* unitl we know the real lenght */
+ header[1] = 0;
+ header[2] = 0;
+ header[3] = 0;
+
+ datarecv = 0;
+ *buf = NULL;
+ *len = 0;
+ *errnop = 0;
+
+ while (datarecv < header[0]) {
+ struct pollfd pfd;
+ int bufrecv;
+ int res, error;
+
+ pfd.fd = sss_cli_sd;
+ pfd.events = POLLIN;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+ *errnop = EPIPE;
+ }
+ if (!(pfd.revents & POLLIN)) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one avail ?? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop) {
+ sss_cli_close_socket();
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ if (datarecv < SSS_NSS_HEADER_SIZE) {
+ res = read(sss_cli_sd,
+ (char *)header + datarecv,
+ SSS_NSS_HEADER_SIZE - datarecv);
+ } else {
+ bufrecv = datarecv - SSS_NSS_HEADER_SIZE;
+ res = read(sss_cli_sd,
+ (char *)(*buf) + bufrecv,
+ header[0] - datarecv);
+ }
+
+ if ((res == -1) || (res == 0)) {
+
+ /* Read failed. I think the only useful thing
+ * we can do here is just return -1 and fail
+ * since the transaction has failed half way
+ * through. */
+
+ sss_cli_close_socket();
+ *errnop = errno;
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ datarecv += res;
+
+ if (datarecv == SSS_NSS_HEADER_SIZE && *len == 0) {
+ /* at this point recv buf is not yet
+ * allocated and the header has just
+ * been read, do checks and proceed */
+ if (header[2] != 0) {
+ /* server side error */
+ sss_cli_close_socket();
+ *errnop = header[2];
+ if (*errnop == EAGAIN) {
+ return NSS_STATUS_TRYAGAIN;
+ } else {
+ return NSS_STATUS_UNAVAIL;
+ }
+ }
+ if (header[1] != cmd) {
+ /* wrong command id */
+ sss_cli_close_socket();
+ *errnop = EBADMSG;
+ return NSS_STATUS_UNAVAIL;
+ }
+ if (header[0] > SSS_NSS_HEADER_SIZE) {
+ *len = header[0] - SSS_NSS_HEADER_SIZE;
+ *buf = malloc(*len);
+ if (!*buf) {
+ sss_cli_close_socket();
+ *errnop = ENOMEM;
+ return NSS_STATUS_UNAVAIL;
+ }
+ }
+ }
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+/* this function will check command codes match and returned length is ok */
+/* repbuf and replen report only the data section not the header */
+static enum nss_status sss_nss_make_request_nochecks(
+ enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum nss_status ret;
+ uint8_t *buf = NULL;
+ int len = 0;
+
+ /* send data */
+ ret = sss_nss_send_req(cmd, rd, errnop);
+ if (ret != NSS_STATUS_SUCCESS) {
+ return ret;
+ }
+
+ /* data sent, now get reply */
+ ret = sss_nss_recv_rep(cmd, &buf, &len, errnop);
+ if (ret != NSS_STATUS_SUCCESS) {
+ return ret;
+ }
+
+ /* we got through, now we have the custom data in buf if any,
+ * return it if requested */
+ if (repbuf && buf) {
+ *repbuf = buf;
+ if (replen) {
+ *replen = len;
+ }
+ } else {
+ free(buf);
+ if (replen) {
+ *replen = 0;
+ }
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+/* GET_VERSION Reply:
+ * 0-3: 32bit unsigned version number
+ */
+
+static int sss_nss_check_version(const char *socket_name)
+{
+ uint8_t *repbuf;
+ size_t replen;
+ enum nss_status nret;
+ int errnop;
+ int res = NSS_STATUS_UNAVAIL;
+ uint32_t expected_version;
+ struct sss_cli_req_data req;
+
+ if (strcmp(socket_name, SSS_NSS_SOCKET_NAME) == 0) {
+ expected_version = SSS_NSS_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_PAM_SOCKET_NAME) == 0 ||
+ strcmp(socket_name, SSS_PAM_PRIV_SOCKET_NAME) == 0) {
+ expected_version = SSS_PAM_PROTOCOL_VERSION;
+ } else {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ req.len = sizeof(expected_version);
+ req.data = &expected_version;
+
+ nret = sss_nss_make_request_nochecks(SSS_GET_VERSION, &req,
+ &repbuf, &replen, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ if (!repbuf) {
+ return res;
+ }
+
+ if (((uint32_t *)repbuf)[0] == expected_version) {
+ res = NSS_STATUS_SUCCESS;
+ }
+
+ free(repbuf);
+ return res;
+}
+
+/* this 2 functions are adapted from samba3 winbinbd's wb_common.c */
+
+/* Make sure socket handle isn't stdin (0), stdout(1) or stderr(2) by setting
+ * the limit to 3 */
+#define RECURSION_LIMIT 3
+
+static int make_nonstd_fd_internals(int fd, int limit)
+{
+ int new_fd;
+ if (fd >= 0 && fd <= 2) {
+#ifdef F_DUPFD
+ if ((new_fd = fcntl(fd, F_DUPFD, 3)) == -1) {
+ return -1;
+ }
+ /* Paranoia */
+ if (new_fd < 3) {
+ close(new_fd);
+ return -1;
+ }
+ close(fd);
+ return new_fd;
+#else
+ if (limit <= 0)
+ return -1;
+
+ new_fd = dup(fd);
+ if (new_fd == -1)
+ return -1;
+
+ /* use the program stack to hold our list of FDs to close */
+ new_fd = make_nonstd_fd_internals(new_fd, limit - 1);
+ close(fd);
+ return new_fd;
+#endif
+ }
+ return fd;
+}
+
+/****************************************************************************
+ Set a fd into blocking/nonblocking mode. Uses POSIX O_NONBLOCK if available,
+ else
+ if SYSV use O_NDELAY
+ if BSD use FNDELAY
+ Set close on exec also.
+****************************************************************************/
+
+static int make_safe_fd(int fd)
+{
+ int result, flags;
+ int new_fd = make_nonstd_fd_internals(fd, RECURSION_LIMIT);
+ if (new_fd == -1) {
+ close(fd);
+ return -1;
+ }
+
+ /* Socket should be nonblocking. */
+#ifdef O_NONBLOCK
+#define FLAG_TO_SET O_NONBLOCK
+#else
+#ifdef SYSV
+#define FLAG_TO_SET O_NDELAY
+#else /* BSD */
+#define FLAG_TO_SET FNDELAY
+#endif
+#endif
+
+ if ((flags = fcntl(new_fd, F_GETFL)) == -1) {
+ close(new_fd);
+ return -1;
+ }
+
+ flags |= FLAG_TO_SET;
+ if (fcntl(new_fd, F_SETFL, flags) == -1) {
+ close(new_fd);
+ return -1;
+ }
+
+#undef FLAG_TO_SET
+
+ /* Socket should be closed on exec() */
+#ifdef FD_CLOEXEC
+ result = flags = fcntl(new_fd, F_GETFD, 0);
+ if (flags >= 0) {
+ flags |= FD_CLOEXEC;
+ result = fcntl( new_fd, F_SETFD, flags );
+ }
+ if (result < 0) {
+ close(new_fd);
+ return -1;
+ }
+#endif
+ return new_fd;
+}
+
+static int sss_nss_open_socket(int *errnop, const char *socket_name)
+{
+ struct sockaddr_un nssaddr;
+ int inprogress = 1;
+ int wait_time, sleep_time;
+ int sd;
+
+ memset(&nssaddr, 0, sizeof(struct sockaddr_un));
+ nssaddr.sun_family = AF_UNIX;
+ strncpy(nssaddr.sun_path, socket_name,
+ strlen(socket_name) + 1);
+
+ sd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sd == -1) {
+ *errnop = errno;
+ return -1;
+ }
+
+ /* set as non-blocking, close on exec, and make sure standard
+ * descriptors are not used */
+ sd = make_safe_fd(sd);
+ if (sd == -1) {
+ *errnop = errno;
+ return -1;
+ }
+
+ /* this piece is adapted from winbind client code */
+ wait_time = 0;
+ sleep_time = 0;
+ while(inprogress) {
+ int connect_errno = 0;
+ socklen_t errnosize;
+ struct timeval tv;
+ fd_set w_fds;
+ int ret;
+
+ wait_time += sleep_time;
+
+ ret = connect(sd, (struct sockaddr *)&nssaddr,
+ sizeof(nssaddr));
+ if (ret == 0) {
+ return sd;
+ }
+
+ switch(errno) {
+ case EINPROGRESS:
+ FD_ZERO(&w_fds);
+ FD_SET(sd, &w_fds);
+ tv.tv_sec = SSS_CLI_SOCKET_TIMEOUT - wait_time;
+ tv.tv_usec = 0;
+
+ ret = select(sd + 1, NULL, &w_fds, NULL, &tv);
+
+ if (ret > 0) {
+ errnosize = sizeof(connect_errno);
+ ret = getsockopt(sd, SOL_SOCKET, SO_ERROR,
+ &connect_errno, &errnosize);
+ if (ret >= 0 && connect_errno == 0) {
+ return sd;
+ }
+ }
+ wait_time += SSS_CLI_SOCKET_TIMEOUT;
+ break;
+ case EAGAIN:
+ if (wait_time < SSS_CLI_SOCKET_TIMEOUT) {
+ sleep_time = rand() % 2 + 1;
+ sleep(sleep_time);
+ }
+ break;
+ default:
+ *errnop = errno;
+ inprogress = 0;
+ break;
+ }
+
+ if (wait_time >= SSS_CLI_SOCKET_TIMEOUT) {
+ inprogress = 0;
+ }
+ }
+
+ /* if we get here connect() failed or we timed out */
+
+ close(sd);
+ return -1;
+}
+
+static enum sss_status sss_cli_check_socket(int *errnop, const char *socket_name)
+{
+ static pid_t mypid;
+ int mysd;
+
+ if (getpid() != mypid) {
+ sss_cli_close_socket();
+ mypid = getpid();
+ }
+
+ /* check if the socket has been closed on the other side */
+ if (sss_cli_sd != -1) {
+ struct pollfd pfd;
+ int res, error;
+
+ *errnop = 0;
+ pfd.fd = sss_cli_sd;
+ pfd.events = POLLIN | POLLOUT;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, SSS_CLI_SOCKET_TIMEOUT);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+ *errnop = EPIPE;
+ }
+ if (!(pfd.revents & (POLLIN | POLLOUT))) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one avail ?? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop) {
+ sss_cli_close_socket();
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ return SSS_STATUS_SUCCESS;
+ }
+
+ mysd = sss_nss_open_socket(errnop, socket_name);
+ if (mysd == -1) {
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ sss_cli_sd = mysd;
+
+ if (sss_nss_check_version(socket_name) == NSS_STATUS_SUCCESS) {
+ return SSS_STATUS_SUCCESS;
+ }
+
+ sss_cli_close_socket();
+ *errnop = EFAULT;
+ return SSS_STATUS_UNAVAIL;
+}
+
+/* this function will check command codes match and returned length is ok */
+/* repbuf and replen report only the data section not the header */
+enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum nss_status ret;
+ char *envval;
+
+ /* avoid looping in the nss daemon */
+ envval = getenv("_SSS_LOOPS");
+ if (envval && strcmp(envval, "NO") == 0) {
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ return sss_nss_make_request_nochecks(cmd, rd, repbuf, replen, errnop);
+}
+
+int sss_pam_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ int ret;
+ char *envval;
+ struct stat stat_buf;
+
+ /* avoid looping in the pam daemon */
+ envval = getenv("_SSS_LOOPS");
+ if (envval && strcmp(envval, "NO") == 0) {
+ return PAM_SERVICE_ERR;
+ }
+
+ /* only root shall use the privileged pipe */
+ if (getuid() == 0 && getgid() == 0) {
+ ret = stat(SSS_PAM_PRIV_SOCKET_NAME, &stat_buf);
+ if (ret != 0) return PAM_SERVICE_ERR;
+ if ( ! (stat_buf.st_uid == 0 &&
+ stat_buf.st_gid == 0 &&
+ (stat_buf.st_mode&(S_IFSOCK|S_IRUSR|S_IWUSR)) == stat_buf.st_mode)) {
+ return PAM_SERVICE_ERR;
+ }
+
+ ret = sss_cli_check_socket(errnop, SSS_PAM_PRIV_SOCKET_NAME);
+ } else {
+ ret = sss_cli_check_socket(errnop, SSS_PAM_SOCKET_NAME);
+ }
+ if (ret != NSS_STATUS_SUCCESS) {
+ return PAM_SERVICE_ERR;
+ }
+
+ return sss_nss_make_request_nochecks(cmd, rd, repbuf, replen, errnop);
+}
diff --git a/src/sss_client/group.c b/src/sss_client/group.c
new file mode 100644
index 00000000..675b8b71
--- /dev/null
+++ b/src/sss_client/group.c
@@ -0,0 +1,435 @@
+/*
+ * System Security Services Daemon. NSS client interface
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* GROUP database NSS interface */
+
+#include <nss.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include "sss_cli.h"
+
+static struct sss_nss_getgrent_data {
+ size_t len;
+ size_t ptr;
+ uint8_t *data;
+} sss_nss_getgrent_data;
+
+static void sss_nss_getgrent_data_clean(void) {
+
+ if (sss_nss_getgrent_data.data != NULL) {
+ free(sss_nss_getgrent_data.data);
+ sss_nss_getgrent_data.data = NULL;
+ }
+ sss_nss_getgrent_data.len = 0;
+ sss_nss_getgrent_data.ptr = 0;
+}
+
+/* GETGRNAM Request:
+ *
+ * 0-X: string with name
+ *
+ * GERTGRGID Request:
+ *
+ * 0-7: 32bit number with gid
+ *
+ * INITGROUPS Request:
+ *
+ * 0-3: 32bit number with gid
+ * 4-7: 32bit unsigned with max num of entries
+ *
+ * Replies:
+ *
+ * 0-3: 32bit unsigned number of results
+ * 4-7: 32bit unsigned (reserved/padding)
+ * For each result (64bit padded ?):
+ * 0-3: 32bit number gid
+ * 4-7: 32bit unsigned number of members
+ * 8-X: sequence of 0 terminated strings (name, passwd, mem..)
+ *
+ * FIXME: do we need to pad so that each result is 32 bit aligned ?
+ */
+struct sss_nss_gr_rep {
+ struct group *result;
+ char *buffer;
+ size_t buflen;
+};
+
+static int sss_nss_getgr_readrep(struct sss_nss_gr_rep *pr,
+ uint8_t *buf, size_t *len)
+{
+ size_t i, l, slen, ptmem;
+ ssize_t dlen;
+ char *sbuf;
+ uint32_t mem_num;
+
+ if (*len < 11) { /* not enough space for data, bad packet */
+ return EBADMSG;
+ }
+
+ pr->result->gr_gid = ((uint32_t *)buf)[0];
+ mem_num = ((uint32_t *)buf)[1];
+
+ sbuf = (char *)&buf[8];
+ slen = *len - 8;
+ dlen = pr->buflen;
+
+ pr->result->gr_name = &(pr->buffer[0]);
+ i = 0;
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ pr->result->gr_passwd = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ /* now members */
+ pr->result->gr_mem = (char **)&(pr->buffer[i]);
+ ptmem = sizeof(char *) * (mem_num + 1);
+ if (ptmem > dlen) {
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ dlen -= ptmem;
+ ptmem += i;
+ pr->result->gr_mem[mem_num] = NULL; /* terminate array */
+
+ for (l = 0; l < mem_num; l++) {
+ pr->result->gr_mem[l] = &(pr->buffer[ptmem]);
+ while ((slen > i) && (dlen > 0)) {
+ pr->buffer[ptmem] = sbuf[i];
+ if (pr->buffer[ptmem] == '\0') break;
+ i++;
+ dlen--;
+ ptmem++;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+ ptmem++;
+ }
+
+ *len = slen -i;
+ return 0;
+}
+
+/* INITGROUP Reply:
+ *
+ * 0-3: 32bit unsigned number of results
+ * 4-7: 32bit unsigned (reserved/padding)
+ * For each result:
+ * 0-4: 32bit number with gid
+ */
+
+
+enum nss_status _nss_sss_initgroups_dyn(const char *user, gid_t group,
+ long int *start, long int *size,
+ gid_t **groups, long int limit,
+ int *errnop)
+{
+ struct sss_cli_req_data rd;
+ uint8_t *repbuf;
+ size_t replen;
+ enum nss_status nret;
+ uint32_t *rbuf;
+ uint32_t num_ret;
+ long int l, max_ret;
+
+ rd.len = strlen(user) +1;
+ rd.data = user;
+
+ nret = sss_nss_make_request(SSS_NSS_INITGR, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ /* no results if not found */
+ num_ret = ((uint32_t *)repbuf)[0];
+ if (num_ret == 0) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+ max_ret = num_ret;
+
+ /* check we have enough space in the buffer */
+ if ((*size - *start) < num_ret) {
+ long int newsize;
+ gid_t *newgroups;
+
+ newsize = *size + num_ret;
+ if ((limit > 0) && (newsize > limit)) {
+ newsize = limit;
+ max_ret = newsize - *start;
+ }
+
+ newgroups = (gid_t *)realloc((*groups), newsize * sizeof(**groups));
+ if (!newgroups) {
+ *errnop = ENOMEM;
+ free(repbuf);
+ return NSS_STATUS_TRYAGAIN;
+ }
+ *groups = newgroups;
+ *size = newsize;
+ }
+
+ rbuf = &((uint32_t *)repbuf)[2];
+ for (l = 0; l < max_ret; l++) {
+ (*groups)[*start] = rbuf[l];
+ *start += 1;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+
+enum nss_status _nss_sss_getgrnam_r(const char *name, struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_gr_rep grrep;
+ uint8_t *repbuf;
+ size_t replen, len;
+ enum nss_status nret;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ rd.len = strlen(name) + 1;
+ rd.data = name;
+
+ nret = sss_nss_make_request(SSS_NSS_GETGRNAM, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ grrep.result = result;
+ grrep.buffer = buffer;
+ grrep.buflen = buflen;
+
+ /* no results if not found */
+ if (((uint32_t *)repbuf)[0] == 0) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* only 1 result is accepted for this function */
+ if (((uint32_t *)repbuf)[0] != 1) {
+ *errnop = EBADMSG;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ len = replen - 8;
+ ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_getgrgid_r(gid_t gid, struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_gr_rep grrep;
+ uint8_t *repbuf;
+ size_t replen, len;
+ enum nss_status nret;
+ uint32_t group_gid;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ group_gid = gid;
+ rd.len = sizeof(uint32_t);
+ rd.data = &group_gid;
+
+ nret = sss_nss_make_request(SSS_NSS_GETGRGID, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ grrep.result = result;
+ grrep.buffer = buffer;
+ grrep.buflen = buflen;
+
+ /* no results if not found */
+ if (((uint32_t *)repbuf)[0] == 0) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* only 1 result is accepted for this function */
+ if (((uint32_t *)repbuf)[0] != 1) {
+ *errnop = EBADMSG;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ len = replen - 8;
+ ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_setgrent(void)
+{
+ enum nss_status nret;
+ int errnop;
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_getgrent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_SETGRENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ return nret;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_getgrent_r(struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_gr_rep grrep;
+ uint8_t *repbuf;
+ size_t replen;
+ enum nss_status nret;
+ uint32_t num_entries;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ /* if there are leftovers return the next one */
+ if (sss_nss_getgrent_data.data != NULL &&
+ sss_nss_getgrent_data.ptr < sss_nss_getgrent_data.len) {
+
+ repbuf = (uint8_t *)sss_nss_getgrent_data.data +
+ sss_nss_getgrent_data.ptr;
+ replen = sss_nss_getgrent_data.len -
+ sss_nss_getgrent_data.ptr;
+
+ grrep.result = result;
+ grrep.buffer = buffer;
+ grrep.buflen = buflen;
+
+ ret = sss_nss_getgr_readrep(&grrep, repbuf, &replen);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* advance buffer pointer */
+ sss_nss_getgrent_data.ptr = sss_nss_getgrent_data.len - replen;
+
+ return NSS_STATUS_SUCCESS;
+ }
+
+ /* release memory if any */
+ sss_nss_getgrent_data_clean();
+
+ /* retrieve no more than SSS_NSS_MAX_ENTRIES at a time */
+ num_entries = SSS_NSS_MAX_ENTRIES;
+ rd.len = sizeof(uint32_t);
+ rd.data = &num_entries;
+
+ nret = sss_nss_make_request(SSS_NSS_GETGRENT, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ /* no results if not found */
+ if ((((uint32_t *)repbuf)[0] == 0) || (replen - 8 == 0)) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ sss_nss_getgrent_data.data = repbuf;
+ sss_nss_getgrent_data.len = replen;
+ sss_nss_getgrent_data.ptr = 8; /* skip metadata fields */
+
+ /* call again ourselves, this will return the first result */
+ return _nss_sss_getgrent_r(result, buffer, buflen, errnop);
+}
+
+enum nss_status _nss_sss_endgrent(void)
+{
+ enum nss_status nret;
+ int errnop;
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_getgrent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_ENDGRENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ return nret;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
diff --git a/src/sss_client/man/pam_sss.8.xml b/src/sss_client/man/pam_sss.8.xml
new file mode 100644
index 00000000..f6ac9f47
--- /dev/null
+++ b/src/sss_client/man/pam_sss.8.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<reference>
+<title>SSSD Manual pages</title>
+<refentry>
+ <refentryinfo>
+ <productname>SSSD</productname>
+ <orgname>The SSSD upstream - http://fedorahosted.org/sssd</orgname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>pam_sss</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv id='name'>
+ <refname>pam_sss</refname>
+ <refpurpose>PAM module for SSSD</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id='synopsis'>
+ <cmdsynopsis>
+ <command>pam_sss.so</command>
+ <arg choice='opt'>
+ <replaceable>forward_pass</replaceable>
+ </arg>
+ <arg choice='opt'>
+ <replaceable>use_first_pass</replaceable>
+ </arg>
+ <arg choice='opt'>
+ <replaceable>use_authtok</replaceable>
+ </arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id='description'>
+ <title>DESCRIPTION</title>
+ <para><command>pam_sss.so</command> is the PAM interface to the System
+ Security Services daemon (SSSD). Errors and results are logged through
+ <command>syslog(3)</command> with the LOG_AUTHPRIV facility.</para>
+ </refsect1>
+
+ <refsect1 id='options'>
+ <title>OPTIONS</title>
+ <variablelist remap='IP'>
+ <varlistentry>
+ <term>
+ <option>forward_pass</option>
+ </term>
+ <listitem>
+ <para>If <option>forward_pass</option> is set the entered
+ password is put on the stack for other PAM modules to use.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>use_first_pass</option>
+ </term>
+ <listitem>
+ <para>The argument use_first_pass forces the module to use
+ a previous stacked modules password and will never prompt
+ the user - if no password is available or the password is
+ not appropriate, the user will be denied access.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>use_authtok</option>
+ </term>
+ <listitem>
+ <para>When password changing enforce the module to set the
+ new password to the one provided by a previously stacked
+ password module.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='module_types_provides'>
+ <title>MODULE TYPES PROVIDED</title>
+ <para>All module types (<option>account</option>, <option>auth</option>,
+ <option>password</option> and <option>session</option>) are provided.
+ </para>
+ </refsect1>
+
+ <refsect1 id='see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>sssd.conf</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+</refentry>
+</reference>
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
new file mode 100644
index 00000000..8a1e3129
--- /dev/null
+++ b/src/sss_client/pam_sss.c
@@ -0,0 +1,1166 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+#define PAM_SM_SESSION
+#define PAM_SM_PASSWORD
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
+#include "sss_pam_macros.h"
+
+#include "sss_cli.h"
+
+#include <libintl.h>
+#define _(STRING) dgettext (PACKAGE, STRING)
+#include "config.h"
+
+#define FLAGS_USE_FIRST_PASS (1 << 0)
+#define FLAGS_FORWARD_PASS (1 << 1)
+#define FLAGS_USE_AUTHTOK (1 << 2)
+
+struct pam_items {
+ const char* pam_service;
+ const char* pam_user;
+ const char* pam_tty;
+ const char* pam_ruser;
+ const char* pam_rhost;
+ char* pam_authtok;
+ char* pam_newauthtok;
+ const char* pamstack_authtok;
+ const char* pamstack_oldauthtok;
+ size_t pam_service_size;
+ size_t pam_user_size;
+ size_t pam_tty_size;
+ size_t pam_ruser_size;
+ size_t pam_rhost_size;
+ int pam_authtok_type;
+ size_t pam_authtok_size;
+ int pam_newauthtok_type;
+ size_t pam_newauthtok_size;
+ pid_t cli_pid;
+ const char *login_name;
+};
+
+#define DEBUG_MGS_LEN 1024
+#define MAX_AUTHTOK_SIZE (1024*1024)
+#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)")
+
+static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+
+#ifdef DEBUG
+ va_list apd;
+ char debug_msg[DEBUG_MGS_LEN];
+ int ret;
+ va_copy(apd, ap);
+
+ ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd);
+ if (ret >= DEBUG_MGS_LEN) {
+ D(("the following message is truncated: %s", debug_msg));
+ } else if (ret < 0) {
+ D(("vsnprintf failed to format debug message!"));
+ } else {
+ D((debug_msg));
+ }
+
+ va_end(apd);
+#endif
+
+ pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap);
+
+ va_end(ap);
+}
+
+
+static size_t add_authtok_item(enum pam_item_type type,
+ enum sss_authtok_type authtok_type,
+ const char *tok, const size_t size,
+ uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ if (tok == NULL) return 0;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = size + sizeof(uint32_t);
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = authtok_type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ memcpy(&buf[rp], tok, size);
+ rp += size;
+
+ return rp;
+}
+
+
+static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
+ uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = sizeof(uint32_t);
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = val;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ return rp;
+}
+
+static size_t add_string_item(enum pam_item_type type, const char *str,
+ const size_t size, uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ if (str == NULL || *str == '\0') return 0;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = size;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ memcpy(&buf[rp], str, size);
+ rp += size;
+
+ return rp;
+}
+
+static void overwrite_and_free_authtoks(struct pam_items *pi)
+{
+ if (pi->pam_authtok != NULL) {
+ _pam_overwrite_n((void *)pi->pam_authtok, pi->pam_authtok_size);
+ free((void *)pi->pam_authtok);
+ pi->pam_authtok = NULL;
+ }
+
+ if (pi->pam_newauthtok != NULL) {
+ _pam_overwrite_n((void *)pi->pam_newauthtok, pi->pam_newauthtok_size);
+ free((void *)pi->pam_newauthtok);
+ pi->pam_newauthtok = NULL;
+ }
+}
+
+static int pack_message_v3(struct pam_items *pi, size_t *size,
+ uint8_t **buffer) {
+ int len;
+ uint8_t *buf;
+ int rp;
+ uint32_t terminator = SSS_END_OF_PAM_REQUEST;
+
+ len = sizeof(uint32_t) +
+ 2*sizeof(uint32_t) + pi->pam_user_size +
+ sizeof(uint32_t);
+ len += *pi->pam_service != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_service_size : 0;
+ len += *pi->pam_tty != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_tty_size : 0;
+ len += *pi->pam_ruser != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
+ len += *pi->pam_rhost != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
+ len += pi->pam_authtok != NULL ?
+ 3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
+ len += pi->pam_newauthtok != NULL ?
+ 3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
+ len += 3*sizeof(uint32_t); /* cli_pid */
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ D(("malloc failed."));
+ return PAM_BUF_ERR;
+ }
+
+ rp = 0;
+ ((uint32_t *)(&buf[rp]))[0] = SSS_START_OF_PAM_REQUEST;
+ rp += sizeof(uint32_t);
+
+ rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
+ pi->pam_service_size, &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
+ &buf[rp]);
+
+ rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
+ &buf[rp]);
+
+ rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
+ pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);
+
+ rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
+ pi->pam_newauthtok, pi->pam_newauthtok_size,
+ &buf[rp]);
+
+ memcpy(&buf[rp], &terminator, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ if (rp != len) {
+ D(("error during packet creation."));
+ return PAM_BUF_ERR;
+ }
+
+ *size = len;
+ *buffer = buf;
+
+ return 0;
+}
+
+static int null_strcmp(const char *s1, const char *s2) {
+ if (s1 == NULL && s2 == NULL) return 0;
+ if (s1 == NULL && s2 != NULL) return -1;
+ if (s1 != NULL && s2 == NULL) return 1;
+ return strcmp(s1, s2);
+}
+
+enum {
+ SSS_PAM_CONV_DONE = 0,
+ SSS_PAM_CONV_STD,
+ SSS_PAM_CONV_REENTER,
+};
+
+static int do_pam_conversation(pam_handle_t *pamh, const int msg_style,
+ const char *msg,
+ const char *reenter_msg,
+ char **answer)
+{
+ int ret;
+ int state = SSS_PAM_CONV_STD;
+ struct pam_conv *conv;
+ struct pam_message *mesg[1];
+ struct pam_response *resp=NULL;
+
+ if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) &&
+ msg == NULL) return PAM_SYSTEM_ERR;
+
+ if ((msg_style == PAM_PROMPT_ECHO_OFF ||
+ msg_style == PAM_PROMPT_ECHO_ON) &&
+ (msg == NULL || answer == NULL)) return PAM_SYSTEM_ERR;
+
+ if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) {
+ logger(pamh, LOG_INFO, "User %s message: %s",
+ msg_style == PAM_TEXT_INFO ? "info" : "error",
+ msg);
+ }
+
+ ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+ if (ret != PAM_SUCCESS) return ret;
+
+ do {
+ mesg[0] = malloc(sizeof(struct pam_message));
+ if (mesg[0] == NULL) {
+ D(("Malloc failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ mesg[0]->msg_style = msg_style;
+ if (state == SSS_PAM_CONV_REENTER) {
+ mesg[0]->msg = reenter_msg;
+ } else {
+ mesg[0]->msg = msg;
+ }
+
+ ret=conv->conv(1, (const struct pam_message **) mesg, &resp,
+ conv->appdata_ptr);
+ free(mesg[0]);
+ if (ret != PAM_SUCCESS) {
+ D(("Conversation failure: %s.", pam_strerror(pamh,ret)));
+ return ret;
+ }
+
+ if (msg_style == PAM_PROMPT_ECHO_OFF ||
+ msg_style == PAM_PROMPT_ECHO_ON) {
+ if (resp == NULL) {
+ D(("response expected, but resp==NULL"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ if (state == SSS_PAM_CONV_REENTER) {
+ if (null_strcmp(*answer, resp[0].resp) != 0) {
+ logger(pamh, LOG_NOTICE, "Passwords do not match.");
+ _pam_overwrite((void *)resp[0].resp);
+ free(resp[0].resp);
+ if (*answer != NULL) {
+ _pam_overwrite((void *)*answer);
+ free(*answer);
+ *answer = NULL;
+ }
+ ret = do_pam_conversation(pamh, PAM_ERROR_MSG,
+ _("Passwords do not match"),
+ NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+ return PAM_CRED_ERR;
+ }
+ _pam_overwrite((void *)resp[0].resp);
+ free(resp[0].resp);
+ } else {
+ if (resp[0].resp == NULL) {
+ D(("Empty password"));
+ *answer = NULL;
+ } else {
+ *answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+ _pam_overwrite((void *)resp[0].resp);
+ free(resp[0].resp);
+ if(*answer == NULL) {
+ D(("strndup failed"));
+ return PAM_BUF_ERR;
+ }
+ }
+ }
+ free(resp);
+ resp = NULL;
+ }
+
+ if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) {
+ state = SSS_PAM_CONV_REENTER;
+ } else {
+ state = SSS_PAM_CONV_DONE;
+ }
+ } while (state != SSS_PAM_CONV_DONE);
+
+ return PAM_SUCCESS;
+}
+
+static int user_info_offline_auth(pam_handle_t *pamh, size_t buflen,
+ uint8_t *buf)
+{
+ int ret;
+ long long expire_date;
+ struct tm tm;
+ char expire_str[128];
+ char user_msg[256];
+
+ expire_str[0] = '\0';
+
+ if (buflen != sizeof(uint32_t) + sizeof(long long)) {
+ D(("User info response data has the wrong size"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&expire_date, buf + sizeof(uint32_t), sizeof(long long));
+
+ if (expire_date > 0) {
+ if (localtime_r((time_t *) &expire_date, &tm) != NULL) {
+ ret = strftime(expire_str, sizeof(expire_str), "%c", &tm);
+ if (ret == 0) {
+ D(("strftime failed."));
+ expire_str[0] = '\0';
+ }
+ } else {
+ D(("localtime_r failed"));
+ }
+ }
+
+ ret = snprintf(user_msg, sizeof(user_msg), "%s%s%s.",
+ _("Offline authentication"),
+ expire_str[0] ? _(", your cached password will expire at: ") : "",
+ expire_str[0] ? expire_str : "");
+ if (ret < 0 || ret >= sizeof(user_msg)) {
+ D(("snprintf failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int user_info_offline_auth_delayed(pam_handle_t *pamh, size_t buflen,
+ uint8_t *buf)
+{
+ int ret;
+ long long delayed_until;
+ struct tm tm;
+ char delay_str[128];
+ char user_msg[256];
+
+ delay_str[0] = '\0';
+
+ if (buflen != sizeof(uint32_t) + sizeof(long long)) {
+ D(("User info response data has the wrong size"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&delayed_until, buf + sizeof(uint32_t), sizeof(long long));
+
+ if (delayed_until <= 0) {
+ D(("User info response data has an invalid value"));
+ return PAM_BUF_ERR;
+ }
+
+ if (localtime_r((time_t *) &delayed_until, &tm) != NULL) {
+ ret = strftime(delay_str, sizeof(delay_str), "%c", &tm);
+ if (ret == 0) {
+ D(("strftime failed."));
+ delay_str[0] = '\0';
+ }
+ } else {
+ D(("localtime_r failed"));
+ }
+
+ ret = snprintf(user_msg, sizeof(user_msg), "%s%s.",
+ _("Offline authentication, authentication is denied until: "),
+ delay_str);
+ if (ret < 0 || ret >= sizeof(user_msg)) {
+ D(("snprintf failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int user_info_offline_chpass(pam_handle_t *pamh, size_t buflen,
+ uint8_t *buf)
+{
+ int ret;
+
+ if (buflen != sizeof(uint32_t)) {
+ D(("User info response data has the wrong size"));
+ return PAM_BUF_ERR;
+ }
+
+ ret = do_pam_conversation(pamh, PAM_TEXT_INFO,
+ _("System is offline, password change not possible"),
+ NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen,
+ uint8_t *buf)
+{
+ int ret;
+ uint32_t msg_len;
+ char user_msg[256];
+
+ if (buflen < 2* sizeof(uint32_t)) {
+ D(("User info response data is too short"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t));
+
+ if (buflen != 2* sizeof(uint32_t) + msg_len) {
+ D(("User info response data has the wrong size"));
+ return PAM_BUF_ERR;
+ }
+
+ ret = snprintf(user_msg, sizeof(user_msg), "%s%s%.*s",
+ _("Password change failed. "),
+ msg_len > 0 ? _("Server message: ") : "",
+ msg_len,
+ msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" );
+ if (ret < 0 || ret >= sizeof(user_msg)) {
+ D(("snprintf failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+
+static int eval_user_info_response(pam_handle_t *pamh, size_t buflen,
+ uint8_t *buf)
+{
+ int ret;
+ uint32_t type;
+
+ if (buflen < sizeof(uint32_t)) {
+ D(("User info response data is too short"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&type, buf, sizeof(uint32_t));
+
+ switch(type) {
+ case SSS_PAM_USER_INFO_OFFLINE_AUTH:
+ ret = user_info_offline_auth(pamh, buflen, buf);
+ break;
+ case SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED:
+ ret = user_info_offline_auth_delayed(pamh, buflen, buf);
+ break;
+ case SSS_PAM_USER_INFO_OFFLINE_CHPASS:
+ ret = user_info_offline_chpass(pamh, buflen, buf);
+ break;
+ case SSS_PAM_USER_INFO_CHPASS_ERROR:
+ ret = user_info_chpass_error(pamh, buflen, buf);
+ break;
+ default:
+ D(("Unknown user info type [%d]", type));
+ ret = PAM_SYSTEM_ERR;
+ }
+
+ return ret;
+}
+
+static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf)
+{
+ int ret;
+ size_t p=0;
+ char *env_item;
+ int32_t c;
+ int32_t type;
+ int32_t len;
+ int32_t pam_status;
+
+ if (buflen < (2*sizeof(int32_t))) {
+ D(("response buffer is too small"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&pam_status, buf+p, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+
+ memcpy(&c, buf+p, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+ while(c>0) {
+ if (buflen < (p+2*sizeof(int32_t))) {
+ D(("response buffer is too small"));
+ return PAM_BUF_ERR;
+ }
+
+ memcpy(&type, buf+p, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+ memcpy(&len, buf+p, sizeof(int32_t));
+ p += sizeof(int32_t);
+
+ if (buflen < (p + len)) {
+ D(("response buffer is too small"));
+ return PAM_BUF_ERR;
+ }
+
+ switch(type) {
+ case SSS_PAM_SYSTEM_INFO:
+ if (buf[p + (len -1)] != '\0') {
+ D(("user info does not end with \\0."));
+ break;
+ }
+ logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]);
+ break;
+ case SSS_PAM_DOMAIN_NAME:
+ D(("domain name: [%s]", &buf[p]));
+ break;
+ case SSS_ENV_ITEM:
+ case SSS_PAM_ENV_ITEM:
+ case SSS_ALL_ENV_ITEM:
+ if (buf[p + (len -1)] != '\0') {
+ D(("env item does not end with \\0."));
+ break;
+ }
+
+ D(("env item: [%s]", &buf[p]));
+ if (type == SSS_PAM_ENV_ITEM || type == SSS_ALL_ENV_ITEM) {
+ ret = pam_putenv(pamh, (char *)&buf[p]);
+ if (ret != PAM_SUCCESS) {
+ D(("pam_putenv failed."));
+ break;
+ }
+ }
+
+ if (type == SSS_ENV_ITEM || type == SSS_ALL_ENV_ITEM) {
+ env_item = strdup((char *)&buf[p]);
+ if (env_item == NULL) {
+ D(("strdup failed"));
+ break;
+ }
+ ret = putenv(env_item);
+ if (ret == -1) {
+ D(("putenv failed."));
+ break;
+ }
+ }
+ break;
+ case SSS_PAM_USER_INFO:
+ ret = eval_user_info_response(pamh, len, &buf[p]);
+ if (ret != PAM_SUCCESS) {
+ D(("eval_user_info_response failed"));
+ }
+ break;
+ default:
+ D(("Unknown response type [%d]", type));
+ }
+ p += len;
+
+ --c;
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
+{
+ int ret;
+
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+ pi->pam_authtok = NULL;
+ pi->pam_authtok_size = 0;
+ pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+ pi->pam_newauthtok = NULL;
+ pi->pam_newauthtok_size = 0;
+
+ ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pam_service == NULL) pi->pam_service="";
+ pi->pam_service_size=strlen(pi->pam_service)+1;
+
+ ret = pam_get_item(pamh, PAM_USER, (const void **) &(pi->pam_user));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pam_user == NULL) {
+ D(("No user found, aborting."));
+ return PAM_BAD_ITEM;
+ }
+ if (strcmp(pi->pam_user, "root") == 0) {
+ D(("pam_sss will not handle root."));
+ return PAM_USER_UNKNOWN;
+ }
+ pi->pam_user_size=strlen(pi->pam_user)+1;
+
+
+ ret = pam_get_item(pamh, PAM_TTY, (const void **) &(pi->pam_tty));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pam_tty == NULL) pi->pam_tty="";
+ pi->pam_tty_size=strlen(pi->pam_tty)+1;
+
+ ret = pam_get_item(pamh, PAM_RUSER, (const void **) &(pi->pam_ruser));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pam_ruser == NULL) pi->pam_ruser="";
+ pi->pam_ruser_size=strlen(pi->pam_ruser)+1;
+
+ ret = pam_get_item(pamh, PAM_RHOST, (const void **) &(pi->pam_rhost));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pam_rhost == NULL) pi->pam_rhost="";
+ pi->pam_rhost_size=strlen(pi->pam_rhost)+1;
+
+ ret = pam_get_item(pamh, PAM_AUTHTOK,
+ (const void **) &(pi->pamstack_authtok));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pamstack_authtok == NULL) pi->pamstack_authtok="";
+
+ ret = pam_get_item(pamh, PAM_OLDAUTHTOK,
+ (const void **) &(pi->pamstack_oldauthtok));
+ if (ret != PAM_SUCCESS) return ret;
+ if (pi->pamstack_oldauthtok == NULL) pi->pamstack_oldauthtok="";
+
+ pi->cli_pid = getpid();
+
+ pi->login_name = pam_modutil_getlogin(pamh);
+ if (pi->login_name == NULL) pi->login_name="";
+
+ return PAM_SUCCESS;
+}
+
+static void print_pam_items(struct pam_items *pi)
+{
+ if (pi == NULL) return;
+
+ D(("Service: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_service)));
+ D(("User: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_user)));
+ D(("Tty: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_tty)));
+ D(("Ruser: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_ruser)));
+ D(("Rhost: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_rhost)));
+ D(("Pamstack_Authtok: %s",
+ CHECK_AND_RETURN_PI_STRING(pi->pamstack_authtok)));
+ D(("Pamstack_Oldauthtok: %s",
+ CHECK_AND_RETURN_PI_STRING(pi->pamstack_oldauthtok)));
+ D(("Authtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_authtok)));
+ D(("Newauthtok: %s", CHECK_AND_RETURN_PI_STRING(pi->pam_newauthtok)));
+ D(("Cli_PID: %d", pi->cli_pid));
+}
+
+static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi,
+ enum sss_cli_command task)
+{
+ int ret;
+ int errnop;
+ struct sss_cli_req_data rd;
+ uint8_t *buf = NULL;
+ uint8_t *repbuf = NULL;
+ size_t replen;
+ int pam_status = PAM_SYSTEM_ERR;
+
+ print_pam_items(pi);
+
+ ret = pack_message_v3(pi, &rd.len, &buf);
+ if (ret != 0) {
+ D(("pack_message failed."));
+ pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ rd.data = buf;
+
+ ret = sss_pam_make_request(task, &rd, &repbuf, &replen, &errnop);
+
+ if (ret != NSS_STATUS_SUCCESS) {
+ logger(pamh, LOG_ERR, "Request to sssd failed.");
+ pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+/* FIXME: add an end signature */
+ if (replen < (2*sizeof(int32_t))) {
+ D(("response not in expected format."));
+ pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ pam_status = ((int32_t *)repbuf)[0];
+ ret = eval_response(pamh, replen, repbuf);
+ if (ret != PAM_SUCCESS) {
+ D(("eval_response failed."));
+ pam_status = ret;
+ goto done;
+ }
+
+ switch (task) {
+ case SSS_PAM_AUTHENTICATE:
+ logger(pamh, (pam_status == PAM_SUCCESS ? LOG_INFO : LOG_NOTICE),
+ "authentication %s; logname=%s uid=%lu euid=%d tty=%s "
+ "ruser=%s rhost=%s user=%s",
+ pam_status == PAM_SUCCESS ? "success" : "failure",
+ pi->login_name, getuid(), (unsigned long) geteuid(),
+ pi->pam_tty, pi->pam_ruser, pi->pam_rhost, pi->pam_user);
+ if (pam_status != PAM_SUCCESS) {
+ logger(pamh, LOG_NOTICE, "received for user %s: %d (%s)",
+ pi->pam_user, pam_status,
+ pam_strerror(pamh,pam_status));
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (pam_status != PAM_SUCCESS) {
+ logger(pamh, LOG_NOTICE,
+ "Authentication failed for user %s: %d (%s)",
+ pi->pam_user, pam_status,
+ pam_strerror(pamh,pam_status));
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ if (pam_status != PAM_SUCCESS) {
+ logger(pamh, LOG_NOTICE,
+ "Password change failed for user %s: %d (%s)",
+ pi->pam_user, pam_status,
+ pam_strerror(pamh,pam_status));
+ }
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ if (pam_status != PAM_SUCCESS) {
+ logger(pamh, LOG_NOTICE,
+ "Access denied for user %s: %d (%s)",
+ pi->pam_user, pam_status,
+ pam_strerror(pamh,pam_status));
+ }
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ break;
+ default:
+ D(("Illegal task [%d]", task));
+ return PAM_SYSTEM_ERR;
+ }
+
+done:
+ if (buf != NULL ) {
+ _pam_overwrite_n((void *)buf, rd.len);
+ free(buf);
+ }
+ free(repbuf);
+
+ return pam_status;
+}
+
+static int prompt_password(pam_handle_t *pamh, struct pam_items *pi)
+{
+ int ret;
+ char *answer = NULL;
+
+ ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, _("Password: "),
+ NULL, &answer);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return ret;
+ }
+
+ if (answer == NULL) {
+ pi->pam_authtok = NULL;
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+ pi->pam_authtok_size=0;
+ } else {
+ pi->pam_authtok = strdup(answer);
+ _pam_overwrite((void *)answer);
+ free(answer);
+ answer=NULL;
+ if (pi->pam_authtok == NULL) {
+ return PAM_BUF_ERR;
+ }
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ pi->pam_authtok_size=strlen(pi->pam_authtok);
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi)
+{
+ int ret;
+ char *answer = NULL;
+
+ ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF,
+ _("New Password: "),
+ _("Reenter new Password: "),
+ &answer);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return ret;
+ }
+ if (answer == NULL) {
+ pi->pam_newauthtok = NULL;
+ pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+ pi->pam_newauthtok_size=0;
+ } else {
+ pi->pam_newauthtok = strdup(answer);
+ _pam_overwrite((void *)answer);
+ free(answer);
+ answer=NULL;
+ if (pi->pam_newauthtok == NULL) {
+ return PAM_BUF_ERR;
+ }
+ pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ pi->pam_newauthtok_size=strlen(pi->pam_newauthtok);
+ }
+
+ return PAM_SUCCESS;
+}
+
+static void eval_argv(pam_handle_t *pamh, int argc, const char **argv,
+ uint32_t *flags)
+{
+ for (; argc-- > 0; ++argv) {
+ if (strcmp(*argv, "forward_pass") == 0) {
+ *flags |= FLAGS_FORWARD_PASS;
+ } else if (strcmp(*argv, "use_first_pass") == 0) {
+ *flags |= FLAGS_USE_FIRST_PASS;
+ } else if (strcmp(*argv, "use_authtok") == 0) {
+ *flags |= FLAGS_USE_AUTHTOK;
+ } else {
+ logger(pamh, LOG_WARNING, "unknown option: %s", *argv);
+ }
+ }
+
+ return;
+}
+
+static int get_authtok_for_authentication(pam_handle_t *pamh,
+ struct pam_items *pi,
+ uint32_t flags)
+{
+ int ret;
+
+ if (flags & FLAGS_USE_FIRST_PASS) {
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ pi->pam_authtok = strdup(pi->pamstack_authtok);
+ if (pi->pam_authtok == NULL) {
+ D(("option use_first_pass set, but no password found"));
+ return PAM_BUF_ERR;
+ }
+ pi->pam_authtok_size = strlen(pi->pam_authtok);
+ } else {
+ ret = prompt_password(pamh, pi);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get password from user"));
+ return ret;
+ }
+
+ if (flags & FLAGS_FORWARD_PASS) {
+ ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok);
+ if (ret != PAM_SUCCESS) {
+ D(("Failed to set PAM_AUTHTOK [%s], "
+ "authtok may not be available for other modules",
+ pam_strerror(pamh,ret)));
+ }
+ }
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int get_authtok_for_password_change(pam_handle_t *pamh,
+ struct pam_items *pi,
+ uint32_t flags,
+ int pam_flags)
+{
+ int ret;
+
+ /* we query for the old password during PAM_PRELIM_CHECK to make
+ * pam_sss work e.g. with pam_cracklib */
+ if (pam_flags & PAM_PRELIM_CHECK) {
+ if (getuid() != 0 && !(flags & FLAGS_USE_FIRST_PASS)) {
+ ret = prompt_password(pamh, pi);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get password from user"));
+ return ret;
+ }
+
+ ret = pam_set_item(pamh, PAM_OLDAUTHTOK, pi->pam_authtok);
+ if (ret != PAM_SUCCESS) {
+ D(("Failed to set PAM_OLDAUTHTOK [%s], "
+ "oldauthtok may not be available",
+ pam_strerror(pamh,ret)));
+ return ret;
+ }
+ }
+
+ return PAM_SUCCESS;
+ }
+
+ if (pi->pamstack_oldauthtok == NULL) {
+ if (getuid() != 0) {
+ D(("no password found for chauthtok"));
+ return PAM_BUF_ERR;
+ } else {
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+ pi->pam_authtok = NULL;
+ pi->pam_authtok_size = 0;
+ }
+ } else {
+ pi->pam_authtok = strdup(pi->pamstack_oldauthtok);
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ pi->pam_authtok_size = strlen(pi->pam_authtok);
+ }
+
+ if (flags & FLAGS_USE_AUTHTOK) {
+ pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ pi->pam_newauthtok = strdup(pi->pamstack_authtok);
+ if (pi->pam_newauthtok == NULL) {
+ D(("option use_authtok set, but no new password found"));
+ return PAM_BUF_ERR;
+ }
+ pi->pam_newauthtok_size = strlen(pi->pam_newauthtok);
+ } else {
+ ret = prompt_new_password(pamh, pi);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get new password from user"));
+ return ret;
+ }
+
+ if (flags & FLAGS_FORWARD_PASS) {
+ ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_newauthtok);
+ if (ret != PAM_SUCCESS) {
+ D(("Failed to set PAM_AUTHTOK [%s], "
+ "oldauthtok may not be available",
+ pam_strerror(pamh,ret)));
+ }
+ }
+ }
+
+ return PAM_SUCCESS;
+}
+
+static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
+ int pam_flags, int argc, const char **argv)
+{
+ int ret;
+ struct pam_items pi;
+ uint32_t flags = 0;
+
+ bindtextdomain(PACKAGE, LOCALEDIR);
+
+ D(("Hello pam_sssd: %d", task));
+
+ eval_argv(pamh, argc, argv, &flags);
+
+ ret = get_pam_items(pamh, &pi);
+ if (ret != PAM_SUCCESS) {
+ D(("get items returned error: %s", pam_strerror(pamh,ret)));
+ return ret;
+ }
+
+
+ switch(task) {
+ case SSS_PAM_AUTHENTICATE:
+ ret = get_authtok_for_authentication(pamh, &pi, flags);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get authentication token: %s",
+ pam_strerror(pamh, ret)));
+ return ret;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get tokens for password change: %s",
+ pam_strerror(pamh, ret)));
+ return ret;
+ }
+ if (pam_flags & PAM_PRELIM_CHECK) {
+ task = SSS_PAM_CHAUTHTOK_PRELIM;
+ }
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ break;
+ default:
+ D(("Illegal task [%d]", task));
+ return PAM_SYSTEM_ERR;
+ }
+
+ ret = send_and_receive(pamh, &pi, task);
+
+ if (ret == PAM_AUTHTOK_EXPIRED && task == SSS_PAM_AUTHENTICATE) {
+ D(("Authtoken expired, trying to change it"));
+ ret = do_pam_conversation(pamh, PAM_ERROR_MSG,
+ _("Password has expired."), NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ pi.pamstack_oldauthtok = pi.pam_authtok;
+ ret = get_authtok_for_password_change(pamh, &pi, flags, pam_flags);
+ if (ret != PAM_SUCCESS) {
+ D(("failed to get tokens for password change: %s",
+ pam_strerror(pamh, ret)));
+ return ret;
+ }
+
+ ret = send_and_receive(pamh, &pi, SSS_PAM_CHAUTHTOK);
+ }
+
+ overwrite_and_free_authtoks(&pi);
+
+ return ret;
+}
+
+PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_AUTHENTICATE, pamh, flags, argc, argv);
+}
+
+
+PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_SETCRED, pamh, flags, argc, argv);
+}
+
+PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_ACCT_MGMT, pamh, flags, argc, argv);
+}
+
+PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_CHAUTHTOK, pamh, flags, argc, argv);
+}
+
+PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_OPEN_SESSION, pamh, flags, argc, argv);
+}
+
+PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
+ const char **argv )
+{
+ return pam_sss(SSS_PAM_CLOSE_SESSION, pamh, flags, argc, argv);
+}
+
+
+#ifdef PAM_STATIC
+
+/* static module data */
+
+struct pam_module _pam_sssd_modstruct ={
+ "pam_sssd",
+ pam_sm_authenticate,
+ pam_sm_setcred,
+ pam_sm_acct_mgmt,
+ pam_sm_open_session,
+ pam_sm_close_session,
+ pam_sm_chauthtok
+};
+
+#endif
diff --git a/src/sss_client/pam_test_client.c b/src/sss_client/pam_test_client.c
new file mode 100644
index 00000000..a088981b
--- /dev/null
+++ b/src/sss_client/pam_test_client.c
@@ -0,0 +1,95 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+
+static struct pam_conv conv = {
+ misc_conv,
+ NULL
+};
+
+int main(int argc, char *argv[]) {
+
+ pam_handle_t *pamh;
+ char *user;
+ char *action;
+ int ret;
+
+ if (argc == 1) {
+ fprintf(stderr, "missing action and user name, using default\n");
+ action = strdup("auth");
+ user = strdup("dummy");
+ } else if (argc == 2) {
+ fprintf(stdout, "using first argument as action and default user name\n");
+ action = strdup(argv[1]);
+ user = strdup("dummy");
+ } else {
+ action = strdup(argv[1]);
+ user = strdup(argv[2]);
+ }
+
+ fprintf(stdout, "action: %s\nuser: %s\n", action,user);
+
+ ret = pam_start("sss_test", user, &conv, &pamh);
+ if (ret != PAM_SUCCESS) {
+ fprintf(stderr, "pam_start failed: %s\n", pam_strerror(pamh, ret));
+ return 1;
+ }
+
+ if ( strncmp(action, "auth", 4)== 0 ) {
+ fprintf(stdout, "testing pam_authenticate\n");
+ ret = pam_authenticate(pamh, 0);
+ fprintf(stderr, "pam_authenticate: %s\n", pam_strerror(pamh, ret));
+ } else if ( strncmp(action, "chau", 4)== 0 ) {
+ fprintf(stdout, "testing pam_chauthtok\n");
+ ret = pam_chauthtok(pamh, 0);
+ fprintf(stderr, "pam_chauthtok: %s\n", pam_strerror(pamh, ret));
+ } else if ( strncmp(action, "acct", 4)== 0 ) {
+ fprintf(stdout, "testing pam_acct_mgmt\n");
+ ret = pam_acct_mgmt(pamh, 0);
+ fprintf(stderr, "pam_acct_mgmt: %s\n", pam_strerror(pamh, ret));
+ } else if ( strncmp(action, "setc", 4)== 0 ) {
+ fprintf(stdout, "testing pam_setcred\n");
+ ret = pam_setcred(pamh, 0);
+ fprintf(stderr, "pam_setcred: %d[%s]\n", ret, pam_strerror(pamh, ret));
+ } else if ( strncmp(action, "open", 4)== 0 ) {
+ fprintf(stdout, "testing pam_open_session\n");
+ ret = pam_open_session(pamh, 0);
+ fprintf(stderr, "pam_open_session: %s\n", pam_strerror(pamh, ret));
+ } else if ( strncmp(action, "clos", 4)== 0 ) {
+ fprintf(stdout, "testing pam_close_session\n");
+ ret = pam_close_session(pamh, 0);
+ fprintf(stderr, "pam_close_session: %s\n", pam_strerror(pamh, ret));
+ } else {
+ fprintf(stderr, "unknown action\n");
+ }
+
+ pam_end(pamh, ret);
+
+ return 0;
+}
diff --git a/src/sss_client/passwd.c b/src/sss_client/passwd.c
new file mode 100644
index 00000000..0d70b684
--- /dev/null
+++ b/src/sss_client/passwd.c
@@ -0,0 +1,373 @@
+/*
+ * System Security Services Daemon. NSS client interface
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* PASSWD database NSS interface */
+
+#include <nss.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include "sss_cli.h"
+
+static struct sss_nss_getpwent_data {
+ size_t len;
+ size_t ptr;
+ uint8_t *data;
+} sss_nss_getpwent_data;
+
+static void sss_nss_getpwent_data_clean(void) {
+
+ if (sss_nss_getpwent_data.data != NULL) {
+ free(sss_nss_getpwent_data.data);
+ sss_nss_getpwent_data.data = NULL;
+ }
+ sss_nss_getpwent_data.len = 0;
+ sss_nss_getpwent_data.ptr = 0;
+}
+
+/* GETPWNAM Request:
+ *
+ * 0-X: string with name
+ *
+ * GERTPWUID Request:
+ *
+ * 0-3: 32bit number with uid
+ *
+ * Replies:
+ *
+ * 0-3: 32bit unsigned number of results
+ * 4-7: 32bit unsigned (reserved/padding)
+ * For each result:
+ * 0-3: 32bit number uid
+ * 4-7: 32bit number gid
+ * 8-X: sequence of 5, 0 terminated, strings (name, passwd, gecos, dir, shell)
+ */
+
+struct sss_nss_pw_rep {
+ struct passwd *result;
+ char *buffer;
+ size_t buflen;
+};
+
+static int sss_nss_getpw_readrep(struct sss_nss_pw_rep *pr,
+ uint8_t *buf, size_t *len)
+{
+ size_t i, slen, dlen;
+ char *sbuf;
+
+ if (*len < 13) { /* not enough space for data, bad packet */
+ return EBADMSG;
+ }
+
+ pr->result->pw_uid = ((uint32_t *)buf)[0];
+ pr->result->pw_gid = ((uint32_t *)buf)[1];
+
+ sbuf = (char *)&buf[8];
+ slen = *len - 8;
+ dlen = pr->buflen;
+
+ i = 0;
+ pr->result->pw_name = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ pr->result->pw_passwd = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ pr->result->pw_gecos = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ pr->result->pw_dir = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ i++;
+ dlen--;
+
+ pr->result->pw_shell = &(pr->buffer[i]);
+ while (slen > i && dlen > 0) {
+ pr->buffer[i] = sbuf[i];
+ if (pr->buffer[i] == '\0') break;
+ i++;
+ dlen--;
+ }
+ if (slen <= i) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (dlen <= 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+
+ *len = slen -i -1;
+
+ return 0;
+}
+
+enum nss_status _nss_sss_getpwnam_r(const char *name, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_pw_rep pwrep;
+ uint8_t *repbuf;
+ size_t replen, len;
+ enum nss_status nret;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ rd.len = strlen(name) + 1;
+ rd.data = name;
+
+ nret = sss_nss_make_request(SSS_NSS_GETPWNAM, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ pwrep.result = result;
+ pwrep.buffer = buffer;
+ pwrep.buflen = buflen;
+
+ /* no results if not found */
+ if (((uint32_t *)repbuf)[0] == 0) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* only 1 result is accepted for this function */
+ if (((uint32_t *)repbuf)[0] != 1) {
+ *errnop = EBADMSG;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ len = replen - 8;
+ ret = sss_nss_getpw_readrep(&pwrep, repbuf+8, &len);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_getpwuid_r(uid_t uid, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_pw_rep pwrep;
+ uint8_t *repbuf;
+ size_t replen, len;
+ enum nss_status nret;
+ uint32_t user_uid;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ user_uid = uid;
+ rd.len = sizeof(uint32_t);
+ rd.data = &user_uid;
+
+ nret = sss_nss_make_request(SSS_NSS_GETPWUID, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ pwrep.result = result;
+ pwrep.buffer = buffer;
+ pwrep.buflen = buflen;
+
+ /* no results if not found */
+ if (((uint32_t *)repbuf)[0] == 0) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ /* only 1 result is accepted for this function */
+ if (((uint32_t *)repbuf)[0] != 1) {
+ *errnop = EBADMSG;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ len = replen - 8;
+ ret = sss_nss_getpw_readrep(&pwrep, repbuf+8, &len);
+ free(repbuf);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_setpwent(void)
+{
+ enum nss_status nret;
+ int errnop;
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_getpwent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_SETPWENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ return nret;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_sss_getpwent_r(struct passwd *result,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ struct sss_cli_req_data rd;
+ struct sss_nss_pw_rep pwrep;
+ uint8_t *repbuf;
+ size_t replen;
+ enum nss_status nret;
+ uint32_t num_entries;
+ int ret;
+
+ /* Caught once glibc passing in buffer == 0x0 */
+ if (!buffer || !buflen) return ERANGE;
+
+ /* if there are leftovers return the next one */
+ if (sss_nss_getpwent_data.data != NULL &&
+ sss_nss_getpwent_data.ptr < sss_nss_getpwent_data.len) {
+
+ repbuf = sss_nss_getpwent_data.data + sss_nss_getpwent_data.ptr;
+ replen = sss_nss_getpwent_data.len - sss_nss_getpwent_data.ptr;
+
+ pwrep.result = result;
+ pwrep.buffer = buffer;
+ pwrep.buflen = buflen;
+
+ ret = sss_nss_getpw_readrep(&pwrep, repbuf, &replen);
+ if (ret) {
+ *errnop = ret;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ /* advance buffer pointer */
+ sss_nss_getpwent_data.ptr = sss_nss_getpwent_data.len - replen;
+
+ return NSS_STATUS_SUCCESS;
+ }
+
+ /* release memory if any */
+ sss_nss_getpwent_data_clean();
+
+ /* retrieve no more than SSS_NSS_MAX_ENTRIES at a time */
+ num_entries = SSS_NSS_MAX_ENTRIES;
+ rd.len = sizeof(uint32_t);
+ rd.data = &num_entries;
+
+ nret = sss_nss_make_request(SSS_NSS_GETPWENT, &rd,
+ &repbuf, &replen, errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ return nret;
+ }
+
+ /* no results if not found */
+ if ((((uint32_t *)repbuf)[0] == 0) || (replen - 8 == 0)) {
+ free(repbuf);
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ sss_nss_getpwent_data.data = repbuf;
+ sss_nss_getpwent_data.len = replen;
+ sss_nss_getpwent_data.ptr = 8; /* skip metadata fields */
+
+ /* call again ourselves, this will return the first result */
+ return _nss_sss_getpwent_r(result, buffer, buflen, errnop);
+}
+
+enum nss_status _nss_sss_endpwent(void)
+{
+ enum nss_status nret;
+ int errnop;
+
+ /* make sure we do not have leftovers, and release memory */
+ sss_nss_getpwent_data_clean();
+
+ nret = sss_nss_make_request(SSS_NSS_ENDPWENT,
+ NULL, NULL, NULL, &errnop);
+ if (nret != NSS_STATUS_SUCCESS) {
+ errno = errnop;
+ return nret;
+ }
+
+ return NSS_STATUS_SUCCESS;
+}
diff --git a/src/sss_client/protos.h b/src/sss_client/protos.h
new file mode 100644
index 00000000..adb0b7bb
--- /dev/null
+++ b/src/sss_client/protos.h
@@ -0,0 +1,137 @@
+/*
+ * System Security Services Daemon. NSS Interface
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * You can used this header file in any way you see fit provided copyright
+ * notices are preserved.
+ *
+ */
+
+#if 0
+/* SHADOW database NSS interface */
+enum nss_status _nss_sss_getspnam_r(const char *name, struct spwd *result,
+ char *buffer, size_t buflen, int *errnop);
+enum nss_status _nss_sss_setspent(void);
+enum nss_status _nss_sss_getspent_r(struct spwd *result,
+ char *buffer, size_t buflen, int *errnop);
+enum nss_status _nss_sss_endspent(void);
+
+
+/* HOSTS database NSS interface */
+enum nss_status _nss_sss_gethostbyname_r(const char *name,
+ struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+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);
+enum nss_status _nss_sss_gethostbyaddr_r(const void *addr, socklen_t len,
+ int af, struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+enum nss_status _nss_sss_sethostent(void);
+enum nss_status _nss_sss_gethostent_r(struct hostent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+enum nss_status _nss_sss_endhostent(void);
+
+/* NETGROUP database NSS interface */
+enum nss_status _nss_sss_setnetgrent(const char *netgroup,
+ struct __netgrent *result);
+enum nss_status _nss_sss_getnetgrent_r(struct __netgrent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_ldap_endnetgrent(void);
+/* too bad innetgr is currently implemented as an iteration over
+ * {set|get|end}netgroup ... */
+
+/* NETWORKS database NSS interface */
+enum nss_status _nss_sss_getnetbyname_r(const char *name,
+ struct netent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+enum nss_status _nss_sss_getnetbyaddr_r(uint32_t addr, int af,
+ struct netent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+enum nss_status _nss_sss_setnetent(void);
+enum nss_status _nss_sss_getnetent_r(struct netent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop);
+enum nss_status _nss_sss_endnetent(void);
+
+
+/* PROTOCOLS database NSS interface */
+enum nss_status _nss_sss_getprotobyname_r(const char *name,
+ struct protoent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_getprotobynumber_r(int number,
+ struct protoent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_setprotoent(void);
+enum nss_status _nss_sss_getprotoent_r(struct protoent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_endprotoent(void);
+
+/* SERVICES database NSS interface */
+enum nss_status _nss_sss_getservbyname_r(const char *name,
+ const char *protocol,
+ struct servent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_getservbyport_r(int port, const char *protocol,
+ struct servent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_setservent(void);
+enum nss_status _nss_sss_getservent_r(struct servent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_endservent(void);
+
+/* ALIASES database NSS interface */
+enum nss_status _nss_sss_getaliasbyname_r(const char *name,
+ struct aliasent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_setaliasent(void);
+enum nss_status _nss_sss_getaliasent_r(struct aliasent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_endaliasent(void);
+
+/* ETHERS database NSS interface */
+enum nss_status _nss_sss_gethostton_r(const char *name,
+ struct etherent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_getntohost_r(const struct ether_addr *addr,
+ struct etherent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_setetherent(void);
+enum nss_status _nss_sss_getetherent_r(struct etherent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_endetherent(void);
+
+/* RPC database NSS interface */
+enum nss_status _nss_sss_getrpcbyname_r(const char *name,
+ struct rpcent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_getrpcbynumber_r(int number, struct rpcent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_setrpcent(void);
+enum nss_status _nss_sss_getrpcent_r(struct rpcent *result,
+ char *buffer, size_t buflen,
+ int *errnop);
+enum nss_status _nss_sss_endrpcent(void);
+
+#endif
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
new file mode 100644
index 00000000..7e9a81ff
--- /dev/null
+++ b/src/sss_client/sss_cli.h
@@ -0,0 +1,220 @@
+/*
+ * System Security Services Daemon. Client Interface for NSS and PAM.
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * You can used this header file in any way you see fit provided copyright
+ * notices are preserved.
+ *
+ */
+
+#ifndef _SSSCLI_H
+#define _SSSCLI_H
+
+#include <nss.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifndef HAVE_ERRNO_T
+#define HAVE_ERRNO_T
+typedef int errno_t;
+#endif
+
+#define SSS_NSS_PROTOCOL_VERSION 1
+#define SSS_PAM_PROTOCOL_VERSION 3
+
+enum sss_cli_command {
+/* null */
+ SSS_CLI_NULL = 0x0000,
+
+/* version */
+ SSS_GET_VERSION = 0x0001,
+
+/* passwd */
+
+ SSS_NSS_GETPWNAM = 0x0011,
+ SSS_NSS_GETPWUID = 0x0012,
+ SSS_NSS_SETPWENT = 0x0013,
+ SSS_NSS_GETPWENT = 0x0014,
+ SSS_NSS_ENDPWENT = 0x0015,
+
+/* group */
+
+ SSS_NSS_GETGRNAM = 0x0021,
+ SSS_NSS_GETGRGID = 0x0022,
+ SSS_NSS_SETGRENT = 0x0023,
+ SSS_NSS_GETGRENT = 0x0024,
+ SSS_NSS_ENDGRENT = 0x0025,
+ SSS_NSS_INITGR = 0x0026,
+
+#if 0
+/* aliases */
+
+ SSS_NSS_GETALIASBYNAME = 0x0031,
+ SSS_NSS_GETALIASBYPORT = 0x0032,
+ SSS_NSS_SETALIASENT = 0x0033,
+ SSS_NSS_GETALIASENT = 0x0034,
+ SSS_NSS_ENDALIASENT = 0x0035,
+
+/* ethers */
+
+ SSS_NSS_GETHOSTTON = 0x0041,
+ SSS_NSS_GETNTOHOST = 0x0042,
+ SSS_NSS_SETETHERENT = 0x0043,
+ SSS_NSS_GETETHERENT = 0x0044,
+ SSS_NSS_ENDETHERENT = 0x0045,
+
+/* hosts */
+
+ SSS_NSS_GETHOSTBYNAME = 0x0051,
+ SSS_NSS_GETHOSTBYNAME2 = 0x0052,
+ SSS_NSS_GETHOSTBYADDR = 0x0053,
+ SSS_NSS_SETHOSTENT = 0x0054,
+ SSS_NSS_GETHOSTENT = 0x0055,
+ SSS_NSS_ENDHOSTENT = 0x0056,
+
+/* netgroup */
+
+ SSS_NSS_SETNETGRENT = 0x0061,
+ SSS_NSS_GETNETGRENT = 0x0062,
+ SSS_NSS_ENDNETGRENT = 0x0063,
+ /* SSS_NSS_INNETGR = 0x0064, */
+
+/* networks */
+
+ SSS_NSS_GETNETBYNAME = 0x0071,
+ SSS_NSS_GETNETBYADDR = 0x0072,
+ SSS_NSS_SETNETENT = 0x0073,
+ SSS_NSS_GETNETENT = 0x0074,
+ SSS_NSS_ENDNETENT = 0x0075,
+
+/* protocols */
+
+ SSS_NSS_GETPROTOBYNAME = 0x0081,
+ SSS_NSS_GETPROTOBYNUM = 0x0082,
+ SSS_NSS_SETPROTOENT = 0x0083,
+ SSS_NSS_GETPROTOENT = 0x0084,
+ SSS_NSS_ENDPROTOENT = 0x0085,
+
+/* rpc */
+
+ SSS_NSS_GETRPCBYNAME = 0x0091,
+ SSS_NSS_GETRPCBYNUM = 0x0092,
+ SSS_NSS_SETRPCENT = 0x0093,
+ SSS_NSS_GETRPCENT = 0x0094,
+ SSS_NSS_ENDRPCENT = 0x0095,
+
+/* services */
+
+ SSS_NSS_GETSERVBYNAME = 0x00A1,
+ SSS_NSS_GETSERVBYPORT = 0x00A2,
+ SSS_NSS_SETSERVENT = 0x00A3,
+ SSS_NSS_GETSERVENT = 0x00A4,
+ SSS_NSS_ENDSERVENT = 0x00A5,
+
+/* shadow */
+
+ SSS_NSS_GETSPNAM = 0x00B1,
+ SSS_NSS_GETSPUID = 0x00B2,
+ SSS_NSS_SETSPENT = 0x00B3,
+ SSS_NSS_GETSPENT = 0x00B4,
+ SSS_NSS_ENDSPENT = 0x00B5,
+#endif
+
+/* PAM related calls */
+ SSS_PAM_AUTHENTICATE = 0x00F1,
+ SSS_PAM_SETCRED = 0x00F2,
+ SSS_PAM_ACCT_MGMT = 0x00F3,
+ SSS_PAM_OPEN_SESSION = 0x00F4,
+ SSS_PAM_CLOSE_SESSION = 0x00F5,
+ SSS_PAM_CHAUTHTOK = 0x00F6,
+ SSS_PAM_CHAUTHTOK_PRELIM = 0x00F7,
+
+};
+
+enum sss_authtok_type {
+ SSS_AUTHTOK_TYPE_EMPTY = 0x0000,
+ SSS_AUTHTOK_TYPE_PASSWORD = 0x0001,
+};
+
+#define SSS_START_OF_PAM_REQUEST 0x4d415049
+#define SSS_END_OF_PAM_REQUEST 0x4950414d
+
+enum pam_item_type {
+ SSS_PAM_ITEM_EMPTY = 0x0000,
+ SSS_PAM_ITEM_USER,
+ SSS_PAM_ITEM_SERVICE,
+ SSS_PAM_ITEM_TTY,
+ SSS_PAM_ITEM_RUSER,
+ SSS_PAM_ITEM_RHOST,
+ SSS_PAM_ITEM_AUTHTOK,
+ SSS_PAM_ITEM_NEWAUTHTOK,
+ SSS_PAM_ITEM_CLI_LOCALE,
+ SSS_PAM_ITEM_CLI_PID,
+};
+
+#define SSS_NSS_MAX_ENTRIES 256
+#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4)
+struct sss_cli_req_data {
+ size_t len;
+ const void *data;
+};
+
+/* this is in milliseconds, wait up to 300 seconds */
+#define SSS_CLI_SOCKET_TIMEOUT 300000
+
+enum sss_status {
+ SSS_STATUS_UNAVAIL,
+ SSS_STATUS_SUCCESS
+};
+
+enum response_type {
+ SSS_PAM_SYSTEM_INFO = 0x01,
+ SSS_PAM_DOMAIN_NAME,
+ SSS_PAM_ENV_ITEM, /* only pam environment */
+ SSS_ENV_ITEM, /* only user environment */
+ SSS_ALL_ENV_ITEM, /* pam and user environment */
+ SSS_PAM_USER_INFO
+};
+
+enum user_info_type {
+ SSS_PAM_USER_INFO_OFFLINE_AUTH = 0x01,
+ SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED,
+ SSS_PAM_USER_INFO_OFFLINE_CHPASS,
+ SSS_PAM_USER_INFO_CHPASS_ERROR
+};
+
+enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop);
+
+int sss_pam_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop);
+
+#endif /* _SSSCLI_H */
+
+#if 0
+
+/* GETSPNAM Request:
+ *
+ * 0-X: string with name
+ *
+ * Replies:
+ *
+ * 0-3: 32bit unsigned number of results
+ * 4-7: 32bit unsigned (reserved/padding)
+ * For each result:
+ * 0-7: 64bit unsigned with Date of last change
+ * 8-15: 64bit unsigned with Min #days between changes
+ * 16-23: 64bit unsigned with Max #days between changes
+ * 24-31: 64bit unsigned with #days before pwd expires
+ * 32-39: 64bit unsigned with #days after pwd expires until account is disabled
+ * 40-47: 64bit unsigned with expiration date in days since 1970-01-01
+ * 48-55: 64bit unsigned (flags/reserved)
+ * 56-X: sequence of 2, 0 terminated, strings (name, pwd) 64bit padded
+ */
+#endif
+
diff --git a/src/sss_client/sss_nss.exports b/src/sss_client/sss_nss.exports
new file mode 100644
index 00000000..bcc6b10e
--- /dev/null
+++ b/src/sss_client/sss_nss.exports
@@ -0,0 +1,73 @@
+EXPORTED {
+
+ # public functions
+ global:
+
+ _nss_sss_getpwnam_r;
+ _nss_sss_getpwuid_r;
+ _nss_sss_setpwent;
+ _nss_sss_getpwent_r;
+ _nss_sss_endpwent;
+
+ _nss_sss_getgrnam_r;
+ _nss_sss_getgrgid_r;
+ _nss_sss_setgrent;
+ _nss_sss_getgrent_r;
+ _nss_sss_endgrent;
+ _nss_sss_initgroups_dyn;
+
+ #_nss_sss_getaliasbyname_r;
+ #_nss_sss_setaliasent;
+ #_nss_sss_getaliasent_r;
+ #_nss_sss_endaliasent;
+
+ #_nss_sss_gethostton_r;
+ #_nss_sss_getntohost_r;
+ #_nss_sss_setetherent;
+ #_nss_sss_getetherent_r;
+ #_nss_sss_endetherent;
+
+ #_nss_sss_gethostbyname_r;
+ #_nss_sss_gethostbyname2_r;
+ #_nss_sss_gethostbyaddr_r;
+ #_nss_sss_sethostent;
+ #_nss_sss_gethostent_r;
+ #_nss_sss_endhostent;
+
+ #_nss_sss_setnetgrent;
+ #_nss_sss_getnetgrent_r;
+ #_nss_sss_endnetgrent;
+
+ #_nss_sss_getnetbyname_r;
+ #_nss_sss_getnetbyaddr_r;
+ #_nss_sss_setnetent;
+ #_nss_sss_getnetent_r;
+ #_nss_sss_endnetent;
+
+ #_nss_sss_getprotobyname_r;
+ #_nss_sss_getprotobynumber_r;
+ #_nss_sss_setprotoent;
+ #_nss_sss_getprotoent_r;
+ #_nss_sss_endprotoent;
+
+ #_nss_sss_getrpcbyname_r;
+ #_nss_sss_getrpcbynumber_r;
+ #_nss_sss_setrpcent;
+ #_nss_sss_getrpcent_r;
+ #_nss_sss_endrpcent;
+
+ #_nss_sss_getservbyname_r;
+ #_nss_sss_getservbyport_r;
+ #_nss_sss_setservent;
+ #_nss_sss_getservent_r;
+ #_nss_sss_endservent;
+
+ #_nss_sss_getspnam_r;
+ #_nss_sss_setspent;
+ #_nss_sss_getspent_r;
+ #_nss_sss_endspent;
+
+ # everything else is local
+ local:
+ *;
+};
diff --git a/src/sss_client/sss_pam.exports b/src/sss_client/sss_pam.exports
new file mode 100644
index 00000000..9afa106b
--- /dev/null
+++ b/src/sss_client/sss_pam.exports
@@ -0,0 +1,4 @@
+{
+ global:
+ *;
+};
diff --git a/src/sss_client/sss_pam_macros.h b/src/sss_client/sss_pam_macros.h
new file mode 100644
index 00000000..5277acd0
--- /dev/null
+++ b/src/sss_client/sss_pam_macros.h
@@ -0,0 +1,30 @@
+/*
+ * System Security Services Daemon. Client Interface for NSS and PAM.
+ *
+ * Copyright (C) Stephen Gallagher 2009
+ *
+ * You can used this header file in any way you see fit provided copyright
+ * notices are preserved.
+ *
+ */
+
+#ifndef _SSS_PAM_MACROS_H
+#define _SSS_PAM_MACROS_H
+
+/* Older versions of the pam development headers do not include the
+ * _pam_overwrite_n(n,x) macro. This implementation is copied from
+ * the Fedora 11 _pam_macros.h.
+ */
+#include <security/_pam_macros.h>
+#ifndef _pam_overwrite_n
+#define _pam_overwrite_n(x,n) \
+do { \
+ register char *__xx__; \
+ register unsigned int __i__ = 0; \
+ if ((__xx__=(x))) \
+ for (;__i__<n; __i__++) \
+ __xx__[__i__] = 0; \
+} while (0)
+#endif /* _pam_overwrite_n */
+
+#endif /* _SSS_PAM_MACROS_H */
diff --git a/src/sysv/SUSE/sssd b/src/sysv/SUSE/sssd
new file mode 100644
index 00000000..34fd837b
--- /dev/null
+++ b/src/sysv/SUSE/sssd
@@ -0,0 +1,78 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: sssd
+# Required-Start: $remote_fs $time
+# Should-Start: $syslog
+# Should-Stop: $syslog
+# Required-Stop: $remote_fs
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 4 6
+# Short-Description: System Security Services Daemon
+# Description: Provides a set of daemons to manage access to remote directories
+# and authentication mechanisms. It provides an NSS and PAM
+# interface toward the system and a pluggable backend system to
+# connect to multiple different account sources. It is also the
+# basis to provide client auditing and policy services for projects
+# like FreeIPA.
+### END INIT INFO
+
+RETVAL=0
+prog="sssd"
+
+# Source function library.
+. /etc/rc.status
+rc_reset
+
+SSSD=/usr/sbin/sssd
+PID_FILE=/var/run/sssd.pid
+
+case "$1" in
+ start)
+ echo -n "Starting $prog "
+ /sbin/startproc $SSSD -D 2>/dev/null
+ rc_status -v
+ ;;
+
+ stop)
+ echo -n "Shutting down $prog "
+ /sbin/killproc -p $PID_FILE $SSSD -TERM
+ rc_status -v
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ rc_status
+ ;;
+
+ reload)
+ echo -n "Reload service $prog "
+ killproc $SSSD -HUP
+ rc_status -v
+ ;;
+
+ force-reload)
+ $0 reload
+ ;;
+
+ status)
+ echo -n "Checking for service $prog"
+ /sbin/checkproc $SSSD
+ rc_status -v
+ ;;
+
+ condrestart|try-restart)
+ $0 status
+ if test $? = 0; then
+ $0 restart
+ else
+ rc_reset # Not running is not a failure.
+ fi
+ rc_status
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
+ exit 1
+esac
+rc_exit
+
diff --git a/src/sysv/sssd b/src/sysv/sssd
new file mode 100644
index 00000000..47804371
--- /dev/null
+++ b/src/sysv/sssd
@@ -0,0 +1,121 @@
+#!/bin/sh
+#
+#
+# chkconfig: - 30 80
+# description: Provides a set of daemons to manage access to remote directories
+# and authentication mechanisms. It provides an NSS and PAM
+# interface toward the system and a pluggable backend system to
+# connect to multiple different account sources. It is also the
+# basis to provide client auditing and policy services for projects
+# like FreeIPA.
+#
+### BEGIN INIT INFO
+# Provides: sssd
+# Required-Start: $remote_fs $time
+# Should-Start: $syslog
+# Should-Stop: $null
+# Required-Stop: $null
+# Default-Stop: 0 1 6
+# Short-Description: System Security Services Daemon
+# Description: Provides a set of daemons to manage access to remote directories
+# and authentication mechanisms. It provides an NSS and PAM
+# interface toward the system and a pluggable backend system to
+# connect to multiple different account sources. It is also the
+# basis to provide client auditing and policy services for projects
+# like FreeIPA.
+### END INIT INFO
+
+RETVAL=0
+prog="sssd"
+
+# Source function library.
+. /etc/init.d/functions
+
+SSSD=/usr/sbin/sssd
+
+LOCK_FILE=/var/lock/subsys/sssd
+PID_FILE=/var/run/sssd.pid
+
+start() {
+ [ -x $SSSD ] || exit 5
+ echo -n $"Starting $prog: "
+ daemon $SSSD -f -D && success || failure
+ RETVAL=$?
+ echo
+ [ "$RETVAL" = 0 ] && touch $LOCK_FILE
+ return $RETVAL
+}
+
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc -p $PID_FILE $SSSD -TERM
+ RETVAL=$?
+ echo
+ [ "$RETVAL" = 0 ] && rm -f $LOCK_FILE
+ return $RETVAL
+}
+
+reload() {
+ echo -n $"Reloading $prog: "
+ killproc $SSSD -HUP
+ RETVAL=$?
+ echo
+ return $RETVAL
+}
+
+restart() {
+ stop
+ start
+}
+
+force_reload() {
+ restart
+}
+
+rh_status() {
+ # run checks to determine if the service is running or use generic status
+ status $prog
+}
+
+rh_status_q() {
+ rh_status >/dev/null 2>&1
+}
+
+case "$1" in
+ start)
+ rh_status_q && exit 0
+ $1
+ ;;
+
+ stop)
+ rh_status_q || exit 0
+ $1
+ ;;
+
+ restart)
+ $1
+ ;;
+
+ reload)
+ rh_status_q || exit 7
+ $1
+ ;;
+
+ force-reload)
+ force_reload
+ ;;
+
+ status)
+ rh_status
+ ;;
+
+ condrestart|try-restart)
+ rh_status_q || exit 0
+ restart
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
+ exit 2
+esac
+exit $?
+
diff --git a/src/tests/auth-tests.c b/src/tests/auth-tests.c
new file mode 100644
index 00000000..71215bcd
--- /dev/null
+++ b/src/tests/auth-tests.c
@@ -0,0 +1,343 @@
+/*
+ SSSD
+
+ Test for local authentication utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2010 Red Hat
+
+ 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 <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include <check.h>
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+
+#define TESTS_PATH "tests_auth"
+#define TEST_CONF_FILE "tests_conf.ldb"
+
+struct sysdb_test_ctx {
+ struct sysdb_ctx *sysdb;
+ struct confdb_ctx *confdb;
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+};
+
+static int setup_sysdb_tests(struct sysdb_test_ctx **ctx)
+{
+ struct sysdb_test_ctx *test_ctx;
+ char *conf_db;
+ int ret;
+
+ const char *val[2];
+ val[1] = NULL;
+
+ /* Create tests directory if it doesn't exist */
+ /* (relative to current dir) */
+ ret = mkdir(TESTS_PATH, 0775);
+ if (ret == -1 && errno != EEXIST) {
+ fail("Could not create %s directory", TESTS_PATH);
+ return EFAULT;
+ }
+
+ test_ctx = talloc_zero(NULL, struct sysdb_test_ctx);
+ if (test_ctx == NULL) {
+ fail("Could not allocate memory for test context");
+ return ENOMEM;
+ }
+
+ /* Create an event context
+ * It will not be used except in confdb_init and sysdb_init
+ */
+ test_ctx->ev = tevent_context_init(test_ctx);
+ if (test_ctx->ev == NULL) {
+ fail("Could not create event context");
+ talloc_free(test_ctx);
+ return EIO;
+ }
+
+ conf_db = talloc_asprintf(test_ctx, "%s/%s", TESTS_PATH, TEST_CONF_FILE);
+ if (conf_db == NULL) {
+ fail("Out of memory, aborting!");
+ talloc_free(test_ctx);
+ return ENOMEM;
+ }
+ DEBUG(3, ("CONFDB: %s\n", conf_db));
+
+ /* Connect to the conf db */
+ ret = confdb_init(test_ctx, &test_ctx->confdb, conf_db);
+ if (ret != EOK) {
+ fail("Could not initialize connection to the confdb");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "LOCAL";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/sssd", "domains", val);
+ if (ret != EOK) {
+ fail("Could not initialize domains placeholder");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "local";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "id_provider", val);
+ if (ret != EOK) {
+ fail("Could not initialize provider");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "TRUE";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "enumerate", val);
+ if (ret != EOK) {
+ fail("Could not initialize LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "TRUE";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "cache_credentials", val);
+ if (ret != EOK) {
+ fail("Could not initialize LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ ret = confdb_get_domain(test_ctx->confdb, "local", &test_ctx->domain);
+ if (ret != EOK) {
+ fail("Could not retrieve LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ ret = sysdb_domain_init(test_ctx, test_ctx->ev,
+ test_ctx->domain, TESTS_PATH, &test_ctx->sysdb);
+ if (ret != EOK) {
+ fail("Could not initialize connection to the sysdb (%d)", ret);
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ *ctx = test_ctx;
+ return EOK;
+}
+
+static void do_failed_login_test(uint32_t failed_login_attempts,
+ time_t last_failed_login,
+ int offline_failed_login_attempts,
+ int offline_failed_login_delay,
+ int expected_result,
+ int expected_counter,
+ time_t expected_delay)
+{
+ struct sysdb_test_ctx *test_ctx;
+ int ret;
+ const char *val[2];
+ val[1] = NULL;
+ struct ldb_message *ldb_msg;
+ uint32_t returned_failed_login_attempts;
+ time_t delayed_until;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ fail_unless(ret == EOK, "Could not set up the test");
+
+ val[0] = talloc_asprintf(test_ctx, "%u", offline_failed_login_attempts);
+ fail_unless(val[0] != NULL, "talloc_sprintf failed");
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/pam", CONFDB_PAM_FAILED_LOGIN_ATTEMPTS, val);
+ fail_unless(ret == EOK, "Could not set offline_failed_login_attempts");
+
+ val[0] = talloc_asprintf(test_ctx, "%u", offline_failed_login_delay);
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/pam", CONFDB_PAM_FAILED_LOGIN_DELAY, val);
+ fail_unless(ret == EOK, "Could not set offline_failed_login_delay");
+
+ ldb_msg = ldb_msg_new(test_ctx);
+ fail_unless(ldb_msg != NULL, "ldb_msg_new failed");
+
+ ret = ldb_msg_add_fmt(ldb_msg, SYSDB_FAILED_LOGIN_ATTEMPTS, "%u",
+ failed_login_attempts);
+ fail_unless(ret == EOK, "ldb_msg_add_string failed");
+
+ ret = ldb_msg_add_fmt(ldb_msg, SYSDB_LAST_FAILED_LOGIN, "%lld",
+ (long long) last_failed_login);
+ fail_unless(ret == EOK, "ldb_msg_add_string failed");
+
+ ret = check_failed_login_attempts(test_ctx, test_ctx->confdb, ldb_msg,
+ &returned_failed_login_attempts,
+ &delayed_until);
+ fail_unless(ret == expected_result,
+ "check_failed_login_attempts returned wrong error code, "
+ "expected [%d], got [%d]", expected_result, ret);
+
+ fail_unless(returned_failed_login_attempts == expected_counter,
+ "check_failed_login_attempts returned wrong number of failed "
+ "login attempts, expected [%d], got [%d]",
+ expected_counter, failed_login_attempts);
+
+ fail_unless(delayed_until == expected_delay,
+ "check_failed_login_attempts wrong delay, "
+ "expected [%d], got [%d]",
+ expected_delay, delayed_until);
+
+ talloc_free(test_ctx);
+}
+
+START_TEST(test_failed_login_attempts)
+{
+ time_t now;
+
+ /* if offline_failed_login_attempts == 0 a login is never denied */
+ do_failed_login_test(0, 0, 0, 5, EOK, 0, -1);
+ do_failed_login_test(0, time(NULL), 0, 5, EOK, 0, -1);
+ do_failed_login_test(2, 0, 0, 5, EOK, 2, -1);
+ do_failed_login_test(2, time(NULL), 0, 5, EOK, 2, -1);
+
+ do_failed_login_test(0, 0, 0, 0, EOK, 0, -1);
+ do_failed_login_test(0, time(NULL), 0, 0, EOK, 0, -1);
+ do_failed_login_test(2, 0, 0, 0, EOK, 2, -1);
+ do_failed_login_test(2, time(NULL), 0, 0, EOK, 2, -1);
+
+ /* if offline_failed_login_attempts != 0 and
+ * offline_failed_login_delay == 0 a login is denied if the number of
+ * failed attempts >= offline_failed_login_attempts */
+ do_failed_login_test(0, 0, 2, 0, EOK, 0, -1);
+ do_failed_login_test(0, time(NULL), 2, 0, EOK, 0, -1);
+ do_failed_login_test(2, 0, 2, 0, EACCES, 2, -1);
+ do_failed_login_test(2, time(NULL), 2, 0, EACCES, 2, -1);
+
+ /* if offline_failed_login_attempts != 0 and
+ * offline_failed_login_delay != 0 a login is denied only if the number of
+ * failed attempts >= offline_failed_login_attempts AND the last failed
+ * login attempt is not longer than offline_failed_login_delay ago */
+ do_failed_login_test(0, 0, 2, 5, EOK, 0, -1);
+ do_failed_login_test(0, time(NULL), 2, 5, EOK, 0, -1);
+ do_failed_login_test(2, 0, 2, 5, EOK, 0, -1);
+ now = time(NULL);
+ do_failed_login_test(2, now, 2, 5, EACCES, 2, (now + 5 * 60));
+
+}
+END_TEST
+
+Suite *auth_suite (void)
+{
+ Suite *s = suite_create ("auth");
+
+ TCase *tc_auth = tcase_create ("auth");
+
+ tcase_add_test (tc_auth, test_failed_login_attempts);
+ tcase_set_timeout(tc_auth, 60);
+
+ suite_add_tcase (s, tc_auth);
+
+ return s;
+}
+
+static int clean_db_dir(void)
+{
+ int ret;
+
+ ret = unlink(TESTS_PATH"/"TEST_CONF_FILE);
+ if (ret != EOK && errno != ENOENT) {
+ fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
+ errno, strerror(errno));
+ return ret;
+ }
+
+ ret = unlink(TESTS_PATH"/"LOCAL_SYSDB_FILE);
+ if (ret != EOK && errno != ENOENT) {
+ fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
+ errno, strerror(errno));
+ return ret;
+ }
+
+ ret = rmdir(TESTS_PATH);
+ if (ret != EOK && errno != ENOENT) {
+ fprintf(stderr, "Could not delete the test directory (%d) (%s)\n",
+ errno, strerror(errno));
+ return ret;
+ }
+
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int ret;
+ int opt;
+ int failure_count;
+ poptContext pc;
+ Suite *s = auth_suite ();
+ SRunner *sr = srunner_create (s);
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ ret = clean_db_dir();
+ if (ret != EOK) {
+ fprintf(stderr, "Could not delete the db directory (%d) (%s)\n",
+ errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ if (failure_count == 0) {
+ ret = clean_db_dir();
+ if (ret != EOK) {
+ fprintf(stderr, "Could not delete the db directory (%d) (%s)\n",
+ errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+}
diff --git a/src/tests/check_and_open-tests.c b/src/tests/check_and_open-tests.c
new file mode 100644
index 00000000..b0d638b5
--- /dev/null
+++ b/src/tests/check_and_open-tests.c
@@ -0,0 +1,218 @@
+/*
+ SSSD
+
+ Utilities tests check_and_open
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <stdlib.h>
+#include <check.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "util/util.h"
+
+#define SUFFIX ".symlink"
+
+#define FILENAME_TEMPLATE "check_and_open-tests-XXXXXX"
+char *filename;
+uid_t uid;
+gid_t gid;
+mode_t mode;
+int fd;
+
+void setup_check_and_open(void)
+{
+ int ret;
+
+ filename = strdup(FILENAME_TEMPLATE);
+ fail_unless(filename != NULL, "strdup failed");
+ ret = mkstemp(filename);
+ fail_unless(ret != -1, "mkstemp failed [%d][%s]", errno, strerror(errno));
+ close(ret);
+
+ uid = getuid();
+ gid = getgid();
+ mode = (S_IRUSR | S_IWUSR);
+ fd = -1;
+}
+
+void teardown_check_and_open(void)
+{
+ int ret;
+
+ if (fd != -1) {
+ ret = close(fd);
+ fail_unless(ret == 0, "close failed [%d][%s]", errno, strerror(errno));
+ }
+
+ fail_unless(filename != NULL, "unknown filename");
+ ret = unlink(filename);
+ free(filename);
+ fail_unless(ret == 0, "unlink failed [%d][%s]", errno, strerror(errno));
+}
+
+START_TEST(test_wrong_filename)
+{
+ int ret;
+
+ ret = check_and_open_readonly("/bla/bla/bla", &fd, uid, gid, mode);
+ fail_unless(ret == ENOENT,
+ "check_and_open_readonly succeeded on non-existing file");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_symlink)
+{
+ int ret;
+ char *newpath;
+ size_t newpath_length;
+
+ newpath_length = strlen(filename) + strlen(SUFFIX) + 1;
+ newpath = malloc((newpath_length) * sizeof(char));
+ fail_unless(newpath != NULL, "malloc failed");
+
+ ret = snprintf(newpath, newpath_length, "%s%s", filename, SUFFIX);
+ fail_unless(ret == newpath_length - 1,
+ "snprintf failed: expected [%d] got [%d]", newpath_length -1,
+ ret);
+
+ ret = symlink(filename, newpath);
+ fail_unless(ret == 0, "symlink failed [%d][%s]", ret, strerror(ret));
+
+ ret = check_and_open_readonly(newpath, &fd, uid, gid, mode);
+ unlink(newpath);
+ fail_unless(ret == EINVAL,
+ "check_and_open_readonly succeeded on symlink");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_not_regular_file)
+{
+ int ret;
+
+ ret = check_and_open_readonly("/dev/null", &fd, uid, gid, mode);
+ fail_unless(ret == EINVAL,
+ "check_and_open_readonly succeeded on non-regular file");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_wrong_uid)
+{
+ int ret;
+
+ ret = check_and_open_readonly(filename, &fd, uid+1, gid, mode);
+ fail_unless(ret == EINVAL,
+ "check_and_open_readonly succeeded with wrong uid");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_wrong_gid)
+{
+ int ret;
+
+ ret = check_and_open_readonly(filename, &fd, uid, gid+1, mode);
+ fail_unless(ret == EINVAL,
+ "check_and_open_readonly succeeded with wrong gid");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_wrong_permission)
+{
+ int ret;
+
+ ret = check_and_open_readonly(filename, &fd, uid, gid, (mode|S_IWOTH));
+ fail_unless(ret == EINVAL,
+ "check_and_open_readonly succeeded with wrong mode");
+ fail_unless(fd == -1, "check_and_open_readonly file descriptor not -1");
+}
+END_TEST
+
+START_TEST(test_ok)
+{
+ int ret;
+
+ ret = check_and_open_readonly(filename, &fd, uid, gid, mode);
+ fail_unless(ret == EOK,
+ "check_and_open_readonly failed");
+ fail_unless(fd >= 0,
+ "check_and_open_readonly returned illegal file descriptor");
+}
+END_TEST
+
+START_TEST(test_write)
+{
+ int ret;
+ ssize_t size;
+ errno_t my_errno;
+
+ ret = check_and_open_readonly(filename, &fd, uid, gid, mode);
+ fail_unless(ret == EOK,
+ "check_and_open_readonly failed");
+ fail_unless(fd >= 0,
+ "check_and_open_readonly returned illegal file descriptor");
+
+ size = write(fd, "abc", 3);
+ my_errno = errno;
+ fail_unless(size == -1, "check_and_open_readonly file is not readonly");
+ fail_unless(my_errno == EBADF,
+ "write failed for other reason than readonly");
+}
+END_TEST
+
+Suite *check_and_open_suite (void)
+{
+ Suite *s = suite_create ("check_and_open");
+
+ TCase *tc_check_and_open_readonly = tcase_create ("check_and_open_readonly");
+ tcase_add_checked_fixture (tc_check_and_open_readonly,
+ setup_check_and_open,
+ teardown_check_and_open);
+ tcase_add_test (tc_check_and_open_readonly, test_wrong_filename);
+ tcase_add_test (tc_check_and_open_readonly, test_not_regular_file);
+ tcase_add_test (tc_check_and_open_readonly, test_symlink);
+ tcase_add_test (tc_check_and_open_readonly, test_wrong_uid);
+ tcase_add_test (tc_check_and_open_readonly, test_wrong_gid);
+ tcase_add_test (tc_check_and_open_readonly, test_wrong_permission);
+ tcase_add_test (tc_check_and_open_readonly, test_ok);
+ tcase_add_test (tc_check_and_open_readonly, test_write);
+ suite_add_tcase (s, tc_check_and_open_readonly);
+
+ return s;
+}
+
+int main(void)
+{
+ int number_failed;
+ Suite *s = check_and_open_suite ();
+ SRunner *sr = srunner_create (s);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/src/tests/common.c b/src/tests/common.c
new file mode 100644
index 00000000..50dc61b1
--- /dev/null
+++ b/src/tests/common.c
@@ -0,0 +1,109 @@
+/*
+ SSSD
+
+ Common utilities for check-based tests using talloc.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <check.h>
+#include <stdio.h>
+
+#include "tests/common.h"
+#include "util/dlinklist.h"
+#include "util/util.h"
+
+TALLOC_CTX *global_talloc_context = NULL;
+
+struct size_snapshot {
+ struct size_snapshot *prev;
+ struct size_snapshot *next;
+
+ TALLOC_CTX *ctx;
+ size_t bytes_allocated;
+};
+
+static struct size_snapshot *snapshot_stack;
+
+void
+_check_leaks(TALLOC_CTX *ctx, size_t bytes, const char *location)
+{
+ size_t bytes_allocated;
+
+ bytes_allocated = talloc_total_size(ctx);
+ if (bytes_allocated != bytes) {
+ fprintf(stderr, "Leak report for %s:\n", location);
+ talloc_report_full(ctx, stderr);
+ fail("%s: memory leaks detected, %d bytes still allocated",
+ location, bytes_allocated - bytes);
+ }
+}
+
+void
+check_leaks_push(TALLOC_CTX *ctx)
+{
+ struct size_snapshot *snapshot;
+
+ snapshot = talloc(NULL, struct size_snapshot);
+ snapshot->ctx = ctx;
+ snapshot->bytes_allocated = talloc_total_size(ctx);
+ DLIST_ADD(snapshot_stack, snapshot);
+}
+
+void
+_check_leaks_pop(TALLOC_CTX *ctx, const char *location)
+{
+ struct size_snapshot *snapshot;
+ TALLOC_CTX *old_ctx;
+ size_t bytes_allocated;
+
+ if (snapshot_stack == NULL) {
+ fail("%s: trying to pop an empty stack");
+ }
+
+ snapshot = snapshot_stack;
+ DLIST_REMOVE(snapshot_stack, snapshot);
+
+ old_ctx = snapshot->ctx;
+ bytes_allocated = snapshot->bytes_allocated;
+
+ fail_if(old_ctx != ctx, "Bad push/pop order");
+
+ talloc_zfree(snapshot);
+ _check_leaks(old_ctx, bytes_allocated, location);
+}
+
+void
+leak_check_setup(void)
+{
+ talloc_enable_null_tracking();
+ global_talloc_context = talloc_new(NULL);
+ fail_unless(global_talloc_context != NULL, "talloc_new failed");
+ check_leaks_push(global_talloc_context);
+}
+
+void
+leak_check_teardown(void)
+{
+ check_leaks_pop(global_talloc_context);
+ if (snapshot_stack != NULL) {
+ fail("Exiting with a non-empty stack");
+ }
+ check_leaks(global_talloc_context, 0);
+}
diff --git a/src/tests/common.h b/src/tests/common.h
new file mode 100644
index 00000000..0e954d7d
--- /dev/null
+++ b/src/tests/common.h
@@ -0,0 +1,21 @@
+#ifndef __TESTS_COMMON_H__
+#define __TESTS_COMMON_H__
+
+#include <talloc.h>
+
+extern TALLOC_CTX *global_talloc_context;
+
+#define check_leaks(ctx, bytes) _check_leaks((ctx), (bytes), __location__)
+void _check_leaks(TALLOC_CTX *ctx,
+ size_t bytes,
+ const char *location);
+
+void check_leaks_push(TALLOC_CTX *ctx);
+
+#define check_leaks_pop(ctx) _check_leaks_pop((ctx), __location__)
+void _check_leaks_pop(TALLOC_CTX *ctx, const char *location);
+
+void leak_check_setup(void);
+void leak_check_teardown(void);
+
+#endif /* !__TESTS_COMMON_H__ */
diff --git a/src/tests/fail_over-tests.c b/src/tests/fail_over-tests.c
new file mode 100644
index 00000000..e794f03b
--- /dev/null
+++ b/src/tests/fail_over-tests.c
@@ -0,0 +1,304 @@
+/*
+ SSSD
+
+ Fail over tests.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <arpa/inet.h>
+
+#include <check.h>
+#include <popt.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "resolv/async_resolv.h"
+#include "tests/common.h"
+#include "util/util.h"
+
+/* Interface under test */
+#include "providers/fail_over.h"
+
+int use_net_test;
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct resolv_ctx *resolv;
+ struct fo_ctx *fo_ctx;
+ int tasks;
+};
+
+struct task {
+ struct test_ctx *test_ctx;
+ const char *location;
+ int recv;
+ int port;
+ int new_server_status;
+ int new_port_status;
+};
+
+static struct test_ctx *
+setup_test(void)
+{
+ struct test_ctx *ctx;
+ int ret;
+
+ ctx = talloc_zero(global_talloc_context, struct test_ctx);
+ fail_if(ctx == NULL, "Could not allocate memory for test context");
+
+ ctx->ev = tevent_context_init(ctx);
+ if (ctx->ev == NULL) {
+ talloc_free(ctx);
+ fail("Could not init tevent context");
+ }
+
+ ret = resolv_init(ctx, ctx->ev, 5, &ctx->resolv);
+ if (ret != EOK) {
+ talloc_free(ctx);
+ fail("Could not init resolv context");
+ }
+
+ ctx->fo_ctx = fo_context_init(ctx, 5 * 60);
+ if (ctx->fo_ctx == NULL) {
+ talloc_free(ctx);
+ fail("Could not init fail over context");
+ }
+
+ return ctx;
+}
+
+static void
+test_loop(struct test_ctx *data)
+{
+ while (data->tasks != 0)
+ tevent_loop_once(data->ev);
+}
+
+START_TEST(test_fo_new_service)
+{
+ int i;
+ int ret;
+ struct test_ctx *ctx;
+ struct fo_service *service;
+ struct fo_service *services[10];
+
+ ctx = setup_test();
+ check_leaks_push(ctx);
+
+ for (i = 0; i < 10; i++) {
+ char buf[16];
+ sprintf(buf, "service_%d", i);
+
+ check_leaks_push(ctx);
+ ret = fo_new_service(ctx->fo_ctx, buf, &services[i]);
+ fail_if(ret != EOK);
+ }
+
+ ret = fo_new_service(ctx->fo_ctx, "service_3", &service);
+ fail_if(ret != EEXIST);
+
+ for (i = 9; i >= 0; i--) {
+ char buf[16];
+ sprintf(buf, "service_%d", i);
+
+ ret = fo_get_service(ctx->fo_ctx, buf, &service);
+ fail_if(ret != EOK);
+ fail_if(service != services[i]);
+ talloc_free(service);
+ check_leaks_pop(ctx);
+
+ ret = fo_get_service(ctx->fo_ctx, buf, &service);
+ fail_if(ret != ENOENT);
+ }
+
+ check_leaks_pop(ctx);
+ talloc_free(ctx);
+}
+END_TEST
+
+static void
+test_resolve_service_callback(struct tevent_req *req)
+{
+ uint64_t recv_status;
+ int port;
+ struct task *task;
+ struct fo_server *server = NULL;
+ struct hostent *he;
+ int i;
+
+ task = tevent_req_callback_data(req, struct task);
+
+ task->test_ctx->tasks--;
+
+ recv_status = fo_resolve_service_recv(req, &server);
+ talloc_free(req);
+ fail_if(recv_status != task->recv, "%s: Expected return of %d, got %d",
+ task->location, task->recv, recv_status);
+ if (recv_status != EOK)
+ return;
+ fail_if(server == NULL);
+ port = fo_get_server_port(server);
+ fail_if(port != task->port, "%s: Expected port %d, got %d", task->location,
+ task->port, port);
+
+ if (task->new_port_status >= 0)
+ fo_set_port_status(server, task->new_port_status);
+ if (task->new_server_status >= 0)
+ fo_set_server_status(server, task->new_server_status);
+
+ he = fo_get_server_hostent(server);
+ fail_if(he == NULL, "%s: fo_get_server_hostent() returned NULL");
+ for (i = 0; he->h_addr_list[i]; i++) {
+ char buf[256];
+
+ inet_ntop(he->h_addrtype, he->h_addr_list[i], buf, sizeof(buf));
+ fail_if(strcmp(buf, "127.0.0.1") != 0 && strcmp(buf, "::1") != 0);
+ }
+
+}
+
+#define get_request(a, b, c, d, e, f) \
+ _get_request(a, b, c, d, e, f, __location__)
+
+static void
+_get_request(struct test_ctx *test_ctx, struct fo_service *service,
+ int expected_recv, int expected_port, int new_port_status,
+ int new_server_status, const char *location)
+{
+ struct tevent_req *req;
+ struct task *task;
+
+ task = talloc(test_ctx, struct task);
+ fail_if(task == NULL);
+
+ task->test_ctx = test_ctx;
+ task->recv = expected_recv;
+ task->port = expected_port;
+ task->new_port_status = new_port_status;
+ task->new_server_status = new_server_status;
+ task->location = location;
+ test_ctx->tasks++;
+
+ req = fo_resolve_service_send(test_ctx, test_ctx->ev, test_ctx->resolv, service);
+ fail_if(req == NULL, "%s: fo_resolve_service_send() failed", location);
+
+ tevent_req_set_callback(req, test_resolve_service_callback, task);
+ test_loop(test_ctx);
+}
+
+START_TEST(test_fo_resolve_service)
+{
+ struct test_ctx *ctx;
+ struct fo_service *service[2];
+
+ ctx = setup_test();
+ fail_if(ctx == NULL);
+
+ /* Add service. */
+ fail_if(fo_new_service(ctx->fo_ctx, "http", &service[0]) != EOK);
+
+ fail_if(fo_new_service(ctx->fo_ctx, "ldap", &service[1]) != EOK);
+
+ /* Add servers. */
+ fail_if(fo_add_server(service[0], "localhost", 20, NULL) != EOK);
+ fail_if(fo_add_server(service[0], "127.0.0.1", 80, NULL) != EOK);
+
+ fail_if(fo_add_server(service[1], "localhost", 30, NULL) != EOK);
+ fail_if(fo_add_server(service[1], "127.0.0.1", 389, NULL) != EOK);
+ fail_if(fo_add_server(service[1], "127.0.0.1", 389, NULL) != EEXIST);
+
+ /* Make requests. */
+ get_request(ctx, service[0], EOK, 20, PORT_WORKING, -1);
+ get_request(ctx, service[0], EOK, 20, -1, SERVER_NOT_WORKING);
+ get_request(ctx, service[0], EOK, 80, PORT_WORKING, -1);
+ get_request(ctx, service[0], EOK, 80, PORT_NOT_WORKING, -1);
+ get_request(ctx, service[0], ENOENT, 0, -1, -1);
+
+ get_request(ctx, service[1], EOK, 389, PORT_WORKING, -1);
+ get_request(ctx, service[1], EOK, 389, -1, SERVER_NOT_WORKING);
+ get_request(ctx, service[1], ENOENT, 0, -1, -1);
+
+ talloc_free(ctx);
+}
+END_TEST
+
+Suite *
+create_suite(void)
+{
+ Suite *s = suite_create("fail_over");
+
+ TCase *tc = tcase_create("FAIL_OVER Tests");
+
+ tcase_add_checked_fixture(tc, leak_check_setup, leak_check_teardown);
+ /* Do some testing */
+ tcase_add_test(tc, test_fo_new_service);
+ tcase_add_test(tc, test_fo_resolve_service);
+ if (use_net_test) {
+ }
+ /* Add all test cases to the test suite */
+ suite_add_tcase(s, tc);
+
+ return s;
+}
+
+int
+main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int failure_count;
+ Suite *suite;
+ SRunner *sr;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ { "use-net-test", 'n', POPT_ARG_NONE, 0, 'n', "Run tests that need an active internet connection", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ case 'n':
+ use_net_test = 1;
+ break;
+
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+ debug_level = debug;
+
+ suite = create_suite();
+ sr = srunner_create(suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/src/tests/files-tests.c b/src/tests/files-tests.c
new file mode 100644
index 00000000..90b97177
--- /dev/null
+++ b/src/tests/files-tests.c
@@ -0,0 +1,323 @@
+/*
+ * Authors:
+ * Jakub Hrozek <jhrozek@redhat.com>
+ *
+ * Copyright (C) 2008 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 3 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdlib.h>
+#include <check.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "config.h"
+#include "tools/tools_util.h"
+#include "util/util.h"
+
+static char tpl_dir[] = "file-tests-dir-XXXXXX";
+static char *dir_path;
+static char *dst_path;
+static uid_t uid;
+static gid_t gid;
+static TALLOC_CTX *test_ctx = NULL;
+
+static void setup_files_test(void)
+{
+ /* create a temporary directory that we fill with stuff later on */
+ test_ctx = talloc_new(NULL);
+ dir_path = mkdtemp(talloc_strdup(test_ctx, tpl_dir));
+ dst_path = mkdtemp(talloc_strdup(test_ctx, tpl_dir));
+
+ uid = getuid();
+ gid = getgid();
+}
+
+static void teardown_files_test(void)
+{
+ char *cmd = NULL;
+
+ /* OK this is crude but since the functions to remove tree are under test.. */
+ if (dir_path && test_ctx) {
+ cmd = talloc_asprintf(test_ctx, "/bin/rm -rf %s\n", dir_path);
+ system(cmd);
+ }
+ if (dst_path && test_ctx) {
+ cmd = talloc_asprintf(test_ctx, "/bin/rm -rf %s\n", dst_path);
+ system(cmd);
+ }
+
+ /* clean up */
+ talloc_zfree(test_ctx);
+}
+
+static int create_simple_file(const char *name, const char *content)
+{
+ int fd;
+ ssize_t size;
+ int ret;
+
+ fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0700);
+ fail_if(fd == -1, "Cannot create simple file\n");
+
+ size = write(fd, "abc", 3);
+ fail_if(size == -1, "Cannot write to file\n");
+
+ ret = fsync(fd);
+ fail_if(ret == -1, "Cannot sync file\n");
+
+ ret = close(fd);
+ fail_if(ret == -1, "Cannot close file\n");
+
+ return ret;
+}
+
+START_TEST(test_remove_tree)
+{
+ int ret;
+ char origpath[PATH_MAX+1];
+
+ errno = 0;
+ getcwd(origpath, PATH_MAX);
+ fail_unless(errno == 0, "Cannot getcwd\n");
+
+ DEBUG(5, ("About to delete %s\n", dir_path));
+
+ /* create a file */
+ ret = chdir(dir_path);
+ fail_if(ret == -1, "Cannot chdir1\n");
+
+ ret = create_simple_file("bar", "bar");
+ fail_if(ret == -1, "Cannot create file1\n");
+
+ /* create a subdir and file inside it */
+ ret = mkdir("subdir", 0700);
+ fail_if(ret == -1, "Cannot create subdir\n");
+
+ ret = chdir("subdir");
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ ret = create_simple_file("foo", "foo");
+ fail_if(ret == -1, "Cannot create file\n");
+
+ /* create another subdir, empty this time */
+ ret = mkdir("subdir2", 0700);
+ fail_if(ret == -1, "Cannot create subdir\n");
+
+ ret = chdir(origpath);
+ fail_if(ret == -1, "Cannot chdir2\n");
+
+ /* go back */
+ ret = chdir(origpath);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ /* and finally wipe it out.. */
+ ret = remove_tree(dir_path);
+ fail_unless(ret == EOK, "remove_tree failed\n");
+
+ /* check if really gone */
+ ret = access(dir_path, F_OK);
+ fail_unless(ret == -1, "directory still there after remove_tree\n");
+}
+END_TEST
+
+START_TEST(test_simple_copy)
+{
+ int ret;
+ char origpath[PATH_MAX+1];
+ char *tmp;
+ int fd = -1;
+
+ errno = 0;
+ getcwd(origpath, PATH_MAX);
+ fail_unless(errno == 0, "Cannot getcwd\n");
+
+ /* create a file */
+ ret = chdir(dir_path);
+ fail_if(ret == -1, "Cannot chdir1\n");
+
+ ret = create_simple_file("bar", "bar");
+ fail_if(ret == -1, "Cannot create file1\n");
+
+ /* create a subdir and file inside it */
+ ret = mkdir("subdir", 0700);
+ fail_if(ret == -1, "Cannot create subdir\n");
+
+ ret = chdir("subdir");
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ ret = create_simple_file("foo", "foo");
+ fail_if(ret == -1, "Cannot create file\n");
+
+ /* go back */
+ ret = chdir(origpath);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ /* and finally copy.. */
+ DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path));
+ ret = copy_tree(dir_path, dst_path, uid, gid);
+ fail_unless(ret == EOK, "copy_tree failed\n");
+
+ /* check if really copied */
+ ret = access(dst_path, F_OK);
+ fail_unless(ret == 0, "destination directory not there\n");
+
+ tmp = talloc_asprintf(test_ctx, "%s/bar", dst_path);
+ ret = check_and_open_readonly(tmp, &fd, uid, gid, 0700);
+ fail_unless(ret == EOK, "Cannot open %s\n");
+ close(fd);
+ talloc_free(tmp);
+}
+END_TEST
+
+START_TEST(test_copy_symlink)
+{
+ int ret;
+ char origpath[PATH_MAX+1];
+ char *tmp;
+ struct stat statbuf;
+
+ errno = 0;
+ getcwd(origpath, PATH_MAX);
+ fail_unless(errno == 0, "Cannot getcwd\n");
+
+ /* create a subdir */
+ ret = chdir(dir_path);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ ret = create_simple_file("footarget", "foo");
+ fail_if(ret == -1, "Cannot create file\n");
+
+ ret = symlink("footarget", "foolink");
+ fail_if(ret == -1, "Cannot create symlink\n");
+
+ /* go back */
+ ret = chdir(origpath);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ /* and finally copy.. */
+ DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path));
+ ret = copy_tree(dir_path, dst_path, uid, gid);
+ fail_unless(ret == EOK, "copy_tree failed\n");
+
+ /* check if really copied */
+ ret = access(dst_path, F_OK);
+ fail_unless(ret == 0, "destination directory not there\n");
+
+ tmp = talloc_asprintf(test_ctx, "%s/foolink", dst_path);
+ ret = lstat(tmp, &statbuf);
+ fail_unless(ret == 0, "cannot stat the symlink %s\n", tmp);
+ fail_unless(S_ISLNK(statbuf.st_mode), "%s not a symlink?\n", tmp);
+ talloc_free(tmp);
+}
+END_TEST
+
+START_TEST(test_copy_node)
+{
+ int ret;
+ char origpath[PATH_MAX+1];
+ char *tmp;
+ struct stat statbuf;
+
+ errno = 0;
+ getcwd(origpath, PATH_MAX);
+ fail_unless(errno == 0, "Cannot getcwd\n");
+
+ /* create a node */
+ ret = chdir(dir_path);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ ret = mknod("testnode", S_IFIFO | S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, 0);
+ fail_unless(ret == 0, "cannot stat /dev/null: %s", strerror(errno));
+
+ /* go back */
+ ret = chdir(origpath);
+ fail_if(ret == -1, "Cannot chdir\n");
+
+ /* and finally copy.. */
+ DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path));
+ ret = copy_tree(dir_path, dst_path, uid, gid);
+ fail_unless(ret == EOK, "copy_tree failed\n");
+
+ /* check if really copied */
+ ret = access(dst_path, F_OK);
+ fail_unless(ret == 0, "destination directory not there\n");
+
+ tmp = talloc_asprintf(test_ctx, "%s/testnode", dst_path);
+ ret = lstat(tmp, &statbuf);
+ fail_unless(ret == 0, "cannot stat the node %s\n", tmp);
+ fail_unless(S_ISFIFO(statbuf.st_mode), "%s not a char device??\n", tmp);
+ talloc_free(tmp);
+}
+END_TEST
+
+static Suite *files_suite(void)
+{
+ Suite *s = suite_create("files_suite");
+
+ TCase *tc_files = tcase_create("files");
+ tcase_add_checked_fixture(tc_files,
+ setup_files_test,
+ teardown_files_test);
+
+ tcase_add_test(tc_files, test_remove_tree);
+ tcase_add_test(tc_files, test_simple_copy);
+ tcase_add_test(tc_files, test_copy_symlink);
+ tcase_add_test(tc_files, test_copy_node);
+ suite_add_tcase(s, tc_files);
+
+ return s;
+}
+
+int main(int argc, char *argv[])
+{
+ int number_failed;
+ int opt;
+ poptContext pc;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, (const char **) argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ poptFreeContext(pc);
+ debug_level = debug;
+
+ Suite *s = files_suite();
+ SRunner *sr = srunner_create(s);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/src/tests/find_uid-tests.c b/src/tests/find_uid-tests.c
new file mode 100644
index 00000000..9eafadd4
--- /dev/null
+++ b/src/tests/find_uid-tests.c
@@ -0,0 +1,125 @@
+/*
+ SSSD
+
+ find_uid - Utilities tests
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <check.h>
+
+#include "util/find_uid.h"
+
+
+START_TEST(test_check_if_uid_is_active_success)
+{
+ uid_t uid;
+ bool result;
+ int ret;
+
+ uid = getuid();
+
+ ret = check_if_uid_is_active(uid, &result);
+ fail_unless(ret == EOK, "check_if_uid_is_active failed.");
+ fail_unless(result, "check_if_uid_is_active did not found my uid [%d]",
+ uid);
+}
+END_TEST
+
+START_TEST(test_check_if_uid_is_active_fail)
+{
+ uid_t uid;
+ bool result;
+ int ret;
+
+ uid = (uid_t) -4;
+
+ ret = check_if_uid_is_active(uid, &result);
+ fail_unless(ret == EOK, "check_if_uid_is_active failed.");
+ fail_unless(!result, "check_if_uid_is_active found (hopefully not active) "
+ "uid [%d]", uid);
+}
+END_TEST
+
+START_TEST(test_get_uid_table)
+{
+ uid_t uid;
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ hash_table_t *table;
+ hash_key_t key;
+ hash_value_t value;
+
+ tmp_ctx = talloc_new(NULL);
+ fail_unless(tmp_ctx != NULL, "talloc_new failed.");
+
+ ret = get_uid_table(tmp_ctx, &table);
+ fail_unless(ret == EOK, "get_uid_table failed.");
+
+ uid = getuid();
+ key.type = HASH_KEY_ULONG;
+ key.ul = (unsigned long) uid;
+
+ ret = hash_lookup(table, &key, &value);
+
+ fail_unless(ret == HASH_SUCCESS, "Cannot find my uid [%d] in the table", uid);
+
+ uid = (uid_t) -4;
+ key.type = HASH_KEY_ULONG;
+ key.ul = (unsigned long) uid;
+
+ ret = hash_lookup(table, &key, &value);
+
+ fail_unless(ret == HASH_ERROR_KEY_NOT_FOUND, "Found (hopefully not active) "
+ "uid [%d] in the table", uid);
+
+ talloc_free(tmp_ctx);
+}
+END_TEST
+
+Suite *find_uid_suite (void)
+{
+ Suite *s = suite_create ("find_uid");
+
+ TCase *tc_find_uid = tcase_create ("find_uid");
+
+ tcase_add_test (tc_find_uid, test_check_if_uid_is_active_success);
+ tcase_add_test (tc_find_uid, test_check_if_uid_is_active_fail);
+ tcase_add_test (tc_find_uid, test_get_uid_table);
+ suite_add_tcase (s, tc_find_uid);
+
+ return s;
+}
+
+int main(void)
+{
+ debug_level = 255;
+ int number_failed;
+ Suite *s = find_uid_suite ();
+ SRunner *sr = srunner_create (s);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/ipa_ldap_opt-tests.c b/src/tests/ipa_ldap_opt-tests.c
new file mode 100644
index 00000000..215f94a4
--- /dev/null
+++ b/src/tests/ipa_ldap_opt-tests.c
@@ -0,0 +1,59 @@
+/*
+ SSSD
+
+ Tests if IPA and LDAP backend options are in sync
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2010 Red Hat
+
+ 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 <check.h>
+#include <stdlib.h>
+
+#include "providers/ipa/ipa_common.h"
+#include "providers/ldap/sdap.h"
+
+START_TEST(test_check_num_opts)
+{
+ fail_if(IPA_OPTS_BASIC_TEST != SDAP_OPTS_BASIC);
+}
+END_TEST
+
+Suite *ipa_ldap_opt_suite (void)
+{
+ Suite *s = suite_create ("ipa_ldap_opt");
+
+ TCase *tc_ipa_ldap_opt = tcase_create ("ipa_ldap_opt");
+
+ tcase_add_test (tc_ipa_ldap_opt, test_check_num_opts);
+ suite_add_tcase (s, tc_ipa_ldap_opt);
+
+ return s;
+}
+
+int main(void)
+{
+ int number_failed;
+ Suite *s = ipa_ldap_opt_suite ();
+ SRunner *sr = srunner_create (s);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/ipa_timerules-tests.c b/src/tests/ipa_timerules-tests.c
new file mode 100644
index 00000000..0a7be90b
--- /dev/null
+++ b/src/tests/ipa_timerules-tests.c
@@ -0,0 +1,580 @@
+/*
+ Timelib
+
+ test_timelib.c
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _XOPEN_SOURCE /* strptime */
+
+#include <check.h>
+#include <popt.h>
+#include <time.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "providers/ipa/ipa_timerules.h"
+#include "util/util.h"
+#include "tests/common.h"
+
+#define CHECK_TIME_RULE_LEAK(ctx, tctx, str, now, result) do { \
+ check_leaks_push(ctx); \
+ ret = check_time_rule(ctx, tctx, str, now, result); \
+ check_leaks_pop(ctx); \
+} while (0)
+
+static TALLOC_CTX *ctx;
+
+static void usage(poptContext pc, const char *error)
+{
+ poptPrintUsage(pc, stderr, 0);
+ if (error) fprintf(stderr, "%s", error);
+}
+
+int str2time_t(const char *fmt, const char *str, time_t *out)
+{
+ char *err;
+ struct tm stm;
+ memset(&stm, 0, sizeof(struct tm));
+
+ err = strptime(str, fmt, &stm);
+ if(!err || err[0] != '\0')
+ return EINVAL;
+
+ DEBUG(9, ("after strptime: %s", asctime(&stm)));
+ stm.tm_isdst = -1;
+ *out = mktime(&stm);
+ DEBUG(9, ("after mktime: %s", ctime(out)));
+ return (*out == -1) ? EINVAL : EOK;
+}
+
+/* Fixtures - open the time library before every test, close it afterwards */
+void setup(void)
+{
+ leak_check_setup();
+
+ ctx = talloc_new(NULL);
+ fail_if(ctx == NULL);
+}
+
+void teardown(void)
+{
+ leak_check_teardown();
+}
+
+/* Test that timelib detects a time rule inside the absolute range */
+START_TEST(test_timelib_absolute_in_range)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test that timelib detects a time rule outside the absolute range */
+START_TEST(test_timelib_absolute_out_of_range)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2007-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test that absolute timeranges work OK with only minimal data supplied */
+START_TEST(test_timelib_absolute_minimal)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216 ~ 20051216", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test a time value "right off the edge" of the time specifier */
+START_TEST(test_timelib_absolute_one_off)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-29", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-32", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+
+/* Test a time value "right on the edge" of the time specifier */
+START_TEST(test_timelib_absolute_one_on)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-31", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_daily_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ /* test edges */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge2)");
+ fail_unless(result == true, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-16-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-15-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_daily_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-21-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off errors */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-29", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (one-off 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-31", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (one-off 2)");
+ fail_unless(result == false, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-10-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-14-59", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-01", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error2");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_weekly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-02-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error1");
+
+ /* test edges - monday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error2");
+
+ /* test edges - friday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error3");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-06-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error3");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_weekly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-04-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off error - monday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off error - friday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-04-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-05-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error3");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_monthly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-07-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d: %s)", ret, strerror(ret));
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-05-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-5,10,15,20-25 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d: %s)", ret, strerror(ret));
+ fail_unless(result == true, "Range check error");
+
+ /* edges - week in */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Sat 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-29-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)");
+ fail_unless(result == true, "Range check error");
+
+ /* edges - day in */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-10-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_monthly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ /* one-off error - week out */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-15-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1 day Sun-Sat 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-28-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)");
+ fail_unless(result == false, "Range check error");
+
+ /* one-off error - day out */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 2-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-11-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_yearly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-08-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly week 1 day 1-7 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error3");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-07-10-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error4");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_yearly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-09-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-11-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule3 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+Suite *create_timelib_suite(void)
+{
+ Suite *s = suite_create("timelib");
+
+ TCase *tc_timelib = tcase_create("Timelib Tests");
+
+
+ /* Add setup() and teardown() methods */
+ tcase_add_checked_fixture(tc_timelib, setup, teardown);
+
+ /* Do some testing */
+ tcase_add_test(tc_timelib, test_timelib_absolute_in_range);
+ tcase_add_test(tc_timelib, test_timelib_absolute_out_of_range);
+ tcase_add_test(tc_timelib, test_timelib_absolute_minimal);
+ tcase_add_test(tc_timelib, test_timelib_absolute_one_off);
+ tcase_add_test(tc_timelib, test_timelib_absolute_one_on);
+
+ tcase_add_test(tc_timelib, test_timelib_periodic_daily_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_daily_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_weekly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_weekly_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_monthly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_monthly_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_yearly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_yearly_out);
+
+ /* Add all test cases to the test suite */
+ suite_add_tcase(s, tc_timelib);
+
+ return s;
+}
+
+int main(int argc, const char *argv[])
+{
+ int ret;
+ poptContext pc;
+ int failure_count;
+ Suite *timelib_suite;
+ SRunner *sr;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0);
+ if((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ return EXIT_FAILURE;
+ }
+ debug_level = debug;
+
+ timelib_suite = create_timelib_suite();
+ sr = srunner_create(timelib_suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
diff --git a/src/tests/krb5_utils-tests.c b/src/tests/krb5_utils-tests.c
new file mode 100644
index 00000000..8676f3bf
--- /dev/null
+++ b/src/tests/krb5_utils-tests.c
@@ -0,0 +1,307 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities tests
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <stdlib.h>
+#include <check.h>
+
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_auth.h"
+
+#define BASE "/abc/def"
+
+#define USERNAME "testuser"
+#define UID "12345"
+#define PRINCIPLE_NAME "testuser@EXAMPLE.COM"
+#define REALM "REALM.ORG"
+#define HOME_DIRECTORY "/home/testuser"
+#define CCACHE_DIR "/var/tmp"
+#define PID "4321"
+
+extern struct dp_option default_krb5_opts[];
+
+TALLOC_CTX *tmp_ctx = NULL;
+struct krb5child_req *kr;
+
+void setup_talloc_context(void)
+{
+ int ret;
+ int i;
+
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+ fail_unless(tmp_ctx == NULL, "Talloc context already initialized.");
+ tmp_ctx = talloc_new(NULL);
+ fail_unless(tmp_ctx != NULL, "Cannot create talloc context.");
+
+ kr = talloc_zero(tmp_ctx, struct krb5child_req);
+ fail_unless(kr != NULL, "Cannot create krb5child_req structure.");
+
+ pd = talloc_zero(tmp_ctx, struct pam_data);
+ fail_unless(pd != NULL, "Cannot create pam_data structure.");
+
+ krb5_ctx = talloc_zero(tmp_ctx, struct krb5_ctx);
+ fail_unless(pd != NULL, "Cannot create krb5_ctx structure.");
+
+ pd->user = discard_const(USERNAME);
+ pd->pw_uid = atoi(UID);
+ pd->upn = PRINCIPLE_NAME;
+ pd->cli_pid = atoi(PID);
+
+ krb5_ctx->opts = talloc_zero_array(tmp_ctx, struct dp_option, KRB5_OPTS);
+ fail_unless(krb5_ctx->opts != NULL, "Cannot created options.");
+ for (i = 0; i < KRB5_OPTS; i++) {
+ krb5_ctx->opts[i].opt_name = default_krb5_opts[i].opt_name;
+ krb5_ctx->opts[i].type = default_krb5_opts[i].type;
+ krb5_ctx->opts[i].def_val = default_krb5_opts[i].def_val;
+ }
+ ret = dp_opt_set_string(krb5_ctx->opts, KRB5_REALM, REALM);
+ fail_unless(ret == EOK, "Failed to set Realm");
+ ret = dp_opt_set_string(krb5_ctx->opts, KRB5_CCACHEDIR, CCACHE_DIR);
+ fail_unless(ret == EOK, "Failed to set Ccache dir");
+
+ kr->homedir = HOME_DIRECTORY;
+
+ kr->pd = pd;
+ kr->krb5_ctx = krb5_ctx;
+
+}
+
+void free_talloc_context(void)
+{
+ int ret;
+ fail_unless(tmp_ctx != NULL, "Talloc context already freed.");
+ ret = talloc_free(tmp_ctx);
+ tmp_ctx = NULL;
+ fail_unless(ret == 0, "Connot free talloc context.");
+}
+
+START_TEST(test_multiple_substitutions)
+{
+ const char *test_template = BASE"_%u_%U_%u";
+ const char *expected = BASE"_"USERNAME"_"UID"_"USERNAME;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_username)
+{
+ const char *test_template = BASE"_%u";
+ const char *expected = BASE"_"USERNAME;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_uid)
+{
+ const char *test_template = BASE"_%U";
+ const char *expected = BASE"_"UID;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_upn)
+{
+ const char *test_template = BASE"_%p";
+ const char *expected = BASE"_"PRINCIPLE_NAME;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_realm)
+{
+ const char *test_template = BASE"_%r";
+ const char *expected = BASE"_"REALM;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_home)
+{
+ const char *test_template = BASE"_%h";
+ const char *expected = BASE"_"HOME_DIRECTORY;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_ccache_dir)
+{
+ const char *test_template = BASE"_%d";
+ const char *expected = BASE"_"CCACHE_DIR;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_pid)
+{
+ const char *test_template = BASE"_%P";
+ const char *expected = BASE"_"PID;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_percent)
+{
+ const char *test_template = BASE"_%%";
+ const char *expected = BASE"_%";
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, expected) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, expected);
+}
+END_TEST
+
+START_TEST(test_unknow_template)
+{
+ const char *test_template = BASE"_%X";
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result == NULL, "Unknown template [%s] should fail.",
+ test_template);
+}
+END_TEST
+
+START_TEST(test_NULL)
+{
+ char *test_template = NULL;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result == NULL, "Expected NULL as a result for an empty input.",
+ test_template);
+}
+END_TEST
+
+START_TEST(test_no_substitution)
+{
+ const char *test_template = BASE;
+ char *result;
+
+ result = expand_ccname_template(tmp_ctx, kr, test_template);
+
+ fail_unless(result != NULL, "Cannot expand template [%s].", test_template);
+ fail_unless(strcmp(result, test_template) == 0,
+ "Expansion failed, result [%s], expected [%s].",
+ result, test_template);
+}
+END_TEST
+
+Suite *krb5_utils_suite (void)
+{
+ Suite *s = suite_create ("krb5_utils");
+
+ TCase *tc_ccname_template = tcase_create ("ccname_template");
+ tcase_add_checked_fixture (tc_ccname_template, setup_talloc_context,
+ free_talloc_context);
+ tcase_add_test (tc_ccname_template, test_no_substitution);
+ tcase_add_test (tc_ccname_template, test_NULL);
+ tcase_add_test (tc_ccname_template, test_unknow_template);
+ tcase_add_test (tc_ccname_template, test_username);
+ tcase_add_test (tc_ccname_template, test_uid);
+ tcase_add_test (tc_ccname_template, test_upn);
+ tcase_add_test (tc_ccname_template, test_realm);
+ tcase_add_test (tc_ccname_template, test_home);
+ tcase_add_test (tc_ccname_template, test_ccache_dir);
+ tcase_add_test (tc_ccname_template, test_pid);
+ tcase_add_test (tc_ccname_template, test_percent);
+ tcase_add_test (tc_ccname_template, test_multiple_substitutions);
+ suite_add_tcase (s, tc_ccname_template);
+
+ return s;
+}
+
+int main(void)
+{
+ int number_failed;
+ Suite *s = krb5_utils_suite ();
+ SRunner *sr = srunner_create (s);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/src/tests/python-test.py b/src/tests/python-test.py
new file mode 100644
index 00000000..e1eaab2d
--- /dev/null
+++ b/src/tests/python-test.py
@@ -0,0 +1,445 @@
+#!/usr/bin/python
+#coding=utf-8
+
+# Authors:
+# Jakub Hrozek <jhrozek@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os
+import tempfile
+import shutil
+import unittest
+import commands
+import errno
+
+# module under test
+import pysss
+
+class LocalTest(unittest.TestCase):
+ local_path = "/var/lib/sss/db/sssd.ldb"
+
+ def setUp(self):
+ self.local = pysss.local()
+
+ def _run_and_check(self, runme):
+ (status, output) = commands.getstatusoutput(runme)
+ self.failUnlessEqual(status, 0, output)
+
+ def _get_object_info(self, name, subtree, domain):
+ search_dn = "dn=name=%s,cn=%s,cn=%s,cn=sysdb" % (name, subtree, domain)
+ (status, output) = commands.getstatusoutput("ldbsearch -H %s %s" % (self.local_path,search_dn))
+
+ if status: return {}
+
+ kw = {}
+ for key, value in [ l.split(':') for l in output.split('\n') if ":" in l ]:
+ kw[key] = value.strip()
+
+ del kw['asq']
+ return kw
+
+ def get_user_info(self, name, domain="LOCAL"):
+ return self._get_object_info(name, "users", domain)
+
+ def get_group_info(self, name, domain="LOCAL"):
+ return self._get_object_info(name, "groups", domain)
+
+ def _validate_object(self, kw, name, **kwargs):
+ if kw == {}: self.fail("Could not get %s info" % name)
+ for key in kwargs.keys():
+ self.assert_(str(kwargs[key]) == str(kw[key]), "%s %s != %s %s" % (key, kwargs[key], key, kw[key]))
+
+ def validate_user(self, username, **kwargs):
+ return self._validate_object(self.get_user_info(username), "user", **kwargs)
+
+ def validate_group(self, groupname, **kwargs):
+ return self._validate_object(self.get_group_info(groupname), "group", **kwargs)
+
+ def _validate_no_object(self, kw, name):
+ if kw != {}:
+ self.fail("Got %s info" % name)
+
+ def validate_no_user(self, username):
+ return self._validate_no_object(self.get_user_info(username), "user")
+
+ def validate_no_group(self, groupname):
+ return self._validate_no_object(self.get_group_info(groupname), "group")
+
+ def _get_object_membership(self, name, subtree, domain):
+ search_dn = "dn=name=%s,cn=%s,cn=%s,cn=sysdb" % (name, subtree, domain)
+ (status, output) = commands.getstatusoutput("ldbsearch -H %s %s" % (self.local_path,search_dn))
+
+ if status:
+ return []
+
+ members = [ value.strip() for key, value in [ l.split(':') for l in output.split('\n') if ":" in l ] if key == "memberof" ]
+ return members
+
+ def _assertMembership(self, name, group_list, subtree, domain):
+ members = self._get_object_membership(name, subtree, domain)
+ for group in group_list:
+ group_dn = "name=%s,cn=groups,cn=%s,cn=sysdb" % (group, domain)
+ if group_dn in members:
+ members.remove(group_dn)
+ else:
+ self.fail("Cannot find required group %s" % group_dn)
+
+ if len(members) > 0:
+ self.fail("More groups than selected")
+
+ def assertUserMembership(self, name, group_list, domain="LOCAL"):
+ return self._assertMembership(name, group_list, "users", domain)
+
+ def assertGroupMembership(self, name, group_list, domain="LOCAL"):
+ return self._assertMembership(name, group_list, "groups", domain)
+
+ def get_user_membership(self, name, domain="LOCAL"):
+ return self._get_object_membership(name, "users", domain)
+
+ def get_group_membership(self, name, domain="LOCAL"):
+ return self._get_object_membership(name, "groups", domain)
+
+ def add_group(self, groupname):
+ self._run_and_check("sss_groupadd %s" % (groupname))
+
+ def remove_group(self, groupname):
+ self._run_and_check("sss_groupdel %s" % (groupname))
+
+ def add_user(self, username):
+ self._run_and_check("sss_useradd %s" % (username))
+
+ def add_user_not_home(self, username):
+ self._run_and_check("sss_useradd -M %s" % (username))
+
+ def remove_user(self, username):
+ self._run_and_check("sss_userdel %s" % (username))
+
+ def remove_user_not_home(self, username):
+ self._run_and_check("sss_userdel -R %s" % (username))
+
+class SanityTest(unittest.TestCase):
+ def testInstantiate(self):
+ "Test that the local backed binding can be instantiated"
+ local = pysss.local()
+ self.assert_(local.__class__, "<type 'sss.local'>")
+
+class UseraddTest(LocalTest):
+ def tearDown(self):
+ if self.username:
+ self.remove_user(self.username)
+
+ def testUseradd(self):
+ "Test adding a local user"
+ self.username = "testUseradd"
+ self.local.useradd(self.username)
+ self.validate_user(self.username)
+ # check home directory was created with default name
+ self.assertEquals(os.access("/home/%s" % self.username, os.F_OK), True)
+
+ def testUseraddWithParams(self):
+ "Test adding a local user with modified parameters"
+ self.username = "testUseraddWithParams"
+ self.local.useradd(self.username,
+ gecos="foo bar",
+ homedir="/home/foobar",
+ shell="/bin/zsh")
+ self.validate_user(self.username,
+ gecos="foo bar",
+ homeDirectory="/home/foobar",
+ loginShell="/bin/zsh")
+ # check home directory was created with nondefault name
+ self.assertEquals(os.access("/home/foobar", os.F_OK), True)
+
+ def testUseraddNoHomedir(self):
+ "Test adding a local user without creating his home dir"
+ self.username = "testUseraddNoHomedir"
+ self.local.useradd(self.username, create_home = False)
+ self.validate_user(self.username)
+ # check home directory was not created
+ self.assertEquals(os.access("/home/%s" % self.username, os.F_OK), False)
+ self.local.userdel(self.username, remove = False)
+ self.username = None # fool tearDown into not removing the user
+
+ def testUseraddAlternateSkeldir(self):
+ "Test adding a local user and init his homedir from a custom location"
+ self.username = "testUseraddAlternateSkeldir"
+
+ skeldir = tempfile.mkdtemp()
+ fd, path = tempfile.mkstemp(dir=skeldir)
+ fdo = os.fdopen(fd)
+ fdo.flush()
+ fdo.close
+ self.assertEquals(os.access(path, os.F_OK), True)
+ filename = os.path.basename(path)
+
+ try:
+ self.local.useradd(self.username, skel = skeldir)
+ self.validate_user(self.username)
+ self.assertEquals(os.access("/home/%s/%s"%(self.username,filename), os.F_OK), True)
+ finally:
+ shutil.rmtree(skeldir)
+
+ def testUseraddToGroups(self):
+ "Test adding a local user with group membership"
+ self.username = "testUseraddToGroups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+ try:
+ self.local.useradd(self.username,
+ groups=["gr1","gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1","gr2"])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+ def testUseraddWithUID(self):
+ "Test adding a local user with a custom UID"
+ self.username = "testUseraddWithUID"
+ self.local.useradd(self.username,
+ uid=1024)
+ self.validate_user(self.username,
+ uidNumber=1024)
+
+class UseraddTestNegative(LocalTest):
+ def testUseraddNoParams(self):
+ "Test that local.useradd() requires the username parameter"
+ self.assertRaises(TypeError, self.local.useradd)
+
+ def testUseraddUserAlreadyExists(self):
+ "Test adding a local with a duplicite name"
+ self.username = "testUseraddUserAlreadyExists"
+ self.local.useradd(self.username)
+ try:
+ self.local.useradd(self.username)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_user(self.username)
+
+ def testUseraddUIDAlreadyExists(self):
+ "Test adding a local with a duplicite user ID"
+ self.username = "testUseraddUIDAlreadyExists1"
+ self.local.useradd(self.username, uid=1025)
+ try:
+ self.local.useradd("testUseraddUIDAlreadyExists2", uid=1025)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_user(self.username)
+
+class UserdelTest(LocalTest):
+ def testUserdel(self):
+ self.add_user("testUserdel")
+ self.assertEquals(os.access("/home/testUserdel", os.F_OK), True)
+ self.validate_user("testUserdel")
+ self.local.userdel("testUserdel")
+ self.validate_no_user("testUserdel")
+ self.assertEquals(os.access("/home/testUserdel", os.F_OK), False)
+
+ def testUserdelNotHomedir(self):
+ self.add_user("testUserdel")
+ self.assertEquals(os.access("/home/testUserdel", os.F_OK), True)
+ self.validate_user("testUserdel")
+ self.local.userdel("testUserdel", remove=False)
+ self.validate_no_user("testUserdel")
+ self.assertEquals(os.access("/home/testUserdel", os.F_OK), True)
+ shutil.rmtree("/home/testUserdel")
+ os.remove("/var/mail/testUserdel")
+
+ def testUserdelNegative(self):
+ self.validate_no_user("testUserdelNegative")
+ try:
+ self.local.userdel("testUserdelNegative")
+ except IOError, e:
+ self.assertEquals(e.errno, errno.ENOENT)
+ else:
+ fail("Was expecting exception")
+
+class UsermodTest(LocalTest):
+ def setUp(self):
+ self.local = pysss.local()
+ self.username = "UsermodTest"
+ self.add_user_not_home(self.username)
+
+ def tearDown(self):
+ self.remove_user_not_home(self.username)
+
+ def testUsermod(self):
+ "Test modifying user attributes"
+ self.local.usermod(self.username,
+ gecos="foo bar",
+ homedir="/home/foobar",
+ shell="/bin/zsh")
+ self.validate_user(self.username,
+ gecos="foo bar",
+ homeDirectory="/home/foobar",
+ loginShell="/bin/zsh")
+
+ def testUsermodUID(self):
+ "Test modifying UID"
+ self.local.usermod(self.username,
+ uid=1024)
+ self.validate_user(self.username,
+ uidNumber=1024)
+
+ def testUsermodGroupMembership(self):
+ "Test adding to and removing from groups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+
+ try:
+ self.local.usermod(self.username,
+ addgroups=["gr1","gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1","gr2"])
+ self.local.usermod(self.username,
+ rmgroups=["gr2"])
+ self.assertUserMembership(self.username,
+ ["gr1"])
+ self.local.usermod(self.username,
+ rmgroups=["gr1"])
+ self.assertUserMembership(self.username,
+ [])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+ def testUsermodLockUnlock(self):
+ "Test locking and unlocking user"
+ self.local.usermod(self.username,
+ lock=self.local.lock)
+ self.validate_user(self.username,
+ disabled="true")
+ self.local.usermod(self.username,
+ lock=self.local.unlock)
+ self.validate_user(self.username,
+ disabled="false")
+
+class GroupaddTest(LocalTest):
+ def tearDown(self):
+ if self.groupname:
+ self.remove_group(self.groupname)
+
+ def testGroupadd(self):
+ "Test adding a local group"
+ self.groupname = "testGroupadd"
+ self.local.groupadd(self.groupname)
+ self.validate_group(self.groupname)
+
+ def testGroupaddWithGID(self):
+ "Test adding a local group with a custom GID"
+ self.groupname = "testUseraddWithGID"
+ self.local.groupadd(self.groupname,
+ gid=1024)
+ self.validate_group(self.groupname,
+ gidNumber=1024)
+
+class GroupaddTestNegative(LocalTest):
+ def testGroupaddNoParams(self):
+ "Test that local.groupadd() requires the groupname parameter"
+ self.assertRaises(TypeError, self.local.groupadd)
+
+ def testGroupaddUserAlreadyExists(self):
+ "Test adding a local with a duplicite name"
+ self.groupname = "testGroupaddUserAlreadyExists"
+ self.local.groupadd(self.groupname)
+ try:
+ self.local.groupadd(self.groupname)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_group(self.groupname)
+
+ def testGroupaddGIDAlreadyExists(self):
+ "Test adding a local with a duplicite group ID"
+ self.groupname = "testGroupaddGIDAlreadyExists1"
+ self.local.groupadd(self.groupname, gid=1025)
+ try:
+ self.local.groupadd("testGroupaddGIDAlreadyExists2", gid=1025)
+ except IOError, e:
+ self.assertEquals(e.errno, errno.EEXIST)
+ else:
+ self.fail("Was expecting exception")
+ finally:
+ self.remove_group(self.groupname)
+
+class GroupdelTest(LocalTest):
+ def testGroupdel(self):
+ self.add_group("testGroupdel")
+ self.validate_group("testGroupdel")
+ self.local.groupdel("testGroupdel")
+ self.validate_no_group("testGroupdel")
+
+ def testGroupdelNegative(self):
+ self.validate_no_group("testGroupdelNegative")
+ try:
+ self.local.groupdel("testGroupdelNegative")
+ except IOError, e:
+ self.assertEquals(e.errno, errno.ENOENT)
+ else:
+ fail("Was expecting exception")
+
+
+class GroupmodTest(LocalTest):
+ def setUp(self):
+ self.local = pysss.local()
+ self.groupname = "GroupmodTest"
+ self.add_group(self.groupname)
+
+ def tearDown(self):
+ self.remove_group(self.groupname)
+
+ def testGroupmodGID(self):
+ "Test modifying UID"
+ self.local.groupmod(self.groupname,
+ gid=1024)
+ self.validate_group(self.groupname,
+ gidNumber=1024)
+
+ def testGroupmodGroupMembership(self):
+ "Test adding to groups"
+ self.add_group("gr1")
+ self.add_group("gr2")
+ try:
+ self.local.groupmod(self.groupname,
+ addgroups=["gr1","gr2"])
+ self.assertGroupMembership(self.groupname,
+ ["gr1","gr2"])
+ self.local.groupmod(self.groupname,
+ rmgroups=["gr2"])
+ self.assertGroupMembership(self.groupname,
+ ["gr1"])
+ self.local.groupmod(self.groupname,
+ rmgroups=["gr1"])
+ self.assertGroupMembership(self.groupname,
+ [])
+ finally:
+ self.remove_group("gr1")
+ self.remove_group("gr2")
+
+# -------------- run the test suite -------------- #
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/src/tests/refcount-tests.c b/src/tests/refcount-tests.c
new file mode 100644
index 00000000..db2a256e
--- /dev/null
+++ b/src/tests/refcount-tests.c
@@ -0,0 +1,232 @@
+/*
+ SSSD
+
+ Reference counting tests.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+
+#include "tests/common.h"
+#include "util/util.h"
+
+/* Interface under test */
+#include "util/refcount.h"
+
+/* Fail the test if object 'obj' does not have 'num' references. */
+#define REF_ASSERT(obj, num) \
+ fail_unless(((obj)->DO_NOT_TOUCH_THIS_MEMBER_refcount == (num)), \
+ "Reference count of " #obj " should be %d but is %d", \
+ (num), (obj)->DO_NOT_TOUCH_THIS_MEMBER_refcount)
+
+#define FILLER_SIZE 32
+
+struct foo {
+ REFCOUNT_COMMON;
+ char a[FILLER_SIZE];
+ char b[FILLER_SIZE];
+};
+
+struct bar {
+ char a[FILLER_SIZE];
+ REFCOUNT_COMMON;
+ char b[FILLER_SIZE];
+};
+
+struct baz {
+ char a[FILLER_SIZE];
+ char b[FILLER_SIZE];
+ REFCOUNT_COMMON;
+};
+
+#define SET_FILLER(target) do { \
+ memset((target)->a, 'a', FILLER_SIZE); \
+ memset((target)->b, 'b', FILLER_SIZE); \
+} while (0)
+
+#define CHECK_FILLER(target) do { \
+ int _counter; \
+ for (_counter = 0; _counter < FILLER_SIZE; _counter++) { \
+ fail_unless((target)->a[_counter] == 'a', "Corrupted memory in " \
+ #target "->a[%d] of size %d", _counter, FILLER_SIZE); \
+ fail_unless((target)->b[_counter] == 'b', "Corrupted memory in " \
+ #target "->b[%d] of size %d", _counter, FILLER_SIZE); \
+ } \
+} while (0)
+
+struct container {
+ struct foo *foo;
+ struct bar *bar;
+ struct baz *baz;
+};
+
+static struct container *global;
+
+START_TEST(test_refcount_basic)
+{
+ struct container *containers;
+ int i;
+
+ /* First allocate our global storage place. */
+ global = talloc(NULL, struct container);
+ fail_if(global == NULL);
+
+ /* Allocate foo. */
+ global->foo = rc_alloc(global, struct foo);
+ fail_if(global->foo == NULL);
+ SET_FILLER(global->foo);
+ REF_ASSERT(global->foo, 1);
+
+ /* Allocate bar. */
+ global->bar = rc_alloc(global, struct bar);
+ fail_if(global->bar == NULL);
+ SET_FILLER(global->bar);
+ REF_ASSERT(global->bar, 1);
+
+ /* Allocate baz. */
+ global->baz = rc_alloc(global, struct baz);
+ fail_if(global->baz == NULL);
+ SET_FILLER(global->baz);
+ REF_ASSERT(global->baz, 1);
+
+ /* Try multiple attaches. */
+ containers = talloc_array(NULL, struct container, 100);
+ fail_if(containers == NULL);
+ for (i = 0; i < 100; i++) {
+ containers[i].foo = rc_reference(containers, struct foo, global->foo);
+ containers[i].bar = rc_reference(containers, struct bar, global->bar);
+ containers[i].baz = rc_reference(containers, struct baz, global->baz);
+ REF_ASSERT(containers[i].foo, i + 2);
+ REF_ASSERT(global->foo, i + 2);
+ REF_ASSERT(containers[i].bar, i + 2);
+ REF_ASSERT(global->bar, i + 2);
+ REF_ASSERT(containers[i].baz, i + 2);
+ REF_ASSERT(global->baz, i + 2);
+ }
+ talloc_free(containers);
+
+ CHECK_FILLER(global->foo);
+ CHECK_FILLER(global->bar);
+ CHECK_FILLER(global->baz);
+
+ REF_ASSERT(global->foo, 1);
+ REF_ASSERT(global->bar, 1);
+ REF_ASSERT(global->baz, 1);
+
+ talloc_free(global);
+}
+END_TEST
+
+START_TEST(test_refcount_swap)
+{
+ void *tmp_ctx;
+ struct container *container1;
+ struct container *container2;
+
+ tmp_ctx = talloc_new(NULL);
+
+ check_leaks_push(tmp_ctx);
+
+ container1 = talloc(tmp_ctx, struct container);
+ container2 = talloc(tmp_ctx, struct container);
+
+ /* Allocate. */
+ container1->foo = rc_alloc(container1, struct foo);
+ fail_if(container1->foo == NULL);
+ SET_FILLER(container1->foo);
+
+ /* Reference. */
+ container2->foo = rc_reference(container2, struct foo, container1->foo);
+ fail_if(container2->foo == NULL);
+
+ /* Make sure everything is as it should be. */
+ fail_unless(container1->foo == container2->foo);
+ REF_ASSERT(container1->foo, 2);
+
+ /* Free in reverse order. */
+ talloc_free(container1);
+ REF_ASSERT(container2->foo, 1);
+ CHECK_FILLER(container2->foo);
+ talloc_free(container2);
+
+ check_leaks_pop(tmp_ctx);
+ talloc_free(tmp_ctx);
+}
+END_TEST
+
+Suite *create_suite(void)
+{
+ Suite *s = suite_create("refcount");
+
+ TCase *tc = tcase_create("REFCOUNT Tests");
+
+ /* Do some testing */
+ tcase_add_checked_fixture(tc, leak_check_setup, leak_check_teardown);
+ tcase_add_test(tc, test_refcount_basic);
+ tcase_add_test(tc, test_refcount_swap);
+
+ /* Add all test cases to the test suite */
+ suite_add_tcase(s, tc);
+
+ return s;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int failure_count;
+ Suite *suite;
+ SRunner *sr;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+ debug_level = debug;
+
+ suite = create_suite();
+ sr = srunner_create(suite);
+ srunner_set_fork_status(sr, CK_FORK);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c
new file mode 100644
index 00000000..04b9e2e7
--- /dev/null
+++ b/src/tests/resolv-tests.c
@@ -0,0 +1,598 @@
+/*
+ SSSD
+
+ Async resolver tests
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <string.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+#include <arpa/inet.h>
+
+#include "tests/common.h"
+#include "util/util.h"
+
+/* Interface under test */
+#include "resolv/async_resolv.h"
+
+static int use_net_test;
+static char *txt_host;
+static char *srv_host;
+
+struct resolv_test_ctx {
+ struct tevent_context *ev;
+ struct resolv_ctx *resolv;
+
+ enum {
+ TESTING_HOSTNAME,
+ TESTING_TXT,
+ TESTING_SRV,
+ } tested_function;
+
+ int error;
+ bool done;
+};
+
+static int setup_resolv_test(struct resolv_test_ctx **ctx)
+{
+ struct resolv_test_ctx *test_ctx;
+ int ret;
+
+ test_ctx = talloc_zero(global_talloc_context, struct resolv_test_ctx);
+ if (test_ctx == NULL) {
+ fail("Could not allocate memory for test context");
+ return ENOMEM;
+ }
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ if (test_ctx->ev == NULL) {
+ fail("Could not init tevent context");
+ talloc_free(test_ctx);
+ return EFAULT;
+ }
+
+ ret = resolv_init(test_ctx, test_ctx->ev, 5, &test_ctx->resolv);
+ if (ret != EOK) {
+ fail("Could not init resolv context");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ *ctx = test_ctx;
+ return EOK;
+}
+
+static int test_loop(struct resolv_test_ctx *data)
+{
+ while (!data->done)
+ tevent_loop_once(data->ev);
+
+ return data->error;
+}
+
+START_TEST(test_copy_hostent)
+{
+ void *ctx;
+ struct hostent *new_he;
+
+ char name[] = "foo.example.com";
+ char alias_1[] = "bar.example.com";
+ char alias_2[] = "baz.example.com";
+ char *aliases[] = { alias_1, alias_2, NULL };
+ char addr_1[] = { 1, 2, 3, 4 };
+ char addr_2[] = { 4, 3, 2, 1 };
+ char *addr_list[] = { addr_1, addr_2, NULL };
+ struct hostent he = {
+ name, aliases, 123 /* Whatever. */,
+ sizeof(addr_1), addr_list
+ };
+
+ ctx = talloc_new(global_talloc_context);
+ fail_if(ctx == NULL);
+
+ check_leaks_push(ctx);
+ new_he = resolv_copy_hostent(ctx, &he);
+ fail_if(new_he == NULL);
+ fail_if(strcmp(new_he->h_name, name));
+ fail_if(strcmp(new_he->h_aliases[0], alias_1));
+ fail_if(strcmp(new_he->h_aliases[1], alias_2));
+ fail_if(new_he->h_aliases[2] != NULL);
+ fail_if(new_he->h_addrtype != 123);
+ fail_if(new_he->h_length != sizeof(addr_1));
+ fail_if(memcmp(new_he->h_addr_list[0], addr_1, sizeof(addr_1)));
+ fail_if(memcmp(new_he->h_addr_list[1], addr_2, sizeof(addr_1)));
+ fail_if(new_he->h_addr_list[2] != NULL);
+
+ talloc_free(new_he);
+ check_leaks_pop(ctx);
+}
+END_TEST
+
+static void test_localhost(struct tevent_req *req)
+{
+ int recv_status;
+ int status;
+ struct hostent *hostent;
+ int i;
+ struct resolv_test_ctx *test_ctx = tevent_req_callback_data(req,
+ struct resolv_test_ctx);
+
+ test_ctx->done = true;
+
+ recv_status = resolv_gethostbyname_recv(req, test_ctx,
+ &status, NULL, &hostent);
+ talloc_zfree(req);
+ if (recv_status != EOK) {
+ DEBUG(2, ("resolv_gethostbyname_recv failed: %d\n", recv_status));
+ test_ctx->error = recv_status;
+ return;
+ }
+ DEBUG(7, ("resolv_gethostbyname_recv status: %d\n", status));
+
+ test_ctx->error = ENOENT;
+ for (i = 0; hostent->h_addr_list[i]; i++) {
+ char addr_buf[256];
+ inet_ntop(hostent->h_addrtype, hostent->h_addr_list[i], addr_buf, sizeof(addr_buf));
+
+ /* test that localhost resolves to 127.0.0.1 or ::1 */
+ if (strcmp(addr_buf, "127.0.0.1") == 0 || strcmp(addr_buf, "::1") == 0) {
+ test_ctx->error = EOK;
+ }
+ }
+ talloc_free(hostent);
+}
+
+START_TEST(test_resolv_localhost)
+{
+ struct resolv_test_ctx *test_ctx;
+ int ret = EOK;
+ struct tevent_req *req;
+ const char *hostname = "localhost.localdomain";
+
+ ret = setup_resolv_test(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up test");
+ return;
+ }
+
+ check_leaks_push(test_ctx);
+ req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+ DEBUG(7, ("Sent resolv_gethostbyname\n"));
+ if (req == NULL) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_localhost, test_ctx);
+ ret = test_loop(test_ctx);
+ }
+
+ check_leaks_pop(test_ctx);
+ fail_unless(ret == EOK);
+
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+static void test_negative(struct tevent_req *req)
+{
+ int recv_status;
+ int status;
+ struct hostent *hostent;
+ struct resolv_test_ctx *test_ctx;
+
+ test_ctx = tevent_req_callback_data(req, struct resolv_test_ctx);
+ test_ctx->done = true;
+
+ recv_status = resolv_gethostbyname_recv(req, test_ctx,
+ &status, NULL, &hostent);
+ talloc_zfree(req);
+ if (recv_status == EOK) {
+ DEBUG(7, ("resolv_gethostbyname_recv succeeded in a negative test"));
+ return;
+ }
+
+ test_ctx->error = status;
+ DEBUG(2, ("resolv_gethostbyname_recv status: %d: %s\n", status, resolv_strerror(status)));
+}
+
+START_TEST(test_resolv_negative)
+{
+ int ret = EOK;
+ struct tevent_req *req;
+ const char *hostname = "sssd.foo";
+ struct resolv_test_ctx *test_ctx;
+
+ ret = setup_resolv_test(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up test");
+ return;
+ }
+
+ check_leaks_push(test_ctx);
+ req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+ DEBUG(7, ("Sent resolv_gethostbyname\n"));
+ if (req == NULL) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_negative, test_ctx);
+ ret = test_loop(test_ctx);
+ }
+
+ check_leaks_pop(test_ctx);
+
+ fail_unless(ret != EOK);
+ fail_unless(test_ctx->error == ARES_ENOTFOUND);
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+static void test_internet(struct tevent_req *req)
+{
+ int recv_status;
+ int status;
+ struct resolv_test_ctx *test_ctx;
+ void *tmp_ctx;
+ struct hostent *hostent = NULL;
+ struct ares_txt_reply *txt_replies = NULL, *txtptr;
+ struct ares_srv_reply *srv_replies = NULL, *srvptr;
+
+ test_ctx = tevent_req_callback_data(req, struct resolv_test_ctx);
+
+ test_ctx->done = true;
+
+ tmp_ctx = talloc_new(test_ctx);
+ check_leaks_push(tmp_ctx);
+
+ switch (test_ctx->tested_function) {
+ case TESTING_HOSTNAME:
+ recv_status = resolv_gethostbyname_recv(req, tmp_ctx,
+ &status, NULL, &hostent);
+ test_ctx->error = (hostent->h_length == 0) ? ENOENT : EOK;
+ break;
+ case TESTING_TXT:
+ recv_status = resolv_gettxt_recv(tmp_ctx, req, &status, NULL,
+ &txt_replies);
+ test_ctx->error = (txt_replies == NULL) ? ENOENT : EOK;
+ for (txtptr = txt_replies; txtptr != NULL; txtptr = txtptr->next) {
+ DEBUG(2, ("TXT Record: %s\n", txtptr->txt));
+ }
+ break;
+ case TESTING_SRV:
+ recv_status = resolv_getsrv_recv(tmp_ctx, req, &status, NULL,
+ &srv_replies);
+ test_ctx->error = (srv_replies == NULL) ? ENOENT : EOK;
+ for (srvptr = srv_replies; srvptr != NULL; srvptr = srvptr->next) {
+ DEBUG(2, ("SRV Record: %d %d %d %s\n", srvptr->weight,
+ srvptr->priority, srvptr->port,
+ srvptr->host));
+ }
+ break;
+ }
+ talloc_zfree(req);
+ fail_if(recv_status != EOK, "The recv function failed: %d", recv_status);
+ DEBUG(7, ("recv status: %d\n", status));
+
+ if (hostent != NULL) {
+ talloc_free(hostent);
+ } else if (txt_replies != NULL) {
+ talloc_free(txt_replies);
+ } else if (srv_replies != NULL) {
+ talloc_free(srv_replies);
+ }
+ check_leaks_pop(tmp_ctx);
+}
+
+START_TEST(test_resolv_internet)
+{
+ int ret = EOK;
+ struct tevent_req *req;
+ const char *hostname = "redhat.com";
+ struct resolv_test_ctx *test_ctx;
+
+ ret = setup_resolv_test(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up test");
+ return;
+ }
+ test_ctx->tested_function = TESTING_HOSTNAME;
+
+ check_leaks_push(test_ctx);
+ req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+ DEBUG(7, ("Sent resolv_gethostbyname\n"));
+ if (req == NULL) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_internet, test_ctx);
+ ret = test_loop(test_ctx);
+ }
+
+ fail_unless(ret == EOK);
+ check_leaks_pop(test_ctx);
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+START_TEST(test_resolv_internet_txt)
+{
+ int ret;
+ struct tevent_req *req;
+ struct resolv_test_ctx *test_ctx;
+
+ ret = setup_resolv_test(&test_ctx);
+ fail_if(ret != EOK, "Could not set up test");
+ test_ctx->tested_function = TESTING_TXT;
+
+ check_leaks_push(test_ctx);
+
+ req = resolv_gettxt_send(test_ctx, test_ctx->ev, test_ctx->resolv, txt_host);
+ fail_if(req == NULL, "Function resolv_gettxt_send failed");
+
+ tevent_req_set_callback(req, test_internet, test_ctx);
+ ret = test_loop(test_ctx);
+ fail_unless(ret == EOK);
+
+ check_leaks_pop(test_ctx);
+
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+START_TEST(test_resolv_internet_srv)
+{
+ int ret;
+ struct tevent_req *req;
+ struct resolv_test_ctx *test_ctx;
+
+ ret = setup_resolv_test(&test_ctx);
+ fail_if(ret != EOK, "Could not set up test");
+ test_ctx->tested_function = TESTING_SRV;
+
+ check_leaks_push(test_ctx);
+
+ req = resolv_getsrv_send(test_ctx, test_ctx->ev, test_ctx->resolv, srv_host);
+ fail_if(req == NULL, "Function resolv_getsrv_send failed");
+
+ tevent_req_set_callback(req, test_internet, test_ctx);
+ ret = test_loop(test_ctx);
+ fail_unless(ret == EOK);
+
+ check_leaks_pop(test_ctx);
+
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+static void resolv_free_context(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct resolv_ctx *rctx = talloc_get_type(ptr, struct resolv_ctx);
+ DEBUG(7, ("freeing the context\n"));
+
+ talloc_free(rctx);
+}
+
+static void resolv_free_done(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct resolv_test_ctx *tctx = talloc_get_type(ptr, struct resolv_test_ctx);
+ DEBUG(7, ("marking test as done\n"));
+
+ tctx->error = EOK;
+ tctx->done = true;
+}
+
+START_TEST(test_resolv_free_context)
+{
+ int ret = EOK;
+ struct tevent_req *req;
+ const char *hostname = "redhat.com";
+ struct resolv_test_ctx *test_ctx;
+ struct tevent_timer *free_timer, *terminate_timer;
+ struct timeval free_tv, terminate_tv;
+
+ ret = setup_resolv_test(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up test");
+ return;
+ }
+
+ req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+ DEBUG(7, ("Sent resolv_gethostbyname\n"));
+ if (req == NULL) {
+ fail("Error calling resolv_gethostbyname_send");
+ goto done;
+ }
+
+ gettimeofday(&free_tv, NULL);
+ free_tv.tv_sec += 1;
+ free_tv.tv_usec = 0;
+ terminate_tv.tv_sec = free_tv.tv_sec + 1;
+ terminate_tv.tv_usec = 0;
+
+ free_timer = tevent_add_timer(test_ctx->ev, test_ctx, free_tv, resolv_free_context, test_ctx->resolv);
+ if (free_timer == NULL) {
+ fail("Error calling tevent_add_timer");
+ goto done;
+ }
+
+ terminate_timer = tevent_add_timer(test_ctx->ev, test_ctx, terminate_tv, resolv_free_done, test_ctx);
+ if (terminate_timer == NULL) {
+ fail("Error calling tevent_add_timer");
+ goto done;
+ }
+
+ ret = test_loop(test_ctx);
+ fail_unless(ret == EOK);
+
+done:
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+static void resolv_free_req(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct tevent_req *req = talloc_get_type(ptr, struct tevent_req);
+ DEBUG(7, ("freeing the request\n"));
+
+ talloc_free(req);
+}
+
+START_TEST(test_resolv_free_req)
+{
+ int ret = EOK;
+ struct tevent_req *req;
+ const char *hostname = "redhat.com";
+ struct resolv_test_ctx *test_ctx;
+ struct tevent_timer *free_timer, *terminate_timer;
+ struct timeval free_tv, terminate_tv;
+
+ ret = setup_resolv_test(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up test");
+ return;
+ }
+
+ check_leaks_push(test_ctx);
+ req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname);
+ DEBUG(7, ("Sent resolv_gethostbyname\n"));
+ if (req == NULL) {
+ fail("Error calling resolv_gethostbyname_send");
+ goto done;
+ }
+
+ gettimeofday(&free_tv, NULL);
+ free_tv.tv_sec += 1;
+ free_tv.tv_usec = 0;
+ terminate_tv.tv_sec = free_tv.tv_sec + 1;
+ terminate_tv.tv_usec = 0;
+
+ free_timer = tevent_add_timer(test_ctx->ev, test_ctx, free_tv, resolv_free_req, req);
+ if (free_timer == NULL) {
+ fail("Error calling tevent_add_timer");
+ goto done;
+ }
+
+ terminate_timer = tevent_add_timer(test_ctx->ev, test_ctx, terminate_tv, resolv_free_done, test_ctx);
+ if (terminate_timer == NULL) {
+ fail("Error calling tevent_add_timer");
+ goto done;
+ }
+
+ ret = test_loop(test_ctx);
+ check_leaks_pop(test_ctx);
+ fail_unless(ret == EOK);
+
+done:
+ talloc_zfree(test_ctx);
+}
+END_TEST
+
+Suite *create_resolv_suite(void)
+{
+ Suite *s = suite_create("resolv");
+
+ TCase *tc_resolv = tcase_create("RESOLV Tests");
+
+ tcase_add_checked_fixture(tc_resolv, leak_check_setup, leak_check_teardown);
+ /* Do some testing */
+ tcase_add_test(tc_resolv, test_copy_hostent);
+ tcase_add_test(tc_resolv, test_resolv_localhost);
+ tcase_add_test(tc_resolv, test_resolv_negative);
+ if (use_net_test) {
+ tcase_add_test(tc_resolv, test_resolv_internet);
+ if (txt_host != NULL) {
+ tcase_add_test(tc_resolv, test_resolv_internet_txt);
+ }
+ if (srv_host != NULL) {
+ tcase_add_test(tc_resolv, test_resolv_internet_srv);
+ }
+ }
+ tcase_add_test(tc_resolv, test_resolv_free_context);
+ tcase_add_test(tc_resolv, test_resolv_free_req);
+
+ /* Add all test cases to the test suite */
+ suite_add_tcase(s, tc_resolv);
+
+ return s;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int failure_count;
+ Suite *resolv_suite;
+ SRunner *sr;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ { "use-net-test", 'n', POPT_ARG_NONE, 0, 'n', "Run tests that need an active internet connection", NULL },
+ { "txt-host", 't', POPT_ARG_STRING, 0, 't', "Specify the host used for TXT record testing", NULL },
+ { "srv-host", 's', POPT_ARG_STRING, 0, 's', "Specify the host used for SRV record testing", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ case 'n':
+ use_net_test = 1;
+ break;
+ case 't':
+ txt_host = poptGetOptArg(pc);
+ break;
+ case 's':
+ srv_host = poptGetOptArg(pc);
+ break;
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+ debug_level = debug;
+
+ resolv_suite = create_resolv_suite();
+ sr = srunner_create(resolv_suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
diff --git a/src/tests/stress-tests.c b/src/tests/stress-tests.c
new file mode 100644
index 00000000..94505318
--- /dev/null
+++ b/src/tests/stress-tests.c
@@ -0,0 +1,328 @@
+/*
+ SSSD
+
+ Stress tests
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <signal.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+
+#include "util/util.h"
+
+#define DEFAULT_START 10
+#define DEFAULT_STOP 20
+
+#define NAME_SIZE 255
+#define CHUNK 64
+
+
+/* How many tests failed */
+int failure_count;
+
+/* Be chatty */
+int verbose;
+
+/*
+ * Look up one user. If the user is not found using getpwnam, the success
+ * or failure depends on enoent_fail being set.
+ */
+int test_lookup_user(const char *name, int enoent_fail)
+{
+ struct passwd *pwd = NULL;
+ int ret = 0;
+ int error;
+
+ errno = 0;
+ pwd = getpwnam(name);
+ error = errno;
+ if (pwd == NULL) {
+ if (errno == 0 || errno == ENOENT) {
+ ret = (enoent_fail == 1) ? ENOENT : 0;
+ }
+ }
+
+ if (ret != 0 && verbose) {
+ fprintf(stderr,
+ "getpwnam failed (name: %s): errno = %d, error = %s\n",
+ name, ret, strerror(ret));
+ }
+
+ return ret;
+}
+
+/*
+ * Look up one group. If the user is not found using getgrnam, the success
+ * or failure depends on enoent_fail being set.
+ */
+int test_lookup_group(const char *name, int enoent_fail)
+{
+ struct group *grp = NULL;
+ int ret = 0;
+
+ errno = 0;
+ grp = getgrnam(name);
+ if (grp == NULL) {
+ if (errno == 0 || errno == ENOENT) {
+ ret = enoent_fail ? ENOENT : 0;
+ }
+ }
+
+ if (ret != 0 && verbose) {
+ fprintf(stderr,
+ "getgrnam failed (name %s): errno = %d, error = %s\n",
+ name, ret, strerror(ret));
+ }
+
+ return ret;
+}
+
+int run_one_testcase(const char *name, int group, int enoent_fail)
+{
+ if (group) {
+ return test_lookup_group(name, enoent_fail);
+ } else {
+ return test_lookup_user(name, enoent_fail);
+ }
+}
+
+/*
+ * Beware, has side-effects: changes global variable failure_count
+ */
+void child_handler(int signum)
+{
+ int status, ret;
+
+ while ((ret = wait(&status)) > 0) {
+ if (ret == -1) {
+ perror("wait");
+ exit(EXIT_FAILURE);
+ }
+
+ if (WIFEXITED(status)) {
+ ret = WEXITSTATUS(status);
+ if (ret) {
+ if (verbose) {
+ fprintf(stderr,
+ "A child exited with error code %d\n",
+ WEXITSTATUS(status));
+ }
+ ++failure_count;
+ }
+ } else ++failure_count;
+ }
+}
+
+int generate_names(TALLOC_CTX *mem_ctx, const char *prefix,
+ int start, int stop, char ***_out)
+{
+ char **out;
+ int num_names = stop-start+1;
+ int idx = 0;
+
+ out = talloc_array(mem_ctx, char *, num_names+1);
+ if (out == NULL) {
+ return ENOMEM;
+ }
+
+ for (idx = 0; idx < num_names; ++idx) {
+ out[idx] = talloc_asprintf(mem_ctx, "%s%d", prefix, idx);
+ if (out[idx] == NULL) {
+ return ENOMEM;
+ }
+ }
+ out[idx] = NULL;
+
+ *_out = out;
+ return EOK;
+}
+
+int read_names(TALLOC_CTX *mem_ctx, FILE *stream, char ***_out)
+{
+ char one_name[NAME_SIZE];
+ int n = 0;
+ int array_size = CHUNK;
+ int ret;
+ char **out;
+
+ out = talloc_array(mem_ctx, char *, CHUNK+1);
+ if (out == NULL) {
+ return ENOMEM;
+ }
+ while (fgets(one_name, NAME_SIZE, stream)) {
+ out[n] = talloc_strdup(mem_ctx, one_name);
+ if (out[n] == NULL) {
+ return ENOMEM;
+ }
+ if ((n++ % CHUNK) == 0) {
+ array_size += CHUNK;
+ out = talloc_realloc(mem_ctx, out, char *, array_size);
+ if (out == NULL) {
+ return ENOMEM;
+ }
+ }
+ }
+
+ if ((ret = ferror(stream))) {
+ return ret;
+ }
+ out[n] = NULL;
+
+ *_out = out;
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int pc_start=DEFAULT_START;
+ int pc_stop=DEFAULT_STOP;
+ int pc_enoent_fail=0;
+ int pc_groups=0;
+ int pc_verbosity = 0;
+ char *pc_prefix = NULL;
+ TALLOC_CTX *ctx = NULL;
+ char **names = NULL;
+
+ int status, idx, ret;
+ pid_t pid;
+ struct sigaction action, old_action;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "groups", 'g', POPT_ARG_NONE, &pc_groups, 0,
+ "Lookup in groups instead of users" },
+ { "prefix", '\0', POPT_ARG_STRING, &pc_prefix, 0,
+ "The username prefix", NULL },
+ { "start", '\0', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
+ &pc_start, 0,
+ "Start value to append to prefix", NULL },
+ { "stop", '\0', POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT,
+ &pc_stop, 0,
+ "End value to append to prefix", NULL },
+ { "enoent-fail", '\0', POPT_ARG_NONE, &pc_enoent_fail, 0,
+ "Fail on not getting the requested NSS data (default: No)",
+ NULL },
+ { "verbose", 'v', POPT_ARG_NONE, 0, 'v',
+ "Be verbose" },
+ POPT_TABLEEND
+ };
+
+ /* parse the params */
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'v':
+ pc_verbosity = 1;
+ break;
+
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ verbose = pc_verbosity;
+
+ if (pc_prefix) {
+ ret = generate_names(ctx, pc_prefix, pc_start, pc_stop, &names);
+ if (ret != EOK) {
+ if (verbose) {
+ errno = ret;
+ perror("generate_names");
+ }
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ ret = read_names(ctx, stdin, &names);
+ if (ret != EOK) {
+ if (verbose) {
+ errno = ret;
+ perror("read_names");
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Reap the children in a handler asynchronously so we can
+ * somehow protect against too many processes */
+ action.sa_handler = child_handler;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGCHLD);
+ action.sa_flags = SA_NOCLDSTOP;
+
+ sigaction(SIGCHLD, &action, &old_action);
+
+ /* Fire up the child processes */
+ idx = 0;
+ for (idx=0; names[idx]; idx++) {
+ pid = fork();
+ if (pid == -1) {
+ /* Try again in hope that some child has exited */
+ if (errno == EAGAIN) {
+ continue;
+ }
+ perror("fork");
+ exit(EXIT_FAILURE);
+ } else if ( pid == 0 ) {
+ /* child */
+ ret = run_one_testcase(names[idx], pc_groups, pc_enoent_fail);
+ exit(ret);
+ }
+ }
+
+ /* Process the rest of the children here in main */
+ sigaction(SIGCHLD, &old_action, NULL);
+ while ((ret = wait(&status)) > 0) {
+ if (ret == -1) {
+ perror("wait");
+ exit(EXIT_FAILURE);
+ }
+
+ if (WIFEXITED(status)) {
+ ret = WEXITSTATUS(status);
+ if (ret) {
+ if (verbose) {
+ fprintf(stderr,
+ "A child exited with error code %d\n",
+ WEXITSTATUS(status));
+ }
+ ++failure_count;
+ }
+ } else ++failure_count;
+ }
+
+ if (pc_verbosity) {
+ fprintf(stderr,
+ "Total tests run: %d\nPassed: %d\nFailed: %d\n",
+ idx,
+ idx - failure_count,
+ failure_count);
+ }
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/src/tests/strtonum-tests.c b/src/tests/strtonum-tests.c
new file mode 100644
index 00000000..7b9cf522
--- /dev/null
+++ b/src/tests/strtonum-tests.c
@@ -0,0 +1,455 @@
+/*
+ SSSD
+
+ InfoPipe
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <errno.h>
+#include <popt.h>
+#include "util/util.h"
+#include "util/strtonum.h"
+
+/********************
+ * Utility routines *
+ ********************/
+#define EXPECT_UNSET_ERRNO(error) \
+ do { \
+ fail_unless(error == 0, "errno unexpectedly set to %d[%s]", \
+ error, strerror(error)); \
+ } while(0)
+
+#define CHECK_RESULT(expected, actual) \
+ do { \
+ fail_unless(result == expected, "Expected %ld, got %ld", \
+ expected, result); \
+ } while(0)
+
+#define CHECK_ERRNO(expected, actual) \
+ do { \
+ fail_unless(error == ERANGE, "Expected errno %d[%s], got %d[%s]", \
+ ERANGE, strerror(ERANGE), \
+ error, strerror(ERANGE)); \
+ } while(0)
+
+#define CHECK_ENDPTR(expected, actual) \
+ do { \
+ fail_unless(actual == expected, "Expected endptr %p, got %p", \
+ expected, actual); \
+ } while(0)
+
+#define CHECK_ZERO_ENDPTR(endptr) \
+ do { \
+ fail_unless(endptr && *endptr == '\0', "Invalid endptr"); \
+ } while(0);
+
+/******************
+ * strtoint tests *
+ ******************/
+
+/* Base-10 */
+START_TEST (test_strtoint32_pos_integer_base_10)
+{
+ int32_t result;
+ const char *input = "123";
+ int32_t expected = 123;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_neg_integer_base_10)
+{
+ int32_t result;
+ const char *input = "-123";
+ int32_t expected = -123;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_pos_integer_intmax_base_10)
+{
+ int32_t result;
+ const char *input = "2147483647";
+ int32_t expected = INT32_MAX;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_neg_integer_intmin_base_10)
+{
+ int32_t result;
+ const char *input = "-2147483648";
+ int32_t expected = INT32_MIN;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_pos_integer_overflow_base_10)
+{
+ int32_t result;
+ const char *input = "8589934592";
+ int32_t expected = INT32_MAX;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ CHECK_ERRNO(ERANGE, error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, actual);
+}
+END_TEST
+
+START_TEST (test_strtoint32_pos_integer_underflow_base_10)
+{
+ int32_t result;
+ const char *input = "-8589934592";
+ int32_t expected = INT32_MIN;
+ char *endptr;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ CHECK_ERRNO(ERANGE, error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, actual);
+}
+END_TEST
+
+START_TEST (test_strtoint32_mixed_alphanumeric_base_10)
+{
+ int32_t result;
+ const char *input = "12b13";
+ int32_t expected = 12;
+ char *endptr;
+ const char *expected_endptr = input+2;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_alphaonly_base_10)
+{
+ int32_t result;
+ const char *input = "alpha";
+ int32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_alphastart_base_10)
+{
+ int32_t result;
+ const char *input = "alpha12345";
+ int32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtoint32_emptystring_base_10)
+{
+ int32_t result;
+ const char *input = "";
+ int32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtoint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+/*******************
+ * strtouint tests *
+ *******************/
+
+/* Base-10 */
+START_TEST (test_strtouint32_pos_integer_base_10)
+{
+ uint32_t result;
+ const char *input = "123";
+ uint32_t expected = 123;
+ char *endptr;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtouint32_neg_integer_base_10)
+{
+ uint32_t result;
+ const char *input = "-123";
+ uint32_t expected = -123;
+ char *endptr;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtouint32_pos_integer_uintmax_base_10)
+{
+ uint32_t result;
+ const char *input = "4294967295";
+ uint32_t expected = UINT32_MAX;
+ char *endptr;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+
+START_TEST (test_strtouint32_pos_integer_overflow_base_10)
+{
+ uint32_t result;
+ const char *input = "8589934592";
+ uint32_t expected = UINT32_MAX;
+ char *endptr;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ CHECK_ERRNO(ERANGE, error);
+ CHECK_ZERO_ENDPTR(endptr);
+ CHECK_RESULT(expected, actual);
+}
+END_TEST
+
+START_TEST (test_strtouint32_mixed_alphanumeric_base_10)
+{
+ uint32_t result;
+ const char *input = "12b13";
+ uint32_t expected = 12;
+ char *endptr;
+ const char *expected_endptr = input+2;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtouint32_alphaonly_base_10)
+{
+ uint32_t result;
+ const char *input = "alpha";
+ uint32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtouint32_alphastart_base_10)
+{
+ uint32_t result;
+ const char *input = "alpha12345";
+ uint32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+START_TEST (test_strtouint32_emptystring_base_10)
+{
+ uint32_t result;
+ const char *input = "";
+ uint32_t expected = 0;
+ char *endptr;
+ const char *expected_endptr = input;
+ errno_t error;
+
+ result = strtouint32(input, &endptr, 10);
+ error = errno;
+
+ EXPECT_UNSET_ERRNO(error);
+ CHECK_ENDPTR(expected_endptr, endptr);
+ CHECK_RESULT(expected, result);
+}
+END_TEST
+
+
+
+Suite *create_strtonum_suite(void)
+{
+ Suite *s = suite_create("strtonum");
+
+ TCase *tc_strtoint32 = tcase_create("strtoint32 Tests");
+ tcase_add_test(tc_strtoint32, test_strtoint32_pos_integer_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_neg_integer_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_pos_integer_intmax_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_neg_integer_intmin_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_pos_integer_overflow_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_pos_integer_underflow_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_mixed_alphanumeric_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_alphaonly_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_alphastart_base_10);
+ tcase_add_test(tc_strtoint32, test_strtoint32_emptystring_base_10);
+
+ TCase *tc_strtouint32 = tcase_create("strtouint32 Tests");
+ tcase_add_test(tc_strtouint32, test_strtouint32_pos_integer_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_neg_integer_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_pos_integer_uintmax_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_pos_integer_overflow_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_mixed_alphanumeric_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_alphaonly_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_alphastart_base_10);
+ tcase_add_test(tc_strtouint32, test_strtouint32_emptystring_base_10);
+
+ /* Add all test cases to the suite */
+ suite_add_tcase(s, tc_strtoint32);
+ suite_add_tcase(s, tc_strtouint32);
+
+ return s;
+}
+
+
+int main(int argc, const char *argv[]) {
+ int opt;
+ poptContext pc;
+ int failure_count;
+ Suite *strtonum_suite;
+ SRunner *sr;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ strtonum_suite = create_strtonum_suite();
+ sr = srunner_create(strtonum_suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
new file mode 100644
index 00000000..8b486b69
--- /dev/null
+++ b/src/tests/sysdb-tests.c
@@ -0,0 +1,3330 @@
+/*
+ SSSD
+
+ System Database
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "util/util.h"
+#include "confdb/confdb_setup.h"
+#include "db/sysdb_private.h"
+
+#define TESTS_PATH "tests_sysdb"
+#define TEST_CONF_FILE "tests_conf.ldb"
+
+#define TEST_ATTR_NAME "test_attr_name"
+#define TEST_ATTR_VALUE "test_attr_value"
+#define TEST_ATTR_UPDATE_VALUE "test_attr_update_value"
+#define TEST_ATTR_ADD_NAME "test_attr_add_name"
+#define TEST_ATTR_ADD_VALUE "test_attr_add_value"
+#define CUSTOM_TEST_CONTAINER "custom_test_container"
+#define CUSTOM_TEST_OBJECT "custom_test_object"
+
+#define ASQ_TEST_USER "testuser27010"
+#define ASQ_TEST_USER_UID 27010
+
+#define MBO_USER_BASE 27500
+#define MBO_GROUP_BASE 28500
+
+struct sysdb_test_ctx {
+ struct sysdb_ctx *sysdb;
+ struct confdb_ctx *confdb;
+ struct tevent_context *ev;
+ struct sss_domain_info *domain;
+};
+
+static int setup_sysdb_tests(struct sysdb_test_ctx **ctx)
+{
+ struct sysdb_test_ctx *test_ctx;
+ char *conf_db;
+ int ret;
+
+ const char *val[2];
+ val[1] = NULL;
+
+ /* Create tests directory if it doesn't exist */
+ /* (relative to current dir) */
+ ret = mkdir(TESTS_PATH, 0775);
+ if (ret == -1 && errno != EEXIST) {
+ fail("Could not create %s directory", TESTS_PATH);
+ return EFAULT;
+ }
+
+ test_ctx = talloc_zero(NULL, struct sysdb_test_ctx);
+ if (test_ctx == NULL) {
+ fail("Could not allocate memory for test context");
+ return ENOMEM;
+ }
+
+ /* Create an event context
+ * It will not be used except in confdb_init and sysdb_init
+ */
+ test_ctx->ev = tevent_context_init(test_ctx);
+ if (test_ctx->ev == NULL) {
+ fail("Could not create event context");
+ talloc_free(test_ctx);
+ return EIO;
+ }
+
+ conf_db = talloc_asprintf(test_ctx, "%s/%s", TESTS_PATH, TEST_CONF_FILE);
+ if (conf_db == NULL) {
+ fail("Out of memory, aborting!");
+ talloc_free(test_ctx);
+ return ENOMEM;
+ }
+ DEBUG(3, ("CONFDB: %s\n", conf_db));
+
+ /* Connect to the conf db */
+ ret = confdb_init(test_ctx, &test_ctx->confdb, conf_db);
+ if (ret != EOK) {
+ fail("Could not initialize connection to the confdb");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "LOCAL";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/sssd", "domains", val);
+ if (ret != EOK) {
+ fail("Could not initialize domains placeholder");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "local";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "id_provider", val);
+ if (ret != EOK) {
+ fail("Could not initialize provider");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "TRUE";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "enumerate", val);
+ if (ret != EOK) {
+ fail("Could not initialize LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ val[0] = "TRUE";
+ ret = confdb_add_param(test_ctx->confdb, true,
+ "config/domain/LOCAL", "cache_credentials", val);
+ if (ret != EOK) {
+ fail("Could not initialize LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ ret = confdb_get_domain(test_ctx->confdb, "local", &test_ctx->domain);
+ if (ret != EOK) {
+ fail("Could not retrieve LOCAL domain");
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ ret = sysdb_domain_init(test_ctx, test_ctx->ev,
+ test_ctx->domain, TESTS_PATH, &test_ctx->sysdb);
+ if (ret != EOK) {
+ fail("Could not initialize connection to the sysdb (%d)", ret);
+ talloc_free(test_ctx);
+ return ret;
+ }
+
+ *ctx = test_ctx;
+ return EOK;
+}
+
+struct test_data {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sysdb_test_ctx *ctx;
+
+ const char *username;
+ const char *groupname;
+ uid_t uid;
+ gid_t gid;
+ const char *shell;
+
+ bool finished;
+ int error;
+
+ struct sysdb_attrs *attrs;
+ const char *attrval; /* testing sysdb_get_user_attr */
+ const char **attrlist;
+ struct ldb_message *msg;
+
+ size_t msgs_count;
+ struct ldb_message **msgs;
+};
+
+static int test_loop(struct test_data *data)
+{
+ while (!data->finished)
+ tevent_loop_once(data->ctx->ev);
+
+ return data->error;
+}
+
+static void test_req_done(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+
+ data->error = sysdb_transaction_commit_recv(req);
+ data->finished = true;
+}
+
+static void test_return(struct test_data *data, int error)
+{
+ struct tevent_req *req;
+
+ if (error != EOK) {
+ goto fail;
+ }
+
+ req = sysdb_transaction_commit_send(data, data->ev, data->handle);
+ if (!req) {
+ error = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(req, test_req_done, data);
+
+ return;
+
+fail:
+ /* free transaction */
+ talloc_zfree(data->handle);
+
+ data->error = error;
+ data->finished = true;
+}
+
+static void test_add_user_done(struct tevent_req *subreq);
+
+static void test_add_user(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ char *homedir;
+ char *gecos;
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, data, &data->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ homedir = talloc_asprintf(data, "/home/testuser%d", data->uid);
+ gecos = talloc_asprintf(data, "Test User %d", data->uid);
+
+ subreq = sysdb_add_user_send(data, data->ev, data->handle,
+ data->ctx->domain, data->username,
+ data->uid, 0,
+ gecos, homedir, "/bin/bash",
+ NULL, 0);
+ if (!subreq) {
+ return test_return(data, ENOMEM);
+ }
+ tevent_req_set_callback(subreq, test_add_user_done, data);
+}
+
+static void test_add_user_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_add_user_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_store_user_done(struct tevent_req *subreq);
+
+static void test_store_user(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ char *homedir;
+ char *gecos;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ homedir = talloc_asprintf(data, "/home/testuser%d", data->uid);
+ gecos = talloc_asprintf(data, "Test User %d", data->uid);
+
+ subreq = sysdb_store_user_send(data, data->ev, data->handle,
+ data->ctx->domain, data->username, "x",
+ data->uid, 0,
+ gecos, homedir,
+ data->shell ? data->shell : "/bin/bash",
+ NULL, -1);
+ if (!subreq) {
+ test_return(data, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, test_store_user_done, data);
+}
+
+static void test_store_user_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_user_done(struct tevent_req *subreq);
+
+static void test_remove_user(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct ldb_dn *user_dn;
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ user_dn = sysdb_user_dn(data->ctx->sysdb, data, "LOCAL", data->username);
+ if (!user_dn) return test_return(data, ENOMEM);
+
+ subreq = sysdb_delete_entry_send(data, data->ev, data->handle, user_dn, true);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_user_done, data);
+}
+
+static void test_remove_user_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_user_by_uid_done(struct tevent_req *subreq);
+
+static void test_remove_user_by_uid(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_delete_user_send(data, data->ev,
+ NULL, data->handle,
+ data->ctx->domain,
+ NULL, data->uid);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_user_by_uid_done, data);
+}
+
+static void test_remove_user_by_uid_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ if (ret == ENOENT) ret = EOK;
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_nonexistent_group_done(struct tevent_req *subreq);
+
+static void test_remove_nonexistent_group(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_delete_group_send(data, data->ev,
+ NULL, data->handle,
+ data->ctx->domain,
+ NULL, data->uid);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_nonexistent_group_done, data);
+}
+
+static void test_remove_nonexistent_group_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_nonexistent_user_done(struct tevent_req *subreq);
+
+static void test_remove_nonexistent_user(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_delete_user_send(data, data->ev,
+ NULL, data->handle,
+ data->ctx->domain,
+ NULL, data->uid);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_nonexistent_user_done, data);
+}
+
+static void test_remove_nonexistent_user_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_add_group_done(struct tevent_req *subreq);
+
+static void test_add_group(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req,
+ struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_add_group_send(data, data->ev, data->handle,
+ data->ctx->domain, data->groupname,
+ data->gid, NULL, 0);
+ if (!subreq) {
+ test_return(data, ret);
+ }
+ tevent_req_set_callback(subreq, test_add_group_done, data);
+}
+
+static void test_add_group_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_add_group_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_store_group_done(struct tevent_req *subreq);
+
+static void test_store_group(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_store_group_send(data, data->ev, data->handle,
+ data->ctx->domain, data->groupname,
+ data->gid, NULL, -1);
+ if (!subreq) {
+ test_return(data, ret);
+ }
+ tevent_req_set_callback(subreq, test_store_group_done, data);
+}
+
+static void test_store_group_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_group_done(struct tevent_req *subreq);
+
+static void test_remove_group(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ struct ldb_dn *group_dn;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ group_dn = sysdb_group_dn(data->ctx->sysdb, data, "LOCAL", data->groupname);
+ if (!group_dn) return test_return(data, ENOMEM);
+
+ subreq = sysdb_delete_entry_send(data, data->ev, data->handle, group_dn, true);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_group_done, data);
+}
+
+static void test_remove_group_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_remove_group_by_gid_done(struct tevent_req *subreq);
+static void test_remove_group_by_gid(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_delete_group_send(data, data->ev,
+ NULL, data->handle,
+ data->ctx->domain,
+ NULL, data->gid);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_remove_group_by_gid_done, data);
+}
+
+static void test_remove_group_by_gid_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ if (ret == ENOENT) ret = EOK;
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_getpwent(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ data->error = ENOENT;
+ break;
+
+ case 1:
+ data->uid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_UIDNUM, 0);
+ break;
+
+ default:
+ data->error = EFAULT;
+ break;
+ }
+}
+
+static void test_getgrent(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ data->error = ENOENT;
+ break;
+
+ case 1:
+ data->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0);
+ break;
+
+ default:
+ data->error = EFAULT;
+ break;
+ }
+}
+
+static void test_getgrgid(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ data->error = ENOENT;
+ break;
+
+ case 1:
+ data->groupname = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, 0);
+ break;
+
+ default:
+ data->error = EFAULT;
+ break;
+ }
+}
+
+static void test_getpwuid(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ data->error = ENOENT;
+ break;
+
+ case 1:
+ data->username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, 0);
+ break;
+
+ default:
+ data->error = EFAULT;
+ break;
+ }
+}
+
+static void test_enumgrent(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ const int expected = 20; /* 10 groups + 10 users (we're MPG) */
+
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ if (res->count != expected) {
+ data->error = EINVAL;
+ return;
+ }
+
+ data->error = EOK;
+}
+
+static void test_enumpwent(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ const int expected = 10;
+
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ if (res->count != expected) {
+ data->error = EINVAL;
+ return;
+ }
+
+ data->error = EOK;
+}
+
+static void test_set_user_attr_done(struct tevent_req *subreq);
+static void test_set_user_attr(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ subreq = sysdb_set_user_attr_send(data, data->ev, data->handle,
+ data->ctx->domain, data->username,
+ data->attrs, SYSDB_MOD_REP);
+ if (!subreq) return test_return(data, ENOMEM);
+
+ tevent_req_set_callback(subreq, test_set_user_attr_done, data);
+}
+
+static void test_set_user_attr_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_get_user_attr(void *pvt, int error, struct ldb_result *res)
+{
+ struct test_data *data = talloc_get_type(pvt, struct test_data);
+ data->finished = true;
+
+ if (error != EOK) {
+ data->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ data->error = ENOENT;
+ break;
+
+ case 1:
+ data->attrval = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SHELL, 0);
+ break;
+
+ default:
+ data->error = EFAULT;
+ break;
+ }
+}
+
+static void test_add_group_member_done(struct tevent_req *subreq);
+
+static void test_add_group_member(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ const char *username;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ username = talloc_asprintf(data, "testuser%d", data->uid);
+ if (username == NULL) {
+ test_return(data, ENOMEM);
+ }
+
+ subreq = sysdb_add_group_member_send(data, data->ev,
+ data->handle, data->ctx->domain,
+ data->groupname, username);
+ if (!subreq) {
+ test_return(data, ENOMEM);
+ }
+
+ tevent_req_set_callback(subreq, test_add_group_member_done, data);
+}
+
+static void test_add_group_member_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret = sysdb_add_group_member_recv(subreq);
+
+ test_return(data, ret);
+}
+
+static void test_remove_group_member_done(struct tevent_req *subreq);
+
+static void test_remove_group_member(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ const char *username;
+ int ret;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ username = talloc_asprintf(data, "testuser%d", data->uid);
+ if (username == NULL) {
+ test_return(data, ENOMEM);
+ }
+
+ subreq = sysdb_remove_group_member_send(data, data->ev,
+ data->handle, data->ctx->domain,
+ data->groupname, username);
+ if (!subreq) {
+ test_return(data, ENOMEM);
+ }
+
+ tevent_req_set_callback(subreq, test_remove_group_member_done, data);
+}
+
+static void test_remove_group_member_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret = sysdb_remove_group_member_recv(subreq);
+
+ test_return(data, ret);
+}
+
+static void test_store_custom_done(struct tevent_req *subreq);
+
+static void test_store_custom(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+ char *object_name;
+
+ ret = sysdb_transaction_recv(subreq, data, &data->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ object_name = talloc_asprintf(data, "%s_%d", CUSTOM_TEST_OBJECT, data->uid);
+ if (!object_name) {
+ return test_return(data, ENOMEM);
+ }
+
+ subreq = sysdb_store_custom_send(data, data->ev, data->handle,
+ data->ctx->domain, object_name,
+ CUSTOM_TEST_CONTAINER, data->attrs);
+ if (!subreq) {
+ return test_return(data, ENOMEM);
+ }
+ tevent_req_set_callback(subreq, test_store_custom_done, data);
+}
+
+static void test_store_custom_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_store_custom_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_search_done(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+
+ data->finished = true;
+ return;
+}
+
+static void test_delete_custom_done(struct tevent_req *subreq);
+
+static void test_delete_custom(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, data, &data->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+
+ subreq = sysdb_delete_custom_send(data, data->ev, data->handle,
+ data->ctx->domain, CUSTOM_TEST_OBJECT,
+ CUSTOM_TEST_CONTAINER);
+ if (!subreq) {
+ return test_return(data, ENOMEM);
+ }
+ tevent_req_set_callback(subreq, test_delete_custom_done, data);
+}
+
+static void test_delete_custom_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_delete_custom_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+static void test_search_all_users_done(struct tevent_req *subreq);
+static void test_search_all_users(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ struct ldb_dn *base_dn;
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, data, &data->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ base_dn = ldb_dn_new_fmt(data, data->ctx->sysdb->ldb, SYSDB_TMPL_USER_BASE,
+ "LOCAL");
+ if (base_dn == NULL) {
+ return test_return(data, ENOMEM);
+ }
+
+ subreq = sysdb_search_entry_send(data, data->ev, data->handle,
+ base_dn, LDB_SCOPE_SUBTREE,
+ "objectClass=user", data->attrlist);
+ if (!subreq) {
+ return test_return(data, ENOMEM);
+ }
+ tevent_req_set_callback(subreq, test_search_all_users_done, data);
+}
+
+static void test_search_all_users_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_search_entry_recv(subreq, data, &data->msgs_count, &data->msgs);
+ talloc_zfree(subreq);
+
+ test_return(data, ret);
+ return;
+}
+
+static void test_delete_recursive_done(struct tevent_req *subreq);
+
+static void test_delete_recursive(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq,
+ struct test_data);
+ int ret;
+ struct ldb_dn *dn;
+
+ ret = sysdb_transaction_recv(subreq, data, &data->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ dn = ldb_dn_new_fmt(data, data->handle->ctx->ldb, SYSDB_DOM_BASE,
+ "LOCAL");
+ if (!dn) {
+ return test_return(data, ENOMEM);
+ }
+
+ subreq = sysdb_delete_recursive_send(data, data->ev, data->handle, dn,
+ false);
+ if (!subreq) {
+ return test_return(data, ENOMEM);
+ }
+ tevent_req_set_callback(subreq, test_delete_recursive_done, data);
+}
+
+static void test_delete_recursive_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_delete_recursive_recv(subreq);
+ talloc_zfree(subreq);
+ fail_unless(ret == EOK, "sysdb_delete_recursive_recv returned [%d]", ret);
+ return test_return(data, ret);
+}
+
+static void test_memberof_store_group_done(struct tevent_req *subreq);
+static void test_memberof_store_group(struct tevent_req *req)
+{
+ struct test_data *data = tevent_req_callback_data(req, struct test_data);
+ struct tevent_req *subreq;
+ int ret;
+ struct sysdb_attrs *attrs = NULL;
+ char *member;
+ int i;
+
+ ret = sysdb_transaction_recv(req, data, &data->handle);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+
+ attrs = sysdb_new_attrs(data);
+ if (!attrs) {
+ return test_return(data, ENOMEM);
+ }
+ for (i = 0; data->attrlist && data->attrlist[i]; i++) {
+ member = sysdb_group_strdn(data, data->ctx->domain->name,
+ data->attrlist[i]);
+ if (!member) {
+ return test_return(data, ENOMEM);
+ }
+ ret = sysdb_attrs_steal_string(attrs, SYSDB_MEMBER, member);
+ if (ret != EOK) {
+ return test_return(data, ret);
+ }
+ }
+
+ subreq = sysdb_store_group_send(data, data->ev, data->handle,
+ data->ctx->domain, data->groupname,
+ data->gid, attrs, -1);
+ if (!subreq) {
+ test_return(data, ret);
+ }
+ tevent_req_set_callback(subreq, test_memberof_store_group_done, data);
+}
+
+static void test_memberof_store_group_done(struct tevent_req *subreq)
+{
+ struct test_data *data = tevent_req_callback_data(subreq, struct test_data);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+
+ return test_return(data, ret);
+}
+
+START_TEST (test_sysdb_store_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+ data->gid = _i;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_store_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store user %s", data->username);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_store_user_existing)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+ data->gid = _i;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+ data->shell = talloc_asprintf(data, "/bin/ksh");
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_store_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store user %s", data->username);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_store_group)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_store_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store POSIX group #%d", _i);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_local_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not remove user %s", data->username);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_local_user_by_uid)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_user_by_uid, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not remove user with uid %d", _i);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_local_group)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not remove group %s", data->groupname);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_local_group_by_gid)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_group_by_gid, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not remove group with gid %d", _i);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_add_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+ data->gid = _i;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_add_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not add user %s", data->username);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_add_group)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+ data->gid = _i;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_add_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not add group %s", data->groupname);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_getpwnam)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct test_data *data_uc;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ ret = sysdb_getpwnam(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ data->username,
+ test_getpwent,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ if (ret) {
+ fail("sysdb_getpwnam failed for username %s (%d: %s)",
+ data->username, ret, strerror(ret));
+ goto done;
+ }
+ fail_unless(data->uid == _i,
+ "Did not find the expected UID");
+
+ /* Search for the user with the wrong case */
+ data_uc = talloc_zero(test_ctx, struct test_data);
+ data_uc->ctx = test_ctx;
+ data_uc->username = talloc_asprintf(data_uc, "TESTUSER%d", _i);
+
+ ret = sysdb_getpwnam(test_ctx,
+ test_ctx->sysdb,
+ data_uc->ctx->domain,
+ data_uc->username,
+ test_getpwent,
+ data_uc);
+ if (ret == EOK) {
+ ret = test_loop(data_uc);
+ }
+
+ fail_unless(ret == ENOENT,
+ "The upper-case username search should fail. ");
+
+done:
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_getgrnam)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct test_data *data_uc;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+
+ ret = sysdb_getgrnam(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ data->groupname,
+ test_getgrent,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ if (ret) {
+ fail("sysdb_getgrnam failed for groupname %s (%d: %s)",
+ data->groupname, ret, strerror(ret));
+ goto done;
+ }
+ fail_unless(data->gid == _i,
+ "Did not find the expected GID (found %d expected %d)",
+ data->gid, _i);
+
+ /* Search for the group with the wrong case */
+ data_uc = talloc_zero(test_ctx, struct test_data);
+ data_uc->ctx = test_ctx;
+ data_uc->groupname = talloc_asprintf(data_uc, "TESTGROUP%d", _i);
+
+ ret = sysdb_getgrnam(test_ctx,
+ test_ctx->sysdb,
+ data_uc->ctx->domain,
+ data_uc->groupname,
+ test_getgrent,
+ data_uc);
+ if (ret == EOK) {
+ ret = test_loop(data_uc);
+ }
+
+ fail_unless(ret == ENOENT,
+ "The upper-case groupname search should fail. ");
+done:
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_getgrgid)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+ const char *groupname = NULL;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ groupname = talloc_asprintf(test_ctx, "testgroup%d", _i);
+ if (groupname == NULL) {
+ fail("Cannot allocate memory");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->gid = _i;
+
+ ret = sysdb_getgrgid(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ data->gid,
+ test_getgrgid,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ if (ret) {
+ fail("sysdb_getgrgid failed for gid %d (%d: %s)",
+ data->gid, ret, strerror(ret));
+ goto done;
+ }
+ fail_unless(strcmp(data->groupname, groupname) == 0,
+ "Did not find the expected groupname (found %s expected %s)",
+ data->groupname, groupname);
+done:
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_getpwuid)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+ const char *username = NULL;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ username = talloc_asprintf(test_ctx, "testuser%d", _i);
+ if (username == NULL) {
+ fail("Cannot allocate memory");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->uid = _i;
+
+ ret = sysdb_getpwuid(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ data->uid,
+ test_getpwuid,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ if (ret) {
+ fail("sysdb_getpwuid failed for uid %d (%d: %s)",
+ data->uid, ret, strerror(ret));
+ goto done;
+ }
+
+ fail_unless(strcmp(data->username, username) == 0,
+ "Did not find the expected username (found %s expected %s)",
+ data->username, username);
+done:
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_enumgrent)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+
+ ret = sysdb_enumgrent(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ test_enumgrent,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ fail_unless(ret == EOK,
+ "sysdb_enumgrent failed (%d: %s)",
+ ret, strerror(ret));
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_enumpwent)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+
+ ret = sysdb_enumpwent(test_ctx,
+ test_ctx->sysdb,
+ data->ctx->domain,
+ NULL,
+ test_enumpwent,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ fail_unless(ret == EOK,
+ "sysdb_enumpwent failed (%d: %s)",
+ ret, strerror(ret));
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+
+START_TEST (test_sysdb_set_user_attr)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ data->attrs = sysdb_new_attrs(test_ctx);
+ if (ret != EOK) {
+ fail("Could not create the changeset");
+ return;
+ }
+
+ ret = sysdb_attrs_add_string(data->attrs,
+ SYSDB_SHELL,
+ "/bin/ksh");
+ if (ret != EOK) {
+ fail("Could not create the changeset");
+ return;
+ }
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_set_user_attr, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not modify user %s", data->username);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_get_user_attr)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+ const char *attrs[] = { SYSDB_SHELL, NULL };
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ ret = sysdb_get_user_attr(data,
+ data->ctx->sysdb,
+ data->ctx->domain,
+ data->username,
+ attrs,
+ test_get_user_attr,
+ data);
+ if (ret == EOK) {
+ ret = test_loop(data);
+ }
+
+ if (ret) {
+ fail("Could not get attributes for user %s", data->username);
+ goto done;
+ }
+ fail_if(strcmp(data->attrval, "/bin/ksh"),
+ "Got bad attribute value for user %s",
+ data->username);
+done:
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_add_group_member)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+ data->uid = _i - 1000; /* the UID of user to add */
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_add_group_member, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not modify group %s", data->groupname);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_group_member)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+ data->uid = _i - 1000; /* the UID of user to add */
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_group_member, data);
+
+ ret = test_loop(data);
+ }
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_nonexistent_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = 12345;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_nonexistent_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != ENOENT, "Unexpected return code %d, expected ENOENT", ret);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_remove_nonexistent_group)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = 12345;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_remove_nonexistent_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != ENOENT, "Unexpected return code %d, expected ENOENT", ret);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_store_custom)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = _i;
+ data->attrs = sysdb_new_attrs(test_ctx);
+ if (ret != EOK) {
+ fail("Could not create attribute list");
+ return;
+ }
+
+ ret = sysdb_attrs_add_string(data->attrs,
+ TEST_ATTR_NAME,
+ TEST_ATTR_VALUE);
+ if (ret != EOK) {
+ fail("Could not add attribute");
+ return;
+ }
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_store_custom, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not add custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_search_custom_by_name)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+ char *object_name;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ fail_unless(data != NULL, "talloc_zero failed");
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->attrlist = talloc_array(test_ctx, const char *, 2);
+ fail_unless(data->attrlist != NULL, "talloc_array failed");
+ data->attrlist[0] = TEST_ATTR_NAME;
+ data->attrlist[1] = NULL;
+
+ object_name = talloc_asprintf(data, "%s_%d", CUSTOM_TEST_OBJECT, 29010);
+ fail_unless(object_name != NULL, "talloc_asprintf failed");
+
+ subreq = sysdb_search_custom_by_name_send(data, data->ev,
+ data->ctx->sysdb, NULL,
+ data->ctx->domain,
+ object_name,
+ CUSTOM_TEST_CONTAINER,
+ data->attrlist);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_custom_recv(subreq, data, &data->msgs_count,
+ &data->msgs);
+ talloc_zfree(subreq);
+ fail_unless(ret == EOK, "sysdb_search_custom_by_name_send failed");
+
+ fail_unless(data->msgs_count == 1,
+ "Wrong number of objects, exptected [1] got [%d]",
+ data->msgs_count);
+ fail_unless(data->msgs[0]->num_elements == 1,
+ "Wrong number of results, expected [1] got [%d]",
+ data->msgs[0]->num_elements);
+ fail_unless(strcmp(data->msgs[0]->elements[0].name, TEST_ATTR_NAME) == 0,
+ "Wrong attribute name");
+ fail_unless(data->msgs[0]->elements[0].num_values == 1,
+ "Wrong number of attribute values");
+ fail_unless(strncmp((const char *)data->msgs[0]->elements[0].values[0].data,
+ TEST_ATTR_VALUE,
+ data->msgs[0]->elements[0].values[0].length) == 0,
+ "Wrong attribute value");
+ }
+
+ fail_if(ret != EOK, "Could not search custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_update_custom)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = 29010;
+ data->attrs = sysdb_new_attrs(test_ctx);
+ if (ret != EOK) {
+ fail("Could not create attribute list");
+ return;
+ }
+
+ ret = sysdb_attrs_add_string(data->attrs,
+ TEST_ATTR_NAME,
+ TEST_ATTR_UPDATE_VALUE);
+ if (ret != EOK) {
+ fail("Could not add attribute");
+ return;
+ }
+
+ ret = sysdb_attrs_add_string(data->attrs,
+ TEST_ATTR_ADD_NAME,
+ TEST_ATTR_ADD_VALUE);
+ if (ret != EOK) {
+ fail("Could not add attribute");
+ return;
+ }
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_store_custom, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not add custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_search_custom_update)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+ char *object_name;
+ struct ldb_message_element *el;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ fail_unless(data != NULL, "talloc_zero failed");
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->attrlist = talloc_array(test_ctx, const char *, 3);
+ fail_unless(data->attrlist != NULL, "talloc_array failed");
+ data->attrlist[0] = TEST_ATTR_NAME;
+ data->attrlist[1] = TEST_ATTR_ADD_NAME;
+ data->attrlist[2] = NULL;
+
+ object_name = talloc_asprintf(data, "%s_%d", CUSTOM_TEST_OBJECT, 29010);
+ fail_unless(object_name != NULL, "talloc_asprintf failed");
+
+ subreq = sysdb_search_custom_by_name_send(data, data->ev,
+ data->ctx->sysdb, NULL,
+ data->ctx->domain,
+ object_name,
+ CUSTOM_TEST_CONTAINER,
+ data->attrlist);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_custom_recv(subreq, data, &data->msgs_count,
+ &data->msgs);
+ talloc_zfree(subreq);
+ fail_unless(ret == EOK, "sysdb_search_custom_by_name_send failed");
+
+ fail_unless(data->msgs_count == 1,
+ "Wrong number of objects, exptected [1] got [%d]",
+ data->msgs_count);
+ fail_unless(data->msgs[0]->num_elements == 2,
+ "Wrong number of results, expected [2] got [%d]",
+ data->msgs[0]->num_elements);
+
+ el = ldb_msg_find_element(data->msgs[0], TEST_ATTR_NAME);
+ fail_unless(el != NULL, "Attribute [%s] not found", TEST_ATTR_NAME);
+ fail_unless(el->num_values == 1, "Wrong number ([%d] instead of 1) "
+ "of attribute values for [%s]", el->num_values, TEST_ATTR_NAME);
+ fail_unless(strncmp((const char *) el->values[0].data, TEST_ATTR_UPDATE_VALUE,
+ el->values[0].length) == 0,
+ "Wrong attribute value");
+
+ el = ldb_msg_find_element(data->msgs[0], TEST_ATTR_ADD_NAME);
+ fail_unless(el != NULL, "Attribute [%s] not found", TEST_ATTR_ADD_NAME);
+ fail_unless(el->num_values == 1, "Wrong number ([%d] instead of 1) "
+ "of attribute values for [%s]", el->num_values, TEST_ATTR_ADD_NAME);
+ fail_unless(strncmp((const char *) el->values[0].data, TEST_ATTR_ADD_VALUE,
+ el->values[0].length) == 0,
+ "Wrong attribute value");
+
+ }
+
+ fail_if(ret != EOK, "Could not search custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_search_custom)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+ const char *filter = "(distinguishedName=*)";
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ fail_unless(data != NULL, "talloc_zero failed");
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->attrlist = talloc_array(test_ctx, const char *, 3);
+ fail_unless(data->attrlist != NULL, "talloc_array failed");
+ data->attrlist[0] = TEST_ATTR_NAME;
+ data->attrlist[1] = TEST_ATTR_ADD_NAME;
+ data->attrlist[2] = NULL;
+
+ subreq = sysdb_search_custom_send(data, data->ev,
+ data->ctx->sysdb, NULL,
+ data->ctx->domain,
+ filter,
+ CUSTOM_TEST_CONTAINER,
+ data->attrlist);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_custom_recv(subreq, data, &data->msgs_count,
+ &data->msgs);
+ talloc_zfree(subreq);
+ fail_unless(ret == EOK, "sysdb_search_custom_send failed");
+
+ fail_unless(data->msgs_count == 10,
+ "Wrong number of objects, exptected [10] got [%d]",
+ data->msgs_count);
+ }
+
+ fail_if(ret != EOK, "Could not search custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_delete_custom)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_delete_custom, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not delete custom object");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_cache_password)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ fail_unless(ret == EOK, "Could not set up the test");
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ req = sysdb_cache_password_send(data, test_ctx->ev, test_ctx->sysdb, NULL,
+ test_ctx->domain, data->username,
+ data->username);
+ fail_unless(req != NULL, "sysdb_cache_password_send failed [%d].", ret);
+
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+ fail_unless(ret == EOK, "test_loop failed [%d].", ret);
+
+ ret = sysdb_cache_password_recv(req);
+ fail_unless(ret == EOK, "sysdb_cache_password request failed [%d].", ret);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+static void cached_authentication_without_expiration(const char *username,
+ const char *password,
+ int expected_result)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+ time_t expire_date;
+ time_t delayed_until;
+ const char *val[2];
+ val[1] = NULL;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ fail_unless(ret == EOK, "Could not set up the test");
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = username;
+
+ val[0] = "0";
+ ret = confdb_add_param(test_ctx->confdb, true, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_CRED_TIMEOUT, val);
+ if (ret != EOK) {
+ fail("Could not initialize provider");
+ talloc_free(test_ctx);
+ return;
+ }
+
+ req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
+ test_ctx->domain, data->username,
+ (const uint8_t *) password, strlen(password),
+ test_ctx->confdb);
+ fail_unless(req != NULL, "sysdb_cache_password_send failed.");
+
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+ fail_unless(ret == EOK, "test_loop failed.");
+
+ ret = sysdb_cache_auth_recv(req, &expire_date, &delayed_until);
+ fail_unless(ret == expected_result, "sysdb_cache_auth request does not "
+ "return expected result [%d].",
+ expected_result);
+
+ fail_unless(expire_date == 0, "Wrong expire date, expected [%d], got [%d]",
+ 0, expire_date);
+
+ fail_unless(delayed_until == -1, "Wrong delay, expected [%d], got [%d]",
+ -1, delayed_until);
+
+ talloc_free(test_ctx);
+}
+
+static void cached_authentication_with_expiration(const char *username,
+ const char *password,
+ int expected_result)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+ time_t expire_date;
+ const char *val[2];
+ val[1] = NULL;
+ time_t now;
+ time_t expected_expire_date;
+ time_t delayed_until;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ fail_unless(ret == EOK, "Could not set up the test");
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = username;
+
+ val[0] = "1";
+ ret = confdb_add_param(test_ctx->confdb, true, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_CRED_TIMEOUT, val);
+ if (ret != EOK) {
+ fail("Could not initialize provider");
+ talloc_free(test_ctx);
+ return;
+ }
+
+ now = time(NULL);
+ expected_expire_date = now + (24 * 60 * 60);
+ DEBUG(9, ("Setting SYSDB_LAST_ONLINE_AUTH to [%lld].\n", (long long) now));
+
+ data->attrs = sysdb_new_attrs(data);
+ ret = sysdb_attrs_add_time_t(data->attrs, SYSDB_LAST_ONLINE_AUTH, now);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ fail_unless(req != NULL, "sysdb_transaction_send failed.");
+
+ tevent_req_set_callback(req, test_set_user_attr, data);
+
+ ret = test_loop(data);
+ fail_unless(ret == EOK, "Could not modify user %s", data->username);
+ talloc_zfree(req);
+
+ data->finished = false;
+ req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
+ test_ctx->domain, data->username,
+ (const uint8_t *) password, strlen(password),
+ test_ctx->confdb);
+ fail_unless(req != NULL, "sysdb_cache_password_send failed.");
+
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+ fail_unless(ret == EOK, "test_loop failed.");
+
+ ret = sysdb_cache_auth_recv(req, &expire_date, &delayed_until);
+ fail_unless(ret == expected_result, "sysdb_cache_auth request does not "
+ "return expected result [%d], got [%d].",
+ expected_result, ret);
+
+ fail_unless(expire_date == expected_expire_date,
+ "Wrong expire date, expected [%d], got [%d]",
+ expected_expire_date, expire_date);
+
+ fail_unless(delayed_until == -1, "Wrong delay, expected [%d], got [%d]",
+ -1, delayed_until);
+
+ talloc_free(test_ctx);
+}
+
+START_TEST (test_sysdb_cached_authentication_missing_password)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *username;
+
+ tmp_ctx = talloc_new(NULL);
+ fail_unless(tmp_ctx != NULL, "talloc_new failed.");
+
+ username = talloc_asprintf(tmp_ctx, "testuser%d", _i);
+ fail_unless(username != NULL, "talloc_asprintf failed.");
+
+ cached_authentication_without_expiration(username, "abc", ENOENT);
+ cached_authentication_with_expiration(username, "abc", ENOENT);
+
+ talloc_free(tmp_ctx);
+
+}
+END_TEST
+
+START_TEST (test_sysdb_cached_authentication_wrong_password)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *username;
+
+ tmp_ctx = talloc_new(NULL);
+ fail_unless(tmp_ctx != NULL, "talloc_new failed.");
+
+ username = talloc_asprintf(tmp_ctx, "testuser%d", _i);
+ fail_unless(username != NULL, "talloc_asprintf failed.");
+
+ cached_authentication_without_expiration(username, "abc", EINVAL);
+ cached_authentication_with_expiration(username, "abc", EINVAL);
+
+ talloc_free(tmp_ctx);
+
+}
+END_TEST
+
+START_TEST (test_sysdb_cached_authentication)
+{
+ TALLOC_CTX *tmp_ctx;
+ char *username;
+
+ tmp_ctx = talloc_new(NULL);
+ fail_unless(tmp_ctx != NULL, "talloc_new failed.");
+
+ username = talloc_asprintf(tmp_ctx, "testuser%d", _i);
+ fail_unless(username != NULL, "talloc_asprintf failed.");
+
+ cached_authentication_without_expiration(username, username, EOK);
+ cached_authentication_with_expiration(username, username, EOK);
+
+ talloc_free(tmp_ctx);
+
+}
+END_TEST
+
+START_TEST (test_sysdb_prepare_asq_test_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i);
+ data->uid = ASQ_TEST_USER_UID;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_add_group_member, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not modify group %s", data->groupname);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_asq_search)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ struct ldb_dn *user_dn;
+ int ret;
+ size_t msgs_count;
+ struct ldb_message **msgs;
+ int i;
+ char *gid_str;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "talloc_array failed");
+
+ data->attrlist[0] = "gidNumber";
+ data->attrlist[1] = NULL;
+
+ user_dn = sysdb_user_dn(data->ctx->sysdb, data, "LOCAL", ASQ_TEST_USER);
+ fail_unless(user_dn != NULL, "sysdb_user_dn failed");
+
+ req = sysdb_asq_search_send(data, data->ev, test_ctx->sysdb, NULL,
+ test_ctx->domain, user_dn, NULL, "memberof",
+ data->attrlist);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_asq_search_recv(req, data, &msgs_count, &msgs);
+ talloc_zfree(req);
+ fail_unless(ret == EOK, "sysdb_asq_search_send failed");
+
+ fail_unless(msgs_count == 10, "wrong number of results, "
+ "found [%d] expected [10]", msgs_count);
+
+ for (i = 0; i < msgs_count; i++) {
+ fail_unless(msgs[i]->num_elements == 1, "wrong number of elements, "
+ "found [%d] expected [1]",
+ msgs[i]->num_elements);
+
+ fail_unless(msgs[i]->elements[0].num_values == 1,
+ "wrong number of values, found [%d] expected [1]",
+ msgs[i]->elements[0].num_values);
+
+ gid_str = talloc_asprintf(data, "%d", 28010 + i);
+ fail_unless(gid_str != NULL, "talloc_asprintf failed.");
+ fail_unless(strncmp(gid_str,
+ (const char *) msgs[i]->elements[0].values[0].data,
+ msgs[i]->elements[0].values[0].length) == 0,
+ "wrong value, found [%.*s] expected [%s]",
+ msgs[i]->elements[0].values[0].length,
+ msgs[i]->elements[0].values[0].data, gid_str);
+ }
+ }
+
+ fail_if(ret != EOK, "Failed to send ASQ search request.\n");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_search_all_users)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+ int i;
+ char *uid_str;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "talloc_array failed");
+
+ data->attrlist[0] = "uidNumber";
+ data->attrlist[1] = NULL;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_all_users, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Search failed");
+
+ fail_unless(data->msgs_count == 10,
+ "wrong number of results, found [%d] expected [10]",
+ data->msgs_count);
+
+ for (i = 0; i < data->msgs_count; i++) {
+ fail_unless(data->msgs[i]->num_elements == 1,
+ "wrong number of elements, found [%d] expected [1]",
+ data->msgs[i]->num_elements);
+
+ fail_unless(data->msgs[i]->elements[0].num_values == 1,
+ "wrong number of values, found [%d] expected [1]",
+ data->msgs[i]->elements[0].num_values);
+
+ uid_str = talloc_asprintf(data, "%d", 27010 + i);
+ fail_unless(uid_str != NULL, "talloc_asprintf failed.");
+ fail_unless(strncmp(uid_str,
+ (char *) data->msgs[i]->elements[0].values[0].data,
+ data->msgs[i]->elements[0].values[0].length) == 0,
+ "wrong value, found [%.*s] expected [%s]",
+ data->msgs[i]->elements[0].values[0].length,
+ data->msgs[i]->elements[0].values[0].data, uid_str);
+ }
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_delete_recursive)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *subreq;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+
+ subreq = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!subreq) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(subreq, test_delete_recursive, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Recursive delete failed");
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_attrs_replace_name)
+{
+ struct sysdb_attrs *attrs;
+ struct ldb_message_element *el;
+ int ret;
+
+ attrs = sysdb_new_attrs(NULL);
+ fail_unless(attrs != NULL, "sysdb_new_attrs failed");
+
+ ret = sysdb_attrs_add_string(attrs, "foo", "bar");
+ fail_unless(ret == EOK, "sysdb_attrs_add_string failed");
+
+ ret = sysdb_attrs_add_string(attrs, "fool", "bool");
+ fail_unless(ret == EOK, "sysdb_attrs_add_string failed");
+
+ ret = sysdb_attrs_add_string(attrs, "foot", "boot");
+ fail_unless(ret == EOK, "sysdb_attrs_add_string failed");
+
+ ret = sysdb_attrs_replace_name(attrs, "foo", "foot");
+ fail_unless(ret == EEXIST,
+ "sysdb_attrs_replace overwrites existing attribute");
+
+ ret = sysdb_attrs_replace_name(attrs, "foo", "oof");
+ fail_unless(ret == EOK, "sysdb_attrs_replace failed");
+
+ ret = sysdb_attrs_get_el(attrs, "foo", &el);
+ fail_unless(ret == EOK, "sysdb_attrs_get_el failed");
+ fail_unless(el->num_values == 0, "Attribute foo is not empty.");
+
+ ret = sysdb_attrs_get_el(attrs, "oof", &el);
+ fail_unless(ret == EOK, "sysdb_attrs_get_el failed");
+ fail_unless(el->num_values == 1,
+ "Wrong number of values for attribute oof, "
+ "expected [1] got [%d].", el->num_values);
+ fail_unless(strncmp("bar", (char *) el->values[0].data,
+ el->values[0].length) == 0,
+ "Wrong value, expected [bar] got [%.*s]", el->values[0].length,
+ el->values[0].data);
+
+ talloc_free(attrs);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_store_group)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = MBO_GROUP_BASE + _i;
+ data->groupname = talloc_asprintf(data, "testgroup%d", data->gid);
+
+ if (_i == 0) {
+ data->attrlist = NULL;
+ } else {
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "talloc_array failed.");
+ data->attrlist[0] = talloc_asprintf(data, "testgroup%d", data->gid - 1);
+ data->attrlist[1] = NULL;
+ }
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_memberof_store_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store POSIX group #%d", data->gid);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_close_loop)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = MBO_GROUP_BASE;
+ data->groupname = talloc_asprintf(data, "testgroup%d", data->gid);
+
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "talloc_array failed.");
+ data->attrlist[0] = talloc_asprintf(data, "testgroup%d", data->gid + 9);
+ data->attrlist[1] = NULL;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_memberof_store_group, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store POSIX group #%d", data->gid);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_store_user)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->uid = MBO_USER_BASE + _i;
+ data->gid = 0; /* MPG domain */
+ data->username = talloc_asprintf(data, "testuser%d", data->uid);
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_store_user, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not store user %s", data->username);
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_add_group_member)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->groupname = talloc_asprintf(data, "testgroup%d", _i + MBO_GROUP_BASE);
+ data->uid = MBO_USER_BASE + _i;
+
+ req = sysdb_transaction_send(data, data->ev, test_ctx->sysdb);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_add_group_member, data);
+
+ ret = test_loop(data);
+ }
+
+ fail_if(ret != EOK, "Could not modify group %s", data->groupname);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_check_memberuid_without_group_5)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i + MBO_GROUP_BASE;
+
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "tallo_array failed.");
+ data->attrlist[0] = "memberuid";
+ data->attrlist[1] = NULL;
+
+ req = sysdb_search_group_by_gid_send(data, data->ev, test_ctx->sysdb, NULL,
+ data->ctx->domain,
+ _i + MBO_GROUP_BASE,
+ data->attrlist);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_group_recv(req, data, &data->msg);
+ talloc_zfree(req);
+ if (_i == 5) {
+ fail_unless(ret == ENOENT,
+ "sysdb_search_group_by_gid_send found "
+ "already deleted group");
+ ret = EOK;
+ } else {
+ fail_unless(ret == EOK, "sysdb_search_group_by_gid_send failed");
+
+ fail_unless(data->msg->num_elements == 1,
+ "Wrong number of results, expected [1] got [%d]",
+ data->msg->num_elements);
+ fail_unless(strcmp(data->msg->elements[0].name, "memberuid") == 0,
+ "Wrong attribute name");
+ fail_unless(data->msg->elements[0].num_values == ((_i + 1) % 6),
+ "Wrong number of attribute values, "
+ "expected [%d] got [%d]", ((_i + 1) % 6),
+ data->msg->elements[0].num_values);
+ }
+ }
+
+ fail_if(ret != EOK, "Could not check group %d", data->gid);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_check_memberuid)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i + MBO_GROUP_BASE;
+
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "tallo_array failed.");
+ data->attrlist[0] = "memberuid";
+ data->attrlist[1] = NULL;
+
+ req = sysdb_search_group_by_gid_send(data, data->ev, test_ctx->sysdb, NULL,
+ data->ctx->domain,
+ _i + MBO_GROUP_BASE,
+ data->attrlist);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_group_recv(req, data, &data->msg);
+ talloc_zfree(req);
+ fail_unless(ret == EOK, "sysdb_search_group_by_gid_send failed");
+
+ fail_unless(data->msg->num_elements == 1,
+ "Wrong number of results, expected [1] got [%d]",
+ data->msg->num_elements);
+ fail_unless(strcmp(data->msg->elements[0].name, "memberuid") == 0,
+ "Wrong attribute name");
+ fail_unless(data->msg->elements[0].num_values == _i + 1,
+ "Wrong number of attribute values, expected [%d] got [%d]",
+ _i + 1, data->msg->elements[0].num_values);
+ }
+
+ fail_if(ret != EOK, "Could not check group %d", data->gid);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_check_memberuid_loop)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i + MBO_GROUP_BASE;
+
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "tallo_array failed.");
+ data->attrlist[0] = "memberuid";
+ data->attrlist[1] = NULL;
+
+ req = sysdb_search_group_by_gid_send(data, data->ev, test_ctx->sysdb, NULL,
+ data->ctx->domain,
+ _i + MBO_GROUP_BASE,
+ data->attrlist);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_group_recv(req, data, &data->msg);
+ talloc_zfree(req);
+ fail_unless(ret == EOK, "sysdb_search_group_by_gid_send failed");
+
+ fail_unless(data->msg->num_elements == 1,
+ "Wrong number of results, expected [1] got [%d]",
+ data->msg->num_elements);
+ fail_unless(strcmp(data->msg->elements[0].name, "memberuid") == 0,
+ "Wrong attribute name");
+ fail_unless(data->msg->elements[0].num_values == 10,
+ "Wrong number of attribute values, expected [%d] got [%d]",
+ 10, data->msg->elements[0].num_values);
+ }
+
+ fail_if(ret != EOK, "Could not check group %d", data->gid);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST (test_sysdb_memberof_check_memberuid_loop_without_group_5)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ struct tevent_req *req;
+ int ret;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ if (ret != EOK) {
+ fail("Could not set up the test");
+ return;
+ }
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->gid = _i + MBO_GROUP_BASE;
+
+ data->attrlist = talloc_array(data, const char *, 2);
+ fail_unless(data->attrlist != NULL, "tallo_array failed.");
+ data->attrlist[0] = "memberuid";
+ data->attrlist[1] = NULL;
+
+ req = sysdb_search_group_by_gid_send(data, data->ev, test_ctx->sysdb, NULL,
+ data->ctx->domain,
+ _i + MBO_GROUP_BASE,
+ data->attrlist);
+ if (!req) {
+ ret = ENOMEM;
+ }
+
+ if (ret == EOK) {
+ tevent_req_set_callback(req, test_search_done, data);
+
+ ret = test_loop(data);
+
+ ret = sysdb_search_group_recv(req, data, &data->msg);
+ talloc_zfree(req);
+ if (_i == 5) {
+ fail_unless(ret == ENOENT,
+ "sysdb_search_group_by_gid_send found "
+ "already deleted group");
+ ret = EOK;
+ } else {
+ fail_unless(ret == EOK, "sysdb_search_group_by_gid_send failed");
+
+ fail_unless(data->msg->num_elements == 1,
+ "Wrong number of results, expected [1] got [%d]",
+ data->msg->num_elements);
+ fail_unless(strcmp(data->msg->elements[0].name, "memberuid") == 0,
+ "Wrong attribute name");
+ fail_unless(data->msg->elements[0].num_values == ((_i + 5) % 10),
+ "Wrong number of attribute values, expected [%d] got [%d]",
+ ((_i + 5) % 10), data->msg->elements[0].num_values);
+ }
+ }
+
+ fail_if(ret != EOK, "Could not check group %d", data->gid);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
+Suite *create_sysdb_suite(void)
+{
+ Suite *s = suite_create("sysdb");
+
+ TCase *tc_sysdb = tcase_create("SYSDB Tests");
+
+ /* Create a new user */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_add_user,27000,27010);
+
+ /* Verify the users were added */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_getpwnam, 27000, 27010);
+
+ /* Create a new group */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_add_group, 28000, 28010);
+
+ /* Verify the groups were added */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_getgrnam, 28000, 28010);
+
+ /* sysdb_store_user allows setting attributes for existing users */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_store_user_existing, 27000, 27010);
+
+ /* test the change */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_get_user_attr, 27000, 27010);
+
+ /* Remove the other half by gid */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_group_by_gid, 28000, 28010);
+
+ /* Remove the other half by uid */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_user_by_uid, 27000, 27010);
+
+ /* Create a new user */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_store_user, 27010, 27020);
+
+ /* Verify the users were added */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_getpwnam, 27010, 27020);
+
+ /* Verify the users can be queried by UID */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_getpwuid, 27010, 27020);
+
+ /* Enumerate the users */
+ tcase_add_test(tc_sysdb, test_sysdb_enumpwent);
+
+ /* Change their attribute */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_set_user_attr, 27010, 27020);
+
+ /* Verify the change */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_get_user_attr, 27010, 27020);
+
+ /* Create a new group */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_store_group, 28010, 28020);
+
+ /* Verify the groups were added */
+
+ /* Verify the groups can be queried by GID */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_getgrgid, 28010, 28020);
+
+ /* Enumerate the groups */
+ tcase_add_test(tc_sysdb, test_sysdb_enumgrent);
+
+ /* Add some members to the groups */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_add_group_member, 28010, 28020);
+
+ /* Authenticate with missing cached password */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_cached_authentication_missing_password,
+ 27010, 27011);
+
+ /* Add a cached password */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_cache_password, 27010, 27011);
+
+ /* Authenticate against cached password */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_cached_authentication_wrong_password,
+ 27010, 27011);
+ tcase_add_loop_test(tc_sysdb, test_sysdb_cached_authentication, 27010, 27011);
+
+ /* ASQ search test */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_prepare_asq_test_user, 28011, 28020);
+ tcase_add_test(tc_sysdb, test_sysdb_asq_search);
+
+ /* Test search with more than one result */
+ tcase_add_test(tc_sysdb, test_sysdb_search_all_users);
+
+ /* Remove the members from the groups */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_remove_group_member, 28010, 28020);
+
+ /* Remove the users by name */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_user, 27010, 27020);
+
+ /* Remove the groups by name */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_remove_local_group, 28010, 28020);
+
+ /* test the ignore_not_found parameter for users */
+ tcase_add_test(tc_sysdb, test_sysdb_remove_nonexistent_user);
+
+ /* test the ignore_not_found parameter for groups */
+ tcase_add_test(tc_sysdb, test_sysdb_remove_nonexistent_group);
+
+ /* test custom operations */
+ tcase_add_loop_test(tc_sysdb, test_sysdb_store_custom, 29010, 29020);
+ tcase_add_test(tc_sysdb, test_sysdb_search_custom_by_name);
+ tcase_add_test(tc_sysdb, test_sysdb_update_custom);
+ tcase_add_test(tc_sysdb, test_sysdb_search_custom_update);
+ tcase_add_test(tc_sysdb, test_sysdb_search_custom);
+ tcase_add_test(tc_sysdb, test_sysdb_delete_custom);
+
+ /* test recursive delete */
+ tcase_add_test(tc_sysdb, test_sysdb_delete_recursive);
+
+ tcase_add_test(tc_sysdb, test_sysdb_attrs_replace_name);
+
+/* Add all test cases to the test suite */
+ suite_add_tcase(s, tc_sysdb);
+
+ TCase *tc_memberof = tcase_create("SYSDB member/memberof/memberuid Tests");
+
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_store_group, 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_store_user, 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_add_group_member,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_check_memberuid,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid,
+ MBO_GROUP_BASE + 5, MBO_GROUP_BASE + 6);
+ tcase_add_loop_test(tc_memberof,
+ test_sysdb_memberof_check_memberuid_without_group_5,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid,
+ MBO_GROUP_BASE , MBO_GROUP_BASE + 10);
+
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_store_group, 0, 10);
+ tcase_add_test(tc_memberof, test_sysdb_memberof_close_loop);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_store_user, 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_add_group_member,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_memberof_check_memberuid_loop,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid,
+ MBO_GROUP_BASE + 5, MBO_GROUP_BASE + 6);
+ tcase_add_loop_test(tc_memberof,
+ test_sysdb_memberof_check_memberuid_loop_without_group_5,
+ 0, 10);
+ tcase_add_loop_test(tc_memberof, test_sysdb_remove_local_group_by_gid,
+ MBO_GROUP_BASE , MBO_GROUP_BASE + 10);
+
+ suite_add_tcase(s, tc_memberof);
+
+ return s;
+}
+
+int main(int argc, const char *argv[]) {
+ int opt;
+ int ret;
+ poptContext pc;
+ int failure_count;
+ Suite *sysdb_suite;
+ SRunner *sr;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ { NULL }
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ ret = unlink(TESTS_PATH"/"LOCAL_SYSDB_FILE);
+ if (ret != EOK && errno != ENOENT) {
+ fprintf(stderr, "Could not delete the test ldb file (%d) (%s)\n",
+ errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ sysdb_suite = create_sysdb_suite();
+ sr = srunner_create(sysdb_suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ if (failure_count == 0) {
+ ret = unlink(TESTS_PATH"/"TEST_CONF_FILE);
+ if (ret != EOK) {
+ fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
+ errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ ret = unlink(TESTS_PATH"/"LOCAL_SYSDB_FILE);
+ if (ret != EOK) {
+ fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n",
+ errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+}
diff --git a/src/tools/files.c b/src/tools/files.c
new file mode 100644
index 00000000..6c644705
--- /dev/null
+++ b/src/tools/files.c
@@ -0,0 +1,736 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+/*
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2001, Marek Michałkiewicz
+ * Copyright (c) 2003 - 2006, Tomasz KÅ‚oczko
+ * Copyright (c) 2007 - 2008, Nicolas François
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the copyright holders or contributors may not be used to
+ * endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <talloc.h>
+
+#include "config.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+int copy_tree(const char *src_root, const char *dst_root,
+ uid_t uid, gid_t gid);
+
+struct copy_ctx {
+ const char *src_orig;
+ const char *dst_orig;
+ dev_t src_dev;
+};
+
+#ifdef HAVE_SELINUX
+/*
+ * selinux_file_context - Set the security context before any file or
+ * directory creation.
+ *
+ * selinux_file_context () should be called before any creation of file,
+ * symlink, directory, ...
+ *
+ * Callers may have to Reset SELinux to create files with default
+ * contexts:
+ * reset_selinux_file_context();
+ */
+int selinux_file_context(const char *dst_name)
+{
+ security_context_t scontext = NULL;
+
+ if (is_selinux_enabled() == 1) {
+ /* Get the default security context for this file */
+ if (matchpathcon(dst_name, 0, &scontext) < 0) {
+ if (security_getenforce () != 0) {
+ return 1;
+ }
+ }
+ /* Set the security context for the next created file */
+ if (setfscreatecon(scontext) < 0) {
+ if (security_getenforce() != 0) {
+ return 1;
+ }
+ }
+ freecon(scontext);
+ }
+
+ return 0;
+}
+
+int reset_selinux_file_context(void)
+{
+ setfscreatecon(NULL);
+ return EOK;
+}
+
+#else /* HAVE_SELINUX */
+int selinux_file_context(const char *dst_name)
+{
+ return EOK;
+}
+
+int reset_selinux_file_context(void)
+{
+ return EOK;
+}
+#endif /* HAVE_SELINUX */
+
+/* wrapper in order not to create a temporary context in
+ * every iteration */
+static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx,
+ dev_t parent_dev,
+ const char *root);
+
+int remove_tree(const char *root)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = remove_tree_with_ctx(tmp_ctx, 0, root);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ * The context is not freed in case of error
+ * because this is a recursive function, will be freed when we
+ * reach the top level remove_tree() again
+ */
+static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx,
+ dev_t parent_dev,
+ const char *root)
+{
+ char *fullpath = NULL;
+ struct dirent *result;
+ struct dirent direntp;
+ struct stat statres;
+ DIR *rootdir = NULL;
+ int ret;
+
+ rootdir = opendir(root);
+ if (rootdir == NULL) {
+ ret = errno;
+ DEBUG(1, ("Cannot open directory %s [%d][%s]",
+ root, ret, strerror(ret)));
+ goto fail;
+ }
+
+ while (readdir_r(rootdir, &direntp, &result) == 0) {
+ if (result == NULL) {
+ /* End of directory */
+ break;
+ }
+
+ if (strcmp (direntp.d_name, ".") == 0 ||
+ strcmp (direntp.d_name, "..") == 0) {
+ continue;
+ }
+
+ fullpath = talloc_asprintf(mem_ctx, "%s/%s", root, direntp.d_name);
+ if (fullpath == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = lstat(fullpath, &statres);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot stat %s: [%d][%s]\n",
+ fullpath, ret, strerror(ret)));
+ goto fail;
+ }
+
+ if (S_ISDIR(statres.st_mode)) {
+ /* if directory, recursively descend, but check if on the same FS */
+ if (parent_dev && parent_dev != statres.st_dev) {
+ DEBUG(1, ("Directory %s is on different filesystem, "
+ "will not follow\n", fullpath));
+ ret = EFAULT;
+ goto fail;
+ }
+
+ ret = remove_tree_with_ctx(mem_ctx, statres.st_dev, fullpath);
+ if (ret != EOK) {
+ DEBUG(1, ("Removing subdirectory %s failed: [%d][%s]\n",
+ fullpath, ret, strerror(ret)));
+ goto fail;
+ }
+ } else {
+ ret = unlink(fullpath);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Removing file %s failed: [%d][%s]\n",
+ fullpath, ret, strerror(ret)));
+ goto fail;
+ }
+ }
+
+ talloc_free(fullpath);
+ }
+
+ ret = closedir(rootdir);
+ if (ret != 0) {
+ ret = errno;
+ goto fail;
+ }
+
+ ret = rmdir(root);
+ if (ret != 0) {
+ ret = errno;
+ goto fail;
+ }
+
+fail:
+ return ret;
+}
+
+static int copy_dir(const char *src, const char *dst,
+ const struct stat *statp, const struct timeval mt[2],
+ uid_t uid, gid_t gid)
+{
+ int ret = 0;
+
+ /*
+ * Create a new target directory, make it owned by
+ * the user and then recursively copy that directory.
+ */
+ selinux_file_context(dst);
+
+ ret = mkdir(dst, statp->st_mode);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot mkdir directory '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = chown(dst, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chown directory '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = chmod(dst, statp->st_mode);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chmod directory '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = copy_tree(src, dst, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot copy directory from '%s' to '%s': [%d][%s].\n",
+ src, dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = utimes(dst, mt);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot set utimes on a directory '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static char *talloc_readlink(TALLOC_CTX *mem_ctx, const char *filename)
+{
+ size_t size = 1024;
+ ssize_t nchars;
+ char *buffer;
+
+ buffer = talloc_array(mem_ctx, char, size);
+ if (!buffer) {
+ return NULL;
+ }
+
+ while (1) {
+ nchars = readlink(filename, buffer, size);
+ if (nchars < 0) {
+ return NULL;
+ }
+
+ if ((size_t) nchars < size) {
+ /* The buffer was large enough */
+ break;
+ }
+
+ /* Try again with a bigger buffer */
+ size *= 2;
+ buffer = talloc_realloc(mem_ctx, buffer, char, size);
+ if (!buffer) {
+ return NULL;
+ }
+ }
+
+ /* readlink does not nul-terminate */
+ buffer[nchars] = '\0';
+ return buffer;
+}
+
+static int copy_symlink(struct copy_ctx *cctx,
+ const char *src,
+ const char *dst,
+ const struct stat *statp,
+ const struct timeval mt[],
+ uid_t uid, gid_t gid)
+{
+ int ret;
+ char *oldlink;
+ char *tmp;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(cctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ /*
+ * Get the name of the file which the link points
+ * to. If that name begins with the original
+ * source directory name, that part of the link
+ * name will be replaced with the original
+ * destination directory name.
+ */
+ oldlink = talloc_readlink(tmp_ctx, src);
+ if (oldlink == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* If src was a link to an entry of the src_orig directory itself,
+ * create a link to the corresponding entry in the dst_orig
+ * directory.
+ * FIXME: This may change a relative link to an absolute link
+ */
+ if (strncmp(oldlink, cctx->src_orig, strlen(cctx->src_orig)) == 0) {
+ tmp = talloc_asprintf(tmp_ctx, "%s%s", cctx->dst_orig, oldlink + strlen(cctx->src_orig));
+ if (tmp == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ talloc_free(oldlink);
+ oldlink = tmp;
+ }
+
+ selinux_file_context(dst);
+
+ ret = symlink(oldlink, dst);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("symlink() failed on file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = lchown(dst, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("lchown() failed on file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int copy_special(const char *dst,
+ const struct stat *statp,
+ const struct timeval mt[],
+ uid_t uid, gid_t gid)
+{
+ int ret = 0;
+
+ selinux_file_context(dst);
+
+ ret = mknod(dst, statp->st_mode & ~07777, statp->st_rdev);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot mknod special file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = chown(dst, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chown special file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = chmod(dst, statp->st_mode & 07777);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chmod special file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ ret = utimes(dst, mt);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot call utimes on special file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static int copy_file(const char *src,
+ const char *dst,
+ const struct stat *statp,
+ const struct timeval mt[],
+ uid_t uid, gid_t gid)
+{
+ int ret;
+ int ifd = -1;
+ int ofd = -1;
+ char buf[1024];
+ ssize_t cnt, written, offset;
+ struct stat fstatbuf;
+
+ ifd = open(src, O_RDONLY);
+ if (ifd < 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot open() source file '%s': [%d][%s].\n",
+ src, ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fstat(ifd, &fstatbuf);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fstat() source file '%s': [%d][%s].\n",
+ src, ret, strerror(ret)));
+ goto fail;
+ }
+
+ if (statp->st_dev != fstatbuf.st_dev ||
+ statp->st_ino != fstatbuf.st_ino) {
+ DEBUG(1, ("File %s was modified between lstat and open.\n", src));
+ ret = EIO;
+ goto fail;
+ }
+
+ selinux_file_context(dst);
+
+ ofd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777);
+ if (ofd < 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot open() destination file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fchown(ofd, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fchown() destination file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fchmod(ofd, statp->st_mode & 07777);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fchmod() destination file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ while ((cnt = read(ifd, buf, sizeof(buf))) > 0) {
+ offset = 0;
+ while (cnt > 0) {
+ written = write(ofd, buf+offset, (size_t)cnt);
+ if (written == -1) {
+ ret = errno;
+ DEBUG(1, ("Cannot write() to source file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+ offset += written;
+ cnt -= written;
+ }
+ }
+ if (cnt == -1) {
+ ret = errno;
+ DEBUG(1, ("Cannot read() from source file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+
+ ret = close(ifd);
+ ifd = -1;
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot close() source file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = close(ofd);
+ ifd = -1;
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot close() destination file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = utimes(dst, mt);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot call utimes() on destination file '%s': [%d][%s].\n",
+ dst, ret, strerror(ret)));
+ goto fail;
+ }
+
+ return EOK;
+
+ /* Reachable by jump only */
+fail:
+ if (ifd != -1) close(ifd);
+ if (ofd != -1) close(ofd);
+ return ret;
+}
+
+/*
+ * The context is not freed in case of error
+ * because this is a recursive function, will be freed when we
+ * reach the top level copy_tree() again
+ */
+static int copy_entry(struct copy_ctx *cctx,
+ const char *src,
+ const char *dst,
+ uid_t uid,
+ gid_t gid)
+{
+ int ret = EOK;
+ struct stat sb;
+ struct timeval mt[2];
+
+ ret = lstat(src, &sb);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(1, ("Cannot lstat() the source file '%s': [%d][%s].\n",
+ src, ret, strerror(ret)));
+ return ret;
+ }
+
+ mt[0].tv_sec = sb.st_atime;
+ mt[0].tv_usec = 0;
+
+ mt[1].tv_sec = sb.st_mtime;
+ mt[1].tv_usec = 0;
+
+ if (S_ISLNK (sb.st_mode)) {
+ ret = copy_symlink(cctx, src, dst, &sb, mt, uid, gid);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot copy symlink '%s' to '%s': [%d][%s]\n",
+ src, dst, ret, strerror(ret)));
+ }
+ return ret;
+ }
+
+ if (S_ISDIR(sb.st_mode)) {
+ /* Check if we're still on the same FS */
+ if (sb.st_dev != cctx->src_dev) {
+ DEBUG(2, ("Will not descend to other FS\n"));
+ /* Skip this without error */
+ return EOK;
+ }
+ return copy_dir(src, dst, &sb, mt, uid, gid);
+ } else if (!S_ISREG(sb.st_mode)) {
+ /*
+ * Deal with FIFOs and special files. The user really
+ * shouldn't have any of these, but it seems like it
+ * would be nice to copy everything ...
+ */
+ return copy_special(dst, &sb, mt, uid, gid);
+ } else {
+ /*
+ * Create the new file and copy the contents. The new
+ * file will be owned by the provided UID and GID values.
+ */
+ return copy_file(src, dst, &sb, mt, uid, gid);
+ }
+
+ return ret;
+}
+
+/*
+ * The context is not freed in case of error
+ * because this is a recursive function, will be freed when we
+ * reach the top level copy_tree() again
+ */
+static int copy_tree_ctx(struct copy_ctx *cctx,
+ const char *src_root,
+ const char *dst_root,
+ uid_t uid,
+ gid_t gid)
+{
+ DIR *src_dir;
+ int ret;
+ struct dirent *result;
+ struct dirent direntp;
+ char *src_name, *dst_name;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(cctx);
+
+ src_dir = opendir(src_root);
+ if (src_dir == NULL) {
+ ret = errno;
+ DEBUG(1, ("Cannot open the source directory %s: [%d][%s].\n",
+ src_root, ret, strerror(ret)));
+ goto fail;
+ }
+
+ while (readdir_r(src_dir, &direntp, &result) == 0) {
+ if (result == NULL) {
+ /* End of directory */
+ break;
+ }
+
+ if (strcmp (direntp.d_name, ".") == 0 ||
+ strcmp (direntp.d_name, "..") == 0) {
+ continue;
+ }
+
+ /* build src and dst paths */
+ src_name = talloc_asprintf(tmp_ctx, "%s/%s", src_root, direntp.d_name);
+ dst_name = talloc_asprintf(tmp_ctx, "%s/%s", dst_root, direntp.d_name);
+ if (dst_name == NULL || src_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* copy */
+ ret = copy_entry(cctx, src_name, dst_name, uid, gid);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot copy '%s' to '%s', error %d\n",
+ src_name, dst_name, ret));
+ goto fail;
+ }
+ talloc_free(src_name);
+ talloc_free(dst_name);
+ }
+
+ ret = closedir(src_dir);
+ if (ret != 0) {
+ ret = errno;
+ goto fail;
+ }
+
+fail:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int copy_tree(const char *src_root, const char *dst_root,
+ uid_t uid, gid_t gid)
+{
+ int ret = EOK;
+ struct copy_ctx *cctx = NULL;
+ struct stat s_src;
+
+ cctx = talloc_zero(NULL, struct copy_ctx);
+
+ ret = lstat(src_root, &s_src);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot lstat the source directory '%s': [%d][%s]\n",
+ src_root, ret, strerror(ret)));
+ goto fail;
+ }
+
+ cctx->src_orig = src_root;
+ cctx->dst_orig = dst_root;
+ cctx->src_dev = s_src.st_dev;
+
+ ret = copy_tree_ctx(cctx, src_root, dst_root, uid, gid);
+ if (ret != EOK) {
+ DEBUG(1, ("copy_tree_ctx failed: [%d][%s]\n", ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ reset_selinux_file_context();
+ talloc_free(cctx);
+ return ret;
+}
+
diff --git a/src/tools/sss_groupadd.c b/src/tools/sss_groupadd.c
new file mode 100644
index 00000000..15eed100
--- /dev/null
+++ b/src/tools/sss_groupadd.c
@@ -0,0 +1,155 @@
+/*
+ SSSD
+
+ sss_groupadd
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ gid_t pc_gid = 0;
+ int pc_debug = 0;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug",'\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
+ 0, _("The debug level to run with"), NULL },
+ { "gid", 'g', POPT_ARG_INT, &pc_gid,
+ 0, _("The GID of the group"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct tools_ctx *tctx = NULL;
+ int ret = EXIT_SUCCESS;
+ const char *pc_groupname = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse params */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ if ((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ /* groupname is an argument, not option */
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to add\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ tctx->octx->gid = pc_gid;
+
+ /* arguments processed, go on to actual work */
+ if (id_in_range(tctx->octx->gid, tctx->octx->domain) != EOK) {
+ ERROR("The selected GID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupadd */
+ ret = groupadd(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ switch (ret) {
+ case ERANGE:
+ ERROR("Could not allocate ID for the group - domain full?\n");
+ break;
+
+ case EEXIST:
+ ERROR("A group with the same name or GID already exists\n");
+ break;
+
+ default:
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ ERROR("Transaction error. Could not add group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_groupdel.c b/src/tools/sss_groupdel.c
new file mode 100644
index 00000000..e5b043e2
--- /dev/null
+++ b/src/tools/sss_groupdel.c
@@ -0,0 +1,155 @@
+/*
+ SSSD
+
+ sss_groupdel
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ int pc_debug = 0;
+ const char *pc_groupname = NULL;
+ struct tools_ctx *tctx = NULL;
+
+ poptContext pc = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
+ 0, _("The debug level to run with"), NULL },
+ POPT_TABLEEND
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse ops_ctx */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ if ((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to delete\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = sysdb_getgrnam_sync(tctx, tctx->ev, tctx->sysdb,
+ tctx->octx->name, tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ /* Error message will be printed in the switch */
+ goto done;
+ }
+
+ if ((tctx->octx->gid < tctx->local->id_min) ||
+ (tctx->local->id_max && tctx->octx->gid > tctx->local->id_max)) {
+ ERROR("Group %s is outside the defined ID range for domain\n",
+ tctx->octx->name);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupdel */
+ ret = groupdel(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+ ret = tctx->error;
+done:
+ if (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such group in local domain. "
+ "Removing groups only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not remove group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_groupmod.c b/src/tools/sss_groupmod.c
new file mode 100644
index 00000000..b25a018d
--- /dev/null
+++ b/src/tools/sss_groupmod.c
@@ -0,0 +1,246 @@
+/*
+ SSSD
+
+ sss_groupmod
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <grp.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ gid_t pc_gid = 0;
+ int pc_debug = 0;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
+ 0, _("The debug level to run with"), NULL },
+ { "append-group", 'a', POPT_ARG_STRING, NULL,
+ 'a', _("Groups to add this group to"), NULL },
+ { "remove-group", 'r', POPT_ARG_STRING, NULL,
+ 'r', _("Groups to remove this group from"), NULL },
+ { "gid", 'g', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_gid,
+ 0, _("The GID of the group"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct tools_ctx *tctx = NULL;
+ char *addgroups = NULL, *rmgroups = NULL;
+ int ret;
+ const char *pc_groupname = NULL;
+ char *badgroup = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'a':
+ addgroups = poptGetOptArg(pc);
+ if (addgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'r':
+ rmgroups = poptGetOptArg(pc);
+ if (rmgroups == NULL) {
+ ret = -1;
+ }
+ break;
+ }
+ }
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* groupname is an argument without --option */
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to modify\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ /* check the username to be able to give sensible error message */
+ ret = sysdb_getgrnam_sync(tctx, tctx->ev, tctx->sysdb,
+ tctx->octx->name, tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ ERROR("Cannot find group in local domain, "
+ "modifying groups is allowed only in local domain\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+
+ tctx->octx->gid = pc_gid;
+
+ if (addgroups) {
+ ret = parse_groups(tctx, addgroups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the group to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the group to\n"));
+ ERROR("Member groups must be in the same domain as parent group\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (rmgroups) {
+ ret = parse_groups(tctx, rmgroups, &tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to remove the group from\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to remove the group from\n"));
+ ERROR("Member groups must be in the same domain as parent group\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->rmgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (id_in_range(tctx->octx->gid, tctx->octx->domain) != EOK) {
+ ERROR("The selected GID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupmod */
+ ret = groupmod(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("Could not modify group - check if member group names are correct\n");
+ break;
+
+ case EFAULT:
+ ERROR("Could not modify group - check if groupname is correct\n");
+ break;
+
+ default:
+ ERROR("Transaction error. Could not modify group.\n");
+ break;
+ }
+
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ free(addgroups);
+ free(rmgroups);
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ exit(ret);
+}
diff --git a/src/tools/sss_groupshow.c b/src/tools/sss_groupshow.c
new file mode 100644
index 00000000..2f848b7d
--- /dev/null
+++ b/src/tools/sss_groupshow.c
@@ -0,0 +1,944 @@
+/*
+ SSSD
+
+ sss_groupshow
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2010
+
+ 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 <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+#define PADDING_SPACES 4
+#define GROUP_SHOW_ATTRS { SYSDB_MEMBEROF, SYSDB_GIDNUM, \
+ SYSDB_MEMBER, SYSDB_NAME, \
+ NULL }
+#define GROUP_SHOW_MPG_ATTRS { SYSDB_MEMBEROF, SYSDB_UIDNUM, \
+ SYSDB_NAME, NULL }
+
+struct group_info {
+ const char *name;
+ gid_t gid;
+ bool mpg;
+
+ const char **user_members;
+ const char **memberofs;
+
+ struct group_info **group_members;
+};
+
+/*==================Helper routines to process results================= */
+const char *rdn_as_string(TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn)
+{
+ const struct ldb_val *val;
+
+ val = ldb_dn_get_rdn_val(dn);
+ if (val == NULL) {
+ return NULL;
+ }
+
+ return ldb_dn_escape_value(mem_ctx, *val);;
+}
+
+static int parse_memberofs(struct ldb_context *ldb,
+ struct ldb_message_element *el,
+ struct group_info *gi)
+{
+ int i;
+ struct ldb_dn *dn = NULL;
+
+ gi->memberofs = talloc_array(gi, const char *, el->num_values+1);
+ if (gi->memberofs == NULL) {
+ return ENOMEM;
+ }
+
+ for (i = 0; i< el->num_values; ++i) {
+ dn = ldb_dn_from_ldb_val(gi, ldb, &(el->values[i]));
+ gi->memberofs[i] = talloc_strdup(gi, rdn_as_string(gi, dn));
+ talloc_zfree(dn);
+ if (gi->memberofs[i] == NULL) {
+ return ENOMEM;
+ }
+ DEBUG(6, ("memberof value: %s\n", gi->memberofs[i]));
+ }
+ gi->memberofs[el->num_values] = NULL;
+
+ return EOK;
+}
+
+static int parse_members(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct sss_domain_info *domain,
+ struct ldb_message_element *el,
+ const char *parent_name,
+ const char ***user_members,
+ const char ***group_members,
+ int *num_group_members)
+{
+ struct ldb_dn *user_basedn = NULL, *group_basedn = NULL;
+ struct ldb_dn *parent_dn = NULL;
+ struct ldb_dn *dn = NULL;
+ const char **um = NULL, **gm = NULL;
+ unsigned int um_index = 0, gm_index = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ user_basedn = ldb_dn_new_fmt(tmp_ctx, ldb,
+ SYSDB_TMPL_USER_BASE,
+ domain->name);
+ group_basedn = ldb_dn_new_fmt(tmp_ctx, ldb,
+ SYSDB_TMPL_GROUP_BASE,
+ domain->name);
+ if (!user_basedn || !group_basedn) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ um = talloc_array(mem_ctx, const char *, el->num_values+1);
+ gm = talloc_array(mem_ctx, const char *, el->num_values+1);
+ if (!um || !gm) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ for (i = 0; i< el->num_values; ++i) {
+ dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &(el->values[i]));
+
+ /* user member or group member? */
+ parent_dn = ldb_dn_get_parent(tmp_ctx, dn);
+ if (ldb_dn_compare_base(parent_dn, user_basedn) == 0) {
+ um[um_index] = rdn_as_string(mem_ctx, dn);
+ if (um[um_index] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(6, ("User member %s\n", um[um_index]));
+ um_index++;
+ } else if (ldb_dn_compare_base(parent_dn, group_basedn) == 0) {
+ gm[gm_index] = rdn_as_string(mem_ctx, dn);
+ if (gm[gm_index] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (parent_name && strcmp(gm[gm_index], parent_name) == 0) {
+ DEBUG(6, ("Skipping circular nesting for group %s\n",
+ gm[gm_index]));
+ continue;
+ }
+ DEBUG(6, ("Group member %s\n", gm[gm_index]));
+ gm_index++;
+ } else {
+ DEBUG(2, ("Group member not a user nor group: %s\n",
+ ldb_dn_get_linearized(dn)));
+ ret = EIO;
+ goto fail;
+ }
+
+ talloc_zfree(dn);
+ talloc_zfree(parent_dn);
+ }
+ um[um_index] = NULL;
+ gm[gm_index] = NULL;
+
+ if (um_index > 0) {
+ um = talloc_realloc(mem_ctx, um, const char *, um_index+1);
+ if (!um) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ } else {
+ talloc_zfree(um);
+ }
+
+ if (gm_index > 0) {
+ gm = talloc_realloc(mem_ctx, gm, const char *, gm_index+1);
+ if (!gm) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ } else {
+ talloc_zfree(gm);
+ }
+
+ *user_members = um;
+ *group_members = gm;
+ *num_group_members = gm_index;
+ talloc_zfree(tmp_ctx);
+ return EOK;
+
+fail:
+ talloc_zfree(um);
+ talloc_zfree(gm);
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
+static int process_group(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_message *msg,
+ struct sss_domain_info *domain,
+ const char *parent_name,
+ struct group_info **info,
+ const char ***group_members,
+ int *num_group_members)
+{
+ struct ldb_message_element *el;
+ int ret;
+ struct group_info *gi = NULL;
+
+ DEBUG(6, ("Found entry %s\n", ldb_dn_get_linearized(msg->dn)));
+
+ gi = talloc_zero(mem_ctx, struct group_info);
+ if (!gi) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* mandatory data - name and gid */
+ gi->name = talloc_strdup(gi,
+ ldb_msg_find_attr_as_string(msg,
+ SYSDB_NAME,
+ NULL));
+ gi->gid = ldb_msg_find_attr_as_uint64(msg,
+ SYSDB_GIDNUM, 0);
+ if (gi->gid == 0 || gi->name == NULL) {
+ DEBUG(3, ("No name or no GID?\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ /* list members */
+ el = ldb_msg_find_element(msg, SYSDB_MEMBER);
+ if (el) {
+ ret = parse_members(gi, ldb, domain, el,
+ parent_name,
+ &gi->user_members,
+ group_members, num_group_members);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ /* list memberofs */
+ el = ldb_msg_find_element(msg, SYSDB_MEMBEROF);
+ if (el) {
+ ret = parse_memberofs(ldb, el, gi);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ *info = gi;
+ return EOK;
+done:
+ talloc_zfree(gi);
+ return ret;
+}
+
+/*========Find info about a group and recursively about subgroups====== */
+struct group_show_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ struct group_info *root;
+ bool recursive;
+};
+
+static void group_show_root_done(struct tevent_req *subreq);
+static void group_show_recurse_done(struct tevent_req *subreq);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *,
+ struct group_show_state *,
+ struct group_info *,
+ const char **,
+ const int );
+static int group_show_recurse_recv(TALLOC_CTX *, struct tevent_req *,
+ struct group_info ***);
+
+struct tevent_req *group_show_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ bool recursive,
+ const char *name)
+{
+ struct group_show_state *search_state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct tevent_req *req = NULL;
+ static const char *attrs[] = GROUP_SHOW_ATTRS;
+
+ req = tevent_req_create(mem_ctx, &search_state, struct group_show_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ search_state->ev = ev;
+ search_state->sysdb = sysdb;
+ search_state->handle = handle;
+ search_state->domain = domain;
+ search_state->recursive = recursive;
+
+ /* First, search for the root group */
+ subreq = sysdb_search_group_by_name_send(search_state,
+ search_state->ev,
+ search_state->sysdb,
+ search_state->handle,
+ search_state->domain,
+ name, attrs);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, group_show_root_done, req);
+
+ return req;
+}
+
+static void group_show_root_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+ int ret;
+ int i;
+ struct ldb_message *msg = NULL;
+ const char **group_members = NULL;
+ int nmembers = 0;
+
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = process_group(state,
+ sysdb_ctx_get_ldb(state->sysdb),
+ msg, state->domain, NULL, &state->root,
+ &group_members, &nmembers);
+ if (ret != EOK) {
+ DEBUG(2, ("Group processing failed: %s (%d)\n",
+ strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (group_members == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (state->recursive == false) {
+ /* if not recursive, just fill in names */
+ state->root->group_members = talloc_array(state->root,
+ struct group_info *,
+ nmembers+1);
+ for (i=0; group_members[i]; i++) {
+ state->root->group_members[i] = talloc_zero(state->root,
+ struct group_info);
+ if (!state->root->group_members) {
+ tevent_req_error(req, ENOMEM);
+ }
+ state->root->group_members[i]->name = talloc_strdup(state->root,
+ group_members[i]);
+ if (!state->root->group_members[i]->name) {
+ tevent_req_error(req, ENOMEM);
+ }
+ }
+ state->root->group_members[nmembers] = NULL;
+
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = group_show_recurse_send(state->root, state,
+ state->root,
+ group_members,
+ nmembers);
+ if (!subreq) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ tevent_req_set_callback(subreq, group_show_recurse_done, req);
+}
+
+static void group_show_recurse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+ int ret;
+
+ ret = group_show_recurse_recv(state->root,
+ subreq,
+ &state->root->group_members);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int group_show_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info **res)
+{
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *res = talloc_move(mem_ctx, &state->root);
+
+ return EOK;
+}
+
+/*==================Recursive search for nested groups================= */
+struct group_show_recurse {
+ const char **names;
+ int current;
+
+ struct group_info *parent;
+ struct group_show_state *state;
+
+ struct group_info **groups;
+};
+
+static int group_show_recurse_search(struct tevent_req *,
+ struct group_show_recurse *);
+static void group_show_recurse_next(struct tevent_req *);
+static void group_show_recurse_level_done(struct tevent_req *);
+static void group_show_recurse_cont(struct tevent_req *);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *mem_ctx,
+ struct group_show_state *state,
+ struct group_info *parent,
+ const char **group_members,
+ const int nmembers)
+{
+ struct tevent_req *req = NULL;
+ struct group_show_recurse *recurse_state = NULL;
+
+ req = tevent_req_create(mem_ctx, &recurse_state, struct group_show_recurse);
+ if (req == NULL) {
+ return NULL;
+ }
+ recurse_state->current = 0;
+ recurse_state->parent = parent;
+ recurse_state->names = group_members;
+ recurse_state->state = state;
+ recurse_state->groups = talloc_array(state->root,
+ struct group_info *,
+ nmembers+1); /* trailing NULL */
+
+ if (!recurse_state->names ||
+ !recurse_state->names[recurse_state->current]) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ if (group_show_recurse_search(req, recurse_state) != EOK) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static int group_show_recurse_search(struct tevent_req *req,
+ struct group_show_recurse *recurse_state)
+{
+ static const char *attrs[] = GROUP_SHOW_ATTRS;
+ struct tevent_req *subreq = NULL;
+
+ /* Skip circular groups */
+ if (strcmp(recurse_state->names[recurse_state->current],
+ recurse_state->parent->name) == 0) {
+ DEBUG(0, ("CIRCULAR DEP DETECTED\n"));
+ group_show_recurse_cont(req);
+ return EOK;
+ }
+
+ subreq = sysdb_search_group_by_name_send(recurse_state->state,
+ recurse_state->state->ev,
+ recurse_state->state->sysdb,
+ recurse_state->state->handle,
+ recurse_state->state->domain,
+ recurse_state->names[recurse_state->current],
+ attrs);
+ if (!subreq) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(subreq, group_show_recurse_next, req);
+
+ return EOK;
+}
+
+static void group_show_recurse_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+ const char **group_members = NULL;
+ int nmembers = 0;
+ struct ldb_message *msg = NULL;
+ int ret;
+ struct tevent_req *recurse_req = NULL;
+
+ ret = sysdb_search_group_recv(subreq, recurse_state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ret = process_group(recurse_state->state->root,
+ sysdb_ctx_get_ldb(recurse_state->state->sysdb),
+ msg,
+ recurse_state->state->domain,
+ recurse_state->parent->name,
+ &recurse_state->groups[recurse_state->current],
+ &group_members,
+ &nmembers);
+ if (ret != EOK) {
+ DEBUG(2, ("Group processing failed: %s (%d)\n",
+ strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* descend to another level */
+ if (nmembers > 0) {
+ recurse_req = group_show_recurse_send(recurse_state,
+ recurse_state->state,
+ recurse_state->groups[recurse_state->current],
+ group_members, nmembers);
+ if (!recurse_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* to free group_members in the callback */
+ group_members = talloc_move(recurse_req, &group_members);
+ tevent_req_set_callback(recurse_req, group_show_recurse_level_done, req);
+ return;
+ }
+
+ /* Move to next group in the same level */
+ group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_level_done(struct tevent_req *recurse_req)
+{
+ int ret;
+ struct tevent_req *req = tevent_req_callback_data(recurse_req,
+ struct tevent_req);
+ struct group_show_recurse *recurse_state = tevent_req_data(recurse_req,
+ struct group_show_recurse);
+
+ ret = group_show_recurse_recv(recurse_state->state->root, recurse_req,
+ &recurse_state->parent->group_members);
+ talloc_zfree(recurse_req);
+ if (ret) {
+ DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ /* Move to next group on the upper level */
+ group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_cont(struct tevent_req *req)
+{
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+ int ret;
+
+ recurse_state->current++;
+ if (recurse_state->names[recurse_state->current] == NULL) {
+ recurse_state->groups[recurse_state->current] = NULL; /* Sentinel */
+ tevent_req_done(req);
+ return;
+ }
+
+ /* examine next group on the same level */
+ ret = group_show_recurse_search(req, recurse_state);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+static int group_show_recurse_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info ***out)
+{
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *out = talloc_move(mem_ctx, &recurse_state->groups);
+
+ return EOK;
+}
+
+/*==================Get info about MPG================================= */
+struct group_show_mpg_state {
+ struct ldb_context *ldb;
+ struct group_info *info;
+};
+
+static void group_show_mpg_done(struct tevent_req *);
+
+struct tevent_req *group_show_mpg_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct group_show_mpg_state *state;
+ static const char *mpg_attrs[] = GROUP_SHOW_MPG_ATTRS;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_show_mpg_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ldb = sysdb_ctx_get_ldb(sysdb);
+
+ subreq = sysdb_search_user_by_name_send(mem_ctx, ev, sysdb, handle,
+ domain, name, mpg_attrs);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, group_show_mpg_done, req);
+
+ return req;
+}
+
+static void group_show_mpg_done(struct tevent_req *subreq)
+{
+ int ret;
+ struct ldb_message *msg = NULL;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_mpg_state *state = tevent_req_data(req,
+ struct group_show_mpg_state);
+
+ ret = sysdb_search_user_recv(subreq, req, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->info = talloc_zero(state, struct group_info);
+ if (!state->info) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->info->name = talloc_strdup(state->info,
+ ldb_msg_find_attr_as_string(msg,
+ SYSDB_NAME,
+ NULL));
+ state->info->gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+ if (state->info->gid == 0 || state->info->name == NULL) {
+ DEBUG(3, ("No name or no GID?\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+ state->info->mpg = true;
+
+ tevent_req_done(req);
+}
+
+static int group_show_mpg_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info **res)
+{
+ struct group_show_mpg_state *state = tevent_req_data(req,
+ struct group_show_mpg_state);
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *res = talloc_move(mem_ctx, &state->info);
+
+ return EOK;
+}
+
+/*==================The main program=================================== */
+struct sss_groupshow_state {
+ struct group_info *root;
+
+ int ret;
+ bool done;
+};
+
+static void sss_group_show_done(struct tevent_req *req)
+{
+ int ret;
+ struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+ struct sss_groupshow_state);
+
+ ret = group_show_recv(sss_state, req, &sss_state->root);
+ talloc_zfree(req);
+
+ sss_state->ret = ret;
+ sss_state->done = true;
+}
+
+static void sss_group_show_mpg_done(struct tevent_req *req)
+{
+ int ret;
+ struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+ struct sss_groupshow_state);
+
+ ret = group_show_mpg_recv(sss_state, req, &sss_state->root);
+ talloc_zfree(req);
+
+ sss_state->ret = ret;
+ sss_state->done = true;
+}
+
+static void print_group_info(struct group_info *g, int level)
+{
+ int i;
+ char padding[512];
+ char fmt[8];
+
+ snprintf(fmt, 8, "%%%ds", level*PADDING_SPACES);
+ snprintf(padding, 512, fmt, "");
+
+ printf(_("%s%sGroup: %s\n"), padding,
+ g->mpg ? _("Magic Private ") : "",
+ g->name);
+ printf(_("%sGID number: %d\n"), padding, g->gid);
+
+ printf(_("%sMember users: "), padding);
+ if (g->user_members) {
+ for (i=0; g->user_members[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ g->user_members[i]);
+ }
+ }
+ printf(_("\n%sIs a member of: "), padding);
+ if (g->memberofs) {
+ for (i=0; g->memberofs[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ g->memberofs[i]);
+ }
+ }
+ printf(_("\n%sMember groups: "), padding);
+}
+
+static void print_recursive(struct group_info **group_members, int level)
+{
+ int i;
+
+ if (group_members == NULL) {
+ return;
+ }
+
+ level++;
+ for (i=0; group_members[i]; ++i) {
+ printf("\n");
+ print_group_info(group_members[i], level);
+ printf("\n");
+ print_recursive(group_members[i]->group_members, level);
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ int pc_debug = 0;
+ bool pc_recursive = false;
+ const char *pc_groupname = NULL;
+ struct tools_ctx *tctx = NULL;
+ struct tevent_req *req = NULL;
+ struct sss_groupshow_state *state = NULL;
+ int i;
+
+ poptContext pc = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
+ 0, _("The debug level to run with"), NULL },
+ { "recursive", 'R', POPT_ARG_NONE, NULL, 'r',
+ _("Print indirect group members recursively"), NULL },
+ POPT_TABLEEND
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse ops_ctx */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'r':
+ pc_recursive = true;
+ break;
+ }
+ }
+
+ debug_level = pc_debug;
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to show\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* The search itself */
+ state = talloc_zero(tctx, struct sss_groupshow_state);
+ if (!state) {
+ goto fini;
+ }
+
+ req = group_show_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+ tctx->local, pc_recursive, tctx->octx->name);
+ if (!req) {
+ ERROR("Cannot initiate search\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ tevent_req_set_callback(req, sss_group_show_done, state);
+ while (!state->done) {
+ tevent_loop_once(tctx->ev);
+ }
+ ret = state->ret;
+
+ /* Also show MPGs */
+ if (ret == ENOENT) {
+ state->done = false;
+ state->ret = EOK;
+
+ req = group_show_mpg_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+ tctx->local, tctx->octx->name);
+ if (!req) {
+ ERROR("Cannot initiate search\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ tevent_req_set_callback(req, sss_group_show_mpg_done, state);
+ while (!state->done) {
+ tevent_loop_once(tctx->ev);
+ }
+ ret = state->ret;
+ }
+
+ /* Process result */
+ if (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such group in local domain. "
+ "Printing groups only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not print group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* print the results */
+ print_group_info(state->root, 0);
+ if (pc_recursive) {
+ printf("\n");
+ print_recursive(state->root->group_members, 0);
+ } else {
+ if (state->root->group_members) {
+ for (i=0; state->root->group_members[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ state->root->group_members[i]->name);
+ }
+ }
+ printf("\n");
+ }
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
diff --git a/src/tools/sss_sync_ops.c b/src/tools/sss_sync_ops.c
new file mode 100644
index 00000000..25b8ac7a
--- /dev/null
+++ b/src/tools/sss_sync_ops.c
@@ -0,0 +1,1838 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <tevent.h>
+#include <talloc.h>
+#include <sys/types.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/sss_sync_ops.h"
+
+/* Default settings for user attributes */
+#define DFL_SHELL_VAL "/bin/bash"
+#define DFL_BASEDIR_VAL "/home"
+#define DFL_CREATE_HOMEDIR "TRUE"
+#define DFL_REMOVE_HOMEDIR "TRUE"
+#define DFL_UMASK 077
+#define DFL_SKEL_DIR "/etc/skel"
+#define DFL_MAIL_DIR "/var/spool/mail"
+
+
+#define VAR_CHECK(var, val, attr, msg) do { \
+ if (var != (val)) { \
+ DEBUG(1, (msg" attribute: %s", attr)); \
+ return val; \
+ } \
+} while(0)
+
+#define SYNC_LOOP(ops, retval) do { \
+ while (!ops->done) { \
+ tevent_loop_once(ev); \
+ } \
+ retval = ops->error; \
+} while(0)
+
+struct sync_op_res {
+ struct ops_ctx *data;
+ int error;
+ bool done;
+};
+
+/*
+ * Generic recv function
+ */
+static int sync_ops_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/*
+ * Generic add member to group
+ */
+struct add_to_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ int cur;
+ struct ops_ctx *data;
+ struct ldb_dn *member_dn;
+};
+
+static void add_to_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *add_to_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data,
+ struct ldb_dn *member_dn)
+{
+ struct add_to_groups_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *parent_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct add_to_groups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+ state->member_dn = member_dn;
+ state->cur = 0;
+
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->addgroups[state->cur]);
+ if (!parent_dn) {
+ return NULL;
+ }
+
+ subreq = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_ADD);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, add_to_groups_done, req);
+ return req;
+}
+
+static void add_to_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct add_to_groups_state *state = tevent_req_data(req,
+ struct add_to_groups_state);
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct tevent_req *next_group_req;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* go on to next group */
+ state->cur++;
+
+ /* check if we added all of them */
+ if (state->data->addgroups[state->cur] == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /* if not, schedule a new addition */
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->addgroups[state->cur]);
+ if (!parent_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ next_group_req = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_ADD);
+ if (!next_group_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(next_group_req, add_to_groups_done, req);
+}
+
+static int add_to_groups_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Generic remove member from group
+ */
+struct remove_from_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ int cur;
+ struct ops_ctx *data;
+ struct ldb_dn *member_dn;
+};
+
+static void remove_from_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *remove_from_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data,
+ struct ldb_dn *member_dn)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *parent_dn;
+ struct remove_from_groups_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct remove_from_groups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+ state->member_dn = member_dn;
+ state->cur = 0;
+
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->rmgroups[state->cur]);
+ if (!parent_dn) {
+ return NULL;
+ }
+
+ subreq = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_DELETE);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, remove_from_groups_done, req);
+ return req;
+}
+
+static void remove_from_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct remove_from_groups_state *state = tevent_req_data(req,
+ struct remove_from_groups_state);
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct tevent_req *next_group_req;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* go on to next group */
+ state->cur++;
+
+ /* check if we removed all of them */
+ if (state->data->rmgroups[state->cur] == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /* if not, schedule a new removal */
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->rmgroups[state->cur]);
+ if (!parent_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ next_group_req = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_DELETE);
+ if (!next_group_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(next_group_req, remove_from_groups_done, req);
+}
+
+static int remove_from_groups_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Add a user
+ */
+struct user_add_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct ops_ctx *data;
+};
+
+static void user_add_to_group_done(struct tevent_req *groupreq);
+static void user_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *user_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_add_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state, struct user_add_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ subreq = sysdb_add_user_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->data->uid, state->data->gid,
+ state->data->gecos, state->data->home,
+ state->data->shell, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_add_done, req);
+ return req;
+}
+
+static void user_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct user_add_state *state = tevent_req_data(req,
+ struct user_add_state);
+ int ret;
+ struct ldb_dn *member_dn;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_add_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups) {
+ member_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!member_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, member_dn);
+ tevent_req_set_callback(groupreq, user_add_to_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void user_add_to_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static int user_add_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Remove a user
+ */
+struct user_del_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct ops_ctx *data;
+};
+
+static void user_del_done(struct tevent_req *subreq);
+
+static struct tevent_req *user_del_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_del_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *user_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct user_del_state);
+ if (req == NULL) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ user_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name, state->data->name);
+ if (!user_dn) {
+ DEBUG(1, ("Could not construct a user DN\n"));
+ return NULL;
+ }
+
+ subreq = sysdb_delete_entry_send(state,
+ state->ev, state->handle,
+ user_dn, false);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_del_done, req);
+ return req;
+}
+
+static void user_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int user_del_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Modify a user
+ */
+struct user_mod_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct sysdb_attrs *attrs;
+ struct ldb_dn *member_dn;
+
+ struct ops_ctx *data;
+};
+
+static int usermod_build_attrs(TALLOC_CTX *mem_ctx,
+ const char *gecos,
+ const char *home,
+ const char *shell,
+ uid_t uid,
+ gid_t gid,
+ int lock,
+ struct sysdb_attrs **_attrs)
+{
+ int ret;
+ struct sysdb_attrs *attrs;
+
+ attrs = sysdb_new_attrs(mem_ctx);
+ if (attrs == NULL) {
+ return ENOMEM;
+ }
+
+ if (shell) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_SHELL,
+ shell);
+ VAR_CHECK(ret, EOK, SYSDB_SHELL,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (home) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_HOMEDIR,
+ home);
+ VAR_CHECK(ret, EOK, SYSDB_HOMEDIR,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (gecos) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_GECOS,
+ gecos);
+ VAR_CHECK(ret, EOK, SYSDB_GECOS,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (uid) {
+ ret = sysdb_attrs_add_long(attrs,
+ SYSDB_UIDNUM,
+ uid);
+ VAR_CHECK(ret, EOK, SYSDB_UIDNUM,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (gid) {
+ ret = sysdb_attrs_add_long(attrs,
+ SYSDB_GIDNUM,
+ gid);
+ VAR_CHECK(ret, EOK, SYSDB_GIDNUM,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (lock == DO_LOCK) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_DISABLED,
+ "true");
+ VAR_CHECK(ret, EOK, SYSDB_DISABLED,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (lock == DO_UNLOCK) {
+ /* PAM code checks for 'false' value in SYSDB_DISABLED attribute */
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_DISABLED,
+ "false");
+ VAR_CHECK(ret, EOK, SYSDB_DISABLED,
+ "Could not add attribute to changeset\n");
+ }
+
+ *_attrs = attrs;
+ return EOK;
+}
+
+static void user_mod_attr_done(struct tevent_req *attrreq);
+static void user_mod_attr_wakeup(struct tevent_req *subreq);
+static void user_mod_rm_group_done(struct tevent_req *groupreq);
+static void user_mod_add_group_done(struct tevent_req *groupreq);
+
+static struct tevent_req *user_mod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_mod_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ int ret;
+ struct timeval tv = { 0, 0 };
+
+ req = tevent_req_create(mem_ctx, &state, struct user_mod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ if (data->addgroups || data->rmgroups) {
+ state->member_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!state->member_dn) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ }
+
+ ret = usermod_build_attrs(state,
+ state->data->gecos,
+ state->data->home,
+ state->data->shell,
+ state->data->uid,
+ state->data->gid,
+ state->data->lock,
+ &state->attrs);
+ if (ret != EOK) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_mod_attr_wakeup, req);
+ return req;
+}
+
+static void user_mod_attr_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ struct tevent_req *attrreq, *groupreq;
+
+ if (state->attrs->num != 0) {
+ attrreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->attrs, SYSDB_MOD_REP);
+ if (!attrreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(attrreq, user_mod_attr_done, req);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ /* No changes to be made, mark request as done */
+ tevent_req_done(req);
+}
+
+static void user_mod_attr_done(struct tevent_req *attrreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(attrreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ int ret;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_set_user_attr_recv(attrreq);
+ talloc_zfree(attrreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void user_mod_rm_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ int ret;
+ struct tevent_req *addreq;
+
+ ret = remove_from_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ addreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!addreq) {
+ tevent_req_error(req, ENOMEM);
+ }
+ tevent_req_set_callback(addreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void user_mod_add_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static int user_mod_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Add a group
+ */
+struct group_add_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sysdb_attrs *attrs;
+
+ struct ops_ctx *data;
+};
+
+static void group_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *group_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_add_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_add_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ subreq = sysdb_add_group_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->data->gid, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_add_done, req);
+ return req;
+}
+
+static void group_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_add_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int group_add_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Delete a group
+ */
+struct group_del_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sysdb_attrs *attrs;
+
+ struct ops_ctx *data;
+};
+
+static void group_del_done(struct tevent_req *subreq);
+
+static struct tevent_req *group_del_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_del_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *group_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_del_state);
+ if (req == NULL) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ group_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name, state->data->name);
+ if (group_dn == NULL) {
+ DEBUG(1, ("Could not construct a group DN\n"));
+ return NULL;
+ }
+
+ subreq = sysdb_delete_entry_send(state,
+ state->ev, state->handle,
+ group_dn, false);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_del_done, req);
+ return req;
+}
+
+static void group_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int group_del_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Modify a group
+ */
+struct group_mod_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct sysdb_attrs *attrs;
+ struct ldb_dn *member_dn;
+
+ struct ops_ctx *data;
+};
+
+static void group_mod_attr_done(struct tevent_req *);
+static void group_mod_attr_wakeup(struct tevent_req *);
+static void group_mod_add_group_done(struct tevent_req *groupreq);
+static void group_mod_rm_group_done(struct tevent_req *groupreq);
+
+static struct tevent_req *group_mod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_mod_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct timeval tv = { 0, 0 };
+
+ req = tevent_req_create(mem_ctx, &state, struct group_mod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ if (data->addgroups || data->rmgroups) {
+ state->member_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!state->member_dn) {
+ return NULL;
+ }
+ }
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_mod_attr_wakeup, req);
+ return req;
+}
+
+static void group_mod_attr_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ struct sysdb_attrs *attrs;
+ struct tevent_req *attrreq;
+ struct tevent_req *groupreq;
+ int ret;
+
+ if (state->data->gid != 0) {
+ attrs = sysdb_new_attrs(NULL);
+ if (!attrs) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, state->data->gid);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ attrreq = sysdb_set_group_attr_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ attrs, SYSDB_MOD_REP);
+ if (!attrreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(attrreq, group_mod_attr_done, req);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ /* No changes to be made, mark request as done */
+ tevent_req_done(req);
+}
+
+static void group_mod_attr_done(struct tevent_req *attrreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(attrreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ int ret;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_set_group_attr_recv(attrreq);
+ talloc_zfree(attrreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void group_mod_rm_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ int ret;
+ struct tevent_req *addreq;
+
+ ret = remove_from_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ addreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!addreq) {
+ tevent_req_error(req, ENOMEM);
+ }
+ tevent_req_set_callback(addreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void group_mod_add_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static int group_mod_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+int userdel_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ int remove_home)
+{
+ int ret;
+ char *conf_path;
+ bool dfl_remove_home;
+
+ conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name);
+ if (!conf_path) {
+ return ENOMEM;
+ }
+
+ /* remove homedir on user creation? */
+ if (!remove_home) {
+ ret = confdb_get_bool(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_REMOVE_HOMEDIR,
+ DFL_REMOVE_HOMEDIR, &dfl_remove_home);
+ if (ret != EOK) {
+ goto done;
+ }
+ data->remove_homedir = dfl_remove_home;
+ } else {
+ data->remove_homedir = (remove_home == DO_REMOVE_HOME);
+ }
+
+ /* a directory to remove mail spools from */
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_MAIL_DIR,
+ DFL_MAIL_DIR, &data->maildir);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(conf_path);
+ return ret;
+}
+
+/*
+ * Default values for add operations
+ */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ int create_home,
+ const char *skeldir)
+{
+ int ret;
+ char *basedir = NULL;
+ char *conf_path = NULL;
+
+ conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name);
+ if (!conf_path) {
+ return ENOMEM;
+ }
+
+ /* gecos */
+ data->gecos = talloc_strdup(mem_ctx, gecos ? gecos : data->name);
+ if (!data->gecos) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(7, ("Gecos: %s\n", data->gecos));
+
+ /* homedir */
+ if (homedir) {
+ data->home = talloc_strdup(data, homedir);
+ } else {
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_DEFAULT_BASEDIR,
+ DFL_BASEDIR_VAL, &basedir);
+ if (ret != EOK) {
+ goto done;
+ }
+ data->home = talloc_asprintf(mem_ctx, "%s/%s", basedir, data->name);
+ }
+ if (!data->home) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(7, ("Homedir: %s\n", data->home));
+
+ /* default shell */
+ if (!shell) {
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_DEFAULT_SHELL,
+ DFL_SHELL_VAL, &data->shell);
+ if (ret != EOK) {
+ goto done;
+ }
+ } else {
+ data->shell = talloc_strdup(mem_ctx, shell);
+ if (!data->shell) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ DEBUG(7, ("Shell: %s\n", data->shell));
+
+ /* create homedir on user creation? */
+ if (!create_home) {
+ ret = confdb_get_bool(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_CREATE_HOMEDIR,
+ DFL_CREATE_HOMEDIR, &data->create_homedir);
+ if (ret != EOK) {
+ goto done;
+ }
+ } else {
+ data->create_homedir = (create_home == DO_CREATE_HOME);
+ }
+ DEBUG(7, ("Auto create homedir: %s\n", data->create_homedir?"True":"False"));
+
+ /* umask to create homedirs */
+ ret = confdb_get_int(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_UMASK,
+ DFL_UMASK, (int *) &data->umask);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(7, ("Umask: %o\n", data->umask));
+
+ /* a directory to create mail spools in */
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_MAIL_DIR,
+ DFL_MAIL_DIR, &data->maildir);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(7, ("Mail dir: %s\n", data->maildir));
+
+ /* skeleton dir */
+ if (!skeldir) {
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_SKEL_DIR,
+ DFL_SKEL_DIR, &data->skeldir);
+ if (ret != EOK) {
+ goto done;
+ }
+ } else {
+ data->skeldir = talloc_strdup(mem_ctx, skeldir);
+ if (!data->skeldir) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ DEBUG(7, ("Skeleton dir: %s\n", data->skeldir));
+
+ ret = EOK;
+done:
+ talloc_free(basedir);
+ talloc_free(conf_path);
+ return ret;
+}
+
+/*
+ * Public interface for adding users
+ */
+static void useradd_done(struct tevent_req *);
+
+int useradd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_add_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, useradd_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void useradd_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_add_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Adding user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for deleting users
+ */
+static void userdel_done(struct tevent_req *req);
+
+int userdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_del_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, userdel_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void userdel_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_del_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Removing user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for modifying users
+ */
+static void usermod_done(struct tevent_req *req);
+
+int usermod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_mod_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, usermod_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void usermod_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_mod_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Modifying user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for adding groups
+ */
+static void groupadd_done(struct tevent_req *);
+
+int groupadd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_add_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupadd_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupadd_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_add_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Adding group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for deleting groups
+ */
+static void groupdel_done(struct tevent_req *req);
+
+int groupdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_del_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupdel_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupdel_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_del_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Removing group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for modifying groups
+ */
+static void groupmod_done(struct tevent_req *req);
+
+int groupmod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_mod_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupmod_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupmod_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_mod_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Modifying group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Synchronous transaction functions
+ */
+static void start_transaction_done(struct tevent_req *req);
+
+void start_transaction(struct tools_ctx *tctx)
+{
+ struct tevent_req *req;
+
+ /* make sure handle is NULL, as it is the spy to check if the transaction
+ * has been started */
+ tctx->handle = NULL;
+ tctx->error = 0;
+
+ req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction\n"));
+ tctx->error = ENOMEM;
+ return;
+ }
+ tevent_req_set_callback(req, start_transaction_done, tctx);
+
+ /* loop to obtain a transaction */
+ while (!tctx->handle && !tctx->error) {
+ tevent_loop_once(tctx->ev);
+ }
+}
+
+static void start_transaction_done(struct tevent_req *req)
+{
+ struct tools_ctx *tctx = tevent_req_callback_data(req,
+ struct tools_ctx);
+ int ret;
+
+ ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+ if (ret) {
+ tctx->error = ret;
+ }
+ if (!tctx->handle) {
+ tctx->error = EIO;
+ }
+ talloc_zfree(req);
+}
+
+static void end_transaction_done(struct tevent_req *req);
+
+void end_transaction(struct tools_ctx *tctx)
+{
+ struct tevent_req *req;
+
+ tctx->error = 0;
+
+ req = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+ if (!req) {
+ /* free transaction and signal error */
+ tctx->error = ENOMEM;
+ return;
+ }
+ tevent_req_set_callback(req, end_transaction_done, tctx);
+
+ /* loop to obtain a transaction */
+ while (!tctx->transaction_done && !tctx->error) {
+ tevent_loop_once(tctx->ev);
+ }
+}
+
+static void end_transaction_done(struct tevent_req *req)
+{
+ struct tools_ctx *tctx = tevent_req_callback_data(req,
+ struct tools_ctx);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(req);
+
+ tctx->transaction_done = true;
+ tctx->error = ret;
+ talloc_zfree(req);
+}
+
+/*
+ * getpwnam, getgrnam and friends
+ */
+static void sss_getpwnam_done(void *ptr, int status,
+ struct ldb_result *lrs);
+
+int sysdb_getpwnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out)
+{
+ int ret;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ if (out == NULL) {
+ DEBUG(1, ("NULL passed for storage pointer\n"));
+ return EINVAL;
+ }
+ res->data = *out;
+
+ ret = sysdb_getpwnam(mem_ctx,
+ sysdb,
+ domain,
+ name,
+ sss_getpwnam_done,
+ res);
+
+ SYNC_LOOP(res, ret);
+
+ return ret;
+}
+
+static void sss_getpwnam_done(void *ptr, int status,
+ struct ldb_result *lrs)
+{
+ struct sync_op_res *res = talloc_get_type(ptr, struct sync_op_res );
+ const char *str;
+
+ res->done = true;
+
+ if (status != LDB_SUCCESS) {
+ res->error = status;
+ return;
+ }
+
+ switch (lrs->count) {
+ case 0:
+ DEBUG(1, ("No result for sysdb_getpwnam call\n"));
+ res->error = ENOENT;
+ break;
+
+ case 1:
+ res->error = EOK;
+ /* fill ops_ctx */
+ res->data->uid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_UIDNUM, 0);
+
+ res->data->gid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_GIDNUM, 0);
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_NAME, NULL);
+ res->data->name = talloc_strdup(res, str);
+ if (res->data->name == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_GECOS, NULL);
+ res->data->gecos = talloc_strdup(res, str);
+ if (res->data->gecos == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_HOMEDIR, NULL);
+ res->data->home = talloc_strdup(res, str);
+ if (res->data->home == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_SHELL, NULL);
+ res->data->shell = talloc_strdup(res, str);
+ if (res->data->shell == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_DISABLED, NULL);
+ if (str == NULL) {
+ res->data->lock = DO_UNLOCK;
+ } else {
+ if (strcasecmp(str, "true") == 0) {
+ res->data->lock = DO_LOCK;
+ } else if (strcasecmp(str, "false") == 0) {
+ res->data->lock = DO_UNLOCK;
+ } else { /* Invalid value */
+ DEBUG(2, ("Invalid value for %s attribute: %s\n",
+ SYSDB_DISABLED, str ? str : "NULL"));
+ res->error = EIO;
+ return;
+ }
+ }
+ break;
+
+ default:
+ DEBUG(1, ("More than one result for sysdb_getpwnam call\n"));
+ res->error = EIO;
+ break;
+ }
+}
+
+static void sss_getgrnam_done(void *ptr, int status,
+ struct ldb_result *lrs);
+
+int sysdb_getgrnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out)
+{
+ int ret;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ if (out == NULL) {
+ DEBUG(1, ("NULL passed for storage pointer\n"));
+ return EINVAL;
+ }
+ res->data = *out;
+
+ ret = sysdb_getgrnam(mem_ctx,
+ sysdb,
+ domain,
+ name,
+ sss_getgrnam_done,
+ res);
+
+ SYNC_LOOP(res, ret);
+
+ return ret;
+}
+
+static void sss_getgrnam_done(void *ptr, int status,
+ struct ldb_result *lrs)
+{
+ struct sync_op_res *res = talloc_get_type(ptr, struct sync_op_res );
+ const char *str;
+
+ res->done = true;
+
+ if (status != LDB_SUCCESS) {
+ res->error = status;
+ return;
+ }
+
+ switch (lrs->count) {
+ case 0:
+ DEBUG(1, ("No result for sysdb_getgrnam call\n"));
+ res->error = ENOENT;
+ break;
+
+ /* sysdb_getgrnam also returns members */
+ default:
+ res->error = EOK;
+ /* fill ops_ctx */
+ res->data->gid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_GIDNUM, 0);
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_NAME, NULL);
+ res->data->name = talloc_strdup(res, str);
+ if (res->data->name == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+ break;
+ }
+}
+
diff --git a/src/tools/sss_sync_ops.h b/src/tools/sss_sync_ops.h
new file mode 100644
index 00000000..383319a8
--- /dev/null
+++ b/src/tools/sss_sync_ops.h
@@ -0,0 +1,125 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __SSS_OPS_H__
+#define __SSS_OPS_H__
+
+#include "tools/tools_util.h"
+#include <stdbool.h>
+
+#define DO_LOCK 1
+#define DO_UNLOCK 2
+
+/* 0 = not set, pick default */
+#define DO_CREATE_HOME 1
+#define DO_NOT_CREATE_HOME 2
+#define DO_REMOVE_HOME 1
+#define DO_NOT_REMOVE_HOME 2
+#define DO_FORCE_REMOVAL 1
+
+struct ops_ctx {
+ struct sss_domain_info *domain;
+
+ char *name;
+ uid_t uid;
+ gid_t gid;
+ char *gecos;
+ char *home;
+ char *shell;
+ int lock;
+
+ bool create_homedir;
+ bool remove_homedir;
+ mode_t umask;
+ char *skeldir;
+ char *maildir;
+
+ char **addgroups;
+ char **rmgroups;
+};
+
+/* default values for add operations */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ int create_home,
+ const char *skeldir);
+
+/* default values for remove operations */
+int userdel_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ int remove_home);
+
+/* synchronous operations */
+int useradd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int userdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int usermod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+
+int groupadd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int groupdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int groupmod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+
+void start_transaction(struct tools_ctx *tctx);
+void end_transaction(struct tools_ctx *tctx);
+
+int sysdb_getpwnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out);
+
+int sysdb_getgrnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out);
+
+#endif /* __SSS_OPS_H__ */
+
diff --git a/src/tools/sss_useradd.c b/src/tools/sss_useradd.c
new file mode 100644
index 00000000..077ac99f
--- /dev/null
+++ b/src/tools/sss_useradd.c
@@ -0,0 +1,349 @@
+/*
+ SSSD
+
+ sss_useradd
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
+{
+ struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx);
+
+ if (error) {
+ tctx->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ tctx->error = ENOENT;
+ break;
+
+ case 1:
+ tctx->octx->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0);
+ if (tctx->octx->gid == 0) {
+ tctx->error = ERANGE;
+ }
+ break;
+
+ default:
+ tctx->error = EFAULT;
+ break;
+ }
+}
+
+/* Returns a gid for a given groupname. If a numerical gid
+ * is given, returns that as integer (rationale: shadow-utils)
+ * On error, returns -EINVAL
+ */
+static int get_gid(struct tools_ctx *tctx, const char *groupname)
+{
+ char *end_ptr;
+ int ret;
+
+ errno = 0;
+ tctx->octx->gid = strtoul(groupname, &end_ptr, 10);
+ if (groupname == '\0' || *end_ptr != '\0' ||
+ errno != 0 || tctx->octx->gid == 0) {
+ /* Does not look like a gid - find the group name */
+
+ ret = sysdb_getgrnam(tctx->octx, tctx->sysdb,
+ tctx->octx->domain, groupname,
+ get_gid_callback, tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
+ goto done;
+ }
+
+ tctx->error = EOK;
+ tctx->octx->gid = 0;
+ while ((tctx->error == EOK) && (tctx->octx->gid == 0)) {
+ tevent_loop_once(tctx->ev);
+ }
+
+ if (tctx->error) {
+ DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
+ goto done;
+ }
+ }
+
+done:
+ return ret;
+}
+
+int main(int argc, const char **argv)
+{
+ uid_t pc_uid = 0;
+ const char *pc_group = NULL;
+ const char *pc_gecos = NULL;
+ const char *pc_home = NULL;
+ char *pc_shell = NULL;
+ int pc_debug = 0;
+ int pc_create_home = 0;
+ const char *pc_username = NULL;
+ const char *pc_skeldir = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL },
+ { "uid", 'u', POPT_ARG_INT, &pc_uid, 0, _("The UID of the user"), NULL },
+ { "gid", 'g', POPT_ARG_STRING, &pc_group, 0, _("The GID or group name of the user"), NULL },
+ { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, _("The comment string"), NULL },
+ { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL },
+ { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL },
+ { "groups", 'G', POPT_ARG_STRING, NULL, 'G', _("Groups"), NULL },
+ { "create-home", 'm', POPT_ARG_NONE, NULL, 'm', _("Create user's directory if it does not exist"), NULL },
+ { "no-create-home", 'M', POPT_ARG_NONE, NULL, 'M', _("Never create user's directory, overrides config"), NULL },
+ { "skel", 'k', POPT_ARG_STRING, &pc_skeldir, 0, _("Specify an alternative skeleton directory") },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct tools_ctx *tctx = NULL;
+ char *groups = NULL;
+ char *badgroup = NULL;
+ int ret;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'G':
+ groups = poptGetOptArg(pc);
+ if (!groups) goto fini;
+
+ case 'm':
+ pc_create_home = DO_CREATE_HOME;
+ break;
+
+ case 'M':
+ pc_create_home = DO_NOT_CREATE_HOME;
+ break;
+ }
+ }
+
+ debug_level = pc_debug;
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* username is an argument without --option */
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, (_("Specify user to add\n")));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (groups) {
+ ret = parse_groups(tctx, groups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the user to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the user to\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ /* Same as shadow-utils useradd, -g can specify gid or group name */
+ if (pc_group != NULL) {
+ ret = get_gid(tctx, pc_group);
+ if (ret != EOK) {
+ ERROR("Cannot get group information for the user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ tctx->octx->uid = pc_uid;
+
+ /*
+ * Fills in defaults for ops_ctx user did not specify.
+ */
+ ret = useradd_defaults(tctx, tctx->confdb, tctx->octx,
+ pc_gecos, pc_home, pc_shell,
+ pc_create_home, pc_skeldir);
+ if (ret != EOK) {
+ ERROR("Cannot set default values\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* arguments processed, go on to actual work */
+ if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
+ ERROR("The selected UID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* useradd */
+ ret = useradd(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+ /* Create user's home directory and/or mail spool */
+ if (tctx->octx->create_homedir) {
+ /* We need to know the UID and GID of the user, if
+ * sysdb did assign it automatically, do a lookup */
+ if (tctx->octx->uid == 0 || tctx->octx->gid == 0) {
+ ret = sysdb_getpwnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ tctx->octx->name,
+ tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ ERROR("Cannot get info about the user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ ret = create_homedir(tctx,
+ tctx->octx->skeldir,
+ tctx->octx->home,
+ tctx->octx->name,
+ tctx->octx->uid,
+ tctx->octx->gid,
+ tctx->octx->umask);
+ if (ret == EEXIST) {
+ ERROR("User's home directory already exists, not copying "
+ "data from skeldir\n");
+ } else if (ret != EOK) {
+ ERROR("Cannot create user's home directory: %s\n", strerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = create_mail_spool(tctx,
+ tctx->octx->name,
+ tctx->octx->maildir,
+ tctx->octx->uid,
+ tctx->octx->gid);
+ if (ret != EOK) {
+ ERROR("Cannot create user's mail spool: %s\n", strerror(ret));
+ DEBUG(1, ("Cannot create user's mail spool: [%d][%s].\n",
+ ret, strerror(ret)));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+done:
+ if (tctx->error) {
+ switch (tctx->error) {
+ case ERANGE:
+ ERROR("Could not allocate ID for the user - domain full?\n");
+ break;
+
+ case EEXIST:
+ ERROR("A user or group with the same name or ID already exists\n");
+ break;
+
+ default:
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n",
+ tctx->error, strerror(tctx->error)));
+ ERROR("Transaction error. Could not add user.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ free(groups);
+ exit(ret);
+}
diff --git a/src/tools/sss_userdel.c b/src/tools/sss_userdel.c
new file mode 100644
index 00000000..e84d78b1
--- /dev/null
+++ b/src/tools/sss_userdel.c
@@ -0,0 +1,205 @@
+/*
+ SSSD
+
+ sss_userdel
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ struct tools_ctx *tctx = NULL;
+ const char *pc_username = NULL;
+
+ int pc_debug = 0;
+ int pc_remove = 0;
+ int pc_force = 0;
+ poptContext pc = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
+ 0, _("The debug level to run with"), NULL },
+ { "remove", 'r', POPT_ARG_NONE, NULL, 'r', _("Remove home directory and mail spool"), NULL },
+ { "no-remove", 'R', POPT_ARG_NONE, NULL, 'R', _("Do not remove home directory and mail spool"), NULL },
+ { "force", 'f', POPT_ARG_NONE, NULL, 'f', _("Force removal of files not owned by the user"), NULL },
+ POPT_TABLEEND
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'r':
+ pc_remove = DO_REMOVE_HOME;
+ break;
+
+ case 'R':
+ pc_remove = DO_NOT_REMOVE_HOME;
+ break;
+
+ case 'f':
+ pc_force = DO_FORCE_REMOVAL;
+ break;
+ }
+ }
+
+ debug_level = pc_debug;
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, _("Specify user to delete\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /*
+ * Fills in defaults for ops_ctx user did not specify.
+ */
+ ret = userdel_defaults(tctx, tctx->confdb, tctx->octx, pc_remove);
+ if (ret != EOK) {
+ ERROR("Cannot set default values\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = sysdb_getpwnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ tctx->octx->name,
+ tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ /* Error message will be printed in the switch */
+ goto done;
+ }
+
+ if ((tctx->octx->uid < tctx->local->id_min) ||
+ (tctx->local->id_max && tctx->octx->uid > tctx->local->id_max)) {
+ ERROR("User %s is outside the defined ID range for domain\n",
+ tctx->octx->name);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* userdel */
+ ret = userdel(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+ if (tctx->octx->remove_homedir) {
+ ret = remove_homedir(tctx,
+ tctx->octx->home,
+ tctx->octx->maildir,
+ tctx->octx->name,
+ tctx->octx->uid,
+ pc_force);
+ if (ret == EPERM) {
+ ERROR("Not removing home dir - not owned by user\n");
+ } else if (ret != EOK) {
+ ERROR("Cannot remove homedir: %s\n", strerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ ret = tctx->error;
+done:
+ if (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such user in local domain. "
+ "Removing users only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not remove user.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_usermod.c b/src/tools/sss_usermod.c
new file mode 100644
index 00000000..a272bc55
--- /dev/null
+++ b/src/tools/sss_usermod.c
@@ -0,0 +1,265 @@
+/*
+ SSSD
+
+ sss_usermod
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int pc_lock = 0;
+ uid_t pc_uid = 0;
+ gid_t pc_gid = 0;
+ char *pc_gecos = NULL;
+ char *pc_home = NULL;
+ char *pc_shell = NULL;
+ int pc_debug = 0;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL },
+ { "uid", 'u', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_uid, 0, _("The UID of the user"), NULL },
+ { "gid", 'g', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_gid, 0, _("The GID of the user"), NULL },
+ { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, _("The comment string"), NULL },
+ { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL },
+ { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL },
+ { "append-group", 'a', POPT_ARG_STRING, NULL, 'a', _("Groups to add this user to"), NULL },
+ { "remove-group", 'r', POPT_ARG_STRING, NULL, 'r', _("Groups to remove this user from"), NULL },
+ { "lock", 'L', POPT_ARG_NONE, NULL, 'L', _("Lock the account"), NULL },
+ { "unlock", 'U', POPT_ARG_NONE, NULL, 'U', _("Unlock the account"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ char *addgroups = NULL, *rmgroups = NULL;
+ int ret;
+ const char *pc_username = NULL;
+ struct tools_ctx *tctx = NULL;
+ char *badgroup = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'a':
+ addgroups = poptGetOptArg(pc);
+ if (addgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'r':
+ rmgroups = poptGetOptArg(pc);
+ if (rmgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'L':
+ pc_lock = DO_LOCK;
+ break;
+
+ case 'U':
+ pc_lock = DO_UNLOCK;
+ break;
+ }
+ }
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ /* username is an argument without --option */
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, _("Specify user to modify\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ /* check the username to be able to give sensible error message */
+ ret = sysdb_getpwnam_sync(tctx, tctx->ev, tctx->sysdb,
+ tctx->octx->name, tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ ERROR("Cannot find user in local domain, "
+ "modifying users is allowed only in local domain\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
+ ERROR("The selected UID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (addgroups) {
+ ret = parse_groups(tctx, addgroups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the user to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the user to\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (rmgroups) {
+ ret = parse_groups(tctx, rmgroups, &tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to remove the user from\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to remove the user from\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->rmgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ tctx->octx->gecos = pc_gecos;
+ tctx->octx->home = pc_home;
+ tctx->octx->shell = pc_shell;
+ tctx->octx->uid = pc_uid;
+ tctx->octx->gid = pc_gid;
+ tctx->octx->lock = pc_lock;
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* usermod */
+ ret = usermod(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ switch (ret) {
+ case ENOENT:
+ ERROR("Could not modify user - check if group names are correct\n");
+ break;
+
+ case EFAULT:
+ ERROR("Could not modify user - user already member of groups?\n");
+ break;
+
+ default:
+ ERROR("Transaction error. Could not modify user.\n");
+ break;
+ }
+
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ free(addgroups);
+ free(rmgroups);
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ exit(ret);
+}
diff --git a/src/tools/tools_util.c b/src/tools/tools_util.c
new file mode 100644
index 00000000..97945238
--- /dev/null
+++ b/src/tools/tools_util.c
@@ -0,0 +1,520 @@
+/*
+ SSSD
+
+ tools_utils.c
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "config.h"
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+static int setup_db(struct tools_ctx *ctx)
+{
+ char *confdb_path;
+ int ret;
+
+ /* Create the event context */
+ ctx->ev = tevent_context_init(ctx);
+ if (ctx->ev == NULL) {
+ DEBUG(1, ("Could not create event context\n"));
+ return EIO;
+ }
+
+ confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (confdb_path == NULL) {
+ return ENOMEM;
+ }
+
+ /* Connect to the conf db */
+ ret = confdb_init(ctx, &ctx->confdb, confdb_path);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not initialize connection to the confdb\n"));
+ return ret;
+ }
+
+ ret = confdb_get_domain(ctx->confdb, "local", &ctx->local);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not get 'local' domain: [%d] [%s]\n", ret, strerror(ret)));
+ return ret;
+ }
+
+ /* open 'local' sysdb at default path */
+ ret = sysdb_domain_init(ctx, ctx->ev, ctx->local, DB_PATH, &ctx->sysdb);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not initialize connection to the sysdb\n"));
+ return ret;
+ }
+
+ talloc_free(confdb_path);
+ return EOK;
+}
+
+/*
+ * Print poptUsage as well as our error message
+ */
+void usage(poptContext pc, const char *error)
+{
+ poptPrintUsage(pc, stderr, 0);
+ if (error) fprintf(stderr, "%s", error);
+}
+
+int parse_groups(TALLOC_CTX *mem_ctx, const char *optstr, char ***_out)
+{
+ char **out;
+ char *orig, *n, *o;
+ char delim = ',';
+ unsigned int tokens = 1;
+ int i;
+
+ orig = talloc_strdup(mem_ctx, optstr);
+ if (!orig) return ENOMEM;
+
+ n = orig;
+ tokens = 1;
+ while ((n = strchr(n, delim))) {
+ n++;
+ tokens++;
+ }
+
+ out = talloc_array(mem_ctx, char *, tokens+1);
+ if (!out) {
+ talloc_free(orig);
+ return ENOMEM;
+ }
+
+ n = o = orig;
+ for (i = 0; i < tokens; i++) {
+ o = n;
+ n = strchr(n, delim);
+ if (!n) {
+ break;
+ }
+ *n = '\0';
+ n++;
+ out[i] = talloc_strdup(out, o);
+ }
+ out[tokens-1] = talloc_strdup(out, o);
+ out[tokens] = NULL;
+
+ talloc_free(orig);
+ *_out = out;
+ return EOK;
+}
+
+int parse_group_name_domain(struct tools_ctx *tctx,
+ char **groups)
+{
+ int i;
+ int ret;
+ char *name = NULL;
+ char *domain = NULL;
+
+ if (!groups) {
+ return EOK;
+ }
+
+ for (i = 0; groups[i]; ++i) {
+ ret = sss_parse_name(tctx, tctx->snctx, groups[i], &domain, &name);
+
+ /* If FQDN is specified, it must be within the same domain as user */
+ if (domain) {
+ if (strcmp(domain, tctx->octx->domain->name) != 0) {
+ return EINVAL;
+ }
+
+ /* Use only groupname */
+ talloc_zfree(groups[i]);
+ groups[i] = talloc_strdup(tctx, name);
+ if (groups[i] == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ talloc_zfree(name);
+ talloc_zfree(domain);
+ }
+
+ talloc_zfree(name);
+ talloc_zfree(domain);
+ return EOK;
+}
+
+int parse_name_domain(struct tools_ctx *tctx,
+ const char *fullname)
+{
+ int ret;
+ char *domain = NULL;
+
+ ret = sss_parse_name(tctx, tctx->snctx, fullname, &domain, &tctx->octx->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Cannot parse full name\n"));
+ return ret;
+ }
+ DEBUG(5, ("Parsed username: %s\n", tctx->octx->name));
+
+ if (domain) {
+ DEBUG(5, ("Parsed domain: %s\n", domain));
+ /* only the local domain, whatever named is allowed in tools */
+ if (strcasecmp(domain, tctx->local->name) != 0) {
+ DEBUG(1, ("Invalid domain %s specified in FQDN\n", domain));
+ return EINVAL;
+ }
+ }
+
+ return EOK;
+}
+
+int check_group_names(struct tools_ctx *tctx,
+ char **grouplist,
+ char **badgroup)
+{
+ int ret;
+ int i;
+ struct ops_ctx *groupinfo;
+
+ groupinfo = talloc_zero(tctx, struct ops_ctx);
+ if (!groupinfo) {
+ return ENOMEM;
+ }
+
+ ret = EOK;
+ for (i=0; grouplist[i]; ++i) {
+ ret = sysdb_getgrnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ grouplist[i],
+ tctx->local,
+ &groupinfo);
+ if (ret) {
+ DEBUG(6, ("Cannot find group %s, ret: %d\n", grouplist[i], ret));
+ break;
+ }
+ }
+
+ talloc_zfree(groupinfo);
+ *badgroup = grouplist[i];
+ return ret;
+}
+
+int id_in_range(uint32_t id,
+ struct sss_domain_info *dom)
+{
+ if (id &&
+ ((id < dom->id_min) ||
+ (dom->id_max && id > dom->id_max))) {
+ return ERANGE;
+ }
+
+ return EOK;
+}
+
+int set_locale(void)
+{
+ char *c;
+
+ c = setlocale(LC_ALL, "");
+ if (c == NULL) {
+ return EIO;
+ }
+
+ errno = 0;
+ c = bindtextdomain(PACKAGE, LOCALEDIR);
+ if (c == NULL) {
+ return errno;
+ }
+
+ errno = 0;
+ c = textdomain(PACKAGE);
+ if (c == NULL) {
+ return errno;
+ }
+
+ return EOK;
+}
+
+int init_sss_tools(struct tools_ctx **_tctx)
+{
+ int ret;
+ struct tools_ctx *tctx;
+
+ tctx = talloc_zero(NULL, struct tools_ctx);
+ if (tctx == NULL) {
+ DEBUG(1, ("Could not allocate memory for tools context\n"));
+ return ENOMEM;
+ }
+
+ /* Connect to the database */
+ ret = setup_db(tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not set up database\n"));
+ goto fini;
+ }
+
+ ret = sss_names_init(tctx, tctx->confdb, &tctx->snctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not set up parsing\n"));
+ goto fini;
+ }
+
+ tctx->octx = talloc_zero(tctx, struct ops_ctx);
+ if (!tctx->octx) {
+ DEBUG(1, ("Could not allocate memory for data context\n"));
+ ERROR("Out of memory\n");
+ ret = ENOMEM;
+ goto fini;
+ }
+ tctx->octx->domain = tctx->local;
+
+ *_tctx = tctx;
+ ret = EOK;
+
+fini:
+ if (ret != EOK) talloc_free(tctx);
+ return ret;
+}
+
+/*
+ * Check is path is owned by uid
+ * returns 0 - owns
+ * -1 - does not own
+ * >0 - an error occured, error code
+ */
+static int is_owner(uid_t uid, const char *path)
+{
+ struct stat statres;
+ int ret;
+
+ ret = stat(path, &statres);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot stat %s: [%d][%s]\n", path, ret, strerror(ret)));
+ return ret;
+ }
+
+ if (statres.st_uid == uid) {
+ return EOK;
+ }
+ return -1;
+}
+
+static int remove_mail_spool(TALLOC_CTX *mem_ctx,
+ const char *maildir,
+ const char *username,
+ uid_t uid,
+ bool force)
+{
+ int ret;
+ char *spool_file;
+
+ spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username);
+ if (spool_file == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (force == false) {
+ /* Check the owner of the mail spool */
+ ret = is_owner(uid, spool_file);
+ switch (ret) {
+ case 0:
+ break;
+ case -1:
+ DEBUG(3, ("%s not owned by %d, not removing\n",
+ spool_file, uid));
+ ret = EACCES;
+ /* FALLTHROUGH */
+ default:
+ goto fail;
+ }
+ }
+
+ ret = unlink(spool_file);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot remove() the spool file %s: [%d][%s]\n",
+ spool_file, ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ talloc_free(spool_file);
+ return ret;
+}
+
+int remove_homedir(TALLOC_CTX *mem_ctx,
+ const char *homedir,
+ const char *maildir,
+ const char *username,
+ uid_t uid, bool force)
+{
+ int ret;
+
+ ret = remove_mail_spool(mem_ctx, maildir, username, uid, force);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot remove user's mail spool\n"));
+ /* Should this be fatal? I don't think so. Maybe convert to ERROR? */
+ }
+
+ if (force == false && is_owner(uid, homedir) == -1) {
+ DEBUG(1, ("Not removing home dir - not owned by user\n"));
+ return EPERM;
+ }
+
+ /* Remove the tree */
+ ret = remove_tree(homedir);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot remove homedir %s: %d\n",
+ homedir, ret));
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* The reason for not putting this into create_homedir
+ * is better granularity when it comes to reporting error
+ * messages and tracebacks in pysss
+ */
+int create_mail_spool(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *maildir,
+ uid_t uid, gid_t gid)
+{
+ char *spool_file = NULL;
+ int fd;
+ int ret;
+
+ spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username);
+ if (spool_file == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ selinux_file_context(spool_file);
+
+ fd = open(spool_file, O_CREAT | O_WRONLY | O_EXCL, 0);
+ if (fd < 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot open() the spool file: [%d][%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fchmod(fd, 0600);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fchmod() the spool file: [%d][%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fchown(fd, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fchown() the spool file: [%d][%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = fsync(fd);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot fsync() the spool file: [%d][%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ret = close(fd);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot close() the spool file: [%d][%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ reset_selinux_file_context();
+ talloc_free(spool_file);
+ return ret;
+}
+
+int create_homedir(TALLOC_CTX *mem_ctx,
+ const char *skeldir,
+ const char *homedir,
+ const char *username,
+ uid_t uid,
+ gid_t gid,
+ mode_t default_umask)
+{
+ int ret;
+
+ selinux_file_context(homedir);
+
+ ret = mkdir(homedir, 0);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot create user's home directory: [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = chown(homedir, uid, gid);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chown user's home directory: [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = chmod(homedir, 0777 & ~default_umask);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot chmod user's home directory: [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ reset_selinux_file_context();
+
+ ret = copy_tree(skeldir, homedir, uid, gid);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot populate user's home directory: [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+done:
+ reset_selinux_file_context();
+ return ret;
+}
+
diff --git a/src/tools/tools_util.h b/src/tools/tools_util.h
new file mode 100644
index 00000000..a643e739
--- /dev/null
+++ b/src/tools/tools_util.h
@@ -0,0 +1,108 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+
+#ifndef __TOOLS_UTIL_H__
+#define __TOOLS_UTIL_H__
+
+#include <popt.h>
+
+#include "util/util.h"
+
+#define CHECK_ROOT(val, prg_name) do { \
+ val = getuid(); \
+ if (val != 0) { \
+ DEBUG(1, ("Running under %d, must be root\n", val)); \
+ ERROR("%s must be run as root\n", prg_name); \
+ val = EXIT_FAILURE; \
+ goto fini; \
+ } \
+} while(0)
+
+struct tools_ctx {
+ struct tevent_context *ev;
+ struct confdb_ctx *confdb;
+ struct sysdb_ctx *sysdb;
+
+ struct sss_names_ctx *snctx;
+ struct sss_domain_info *local;
+
+ struct ops_ctx *octx;
+
+ struct sysdb_handle *handle;
+ bool transaction_done;
+ int error;
+};
+
+int init_sss_tools(struct tools_ctx **_tctx);
+
+void usage(poptContext pc, const char *error);
+
+int set_locale(void);
+
+
+int parse_name_domain(struct tools_ctx *tctx,
+ const char *fullname);
+
+int id_in_range(uint32_t id,
+ struct sss_domain_info *dom);
+
+int parse_groups(TALLOC_CTX *mem_ctx,
+ const char *optstr,
+ char ***_out);
+
+int parse_group_name_domain(struct tools_ctx *tctx,
+ char **groups);
+
+int check_group_names(struct tools_ctx *tctx,
+ char **grouplist,
+ char **badgroup);
+
+int create_homedir(TALLOC_CTX *mem_ctx,
+ const char *skeldir,
+ const char *homedir,
+ const char *username,
+ uid_t uid,
+ gid_t gid,
+ mode_t default_umask);
+
+int create_mail_spool(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *maildir,
+ uid_t uid, gid_t gid);
+
+int remove_homedir(TALLOC_CTX *mem_ctx,
+ const char *homedir,
+ const char *maildir,
+ const char *username,
+ uid_t uid, bool force);
+
+/* from files.c */
+int remove_tree(const char *root);
+
+int copy_tree(const char *src_root,
+ const char *dst_root,
+ uid_t uid, gid_t gid);
+
+int selinux_file_context(const char *dst_name);
+int reset_selinux_file_context(void);
+
+#endif /* __TOOLS_UTIL_H__ */
diff --git a/src/util/backup_file.c b/src/util/backup_file.c
new file mode 100644
index 00000000..cf9ddf30
--- /dev/null
+++ b/src/util/backup_file.c
@@ -0,0 +1,122 @@
+/*
+ SSSD
+
+ Backup files
+
+ Copyright (C) Simo Sorce 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#define BUFFER_SIZE 65536
+
+int backup_file(const char *src_file, int dbglvl)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char buf[BUFFER_SIZE];
+ int src_fd = -1;
+ int dst_fd = -1;
+ char *dst_file;
+ ssize_t count;
+ ssize_t num;
+ ssize_t pos;
+ int ret, i;
+
+ src_fd = open(src_file, O_RDONLY);
+ if (src_fd < 0) {
+ ret = errno;
+ DEBUG(dbglvl, ("Error (%d [%s]) opening source file %s\n",
+ ret, strerror(ret), src_file));
+ goto done;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* try a few times to come up with a new backup file, then give up */
+ for (i = 0; i < 10; i++) {
+ if (i == 0) {
+ dst_file = talloc_asprintf(tmp_ctx, "%s.bak", src_file);
+ } else {
+ dst_file = talloc_asprintf(tmp_ctx, "%s.bak%d", src_file, i);
+ }
+ if (!dst_file) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ errno = 0;
+ dst_fd = open(dst_file, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ ret = errno;
+
+ if (dst_fd > 0) break;
+
+ if (ret != EEXIST) {
+ DEBUG(dbglvl, ("Error (%d [%s]) opening destination file %s\n",
+ ret, strerror(ret), dst_file));
+ goto done;
+ }
+ }
+ if (ret != 0) {
+ DEBUG(dbglvl, ("Error (%d [%s]) opening destination file %s\n",
+ ret, strerror(ret), dst_file));
+ goto done;
+ }
+
+ /* copy file contents */
+ while (1) {
+ num = read(src_fd, buf, BUFFER_SIZE);
+ if (num < 0) {
+ if (errno == EINTR) continue;
+ ret = errno;
+ DEBUG(dbglvl, ("Error (%d [%s]) reading from source %s\n",
+ ret, strerror(ret), src_file));
+ goto done;
+ }
+ if (num == 0) break;
+
+ count = num;
+
+ while (count > 0) {
+ pos = 0;
+ errno = 0;
+ num = write(dst_fd, &buf[pos], count);
+ if (num < 0) {
+ if (errno == EINTR) continue;
+ ret = errno;
+ DEBUG(dbglvl, ("Error (%d [%s]) writing to destination %s\n",
+ ret, strerror(ret), dst_file));
+ goto done;
+ }
+ pos += num;
+ count -= num;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ if (src_fd != -1) close(src_fd);
+ if (dst_fd != -1) close(dst_fd);
+ talloc_free(tmp_ctx);
+ return ret;
+}
diff --git a/src/util/check_and_open.c b/src/util/check_and_open.c
new file mode 100644
index 00000000..5d5b5799
--- /dev/null
+++ b/src/util/check_and_open.c
@@ -0,0 +1,89 @@
+/*
+ SSSD
+
+ Check file permissions and open file
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "util/util.h"
+
+errno_t check_and_open_readonly(const char *filename, int *fd, const uid_t uid,
+ const gid_t gid, const mode_t mode)
+{
+ int ret;
+ struct stat stat_buf;
+ struct stat fd_stat_buf;
+
+ *fd = -1;
+
+ ret = lstat(filename, &stat_buf);
+ if (ret == -1) {
+ DEBUG(1, ("lstat for [%s] failed: [%d][%s].\n", filename, errno,
+ strerror(errno)));
+ return errno;
+ }
+
+ if (!S_ISREG(stat_buf.st_mode)) {
+ DEBUG(1, ("File [%s] is not a regular file.\n", filename));
+ return EINVAL;
+ }
+
+ if ((stat_buf.st_mode & ~S_IFMT) != mode) {
+ DEBUG(1, ("File [%s] has the wrong mode [%.7o], expected [%.7o].\n",
+ filename, (stat_buf.st_mode & ~S_IFMT), mode));
+ return EINVAL;
+ }
+
+ if (stat_buf.st_uid != uid || stat_buf.st_gid != gid) {
+ DEBUG(1, ("File [%s] must be owned by uid [%d] and gid [%d].\n",
+ filename, uid, gid));
+ return EINVAL;
+ }
+
+ *fd = open(filename, O_RDONLY);
+ if (*fd == -1) {
+ DEBUG(1, ("open [%s] failed: [%d][%s].\n", filename, errno,
+ strerror(errno)));
+ return errno;
+ }
+
+ ret = fstat(*fd, &fd_stat_buf);
+ if (ret == -1) {
+ DEBUG(1, ("fstat for [%s] failed: [%d][%s].\n", filename, errno,
+ strerror(errno)));
+ return errno;
+ }
+
+ if (stat_buf.st_dev != fd_stat_buf.st_dev ||
+ stat_buf.st_ino != fd_stat_buf.st_ino) {
+ DEBUG(1, ("File [%s] was modified between lstat and open.\n", filename));
+ close(*fd);
+ *fd = -1;
+ return EIO;
+ }
+
+ return EOK;
+}
+
diff --git a/src/util/crypto_sha512crypt.c b/src/util/crypto_sha512crypt.c
new file mode 100644
index 00000000..9cd03a1e
--- /dev/null
+++ b/src/util/crypto_sha512crypt.c
@@ -0,0 +1,382 @@
+/* This file is based on nss_sha512crypt.c which is based on the work of
+ * Ulrich Drepper (http://people.redhat.com/drepper/SHA-crypt.txt).
+ *
+ * libcrypto is used to provide SHA512 and random number generation.
+ * (http://www.openssl.org/docs/crypto/crypto.html).
+ *
+ * Sumit Bose <sbose@redhat.com>
+ * George McCollister <georgem@novatech-llc.com>
+ */
+/* SHA512-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>. */
+
+#define _GNU_SOURCE
+#include <endian.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include "util/util.h"
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+/* Define our magic string to mark salt for SHA512 "encryption" replacement. */
+const char sha512_salt_prefix[] = "$6$";
+#define SALT_PREF_SIZE (sizeof(sha512_salt_prefix) - 1)
+
+/* Prefix for optional rounds specification. */
+const char sha512_rounds_prefix[] = "rounds=";
+#define ROUNDS_SIZE (sizeof(sha512_rounds_prefix) - 1)
+
+#define SALT_LEN_MAX 16
+#define ROUNDS_DEFAULT 5000
+#define ROUNDS_MIN 1000
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+const char b64t[64] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+/* base64 conversion function */
+static inline void b64_from_24bit(char **dest, size_t *len, size_t n,
+ uint8_t b2, uint8_t b1, uint8_t b0)
+{
+ uint32_t w;
+ size_t i;
+
+ if (*len < n) n = *len;
+
+ w = (b2 << 16) | (b1 << 8) | b0;
+ for (i = 0; i < n; i++) {
+ (*dest)[i] = b64t[w & 0x3f];
+ w >>= 6;
+ }
+
+ *len -= i;
+ *dest += i;
+}
+
+#define PTR_2_INT(x) ((x) - ((__typeof__ (x)) NULL))
+#define ALIGN64 __alignof__(uint64_t)
+
+static int sha512_crypt_r(const char *key,
+ const char *salt,
+ char *buffer, size_t buflen)
+{
+ unsigned char temp_result[64] __attribute__((__aligned__(ALIGN64)));
+ unsigned char alt_result[64] __attribute__((__aligned__(ALIGN64)));
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+ EVP_MD_CTX alt_ctx;
+ EVP_MD_CTX ctx;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *copied_salt = NULL;
+ char *copied_key = NULL;
+ char *p_bytes = NULL;
+ char *s_bytes = NULL;
+ int p1, p2, p3, pt, n;
+ unsigned int part;
+ char *cp, *tmp;
+ int ret;
+
+ /* Find beginning of salt string. The prefix should normally always be
+ * present. Just in case it is not. */
+ if (strncmp(salt, sha512_salt_prefix, SALT_PREF_SIZE) == 0) {
+ /* Skip salt prefix. */
+ salt += SALT_PREF_SIZE;
+ }
+
+ if (strncmp(salt, sha512_rounds_prefix, ROUNDS_SIZE) == 0) {
+ unsigned long int srounds;
+ const char *num;
+ char *endp;
+
+ num = salt + ROUNDS_SIZE;
+ srounds = strtoul(num, &endp, 10);
+ if (*endp == '$') {
+ salt = endp + 1;
+ if (srounds < ROUNDS_MIN) srounds = ROUNDS_MIN;
+ if (srounds > ROUNDS_MAX) srounds = ROUNDS_MAX;
+ rounds = srounds;
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN(strcspn(salt, "$"), SALT_LEN_MAX);
+ key_len = strlen(key);
+
+ if ((PTR_2_INT(key) % ALIGN64) != 0) {
+ tmp = (char *)alloca(key_len + ALIGN64);
+ key = copied_key = memcpy(tmp + ALIGN64 - PTR_2_INT(tmp) % ALIGN64, key, key_len);
+ }
+
+ if (PTR_2_INT(salt) % ALIGN64 != 0) {
+ tmp = (char *)alloca(salt_len + ALIGN64);
+ salt = copied_salt = memcpy(tmp + ALIGN64 - PTR_2_INT(tmp) % ALIGN64, salt, salt_len);
+ }
+
+ EVP_MD_CTX_init(&ctx);
+
+ EVP_MD_CTX_init(&alt_ctx);
+
+ /* Prepare for the real work. */
+ if (!EVP_DigestInit_ex(&ctx, EVP_sha512(), NULL)) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Add the key string. */
+ EVP_DigestUpdate(&ctx, (const unsigned char *)key, key_len);
+
+ /* The last part is the salt string. This must be at most 16
+ * characters and it ends at the first `$' character (for
+ * compatibility with existing implementations). */
+ EVP_DigestUpdate(&ctx, (const unsigned char *)salt, salt_len);
+
+
+ /* Compute alternate SHA512 sum with input KEY, SALT, and KEY.
+ * The final result will be added to the first context. */
+ if (!EVP_DigestInit_ex(&alt_ctx, EVP_sha512(), NULL)) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Add key. */
+ EVP_DigestUpdate(&alt_ctx, (const unsigned char *)key, key_len);
+
+ /* Add salt. */
+ EVP_DigestUpdate(&alt_ctx, (const unsigned char *)salt, salt_len);
+
+ /* Add key again. */
+ EVP_DigestUpdate(&alt_ctx, (const unsigned char *)key, key_len);
+
+ /* Now get result of this (64 bytes) and add it to the other context. */
+ EVP_DigestFinal_ex(&alt_ctx, alt_result, &part);
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 64; cnt -= 64) {
+ EVP_DigestUpdate(&ctx, alt_result, 64);
+ }
+ EVP_DigestUpdate(&ctx, alt_result, cnt);
+
+ /* Take the binary representation of the length of the key and for every
+ * 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1) {
+ if ((cnt & 1) != 0) {
+ EVP_DigestUpdate(&ctx, alt_result, 64);
+ } else {
+ EVP_DigestUpdate(&ctx, (const unsigned char *)key, key_len);
+ }
+ }
+
+ /* Create intermediate result. */
+ EVP_DigestFinal_ex(&ctx, alt_result, &part);
+
+ /* Start computation of P byte sequence. */
+ if (!EVP_DigestInit_ex(&alt_ctx, EVP_sha512(), NULL)) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; cnt++) {
+ EVP_DigestUpdate(&alt_ctx, (const unsigned char *)key, key_len);
+ }
+
+ /* Finish the digest. */
+ EVP_DigestFinal_ex(&alt_ctx, temp_result, &part);
+
+ /* Create byte sequence P. */
+ cp = p_bytes = alloca(key_len);
+ for (cnt = key_len; cnt >= 64; cnt -= 64) {
+ cp = mempcpy(cp, temp_result, 64);
+ }
+ memcpy(cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ if (!EVP_DigestInit_ex(&alt_ctx, EVP_sha512(), NULL)) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* For every character in the password add the entire salt. */
+ for (cnt = 0; cnt < 16 + alt_result[0]; cnt++) {
+ EVP_DigestUpdate(&alt_ctx, (const unsigned char *)salt, salt_len);
+ }
+
+ /* Finish the digest. */
+ EVP_DigestFinal_ex(&alt_ctx, temp_result, &part);
+
+ /* Create byte sequence S. */
+ cp = s_bytes = alloca(salt_len);
+ for (cnt = salt_len; cnt >= 64; cnt -= 64) {
+ cp = mempcpy(cp, temp_result, 64);
+ }
+ memcpy(cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA512 to burn CPU cycles. */
+ for (cnt = 0; cnt < rounds; cnt++) {
+
+ if (!EVP_DigestInit_ex(&ctx, EVP_sha512(), NULL)) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0) {
+ EVP_DigestUpdate(&ctx, (const unsigned char *)p_bytes, key_len);
+ } else {
+ EVP_DigestUpdate(&ctx, alt_result, 64);
+ }
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0) {
+ EVP_DigestUpdate(&ctx, (const unsigned char *)s_bytes, salt_len);
+ }
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0) {
+ EVP_DigestUpdate(&ctx, (const unsigned char *)p_bytes, key_len);
+ }
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0) {
+ EVP_DigestUpdate(&ctx, alt_result, 64);
+ } else {
+ EVP_DigestUpdate(&ctx, (const unsigned char *)p_bytes, key_len);
+ }
+
+ /* Create intermediate result. */
+ EVP_DigestFinal_ex(&ctx, alt_result, &part);
+ }
+
+ /* Now we can construct the result string.
+ * It consists of three parts. */
+ if (buflen <= SALT_PREF_SIZE) {
+ ret = ERANGE;
+ goto done;
+ }
+
+ cp = __stpncpy(buffer, sha512_salt_prefix, SALT_PREF_SIZE);
+ buflen -= SALT_PREF_SIZE;
+
+ if (rounds_custom) {
+ n = snprintf(cp, buflen, "%s%zu$",
+ sha512_rounds_prefix, rounds);
+ if (n < 0 || n >= buflen) {
+ ret = ERANGE;
+ goto done;
+ }
+ cp += n;
+ buflen -= n;
+ }
+
+ if (buflen <= salt_len + 1) {
+ ret = ERANGE;
+ goto done;
+ }
+ cp = __stpncpy(cp, salt, salt_len);
+ *cp++ = '$';
+ buflen -= salt_len + 1;
+
+ /* fuzzyfill the base 64 string */
+ p1 = 0;
+ p2 = 21;
+ p3 = 42;
+ for (n = 0; n < 21; n++) {
+ b64_from_24bit(&cp, &buflen, 4, alt_result[p1], alt_result[p2], alt_result[p3]);
+ if (buflen == 0) {
+ ret = ERANGE;
+ goto done;
+ }
+ pt = p1;
+ p1 = p2 + 1;
+ p2 = p3 + 1;
+ p3 = pt + 1;
+ }
+ /* 64th and last byte */
+ b64_from_24bit(&cp, &buflen, 2, 0, 0, alt_result[p3]);
+ if (buflen == 0) {
+ ret = ERANGE;
+ goto done;
+ }
+
+ *cp = '\0';
+ ret = EOK;
+
+done:
+ /* Clear the buffer for the intermediate result so that people attaching
+ * to processes or reading core dumps cannot get any information. We do it
+ * in this way to clear correct_words[] inside the SHA512 implementation
+ * as well. */
+ EVP_MD_CTX_cleanup(&ctx);
+ EVP_MD_CTX_cleanup(&alt_ctx);
+ if (p_bytes) memset(p_bytes, '\0', key_len);
+ if (s_bytes) memset(s_bytes, '\0', salt_len);
+ if (copied_key) memset(copied_key, '\0', key_len);
+ if (copied_salt) memset(copied_salt, '\0', salt_len);
+ memset(temp_result, '\0', sizeof(temp_result));
+
+ return ret;
+}
+
+int s3crypt_sha512(TALLOC_CTX *memctx,
+ const char *key, const char *salt, char **_hash)
+{
+ char *hash;
+ int hlen = (sizeof (sha512_salt_prefix) - 1
+ + sizeof (sha512_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 86 + 1);
+ int ret;
+
+ hash = talloc_size(memctx, hlen);
+ if (!hash) return ENOMEM;
+
+ ret = sha512_crypt_r(key, salt, hash, hlen);
+ if (ret) return ret;
+
+ *_hash = hash;
+ return ret;
+}
+
+#define SALT_RAND_LEN 12
+
+int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt)
+{
+ uint8_t rb[SALT_RAND_LEN];
+ char *salt, *cp;
+ size_t slen;
+ int ret;
+
+ salt = talloc_size(memctx, SALT_LEN_MAX + 1);
+ if (!salt) {
+ return ENOMEM;
+ }
+
+ ret = RAND_bytes(rb, SALT_RAND_LEN);
+ if (ret == 0) {
+ return EIO;
+ }
+
+ slen = SALT_LEN_MAX;
+ cp = salt;
+ b64_from_24bit(&cp, &slen, 4, rb[0], rb[1], rb[2]);
+ b64_from_24bit(&cp, &slen, 4, rb[3], rb[4], rb[5]);
+ b64_from_24bit(&cp, &slen, 4, rb[6], rb[7], rb[8]);
+ b64_from_24bit(&cp, &slen, 4, rb[9], rb[10], rb[11]);
+ *cp = '\0';
+
+ *_salt = salt;
+
+ return EOK;
+}
+
diff --git a/src/util/debug.c b/src/util/debug.c
new file mode 100644
index 00000000..d26d31c9
--- /dev/null
+++ b/src/util/debug.c
@@ -0,0 +1,154 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 _GNU_SOURCE
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+
+const char *debug_prg_name = "sssd";
+int debug_level = 0;
+int debug_timestamps = 1;
+
+int debug_to_file = 0;
+const char *debug_log_file = "sssd";
+FILE *debug_file = NULL;
+
+
+errno_t set_debug_file_from_fd(const int fd)
+{
+ FILE *dummy;
+
+ dummy = fdopen(fd, "a");
+ if (dummy == NULL) {
+ DEBUG(1, ("fdopen failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ debug_file = dummy;
+
+ return EOK;
+}
+
+void debug_fn(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+
+ vfprintf(debug_file ? debug_file : stderr, format, ap);
+ fflush(debug_file ? debug_file : stderr);
+
+ va_end(ap);
+}
+
+void ldb_debug_messages(void *context, enum ldb_debug_level level,
+ const char *fmt, va_list ap)
+{
+ int loglevel = -1;
+ int ret;
+ char * message = NULL;
+
+ switch(level) {
+ case LDB_DEBUG_FATAL:
+ loglevel = 0;
+ break;
+ case LDB_DEBUG_ERROR:
+ loglevel = 1;
+ break;
+ case LDB_DEBUG_WARNING:
+ loglevel = 6;
+ break;
+ case LDB_DEBUG_TRACE:
+ loglevel = 9;
+ break;
+ }
+
+ ret = vasprintf(&message, fmt, ap);
+ if (ret < 0) {
+ /* ENOMEM */
+ return;
+ }
+
+ if (loglevel <= debug_level) {
+ if (debug_timestamps) {
+ time_t rightnow = time(NULL);
+ char stamp[25];
+ memcpy(stamp, ctime(&rightnow), 24);
+ stamp[24] = '\0';
+ debug_fn("(%s) [%s] [ldb] (%d): %s\n",
+ stamp, debug_prg_name, loglevel, message);
+ } else {
+ debug_fn("[%s] [ldb] (%d): %s\n",
+ debug_prg_name, loglevel, message);
+ }
+ }
+ free(message);
+}
+
+int open_debug_file_ex(const char *filename, FILE **filep)
+{
+ FILE *f = NULL;
+ char *logpath;
+ const char *log_file;
+ mode_t old_umask;
+ int ret;
+
+ if (filename == NULL) {
+ log_file = debug_log_file;
+ } else {
+ log_file = filename;
+ }
+
+ ret = asprintf(&logpath, "%s/%s.log", LOG_PATH, log_file);
+ if (ret == -1) {
+ return ENOMEM;
+ }
+
+ if (debug_file && !filep) fclose(debug_file);
+
+ old_umask = umask(0177);
+ f = fopen(logpath, "a");
+ if (f == NULL) {
+ free(logpath);
+ return EIO;
+ }
+ umask(old_umask);
+
+ if (filep == NULL) {
+ debug_file = f;
+ } else {
+ *filep = f;
+ }
+ free(logpath);
+ return EOK;
+}
+
+int open_debug_file(void)
+{
+ return open_debug_file_ex(NULL, NULL);
+}
diff --git a/src/util/dlinklist.h b/src/util/dlinklist.h
new file mode 100644
index 00000000..be5ff914
--- /dev/null
+++ b/src/util/dlinklist.h
@@ -0,0 +1,116 @@
+/*
+ Unix SMB/CIFS implementation.
+ some simple double linked list macros
+ Copyright (C) Andrew Tridgell 1998
+
+ 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/>.
+*/
+
+/* To use these macros you must have a structure containing a next and
+ prev pointer */
+
+#ifndef _DLINKLIST_H
+#define _DLINKLIST_H
+
+
+/* hook into the front of the list */
+#define DLIST_ADD(list, p) \
+do { \
+ if (!(list)) { \
+ (list) = (p); \
+ (p)->next = (p)->prev = NULL; \
+ } else { \
+ (list)->prev = (p); \
+ (p)->next = (list); \
+ (p)->prev = NULL; \
+ (list) = (p); \
+ }\
+} while (0)
+
+/* remove an element from a list - element doesn't have to be in list. */
+#define DLIST_REMOVE(list, p) \
+do { \
+ if ((p) == (list)) { \
+ (list) = (p)->next; \
+ if (list) (list)->prev = NULL; \
+ } else { \
+ if ((p)->prev) (p)->prev->next = (p)->next; \
+ if ((p)->next) (p)->next->prev = (p)->prev; \
+ } \
+ if ((p) != (list)) (p)->next = (p)->prev = NULL; \
+} while (0)
+
+/* promote an element to the top of the list */
+#define DLIST_PROMOTE(list, p) \
+do { \
+ DLIST_REMOVE(list, p); \
+ DLIST_ADD(list, p); \
+} while (0)
+
+/* hook into the end of the list - needs a tmp pointer */
+#define DLIST_ADD_END(list, p, type) \
+do { \
+ if (!(list)) { \
+ (list) = (p); \
+ (p)->next = (p)->prev = NULL; \
+ } else { \
+ type tmp; \
+ for (tmp = (list); tmp->next; tmp = tmp->next) ; \
+ tmp->next = (p); \
+ (p)->next = NULL; \
+ (p)->prev = tmp; \
+ } \
+} while (0)
+
+/* insert 'p' after the given element 'el' in a list. If el is NULL then
+ this is the same as a DLIST_ADD() */
+#define DLIST_ADD_AFTER(list, p, el) \
+do { \
+ if (!(list) || !(el)) { \
+ DLIST_ADD(list, p); \
+ } else { \
+ p->prev = el; \
+ p->next = el->next; \
+ el->next = p; \
+ if (p->next) p->next->prev = p; \
+ }\
+} while (0)
+
+/* demote an element to the end of the list, needs a tmp pointer */
+#define DLIST_DEMOTE(list, p, type) \
+do { \
+ DLIST_REMOVE(list, p); \
+ DLIST_ADD_END(list, p, type); \
+} while (0)
+
+/* concatenate two lists - putting all elements of the 2nd list at the
+ end of the first list */
+#define DLIST_CONCATENATE(list1, list2, type) \
+do { \
+ if (!(list1)) { \
+ (list1) = (list2); \
+ } else { \
+ type tmp; \
+ for (tmp = (list1); tmp->next; tmp = tmp->next) ; \
+ tmp->next = (list2); \
+ if (list2) { \
+ (list2)->prev = tmp; \
+ } \
+ } \
+} while (0)
+
+#define DLIST_FOR_EACH(p, list) \
+ for ((p) = (list); (p) != NULL; (p) = (p)->next)
+
+#endif /* _DLINKLIST_H */
diff --git a/src/util/find_uid.c b/src/util/find_uid.c
new file mode 100644
index 00000000..965966ef
--- /dev/null
+++ b/src/util/find_uid.c
@@ -0,0 +1,296 @@
+/*
+ SSSD
+
+ Create uid table
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <talloc.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+#include "dhash.h"
+#include "util/util.h"
+
+#define INITIAL_TABLE_SIZE 64
+#define PATHLEN (NAME_MAX + 14)
+#define BUFSIZE 4096
+
+static void *hash_talloc(const size_t size, void *pvt)
+{
+ return talloc_size(pvt, size);
+}
+
+static void hash_talloc_free(void *ptr, void *pvt)
+{
+ talloc_free(ptr);
+}
+
+static errno_t get_uid_from_pid(const pid_t pid, uid_t *uid)
+{
+ int ret;
+ char path[PATHLEN];
+ struct stat stat_buf;
+ int fd;
+ char buf[BUFSIZE];
+ char *p;
+ char *e;
+ char *endptr;
+ long num=0;
+
+ ret = snprintf(path, PATHLEN, "/proc/%d/status", pid);
+ if (ret < 0) {
+ DEBUG(1, ("snprintf failed"));
+ return EINVAL;
+ } else if (ret >= PATHLEN) {
+ DEBUG(1, ("path too long?!?!\n"));
+ return EINVAL;
+ }
+
+ ret = lstat(path, &stat_buf);
+ if (ret == -1) {
+ if (errno == ENOENT) {
+ DEBUG(7, ("Proc file [%s] is not available anymore, continuing.\n",
+ path));
+ return EOK;
+ }
+ DEBUG(1, ("lstat failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ if (!S_ISREG(stat_buf.st_mode)) {
+ DEBUG(1, ("not a regular file\n"));
+ return EINVAL;
+ }
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == ENOENT) {
+ DEBUG(7, ("Proc file [%s] is not available anymore, continuing.\n",
+ path));
+ return EOK;
+ }
+ DEBUG(1, ("open failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+ ret = read(fd, buf, BUFSIZE);
+ if (ret == -1) {
+ DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = close(fd);
+ if (ret == -1) {
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+ p = strstr(buf, "\nUid:\t");
+ if (p != NULL) {
+ p += 6;
+ e = strchr(p,'\t');
+ if (e == NULL) {
+ DEBUG(1, ("missing delimiter.\n"));
+ return EINVAL;
+ } else {
+ *e = '\0';
+ }
+ num = strtol(p, &endptr, 10);
+ if(errno == ERANGE) {
+ DEBUG(1, ("strtol failed [%s].\n", strerror(errno)));
+ return errno;
+ }
+ if (*endptr != '\0') {
+ DEBUG(1, ("uid contains extra characters\n"));
+ return EINVAL;
+ }
+
+ if (num < 0 || num >= INT_MAX) {
+ DEBUG(1, ("uid out of range.\n"));
+ return ERANGE;
+ }
+
+ } else {
+ DEBUG(1, ("format error\n"));
+ return EINVAL;
+ }
+
+ *uid = num;
+
+ return EOK;
+}
+
+static errno_t name_to_pid(const char *name, pid_t *pid)
+{
+ long num;
+ char *endptr;
+
+ errno = 0;
+ num = strtol(name, &endptr, 10);
+ if(errno == ERANGE) {
+ perror("strtol");
+ return errno;
+ }
+
+ if (*endptr != '\0') {
+ DEBUG(1, ("pid string contains extra characters.\n"));
+ return EINVAL;
+ }
+
+ if (num <= 0 || num >= INT_MAX) {
+ DEBUG(1, ("pid out of range.\n"));
+ return ERANGE;
+ }
+
+ *pid = num;
+
+ return EOK;
+}
+
+static int only_numbers(char *p)
+{
+ while(*p!='\0' && isdigit(*p)) ++p;
+ return *p;
+}
+
+static errno_t get_active_uid_linux(hash_table_t *table, uid_t search_uid)
+{
+ DIR *proc_dir = NULL;
+ struct dirent *dirent;
+ int ret;
+ pid_t pid = -1;
+ uid_t uid;
+
+ hash_key_t key;
+ hash_value_t value;
+
+ proc_dir = opendir("/proc");
+ if (proc_dir == NULL) {
+ DEBUG(1, ("Cannot open proc dir.\n"));
+ ret = errno;
+ goto done;
+ };
+
+ errno = 0;
+ while ((dirent = readdir(proc_dir)) != NULL) {
+ if (only_numbers(dirent->d_name) != 0) continue;
+ ret = name_to_pid(dirent->d_name, &pid);
+ if (ret != EOK) {
+ DEBUG(1, ("name_to_pid failed.\n"));
+ goto done;
+ }
+
+ ret = get_uid_from_pid(pid, &uid);
+ if (ret != EOK) {
+ DEBUG(1, ("get_uid_from_pid failed.\n"));
+ goto done;
+ }
+
+ if (table != NULL) {
+ key.type = HASH_KEY_ULONG;
+ key.ul = (unsigned long) uid;
+ value.type = HASH_VALUE_ULONG;
+ value.ul = (unsigned long) uid;
+
+ ret = hash_enter(table, &key, &value);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(1, ("cannot add to table [%s]\n", hash_error_string(ret)));
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ if (uid == search_uid) {
+ ret = EOK;
+ goto done;
+ }
+ }
+
+
+ errno = 0;
+ }
+ if (errno != 0 && dirent == NULL) {
+ DEBUG(1 ,("readdir failed.\n"));
+ ret = errno;
+ goto done;
+ }
+
+ ret = closedir(proc_dir);
+ proc_dir = NULL;
+ if (ret == -1) {
+ DEBUG(1 ,("closedir failed, watch out.\n"));
+ }
+
+ if (table != NULL) {
+ ret = EOK;
+ } else {
+ ret = ENOENT;
+ }
+
+done:
+ if (proc_dir != NULL) closedir(proc_dir);
+ return ret;
+}
+
+errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table)
+{
+#ifdef __linux__
+ int ret;
+
+ ret = hash_create_ex(INITIAL_TABLE_SIZE, table, 0, 0, 0, 0,
+ hash_talloc, hash_talloc_free, mem_ctx,
+ NULL, NULL);
+ if (ret != HASH_SUCCESS) {
+ DEBUG(1, ("hash_create_ex failed [%s]\n", hash_error_string(ret)));
+ return ENOMEM;
+ }
+
+ return get_active_uid_linux(*table, 0);
+#else
+ return ENOSYS;
+#endif
+}
+
+errno_t check_if_uid_is_active(uid_t uid, bool *result)
+{
+ int ret;
+
+ ret = get_active_uid_linux(NULL, uid);
+ if (ret != EOK && ret != ENOENT) {
+ DEBUG(1, ("get_uid_table failed.\n"));
+ return ret;
+ }
+
+ if (ret == EOK) {
+ *result = true;
+ } else {
+ *result = false;
+ }
+
+ return EOK;
+}
diff --git a/src/util/find_uid.h b/src/util/find_uid.h
new file mode 100644
index 00000000..154a5f95
--- /dev/null
+++ b/src/util/find_uid.h
@@ -0,0 +1,36 @@
+/*
+ SSSD
+
+ Create uid table
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+#ifndef __FIND_UID_H__
+#define __FIND_UID_H__
+
+#include <talloc.h>
+#include <sys/types.h>
+
+#include "dhash.h"
+#include "util/util.h"
+
+errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table);
+errno_t check_if_uid_is_active(uid_t uid, bool *result);
+
+#endif /* __FIND_UID_H__ */
diff --git a/src/util/memory.c b/src/util/memory.c
new file mode 100644
index 00000000..a2c8b54b
--- /dev/null
+++ b/src/util/memory.c
@@ -0,0 +1,67 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 "talloc.h"
+#include "util/util.h"
+
+/*
+ * sssd_mem_attach
+ * This function will take a non-talloc pointer and "attach" it to a talloc
+ * memory context. It will accept a destructor for the original pointer
+ * so that when the parent memory context is freed, the non-talloc
+ * pointer will also be freed properly.
+ */
+
+int password_destructor(void *memctx)
+{
+ char *password = (char *)memctx;
+ int i;
+
+ /* zero out password */
+ for (i = 0; password[i]; i++) password[i] = '\0';
+
+ return 0;
+}
+
+static int mem_holder_destructor(void *ptr)
+{
+ struct mem_holder *h;
+
+ h = talloc_get_type(ptr, struct mem_holder);
+ return h->fn(h->mem);
+}
+
+void *sss_mem_attach(TALLOC_CTX *mem_ctx,
+ void *ptr,
+ void_destructor_fn_t *fn)
+{
+ struct mem_holder *h;
+
+ if (!ptr || !fn) return NULL;
+
+ h = talloc(mem_ctx, struct mem_holder);
+ if (!h) return NULL;
+
+ h->mem = ptr;
+ h->fn = fn;
+ talloc_set_destructor((TALLOC_CTX *)h, mem_holder_destructor);
+
+ return h;
+}
diff --git a/src/util/nss_sha512crypt.c b/src/util/nss_sha512crypt.c
new file mode 100644
index 00000000..8ba16d4a
--- /dev/null
+++ b/src/util/nss_sha512crypt.c
@@ -0,0 +1,419 @@
+/* This file is based on the work of Ulrich Drepper
+ * (http://people.redhat.com/drepper/SHA-crypt.txt). I have replaced the
+ * included SHA512 implementation by calls to NSS
+ * (http://www.mozilla.org/projects/security/pki/nss/).
+ *
+ * Sumit Bose <sbose@redhat.com>
+ */
+/* SHA512-based Unix crypt implementation.
+ Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>. */
+
+#define _GNU_SOURCE
+#include <endian.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include "util/util.h"
+
+#include <prinit.h>
+#include <nss.h>
+#include <sechash.h>
+#include <pk11func.h>
+
+static int nspr_nss_init_done = 0;
+
+static int nspr_nss_init(void)
+{
+ int ret;
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+ ret = NSS_NoDB_Init(NULL);
+ if (ret != SECSuccess) {
+ return ret;
+ }
+ nspr_nss_init_done = 1;
+ return 0;
+}
+
+/* added for completness, so far not used */
+#if 0
+static int nspr_nss_cleanup(void)
+{
+ int ret;
+ ret = NSS_Shutdown();
+ if (ret != SECSuccess) {
+ return ret;
+ }
+ PR_Cleanup();
+ nspr_nss_init_done = 0;
+ return 0;
+}
+#endif
+
+/* Define our magic string to mark salt for SHA512 "encryption" replacement. */
+const char sha512_salt_prefix[] = "$6$";
+#define SALT_PREF_SIZE (sizeof(sha512_salt_prefix) - 1)
+
+/* Prefix for optional rounds specification. */
+const char sha512_rounds_prefix[] = "rounds=";
+#define ROUNDS_SIZE (sizeof(sha512_rounds_prefix) - 1)
+
+#define SALT_LEN_MAX 16
+#define ROUNDS_DEFAULT 5000
+#define ROUNDS_MIN 1000
+#define ROUNDS_MAX 999999999
+
+/* Table with characters for base64 transformation. */
+const char b64t[64] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+/* base64 conversion function */
+static inline void b64_from_24bit(char **dest, size_t *len, size_t n,
+ uint8_t b2, uint8_t b1, uint8_t b0)
+{
+ uint32_t w;
+ size_t i;
+
+ if (*len < n) n = *len;
+
+ w = (b2 << 16) | (b1 << 8) | b0;
+ for (i = 0; i < n; i++) {
+ (*dest)[i] = b64t[w & 0x3f];
+ w >>= 6;
+ }
+
+ *len -= i;
+ *dest += i;
+}
+
+#define PTR_2_INT(x) ((x) - ((__typeof__ (x)) NULL))
+#define ALIGN64 __alignof__(uint64_t)
+
+static int sha512_crypt_r(const char *key,
+ const char *salt,
+ char *buffer, size_t buflen)
+{
+ unsigned char temp_result[64] __attribute__((__aligned__(ALIGN64)));
+ unsigned char alt_result[64] __attribute__((__aligned__(ALIGN64)));
+ size_t rounds = ROUNDS_DEFAULT;
+ bool rounds_custom = false;
+ HASHContext *alt_ctx = NULL;
+ HASHContext *ctx = NULL;
+ size_t salt_len;
+ size_t key_len;
+ size_t cnt;
+ char *copied_salt = NULL;
+ char *copied_key = NULL;
+ char *p_bytes = NULL;
+ char *s_bytes = NULL;
+ int p1, p2, p3, pt, n;
+ unsigned int part;
+ char *cp, *tmp;
+ int ret;
+
+ /* Find beginning of salt string. The prefix should normally always be
+ * present. Just in case it is not. */
+ if (strncmp(salt, sha512_salt_prefix, SALT_PREF_SIZE) == 0) {
+ /* Skip salt prefix. */
+ salt += SALT_PREF_SIZE;
+ }
+
+ if (strncmp(salt, sha512_rounds_prefix, ROUNDS_SIZE) == 0) {
+ unsigned long int srounds;
+ const char *num;
+ char *endp;
+
+ num = salt + ROUNDS_SIZE;
+ srounds = strtoul(num, &endp, 10);
+ if (*endp == '$') {
+ salt = endp + 1;
+ if (srounds < ROUNDS_MIN) srounds = ROUNDS_MIN;
+ if (srounds > ROUNDS_MAX) srounds = ROUNDS_MAX;
+ rounds = srounds;
+ rounds_custom = true;
+ }
+ }
+
+ salt_len = MIN(strcspn(salt, "$"), SALT_LEN_MAX);
+ key_len = strlen(key);
+
+ if ((PTR_2_INT(key) % ALIGN64) != 0) {
+ tmp = (char *)alloca(key_len + ALIGN64);
+ key = copied_key = memcpy(tmp + ALIGN64 - PTR_2_INT(tmp) % ALIGN64, key, key_len);
+ }
+
+ if (PTR_2_INT(salt) % ALIGN64 != 0) {
+ tmp = (char *)alloca(salt_len + ALIGN64);
+ salt = copied_salt = memcpy(tmp + ALIGN64 - PTR_2_INT(tmp) % ALIGN64, salt, salt_len);
+ }
+
+ if (!nspr_nss_init_done) {
+ ret = nspr_nss_init();
+ if (ret != SECSuccess) {
+ ret = EIO;
+ goto done;
+ }
+ }
+
+ ctx = HASH_Create(HASH_AlgSHA512);
+ if (!ctx) {
+ ret = EIO;
+ goto done;
+ }
+
+ alt_ctx = HASH_Create(HASH_AlgSHA512);
+ if (!alt_ctx) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Prepare for the real work. */
+ HASH_Begin(ctx);
+
+ /* Add the key string. */
+ HASH_Update(ctx, (const unsigned char *)key, key_len);
+
+ /* The last part is the salt string. This must be at most 16
+ * characters and it ends at the first `$' character (for
+ * compatibility with existing implementations). */
+ HASH_Update(ctx, (const unsigned char *)salt, salt_len);
+
+
+ /* Compute alternate SHA512 sum with input KEY, SALT, and KEY.
+ * The final result will be added to the first context. */
+ HASH_Begin(alt_ctx);
+
+ /* Add key. */
+ HASH_Update(alt_ctx, (const unsigned char *)key, key_len);
+
+ /* Add salt. */
+ HASH_Update(alt_ctx, (const unsigned char *)salt, salt_len);
+
+ /* Add key again. */
+ HASH_Update(alt_ctx, (const unsigned char *)key, key_len);
+
+ /* Now get result of this (64 bytes) and add it to the other context. */
+ HASH_End(alt_ctx, alt_result, &part, HASH_ResultLenContext(alt_ctx));
+
+ /* Add for any character in the key one byte of the alternate sum. */
+ for (cnt = key_len; cnt > 64; cnt -= 64) {
+ HASH_Update(ctx, alt_result, 64);
+ }
+ HASH_Update(ctx, alt_result, cnt);
+
+ /* Take the binary representation of the length of the key and for every
+ * 1 add the alternate sum, for every 0 the key. */
+ for (cnt = key_len; cnt > 0; cnt >>= 1) {
+ if ((cnt & 1) != 0) {
+ HASH_Update(ctx, alt_result, 64);
+ } else {
+ HASH_Update(ctx, (const unsigned char *)key, key_len);
+ }
+ }
+
+ /* Create intermediate result. */
+ HASH_End(ctx, alt_result, &part, HASH_ResultLenContext(ctx));
+
+ /* Start computation of P byte sequence. */
+ HASH_Begin(alt_ctx);
+
+ /* For every character in the password add the entire password. */
+ for (cnt = 0; cnt < key_len; cnt++) {
+ HASH_Update(alt_ctx, (const unsigned char *)key, key_len);
+ }
+
+ /* Finish the digest. */
+ HASH_End(alt_ctx, temp_result, &part, HASH_ResultLenContext(alt_ctx));
+
+ /* Create byte sequence P. */
+ cp = p_bytes = alloca(key_len);
+ for (cnt = key_len; cnt >= 64; cnt -= 64) {
+ cp = mempcpy(cp, temp_result, 64);
+ }
+ memcpy(cp, temp_result, cnt);
+
+ /* Start computation of S byte sequence. */
+ HASH_Begin(alt_ctx);
+
+ /* For every character in the password add the entire salt. */
+ for (cnt = 0; cnt < 16 + alt_result[0]; cnt++) {
+ HASH_Update(alt_ctx, (const unsigned char *)salt, salt_len);
+ }
+
+ /* Finish the digest. */
+ HASH_End(alt_ctx, temp_result, &part, HASH_ResultLenContext(alt_ctx));
+
+ /* Create byte sequence S. */
+ cp = s_bytes = alloca(salt_len);
+ for (cnt = salt_len; cnt >= 64; cnt -= 64) {
+ cp = mempcpy(cp, temp_result, 64);
+ }
+ memcpy(cp, temp_result, cnt);
+
+ /* Repeatedly run the collected hash value through SHA512 to burn CPU cycles. */
+ for (cnt = 0; cnt < rounds; cnt++) {
+
+ HASH_Begin(ctx);
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0) {
+ HASH_Update(ctx, (const unsigned char *)p_bytes, key_len);
+ } else {
+ HASH_Update(ctx, alt_result, 64);
+ }
+
+ /* Add salt for numbers not divisible by 3. */
+ if (cnt % 3 != 0) {
+ HASH_Update(ctx, (const unsigned char *)s_bytes, salt_len);
+ }
+
+ /* Add key for numbers not divisible by 7. */
+ if (cnt % 7 != 0) {
+ HASH_Update(ctx, (const unsigned char *)p_bytes, key_len);
+ }
+
+ /* Add key or last result. */
+ if ((cnt & 1) != 0) {
+ HASH_Update(ctx, alt_result, 64);
+ } else {
+ HASH_Update(ctx, (const unsigned char *)p_bytes, key_len);
+ }
+
+ /* Create intermediate result. */
+ HASH_End(ctx, alt_result, &part, HASH_ResultLenContext(ctx));
+ }
+
+ /* Now we can construct the result string.
+ * It consists of three parts. */
+ if (buflen <= SALT_PREF_SIZE) {
+ ret = ERANGE;
+ goto done;
+ }
+
+ cp = __stpncpy(buffer, sha512_salt_prefix, SALT_PREF_SIZE);
+ buflen -= SALT_PREF_SIZE;
+
+ if (rounds_custom) {
+ n = snprintf(cp, buflen, "%s%zu$",
+ sha512_rounds_prefix, rounds);
+ if (n < 0 || n >= buflen) {
+ ret = ERANGE;
+ goto done;
+ }
+ cp += n;
+ buflen -= n;
+ }
+
+ if (buflen <= salt_len + 1) {
+ ret = ERANGE;
+ goto done;
+ }
+ cp = __stpncpy(cp, salt, salt_len);
+ *cp++ = '$';
+ buflen -= salt_len + 1;
+
+ /* fuzzyfill the base 64 string */
+ p1 = 0;
+ p2 = 21;
+ p3 = 42;
+ for (n = 0; n < 21; n++) {
+ b64_from_24bit(&cp, &buflen, 4, alt_result[p1], alt_result[p2], alt_result[p3]);
+ if (buflen == 0) {
+ ret = ERANGE;
+ goto done;
+ }
+ pt = p1;
+ p1 = p2 + 1;
+ p2 = p3 + 1;
+ p3 = pt + 1;
+ }
+ /* 64th and last byte */
+ b64_from_24bit(&cp, &buflen, 2, 0, 0, alt_result[p3]);
+ if (buflen == 0) {
+ ret = ERANGE;
+ goto done;
+ }
+
+ *cp = '\0';
+ ret = EOK;
+
+done:
+ /* Clear the buffer for the intermediate result so that people attaching
+ * to processes or reading core dumps cannot get any information. We do it
+ * in this way to clear correct_words[] inside the SHA512 implementation
+ * as well. */
+ if (ctx) HASH_Destroy(ctx);
+ if (alt_ctx) HASH_Destroy(alt_ctx);
+ if (p_bytes) memset(p_bytes, '\0', key_len);
+ if (s_bytes) memset(s_bytes, '\0', salt_len);
+ if (copied_key) memset(copied_key, '\0', key_len);
+ if (copied_salt) memset(copied_salt, '\0', salt_len);
+ memset(temp_result, '\0', sizeof(temp_result));
+
+ return ret;
+}
+
+int s3crypt_sha512(TALLOC_CTX *memctx,
+ const char *key, const char *salt, char **_hash)
+{
+ char *hash;
+ int hlen = (sizeof (sha512_salt_prefix) - 1
+ + sizeof (sha512_rounds_prefix) + 9 + 1
+ + strlen (salt) + 1 + 86 + 1);
+ int ret;
+
+ hash = talloc_size(memctx, hlen);
+ if (!hash) return ENOMEM;
+
+ ret = sha512_crypt_r(key, salt, hash, hlen);
+ if (ret) return ret;
+
+ *_hash = hash;
+ return ret;
+}
+
+#define SALT_RAND_LEN 12
+
+int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt)
+{
+ uint8_t rb[SALT_RAND_LEN];
+ char *salt, *cp;
+ size_t slen;
+ int ret;
+
+ if (!nspr_nss_init_done) {
+ ret = nspr_nss_init();
+ if (ret != SECSuccess) {
+ return EIO;
+ }
+ }
+
+ salt = talloc_size(memctx, SALT_LEN_MAX + 1);
+ if (!salt) {
+ return ENOMEM;
+ }
+
+ ret = PK11_GenerateRandom(rb, SALT_RAND_LEN);
+ if (ret != SECSuccess) {
+ return EIO;
+ }
+
+ slen = SALT_LEN_MAX;
+ cp = salt;
+ b64_from_24bit(&cp, &slen, 4, rb[0], rb[1], rb[2]);
+ b64_from_24bit(&cp, &slen, 4, rb[3], rb[4], rb[5]);
+ b64_from_24bit(&cp, &slen, 4, rb[6], rb[7], rb[8]);
+ b64_from_24bit(&cp, &slen, 4, rb[9], rb[10], rb[11]);
+ *cp = '\0';
+
+ *_salt = salt;
+
+ return EOK;
+}
+
diff --git a/src/util/refcount.c b/src/util/refcount.c
new file mode 100644
index 00000000..735170d1
--- /dev/null
+++ b/src/util/refcount.c
@@ -0,0 +1,88 @@
+/*
+ SSSD
+
+ Simple reference counting wrappers for talloc.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+
+#include "refcount.h"
+#include "util/util.h"
+
+struct wrapper {
+ int *refcount;
+ void *ptr;
+};
+
+static int
+refcount_destructor(struct wrapper *wrapper)
+{
+ (*wrapper->refcount)--;
+ if (*wrapper->refcount == 0) {
+ talloc_free(wrapper->ptr);
+ };
+
+ return 0;
+}
+
+void *
+_rc_alloc(const void *context, size_t size, size_t refcount_offset,
+ const char *type_name)
+{
+ struct wrapper *wrapper;
+
+ wrapper = talloc(context, struct wrapper);
+ if (wrapper == NULL) {
+ return NULL;
+ }
+
+ wrapper->ptr = talloc_named_const(NULL, size, type_name);
+ if (wrapper->ptr == NULL) {
+ talloc_free(wrapper);
+ return NULL;
+ };
+
+ wrapper->refcount = (int *)((char *)wrapper->ptr + refcount_offset);
+ *wrapper->refcount = 1;
+
+ talloc_set_destructor(wrapper, refcount_destructor);
+
+ return wrapper->ptr;
+}
+
+void *
+_rc_reference(const void *context, size_t refcount_offset, void *source)
+{
+ struct wrapper *wrapper;
+
+ wrapper = talloc(context, struct wrapper);
+ if (wrapper == NULL) {
+ return NULL;
+ }
+
+ wrapper->ptr = source;
+ wrapper->refcount = (int *)((char *)wrapper->ptr + refcount_offset);
+ (*wrapper->refcount)++;
+
+ talloc_set_destructor(wrapper, refcount_destructor);
+
+ return wrapper->ptr;
+}
diff --git a/src/util/refcount.h b/src/util/refcount.h
new file mode 100644
index 00000000..cc99a173
--- /dev/null
+++ b/src/util/refcount.h
@@ -0,0 +1,39 @@
+#ifndef __REFCOUNT_H__
+#define __REFCOUNT_H__
+
+#include <stddef.h>
+
+#define REFCOUNT_MEMBER_NAME DO_NOT_TOUCH_THIS_MEMBER_refcount
+
+/*
+ * Include this member in your structure in order to be able to use it with
+ * the refcount_* functions.
+ */
+#define REFCOUNT_COMMON int REFCOUNT_MEMBER_NAME
+
+/*
+ * Allocate a new structure that uses reference counting. The resulting pointer
+ * returned. You must not free the returned pointer manually. It will be freed
+ * when 'ctx' is freed with talloc_free() and no other references are left.
+ */
+#define rc_alloc(ctx, type) \
+ (type *)_rc_alloc(ctx, sizeof(type), offsetof(type, REFCOUNT_MEMBER_NAME), \
+ #type)
+
+/*
+ * Increment the reference count of 'src' and return it back if we are
+ * successful. The reference count will be decremented after 'ctx' has been
+ * released by talloc_free(). The function will return NULL in case of failure.
+ */
+#define rc_reference(ctx, type, src) \
+ (type *)_rc_reference(ctx, offsetof(type, REFCOUNT_MEMBER_NAME), src)
+
+/*
+ * These functions should not be used directly. Use the above macros instead.
+ */
+void *_rc_alloc(const void *context, size_t size, size_t refcount_offset,
+ const char *type_name);
+void *_rc_reference(const void *context, size_t refcount_offset, void *source);
+
+
+#endif /* !__REFCOUNT_H__ */
diff --git a/src/util/server.c b/src/util/server.c
new file mode 100644
index 00000000..fd6c4653
--- /dev/null
+++ b/src/util/server.c
@@ -0,0 +1,433 @@
+/*
+ SSSD
+
+ Servers setup routines
+
+ Copyright (C) Andrew Tridgell 1992-2005
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002
+ Copyright (C) James J Myers 2003 <myersjj@samba.org>
+ Copyright (C) Simo Sorce 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "util/util.h"
+#include "ldb.h"
+#include "confdb/confdb.h"
+
+#ifdef HAVE_PRCTL
+#include <sys/prctl.h>
+#endif
+
+/*******************************************************************
+ Close the low 3 fd's and open dev/null in their place.
+********************************************************************/
+static void close_low_fds(void)
+{
+#ifndef VALGRIND
+ int fd;
+ int i;
+
+ close(0);
+ close(1);
+ close(2);
+
+ /* try and use up these file descriptors, so silly
+ library routines writing to stdout etc won't cause havoc */
+ for (i=0;i<3;i++) {
+ fd = open("/dev/null",O_RDWR,0);
+ if (fd < 0)
+ fd = open("/dev/null",O_WRONLY,0);
+ if (fd < 0) {
+ DEBUG(0,("Can't open /dev/null\n"));
+ return;
+ }
+ if (fd != i) {
+ DEBUG(0,("Didn't get file descriptor %d\n",i));
+ return;
+ }
+ }
+#endif
+}
+
+/**
+ Become a daemon, discarding the controlling terminal.
+**/
+
+void become_daemon(bool Fork)
+{
+ int ret;
+
+ if (Fork) {
+ if (fork()) {
+ _exit(0);
+ }
+ }
+
+ /* detach from the terminal */
+ setsid();
+
+ /* chdir to / to be sure we're not on a remote filesystem */
+ errno = 0;
+ if(chdir("/") == -1) {
+ ret = errno;
+ DEBUG(0, ("Cannot change directory (%d [%s])\n",
+ ret, strerror(ret)));
+ return;
+ }
+
+ /* Close fd's 0,1,2. Needed if started by rsh */
+ close_low_fds();
+}
+
+int pidfile(const char *path, const char *name)
+{
+ char pid_str[32];
+ pid_t pid;
+ char *file;
+ int fd;
+ int ret, err;
+
+ file = talloc_asprintf(NULL, "%s/%s.pid", path, name);
+ if (!file) {
+ return ENOMEM;
+ }
+
+ fd = open(file, O_RDONLY, 0644);
+ err = errno;
+ if (fd != -1) {
+
+ pid_str[sizeof(pid_str) -1] = '\0';
+ ret = read(fd, pid_str, sizeof(pid_str) -1);
+ if (ret > 0) {
+ /* let's check the pid */
+
+ pid = (pid_t)atoi(pid_str);
+ if (pid != 0) {
+ errno = 0;
+ ret = kill(pid, 0);
+ /* succeeded in signaling the process -> another sssd process */
+ if (ret == 0) {
+ close(fd);
+ talloc_free(file);
+ return EEXIST;
+ }
+ if (ret != 0 && errno != ESRCH) {
+ err = errno;
+ close(fd);
+ talloc_free(file);
+ return err;
+ }
+ }
+ }
+
+ /* nothing in the file or no process */
+ close(fd);
+ unlink(file);
+
+ } else {
+ if (err != ENOENT) {
+ talloc_free(file);
+ return err;
+ }
+ }
+
+ fd = open(file, O_CREAT | O_WRONLY | O_EXCL, 0644);
+ err = errno;
+ if (fd == -1) {
+ talloc_free(file);
+ return err;
+ }
+ talloc_free(file);
+
+ memset(pid_str, 0, sizeof(pid_str));
+ snprintf(pid_str, sizeof(pid_str) -1, "%u\n", (unsigned int) getpid());
+
+ ret = write(fd, pid_str, strlen(pid_str));
+ err = errno;
+ if (ret != strlen(pid_str)) {
+ close(fd);
+ return err;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+static void sig_hup(int sig)
+{
+ /* cycle log/debug files */
+ return;
+}
+
+static void sig_term(int sig)
+{
+#if HAVE_GETPGRP
+ static int done_sigterm;
+ if (done_sigterm == 0 && getpgrp() == getpid()) {
+ DEBUG(0,("SIGTERM: killing children\n"));
+ done_sigterm = 1;
+ kill(-getpgrp(), SIGTERM);
+ }
+#endif
+ exit(0);
+}
+
+#ifndef HAVE_PRCTL
+static void sig_segv_abrt(int sig)
+{
+#if HAVE_GETPGRP
+ static int done;
+ if (done == 0 && getpgrp() == getpid()) {
+ DEBUG(0,("%s: killing children\n", strsignal(sig)));
+ done = 1;
+ kill(-getpgrp(), SIGTERM);
+ }
+#endif /* HAVE_GETPGRP */
+ exit(1);
+}
+#endif /* HAVE_PRCTL */
+
+/*
+ setup signal masks
+*/
+static void setup_signals(void)
+{
+ /* we are never interested in SIGPIPE */
+ BlockSignals(true, SIGPIPE);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(true, SIGFPE);
+#endif
+
+ /* We are no longer interested in USR1 */
+ BlockSignals(true, SIGUSR1);
+
+#if defined(SIGUSR2)
+ /* We are no longer interested in USR2 */
+ BlockSignals(true, SIGUSR2);
+#endif
+
+ /* POSIX demands that signals are inherited. If the invoking process has
+ * these signals masked, we will have problems, as we won't recieve them. */
+ BlockSignals(false, SIGHUP);
+ BlockSignals(false, SIGTERM);
+
+ CatchSignal(SIGHUP, sig_hup);
+ CatchSignal(SIGTERM, sig_term);
+
+#ifndef HAVE_PRCTL
+ /* If prctl is not defined on the system, try to handle
+ * some common termination signals gracefully */
+ CatchSignal(SIGSEGV, sig_segv_abrt);
+ CatchSignal(SIGABRT, sig_segv_abrt);
+#endif
+
+}
+
+/*
+ handle io on stdin
+*/
+static void server_stdin_handler(struct tevent_context *event_ctx,
+ struct tevent_fd *fde,
+ uint16_t flags, void *private)
+{
+ const char *binary_name = (const char *)private;
+ uint8_t c;
+ if (read(0, &c, 1) == 0) {
+ DEBUG(0,("%s: EOF on stdin - terminating\n", binary_name));
+#if HAVE_GETPGRP
+ if (getpgrp() == getpid()) {
+ kill(-getpgrp(), SIGTERM);
+ }
+#endif
+ exit(0);
+ }
+}
+
+/*
+ main server helpers.
+*/
+
+int die_if_parent_died(void)
+{
+#ifdef HAVE_PRCTL
+ int ret;
+
+ errno = 0;
+ ret = prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(2, ("prctl failed [%d]: %s", ret, strerror(ret)));
+ return ret;
+ }
+#endif
+ return EOK;
+}
+
+int server_setup(const char *name, int flags,
+ const char *conf_entry,
+ struct main_context **main_ctx)
+{
+ struct tevent_context *event_ctx;
+ struct main_context *ctx;
+ uint16_t stdin_event_flags;
+ char *conf_db;
+ int ret = EOK;
+ bool dt;
+ bool dl;
+
+ debug_prg_name = strdup(name);
+ if (!debug_prg_name) {
+ return ENOMEM;
+ }
+
+ setenv("_SSS_LOOPS", "NO", 0);
+
+ setup_signals();
+
+ /* we want default permissions on created files to be very strict,
+ so set our umask to 0177 */
+ umask(0177);
+
+ if (flags & FLAGS_DAEMON) {
+ DEBUG(3,("Becoming a daemon.\n"));
+ become_daemon(true);
+ }
+
+ if (flags & FLAGS_PID_FILE) {
+ ret = pidfile(PID_PATH, name);
+ if (ret != EOK) {
+ DEBUG(0, ("Error creating pidfile! (%d [%s])\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+ }
+
+ /* Set up locale */
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ /* the event context is the top level structure.
+ * Everything else should hang off that */
+ event_ctx = tevent_context_init(talloc_autofree_context());
+ if (event_ctx == NULL) {
+ DEBUG(0,("The event context initialiaziton failed\n"));
+ return 1;
+ }
+
+ ctx = talloc(event_ctx, struct main_context);
+ if (ctx == NULL) {
+ DEBUG(0,("Out of memory, aborting!\n"));
+ return ENOMEM;
+ }
+
+ ctx->event_ctx = event_ctx;
+
+ conf_db = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (conf_db == NULL) {
+ DEBUG(0,("Out of memory, aborting!\n"));
+ return ENOMEM;
+ }
+
+ ret = confdb_init(ctx, &ctx->confdb_ctx, conf_db);
+ if (ret != EOK) {
+ DEBUG(0,("The confdb initialization failed\n"));
+ return ret;
+ }
+
+ /* set debug level if any in conf_entry */
+ ret = confdb_get_int(ctx->confdb_ctx, ctx, conf_entry,
+ CONFDB_SERVICE_DEBUG_LEVEL,
+ debug_level, &debug_level);
+ if (ret != EOK) {
+ DEBUG(0, ("Error reading from confdb (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+
+ /* same for debug timestamps */
+ dt = (debug_timestamps != 0);
+ ret = confdb_get_bool(ctx->confdb_ctx, ctx, conf_entry,
+ CONFDB_SERVICE_DEBUG_TIMESTAMPS,
+ dt, &dt);
+ if (ret != EOK) {
+ DEBUG(0, ("Error reading from confdb (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+ if (dt) debug_timestamps = 1;
+
+ /* same for debug to file */
+ dl = (debug_to_file != 0);
+ ret = confdb_get_bool(ctx->confdb_ctx, ctx, conf_entry,
+ CONFDB_SERVICE_DEBUG_TO_FILES,
+ dl, &dl);
+ if (ret != EOK) {
+ DEBUG(0, ("Error reading from confdb (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+ if (dl) debug_to_file = 1;
+
+ /* open log file if told so */
+ if (debug_to_file) {
+ ret = open_debug_file();
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+ }
+
+ DEBUG(3, ("CONFDB: %s\n", conf_db));
+
+ if (flags & FLAGS_INTERACTIVE) {
+ /* terminate when stdin goes away */
+ stdin_event_flags = TEVENT_FD_READ;
+ } else {
+ /* stay alive forever */
+ stdin_event_flags = 0;
+ }
+
+ /* catch EOF on stdin */
+#ifdef SIGTTIN
+ signal(SIGTTIN, SIG_IGN);
+#endif
+ tevent_add_fd(event_ctx, event_ctx, 0, stdin_event_flags,
+ server_stdin_handler, discard_const(name));
+
+ *main_ctx = ctx;
+ return EOK;
+}
+
+void server_loop(struct main_context *main_ctx)
+{
+ /* wait for events - this is where the server sits for most of its
+ life */
+ tevent_loop_wait(main_ctx->event_ctx);
+
+ /* as everything hangs off this event context, freeing it
+ should initiate a clean shutdown of all services */
+ talloc_free(main_ctx->event_ctx);
+}
diff --git a/src/util/sha512crypt.h b/src/util/sha512crypt.h
new file mode 100644
index 00000000..5512c5d9
--- /dev/null
+++ b/src/util/sha512crypt.h
@@ -0,0 +1,4 @@
+
+int s3crypt_sha512(TALLOC_CTX *mmectx,
+ const char *key, const char *salt, char **_hash);
+int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt);
diff --git a/src/util/signal.c b/src/util/signal.c
new file mode 100644
index 00000000..e4a782da
--- /dev/null
+++ b/src/util/signal.c
@@ -0,0 +1,146 @@
+/*
+ Unix SMB/CIFS implementation.
+ signal handling functions
+
+ Copyright (C) Andrew Tridgell 1998
+
+ 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 "util/util.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+/**
+ * @file
+ * @brief Signal handling
+ */
+
+/****************************************************************************
+ Catch child exits and reap the child zombie status.
+****************************************************************************/
+
+static void sig_cld(int signum)
+{
+ while (waitpid((pid_t)-1,(int *)NULL, WNOHANG) > 0)
+ ;
+
+ /*
+ * Turns out it's *really* important not to
+ * restore the signal handler here if we have real POSIX
+ * signal handling. If we do, then we get the signal re-delivered
+ * immediately - hey presto - instant loop ! JRA.
+ */
+
+#if !defined(HAVE_SIGACTION)
+ CatchSignal(SIGCLD, sig_cld);
+#endif
+}
+
+/****************************************************************************
+catch child exits - leave status;
+****************************************************************************/
+
+static void sig_cld_leave_status(int signum)
+{
+ /*
+ * Turns out it's *really* important not to
+ * restore the signal handler here if we have real POSIX
+ * signal handling. If we do, then we get the signal re-delivered
+ * immediately - hey presto - instant loop ! JRA.
+ */
+
+#if !defined(HAVE_SIGACTION)
+ CatchSignal(SIGCLD, sig_cld_leave_status);
+#endif
+}
+
+/**
+ Block sigs.
+**/
+
+void BlockSignals(bool block, int signum)
+{
+#ifdef HAVE_SIGPROCMASK
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set,signum);
+ sigprocmask(block?SIG_BLOCK:SIG_UNBLOCK,&set,NULL);
+#elif defined(HAVE_SIGBLOCK)
+ if (block) {
+ sigblock(sigmask(signum));
+ } else {
+ sigsetmask(siggetmask() & ~sigmask(signum));
+ }
+#else
+ /* yikes! This platform can't block signals? */
+ static int done;
+ if (!done) {
+ DEBUG(0,("WARNING: No signal blocking available\n"));
+ done=1;
+ }
+#endif
+}
+
+/**
+ Catch a signal. This should implement the following semantics:
+
+ 1) The handler remains installed after being called.
+ 2) The signal should be blocked during handler execution.
+**/
+
+void (*CatchSignal(int signum,void (*handler)(int )))(int)
+{
+#ifdef HAVE_SIGACTION
+ struct sigaction act;
+ struct sigaction oldact;
+
+ ZERO_STRUCT(act);
+
+ act.sa_handler = handler;
+#ifdef SA_RESTART
+ /*
+ * We *want* SIGALRM to interrupt a system call.
+ */
+ if(signum != SIGALRM)
+ act.sa_flags = SA_RESTART;
+#endif
+ sigemptyset(&act.sa_mask);
+ sigaddset(&act.sa_mask,signum);
+ sigaction(signum,&act,&oldact);
+ return oldact.sa_handler;
+#else /* !HAVE_SIGACTION */
+ /* FIXME: need to handle sigvec and systems with broken signal() */
+ return signal(signum, handler);
+#endif
+}
+
+/**
+ Ignore SIGCLD via whatever means is necessary for this OS.
+**/
+
+void CatchChild(void)
+{
+ CatchSignal(SIGCLD, sig_cld);
+}
+
+/**
+ Catch SIGCLD but leave the child around so it's status can be reaped.
+**/
+
+void CatchChildLeaveStatus(void)
+{
+ CatchSignal(SIGCLD, sig_cld_leave_status);
+}
diff --git a/src/util/signal.m4 b/src/util/signal.m4
new file mode 100644
index 00000000..747c7dbf
--- /dev/null
+++ b/src/util/signal.m4
@@ -0,0 +1 @@
+AC_CHECK_FUNCS(sigprocmask sigblock sigaction getpgrp prctl)
diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
new file mode 100644
index 00000000..0bc25df1
--- /dev/null
+++ b/src/util/sss_krb5.c
@@ -0,0 +1,196 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <errno.h>
+#include <talloc.h>
+
+#include "config.h"
+
+#include "util/util.h"
+#include "util/sss_krb5.h"
+
+
+
+const char *KRB5_CALLCONV sss_krb5_get_error_message(krb5_context ctx,
+ krb5_error_code ec)
+{
+#ifdef HAVE_KRB5_GET_ERROR_MESSAGE
+ return krb5_get_error_message(ctx, ec);
+#else
+ int ret;
+ char *s = NULL;
+ int size = sizeof("Kerberos error [XXXXXXXXXXXX]");
+
+ s = malloc(sizeof(char) * (size));
+ if (s == NULL) {
+ return NULL;
+ }
+
+ ret = snprintf(s, size, "Kerberos error [%12d]", ec);
+
+ if (ret < 0 || ret >= size) {
+ return NULL;
+ }
+
+ return s;
+#endif
+}
+
+void KRB5_CALLCONV sss_krb5_free_error_message(krb5_context ctx, const char *s)
+{
+#ifdef HAVE_KRB5_GET_ERROR_MESSAGE
+ krb5_free_error_message(ctx, s);
+#else
+ free(s);
+#endif
+
+ return;
+}
+
+krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_alloc(
+ krb5_context context,
+ krb5_get_init_creds_opt **opt)
+{
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
+ return krb5_get_init_creds_opt_alloc(context, opt);
+#else
+ *opt = calloc(1, sizeof(krb5_get_init_creds_opt));
+ if (*opt == NULL) {
+ return ENOMEM;
+ }
+ krb5_get_init_creds_opt_init(*opt);
+
+ return 0;
+#endif
+}
+
+void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context,
+ krb5_get_init_creds_opt *opt)
+{
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
+ krb5_get_init_creds_opt_free(context, opt);
+#else
+ free(opt);
+#endif
+
+ return;
+}
+
+void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name)
+{
+#ifdef HAVE_KRB5_FREE_UNPARSED_NAME
+ krb5_free_unparsed_name(context, name);
+#else
+ if (name != NULL) {
+ memset(name, 0, strlen(name));
+ free(name);
+ }
+#endif
+}
+
+
+krb5_error_code check_for_valid_tgt(const char *ccname, const char *realm,
+ const char *client_princ_str, bool *result)
+{
+ krb5_context context = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code krberr;
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_creds mcred;
+ krb5_creds cred;
+ char *server_name = NULL;
+ krb5_principal client_principal = NULL;
+ krb5_principal server_principal = NULL;
+
+ *result = false;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ DEBUG(1, ("Failed to init kerberos context\n"));
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_resolve failed.\n"));
+ goto done;
+ }
+
+ server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm);
+ if (server_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_parse_name(context, server_name, &server_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_parse_name(context, client_princ_str, &client_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ memset(&mcred, 0, sizeof(mcred));
+ memset(&cred, 0, sizeof(mcred));
+ mcred.client = client_principal;
+ mcred.server = server_principal;
+
+ krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcred, &cred);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_retrieve_cred failed.\n"));
+ krberr = 0;
+ goto done;
+ }
+
+ DEBUG(7, ("TGT end time [%d].\n", cred.times.endtime));
+
+ if (cred.times.endtime > time(NULL)) {
+ DEBUG(3, ("TGT is valid.\n"));
+ *result = true;
+ }
+ krb5_free_cred_contents(context, &cred);
+
+ krberr = 0;
+
+done:
+ if (client_principal != NULL) {
+ krb5_free_principal(context, client_principal);
+ }
+ if (server_principal != NULL) {
+ krb5_free_principal(context, server_principal);
+ }
+ if (ccache != NULL) {
+ krb5_cc_close(context, ccache);
+ }
+ if (context != NULL) krb5_free_context(context);
+ talloc_free(tmp_ctx);
+ return krberr;
+}
+
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
new file mode 100644
index 00000000..60994e12
--- /dev/null
+++ b/src/util/sss_krb5.h
@@ -0,0 +1,50 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __SSS_KRB5_H__
+#define __SSS_KRB5_H__
+
+#include "config.h"
+
+#include <stdbool.h>
+
+#ifdef HAVE_KRB5_KRB5_H
+#include <krb5/krb5.h>
+#else
+#include <krb5.h>
+#endif
+
+const char * KRB5_CALLCONV sss_krb5_get_error_message (krb5_context,
+ krb5_error_code);
+
+void KRB5_CALLCONV sss_krb5_free_error_message(krb5_context, const char *);
+
+krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_alloc(
+ krb5_context context,
+ krb5_get_init_creds_opt **opt);
+
+void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context,
+ krb5_get_init_creds_opt *opt);
+
+void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name);
+
+krb5_error_code check_for_valid_tgt(const char *ccname, const char *realm,
+ const char *client_princ_str, bool *result);
+#endif /* __SSS_KRB5_H__ */
diff --git a/src/util/sss_ldap.c b/src/util/sss_ldap.c
new file mode 100644
index 00000000..f098e7d6
--- /dev/null
+++ b/src/util/sss_ldap.c
@@ -0,0 +1,70 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <stdlib.h>
+
+#include "config.h"
+
+#include "util/sss_ldap.h"
+
+
+int sss_ldap_control_create(const char *oid, int iscritical,
+ struct berval *value, int dupval,
+ LDAPControl **ctrlp)
+{
+#ifdef HAVE_LDAP_CONTROL_CREATE
+ return ldap_control_create(oid, iscritical, value, dupval, ctrlp);
+#else
+ LDAPControl *lc = NULL;
+
+ if (oid == NULL || ctrlp == NULL) {
+ return LDAP_PARAM_ERROR;
+ }
+
+ lc = calloc(sizeof(LDAPControl), 1);
+ if (lc == NULL) {
+ return LDAP_NO_MEMORY;
+ }
+
+ lc->ldctl_oid = strdup(oid);
+ if (lc->ldctl_oid == NULL) {
+ free(lc);
+ return LDAP_NO_MEMORY;
+ }
+
+ if (value != NULL && value->bv_val != NULL) {
+ if (dupval == 0) {
+ lc->ldctl_value = *value;
+ } else {
+ ber_dupbv(&lc->ldctl_value, value);
+ if (lc->ldctl_value.bv_val == NULL) {
+ free(lc->ldctl_oid);
+ free(lc);
+ return LDAP_NO_MEMORY;
+ }
+ }
+ }
+
+ lc->ldctl_iscritical = iscritical;
+
+ *ctrlp = lc;
+
+ return LDAP_SUCCESS;
+#endif
+}
diff --git a/src/util/sss_ldap.h b/src/util/sss_ldap.h
new file mode 100644
index 00000000..14747dff
--- /dev/null
+++ b/src/util/sss_ldap.h
@@ -0,0 +1,30 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __SSS_LDAP_H__
+#define __SSS_LDAP_H__
+
+#include <ldap.h>
+
+int sss_ldap_control_create(const char *oid, int iscritical,
+ struct berval *value, int dupval,
+ LDAPControl **ctrlp);
+
+#endif /* __SSS_LDAP_H__ */
diff --git a/src/util/strtonum.c b/src/util/strtonum.c
new file mode 100644
index 00000000..744e0f71
--- /dev/null
+++ b/src/util/strtonum.c
@@ -0,0 +1,65 @@
+/*
+ SSSD
+
+ SSSD Utility functions
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "config.h"
+#include "util/util.h"
+#include "util/strtonum.h"
+
+/* strtoint32 */
+int32_t strtoint32(const char *nptr, char **endptr, int base)
+{
+ long long ret = 0;
+
+ errno = 0;
+ ret = strtoll(nptr, endptr, base);
+
+ if (ret > INT32_MAX) {
+ errno = ERANGE;
+ return INT32_MAX;
+ }
+ else if (ret < INT32_MIN) {
+ errno = ERANGE;
+ return INT32_MIN;
+ }
+
+ /* If errno was set by strtoll, we'll pass it back as-is */
+ return (int32_t)ret;
+}
+
+
+/* strtouint32 */
+uint32_t strtouint32(const char *nptr, char **endptr, int base)
+{
+ long long ret = 0;
+ errno = 0;
+ ret = strtoull(nptr, endptr, base);
+
+ if (ret > UINT32_MAX) {
+ errno = ERANGE;
+ return UINT32_MAX;
+ }
+
+ /* If errno was set by strtoll, we'll pass it back as-is */
+ return (uint32_t)ret;
+}
diff --git a/src/util/strtonum.h b/src/util/strtonum.h
new file mode 100644
index 00000000..45095962
--- /dev/null
+++ b/src/util/strtonum.h
@@ -0,0 +1,32 @@
+/*
+ SSSD
+
+ SSSD Utility functions
+
+ Copyright (C) Stephen Gallagher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _STRTONUM_H_
+#define _STRTONUM_H_
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+int32_t strtoint32(const char *nptr, char **endptr, int base);
+uint32_t strtouint32(const char *nptr, char **endptr, int base);
+
+#endif /* _STRTONUM_H_ */
diff --git a/src/util/user_info_msg.c b/src/util/user_info_msg.c
new file mode 100644
index 00000000..547e3bb7
--- /dev/null
+++ b/src/util/user_info_msg.c
@@ -0,0 +1,51 @@
+/*
+ SSSD
+
+ Pack user info messages
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 "util/util.h"
+#include "sss_client/sss_cli.h"
+
+errno_t pack_user_info_chpass_error(TALLOC_CTX *mem_ctx,
+ const char *user_error_message,
+ size_t *resp_len,
+ uint8_t **_resp)
+{
+ uint32_t resp_type = SSS_PAM_USER_INFO_CHPASS_ERROR;
+ size_t err_len;
+ uint8_t *resp;
+
+ err_len = strlen(user_error_message);
+ *resp_len = 2 * sizeof(uint32_t) + err_len;
+ resp = talloc_size(mem_ctx, *resp_len);
+ if (resp == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return ENOMEM;
+ }
+
+ memcpy(resp, &resp_type, sizeof(uint32_t));
+ memcpy(resp + sizeof(uint32_t), &err_len, sizeof(uint32_t));
+ memcpy(resp + 2 * sizeof(uint32_t), user_error_message, err_len);
+
+ *_resp = resp;
+ return EOK;
+}
diff --git a/src/util/user_info_msg.h b/src/util/user_info_msg.h
new file mode 100644
index 00000000..c68d538c
--- /dev/null
+++ b/src/util/user_info_msg.h
@@ -0,0 +1,33 @@
+/*
+ SSSD
+
+ Pack user info messages
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+#ifndef __USER_INFO_MSG_H__
+#define __USER_INFO_MSG_H__
+
+
+errno_t pack_user_info_chpass_error(TALLOC_CTX *mem_ctx,
+ const char *user_error_message,
+ size_t *len,
+ uint8_t **_resp);
+
+#endif /* __USER_INFO_MSG_H__ */
diff --git a/src/util/usertools.c b/src/util/usertools.c
new file mode 100644
index 00000000..738ac62d
--- /dev/null
+++ b/src/util/usertools.c
@@ -0,0 +1,175 @@
+/*
+ SSSD
+
+ User tools
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <pwd.h>
+#include <pcre.h>
+#include <errno.h>
+#include <talloc.h>
+
+#include "confdb/confdb.h"
+#include "util/util.h"
+
+#ifdef HAVE_LIBPCRE_LESSER_THAN_7
+#define NAME_DOMAIN_PATTERN_OPTIONS (PCRE_EXTENDED)
+#else
+#define NAME_DOMAIN_PATTERN_OPTIONS (PCRE_DUPNAMES | PCRE_EXTENDED)
+#endif
+
+char *get_username_from_uid(TALLOC_CTX *mem_ctx, uid_t uid)
+{
+ char *username;
+ struct passwd *pwd;
+
+ pwd = getpwuid(uid);
+ if (!pwd) return NULL;
+
+ username = talloc_strdup(mem_ctx, pwd->pw_name);
+ return username;
+}
+
+static int sss_names_ctx_destructor(struct sss_names_ctx *snctx)
+{
+ if (snctx->re) {
+ pcre_free(snctx->re);
+ snctx->re = NULL;
+ }
+ return 0;
+}
+
+int sss_names_init(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, struct sss_names_ctx **out)
+{
+ struct sss_names_ctx *ctx;
+ const char *errstr;
+ int errval;
+ int errpos;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct sss_names_ctx);
+ if (!ctx) return ENOMEM;
+ talloc_set_destructor(ctx, sss_names_ctx_destructor);
+
+ ret = confdb_get_string(cdb, ctx, CONFDB_MONITOR_CONF_ENTRY,
+ CONFDB_MONITOR_NAME_REGEX, NULL, &ctx->re_pattern);
+ if (ret != EOK) goto done;
+
+ if (!ctx->re_pattern) {
+ ctx->re_pattern = talloc_strdup(ctx,
+ "(?P<name>[^@]+)@?(?P<domain>[^@]*$)");
+ if (!ctx->re_pattern) {
+ ret = ENOMEM;
+ goto done;
+ }
+#ifdef HAVE_LIBPCRE_LESSER_THAN_7
+ } else {
+ DEBUG(2, ("This binary was build with a version of libpcre that does "
+ "not support non-unique named subpatterns.\n"));
+ DEBUG(2, ("Please make sure that your pattern [%s] only contains "
+ "subpatterns with a unique name and uses "
+ "the Python syntax (?P<name>).\n", ctx->re_pattern));
+#endif
+ }
+
+ ret = confdb_get_string(cdb, ctx, CONFDB_MONITOR_CONF_ENTRY,
+ CONFDB_MONITOR_FULL_NAME_FORMAT, NULL, &ctx->fq_fmt);
+ if (ret != EOK) goto done;
+
+ if (!ctx->fq_fmt) {
+ ctx->fq_fmt = talloc_strdup(ctx, "%1$s@%2$s");
+ if (!ctx->fq_fmt) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ ctx->re = pcre_compile2(ctx->re_pattern,
+ NAME_DOMAIN_PATTERN_OPTIONS,
+ &errval, &errstr, &errpos, NULL);
+ if (!ctx->re) {
+ DEBUG(1, ("Invalid Regular Expression pattern at position %d."
+ " (Error: %d [%s])\n", errpos, errval, errstr));
+ ret = EFAULT;
+ goto done;
+ }
+
+ *out = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sss_parse_name(TALLOC_CTX *memctx,
+ struct sss_names_ctx *snctx,
+ const char *orig, char **domain, char **name)
+{
+ pcre *re = snctx->re;
+ const char *result;
+ int ovec[30];
+ int origlen;
+ int ret, strnum;
+
+ origlen = strlen(orig);
+
+ ret = pcre_exec(re, NULL, orig, origlen, 0, PCRE_NOTEMPTY, ovec, 30);
+ if (ret < 0) {
+ DEBUG(2, ("PCRE Matching error, %d\n", ret));
+ return EINVAL;
+ }
+
+ if (ret == 0) {
+ DEBUG(1, ("Too many matches, the pattern is invalid.\n"));
+ }
+
+ strnum = ret;
+
+ result = NULL;
+ ret = pcre_get_named_substring(re, orig, ovec, strnum, "name", &result);
+ if (ret < 0 || !result) {
+ DEBUG(2, ("Name not found!\n"));
+ return EINVAL;
+ }
+ *name = talloc_strdup(memctx, result);
+ pcre_free_substring(result);
+ if (!*name) return ENOMEM;
+
+
+ result = NULL;
+ ret = pcre_get_named_substring(re, orig, ovec, strnum, "domain", &result);
+ if (ret < 0 || !result) {
+ DEBUG(4, ("Domain not provided!\n"));
+ *domain = NULL;
+ } else {
+ /* ignore "" string */
+ if (*result) {
+ *domain = talloc_strdup(memctx, result);
+ pcre_free_substring(result);
+ if (!*domain) return ENOMEM;
+ } else {
+ pcre_free_substring(result);
+ *domain = NULL;
+ }
+ }
+
+ return EOK;
+}
diff --git a/src/util/util.c b/src/util/util.c
new file mode 100644
index 00000000..57ceb597
--- /dev/null
+++ b/src/util/util.c
@@ -0,0 +1,138 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 <ctype.h>
+
+#include "talloc.h"
+#include "util/util.h"
+
+/* split a string into an allocated array of strings.
+ * the separator is a string, and is case-sensitive.
+ * optionally single values can be trimmed of of spaces and tabs */
+int split_on_separator(TALLOC_CTX *mem_ctx, const char *str,
+ const char sep, bool trim, char ***_list, int *size)
+{
+ const char *t, *p, *n;
+ size_t l, len;
+ char **list, **r;
+ const char sep_str[2] = { sep, '\0'};
+
+ if (!str || !*str || !_list) return EINVAL;
+
+ t = str;
+
+ list = NULL;
+ l = 0;
+
+ /* trim leading whitespace */
+ if (trim)
+ while (isspace(*t)) t++;
+
+ /* find substrings separated by the separator */
+ while (t && (p = strpbrk(t, sep_str))) {
+ len = p - t;
+ n = p + 1; /* save next string starting point */
+ if (trim) {
+ /* strip whitespace after the separator
+ * so it's not in the next token */
+ while (isspace(*t)) {
+ t++;
+ len--;
+ if (len == 0) break;
+ }
+ p--;
+ /* strip whitespace before the separator
+ * so it's not in the current token */
+ while (len > 0 && (isspace(*p))) {
+ len--;
+ p--;
+ }
+ }
+
+ /* Add the token to the array, +2 b/c of the trailing NULL */
+ r = talloc_realloc(mem_ctx, list, char *, l + 2);
+ if (!r) {
+ talloc_free(list);
+ return ENOMEM;
+ } else {
+ list = r;
+ }
+
+ if (len == 0) {
+ list[l] = talloc_strdup(list, "");
+ } else {
+ list[l] = talloc_strndup(list, t, len);
+ }
+ if (!list[l]) {
+ talloc_free(list);
+ return ENOMEM;
+ }
+ l++;
+
+ t = n; /* move to next string */
+ }
+
+ /* Handle the last remaining token */
+ if (t) {
+ r = talloc_realloc(mem_ctx, list, char *, l + 2);
+ if (!r) {
+ talloc_free(list);
+ return ENOMEM;
+ } else {
+ list = r;
+ }
+
+ if (trim) {
+ /* trim leading whitespace */
+ len = strlen(t);
+ while (isspace(*t)) {
+ t++;
+ len--;
+ if (len == 0) break;
+ }
+ /* trim trailing whitespace */
+ p = t + len - 1;
+ while (len > 0 && (isspace(*p))) {
+ len--;
+ p--;
+ }
+
+ if (len == 0) {
+ list[l] = talloc_strdup(list, "");
+ } else {
+ list[l] = talloc_strndup(list, t, len);
+ }
+ } else {
+ list[l] = talloc_strdup(list, t);
+ }
+ if (!list[l]) {
+ talloc_free(list);
+ return ENOMEM;
+ }
+ l++;
+ }
+
+ list[l] = NULL; /* terminate list */
+
+ if (size) *size = l + 1;
+ *_list = list;
+
+ return EOK;
+}
diff --git a/src/util/util.h b/src/util/util.h
new file mode 100644
index 00000000..945e20d0
--- /dev/null
+++ b/src/util/util.h
@@ -0,0 +1,256 @@
+/*
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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/>.
+*/
+
+#ifndef __SSSD_UTIL_H__
+#define __SSSD_UTIL_H__
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <libintl.h>
+#include <limits.h>
+#include <locale.h>
+#include <time.h>
+#include <pcre.h>
+#include <sys/types.h>
+
+#include "config.h"
+
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+
+#ifndef HAVE_ERRNO_T
+#define HAVE_ERRNO_T
+typedef int errno_t;
+#endif
+
+#define _(STRING) gettext (STRING)
+
+extern const char *debug_prg_name;
+extern int debug_level;
+extern int debug_timestamps;
+extern int debug_to_file;
+extern const char *debug_log_file;
+void debug_fn(const char *format, ...);
+errno_t set_debug_file_from_fd(const int fd);
+
+#define SSSD_DEBUG_OPTS \
+ {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, \
+ _("Debug level"), NULL}, \
+ {"debug-to-files", 'f', POPT_ARG_NONE, &debug_to_file, 0, \
+ _("Send the debug output to files instead of stderr"), NULL }, \
+ {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0, \
+ _("Add debug timestamps"), NULL},
+
+/** \def DEBUG(level, body)
+ \brief macro to generate debug messages
+
+ \param level the debug level, please respect the following guidelines:
+ - 1 is for critical errors users may find it difficult to understand but
+ are still quite clear
+ - 2-4 is for stuff developers are interested in in general, but
+ shouldn't fill the screen with useless low level verbose stuff
+ - 5-6 is for errors you may want to track, but only if you explicitly
+ looking for additional clues
+ - 7-10 is for informational stuff
+
+ \param body the debug message you want to send, should end with \n
+*/
+#define DEBUG(level, body) do { \
+ if (level <= debug_level) { \
+ if (debug_timestamps) { \
+ time_t rightnow = time(NULL); \
+ char stamp[25]; \
+ memcpy(stamp, ctime(&rightnow), 24); \
+ stamp[24] = '\0'; \
+ debug_fn("(%s) [%s] [%s] (%d): ", \
+ stamp, debug_prg_name, __FUNCTION__, level); \
+ } else { \
+ debug_fn("[%s] [%s] (%d): ", \
+ debug_prg_name, __FUNCTION__, level); \
+ } \
+ debug_fn body; \
+ } \
+} while(0);
+
+#define PRINT(fmt, ...) fprintf(stdout, gettext(fmt), ##__VA_ARGS__)
+#define ERROR(fmt, ...) fprintf(stderr, gettext(fmt), ##__VA_ARGS__)
+
+#ifndef discard_const
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x))
+
+#define EOK 0
+
+#define SSSD_MAIN_OPTS SSSD_DEBUG_OPTS
+
+#define FLAGS_NONE 0x0000
+#define FLAGS_DAEMON 0x0001
+#define FLAGS_INTERACTIVE 0x0002
+#define FLAGS_PID_FILE 0x0004
+
+#ifndef talloc_zfree
+#define talloc_zfree(ptr) do { talloc_free(ptr); ptr = NULL; } while(0)
+#endif
+
+#ifndef discard_const_p
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr)))
+#else
+# define discard_const_p(type, ptr) ((type *)(ptr))
+#endif
+#endif
+
+/* TODO: remove later
+ * These functions are available in the latest tevent and are the ones that
+ * should be used as tevent_req is rightfully opaque there */
+#ifndef tevent_req_data
+#define tevent_req_data(req, type) ((type *)req->private_state)
+#define tevent_req_set_callback(req, func, data) \
+ do { req->async.fn = func; req->async.private_data = data; } while(0)
+#define tevent_req_callback_data(req, type) ((type *)req->async.private_data)
+#define tevent_req_notify_callback(req) \
+ do { \
+ if (req->async.fn != NULL) { \
+ req->async.fn(req); \
+ } \
+ } while(0)
+
+/* noop */
+#define tevent_loop_allow_nesting(x)
+#endif
+
+#define TEVENT_REQ_RETURN_ON_ERROR(req) do { \
+ enum tevent_req_state TRROEstate; \
+ uint64_t TRROEerr; \
+ \
+ if (tevent_req_is_error(req, &TRROEstate, &TRROEerr)) { \
+ if (TRROEstate == TEVENT_REQ_USER_ERROR) { \
+ return TRROEerr; \
+ } \
+ return EIO; \
+ } \
+} while (0)
+
+#define OUT_OF_ID_RANGE(id, min, max) \
+ (id == 0 || (min && (id < min)) || (max && (id > max)))
+
+#define COPY_MEM(to, from, ptr, size) do { \
+ memcpy(to, from, size); \
+ ptr += size; \
+} while(0)
+
+#define COPY_TYPE(to, from, ptr, type) COPY_MEM(to, from, ptr, sizeof(type))
+
+#define COPY_VALUE(to, value, ptr, type) do { \
+ type CV_MACRO_val = (type) value; \
+ COPY_TYPE(to, &CV_MACRO_val, ptr, type); \
+} while(0)
+
+#define COPY_UINT32(to, from, ptr) COPY_TYPE(to, from, ptr, uint32_t)
+#define COPY_UINT32_VALUE(to, value, ptr) COPY_VALUE(to, value, ptr, uint32_t)
+#define COPY_INT32(to, from, ptr) COPY_TYPE(to, from, ptr, int32_t)
+#define COPY_INT32_VALUE(to, value, ptr) COPY_VALUE(to, value, ptr, int32_t)
+
+#define COPY_UINT32_CHECK(to, from, ptr, size) do { \
+ if ((ptr + sizeof(uint32_t)) > size) return EINVAL; \
+ COPY_UINT32(to, from, ptr); \
+} while(0)
+
+#include "util/dlinklist.h"
+
+/* From debug.c */
+void ldb_debug_messages(void *context, enum ldb_debug_level level,
+ const char *fmt, va_list ap);
+int open_debug_file_ex(const char *filename, FILE **filep);
+int open_debug_file(void);
+
+/* from server.c */
+struct main_context {
+ struct tevent_context *event_ctx;
+ struct confdb_ctx *confdb_ctx;
+};
+
+int die_if_parent_died(void);
+int server_setup(const char *name, int flags,
+ const char *conf_entry,
+ struct main_context **main_ctx);
+void server_loop(struct main_context *main_ctx);
+
+/* from signal.c */
+#include <signal.h>
+void BlockSignals(bool block, int signum);
+void (*CatchSignal(int signum,void (*handler)(int )))(int);
+void CatchChild(void);
+void CatchChildLeaveStatus(void);
+
+/* from memory.c */
+typedef int (void_destructor_fn_t)(void *);
+
+struct mem_holder {
+ void *mem;
+ void_destructor_fn_t *fn;
+};
+
+void *sss_mem_attach(TALLOC_CTX *mem_ctx,
+ void *ptr,
+ void_destructor_fn_t *fn);
+
+int password_destructor(void *memctx);
+
+/* from usertools.c */
+char *get_username_from_uid(TALLOC_CTX *mem_ctx, uid_t uid);
+
+struct sss_names_ctx {
+ char *re_pattern;
+ char *fq_fmt;
+
+ pcre *re;
+};
+
+int sss_names_init(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *cdb,
+ struct sss_names_ctx **out);
+
+int sss_parse_name(TALLOC_CTX *memctx,
+ struct sss_names_ctx *snctx,
+ const char *orig, char **domain, char **name);
+
+/* from backup-file.c */
+int backup_file(const char *src, int dbglvl);
+
+/* from check_and_open.c */
+errno_t check_and_open_readonly(const char *filename, int *fd, const uid_t uid,
+ const gid_t gid, const mode_t mode);
+
+/* from util.c */
+int split_on_separator(TALLOC_CTX *mem_ctx, const char *str,
+ const char sep, bool trim, char ***_list, int *size);
+#endif /* __SSSD_UTIL_H__ */