3f51ca
From 8a80363e07b5c9309d785bf3b41f506f32a750a5 Mon Sep 17 00:00:00 2001
3f51ca
From: Alexander Bokovoy <abokovoy@redhat.com>
3f51ca
Date: Tue, 14 Nov 2017 12:44:02 +0200
3f51ca
Subject: [PATCH] ipa-extdom-extop: refactor nsswitch operations
3f51ca
3f51ca
Refactor nsswitch operations in ipa-extdom-extop plugin to allow use
3f51ca
of timeout-enabled nsswitch calls provided by libsss_nss_idmap.
3f51ca
3f51ca
Standard POSIX nsswitch API has no way to cancel requests which may
3f51ca
cause ipa-extdom-extop requests to hang far too long and potentially
3f51ca
exhaust LDAP server workers. In addition, glibc nsswitch API iterates
3f51ca
through all nsswitch modules one by one and with multiple parallel
3f51ca
requests a lock up may happen in an unrelated nsswitch module like
3f51ca
nss_files.so.2.
3f51ca
3f51ca
A solution to the latter issue is to directly load nss_sss.so.2 plugin
3f51ca
and utilize it. This, however, does not solve a problem with lack of
3f51ca
cancellable API.
3f51ca
3f51ca
With SSSD 1.16.1, libsss_nss_idmap provides a timeout-enabled variant of
3f51ca
nsswitch API that is directly integrated with SSSD client side machinery
3f51ca
used by nss_sss.so.2. As result, this API can be used instead of loading
3f51ca
nss_sss.so.2 directly.
3f51ca
3f51ca
To support older SSSD version, both direct loading of nss_sss.so.2 and
3f51ca
new timeout-enabled API are supported by this changeset. An API to
3f51ca
abstract both is designed to be a mix between internal glibc nsswitch
3f51ca
API and external nsswitch API that libsss_nss_idmap mimics. API does not
3f51ca
expose per-call timeout. Instead, it allows to set a timeout per
3f51ca
nsswitch operation context to reduce requirements on information
3f51ca
a caller has to maintain.
3f51ca
3f51ca
A choice which API to use is made at configure time.
3f51ca
3f51ca
In order to test the API, a cmocka test is updated to explicitly load
3f51ca
nss_files.so.2 as a backend. Since use of nss_sss.so.2 would always
3f51ca
depend on availablility of SSSD, predictable testing would not be
3f51ca
possible without it otherwise. Also, cmocka test does not use
3f51ca
nss_wrapper anymore because nss_wrapper overrides higher level glibc
3f51ca
nsswitch API while we are loading an individual nsswitch module
3f51ca
directly.
3f51ca
3f51ca
As result, cmocka test overrides fopen() call used by nss_files.so.2 to
3f51ca
load /etc/passwd and /etc/group. An overridden version changes paths to
3f51ca
/etc/passwd and /etc/group to a local test_data/passwd and
3f51ca
test_data/group. This way we can continue testing a backend API for
3f51ca
ipa-extdom-extop with the same data as with nss_wrapper.
3f51ca
3f51ca
Fixes https://pagure.io/freeipa/issue/5464
3f51ca
3f51ca
Reviewed-By: Christian Heimes <cheimes@redhat.com>
3f51ca
Reviewed-By: Simo Sorce <ssorce@redhat.com>
3f51ca
Reviewed-By: Robbie Harwood <rharwood@redhat.com>
3f51ca
---
3f51ca
 configure.ac                                       |  25 +-
3f51ca
 .../ipa-slapi-plugins/ipa-extdom-extop/Makefile.am |  17 +-
3f51ca
 .../ipa-extdom-extop/back_extdom.h                 |  79 ++++++
3f51ca
 .../ipa-extdom-extop/back_extdom_nss_sss.c         | 276 +++++++++++++++++++++
3f51ca
 .../ipa-extdom-extop/back_extdom_sss_idmap.c       | 260 +++++++++++++++++++
3f51ca
 .../ipa-extdom-extop/ipa_extdom.h                  |  13 +-
3f51ca
 .../ipa-extdom-extop/ipa_extdom_cmocka_tests.c     | 241 +++++++++++++++---
3f51ca
 .../ipa-extdom-extop/ipa_extdom_common.c           | 242 +++++++++---------
3f51ca
 .../ipa-extdom-extop/ipa_extdom_extop.c            |  17 ++
3f51ca
 .../ipa-extdom-extop/test_data/test_setup.sh       |   3 -
3f51ca
 freeipa.spec.in                                    |   1 -
3f51ca
 server.m4                                          |  10 +
3f51ca
 12 files changed, 994 insertions(+), 190 deletions(-)
3f51ca
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
3f51ca
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
3f51ca
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
3f51ca
 delete mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh
3f51ca
3f51ca
diff --git a/configure.ac b/configure.ac
3f51ca
index e7a8b11153209fdfb4903cd3876e21a871a92f03..8a99b028886790aca211ddf164772920221f3ec7 100644
3f51ca
--- a/configure.ac
3f51ca
+++ b/configure.ac
3f51ca
@@ -140,30 +140,6 @@ PKG_CHECK_EXISTS(cmocka,
3f51ca
 )
3f51ca
 AM_CONDITIONAL([HAVE_CMOCKA], [test x$have_cmocka = xyes])
3f51ca
 
3f51ca
-dnl A macro to check presence of a cwrap (http://cwrap.org) wrapper on the system
3f51ca
-dnl Usage:
3f51ca
-dnl     AM_CHECK_WRAPPER(name, conditional)
3f51ca
-dnl If the cwrap library is found, sets the HAVE_$name conditional
3f51ca
-AC_DEFUN([AM_CHECK_WRAPPER],
3f51ca
-[
3f51ca
-    FOUND_WRAPPER=0
3f51ca
-
3f51ca
-    AC_MSG_CHECKING([for $1])
3f51ca
-    PKG_CHECK_EXISTS([$1],
3f51ca
-                     [
3f51ca
-                        AC_MSG_RESULT([yes])
3f51ca
-                        FOUND_WRAPPER=1
3f51ca
-                     ],
3f51ca
-                     [
3f51ca
-                        AC_MSG_RESULT([no])
3f51ca
-                        AC_MSG_WARN([cwrap library $1 not found, some tests will not run])
3f51ca
-                     ])
3f51ca
-
3f51ca
-    AM_CONDITIONAL($2, [ test x$FOUND_WRAPPER = x1])
3f51ca
-])
3f51ca
-
3f51ca
-AM_CHECK_WRAPPER(nss_wrapper, HAVE_NSS_WRAPPER)
3f51ca
-
3f51ca
 dnl ---------------------------------------------------------------------------
3f51ca
 dnl - Check for POPT
3f51ca
 dnl ---------------------------------------------------------------------------
3f51ca
@@ -235,6 +211,7 @@ dnl ---------------------------------------------------------------------------
3f51ca
 AM_COND_IF([ENABLE_SERVER], [
3f51ca
     m4_include(server.m4)
3f51ca
 ])
3f51ca
+AM_CONDITIONAL([USE_SSS_NSS_TIMEOUT], [test "x$ac_cv_have_decl_sss_nss_getpwnam_timeout" = xyes])
3f51ca
 
3f51ca
 dnl ---------------------------------------------------------------------------
3f51ca
 dnl - Check if IPA certauth plugin can be build
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
3f51ca
index 1213965c96607bf14c6c92ce592585aed1a125db..cbdd570eabeb12b95fdc26213a64749f9ba9fdde 100644
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
3f51ca
@@ -25,6 +25,7 @@ libipa_extdom_extop_la_SOURCES = 	\
3f51ca
 	ipa_extdom.h			\
3f51ca
 	ipa_extdom_extop.c		\
3f51ca
 	ipa_extdom_common.c		\
3f51ca
+	back_extdom.h			\
3f51ca
 	$(NULL)
3f51ca
 
3f51ca
 libipa_extdom_extop_la_LDFLAGS = -avoid-version
3f51ca
@@ -34,20 +35,29 @@ libipa_extdom_extop_la_LIBADD = 	\
3f51ca
 	$(SSSNSSIDMAP_LIBS)		\
3f51ca
 	$(NULL)
3f51ca
 
3f51ca
+# We have two backends for nss operations:
3f51ca
+# (1) directly loading nss_sss.so.2
3f51ca
+# (2) using timeout-enabled API from libsss_nss_idmap
3f51ca
+# We prefer (2) if available
3f51ca
+if USE_SSS_NSS_TIMEOUT
3f51ca
+libipa_extdom_extop_la_SOURCES += back_extdom_sss_idmap.c
3f51ca
+else
3f51ca
+libipa_extdom_extop_la_SOURCES += back_extdom_nss_sss.c
3f51ca
+endif
3f51ca
+
3f51ca
+
3f51ca
 TESTS =
3f51ca
 check_PROGRAMS =
3f51ca
 
3f51ca
 if HAVE_CMOCKA
3f51ca
-if HAVE_NSS_WRAPPER
3f51ca
-TESTS_ENVIRONMENT = . ./test_data/test_setup.sh;
3f51ca
 TESTS += extdom_cmocka_tests
3f51ca
 check_PROGRAMS += extdom_cmocka_tests
3f51ca
 endif
3f51ca
-endif
3f51ca
 
3f51ca
 extdom_cmocka_tests_SOURCES = 		\
3f51ca
 	ipa_extdom_cmocka_tests.c	\
3f51ca
 	ipa_extdom_common.c		\
3f51ca
+	back_extdom_nss_sss.c		\
3f51ca
 	$(NULL)
3f51ca
 extdom_cmocka_tests_CFLAGS = $(CMOCKA_CFLAGS)
3f51ca
 extdom_cmocka_tests_LDFLAGS = 	\
3f51ca
@@ -58,6 +68,7 @@ extdom_cmocka_tests_LDADD = 	\
3f51ca
 	$(LDAP_LIBS)		\
3f51ca
 	$(DIRSRV_LIBS)		\
3f51ca
 	$(SSSNSSIDMAP_LIBS)	\
3f51ca
+	-ldl			\
3f51ca
 	$(NULL)
3f51ca
 
3f51ca
 
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
3f51ca
new file mode 100644
3f51ca
index 0000000000000000000000000000000000000000..d2937c8c8ecf8b960b5b31e9449c719bfda86de4
3f51ca
--- /dev/null
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
3f51ca
@@ -0,0 +1,79 @@
3f51ca
+/*
3f51ca
+ * Copyright 2017 Red Hat, Inc.
3f51ca
+ *
3f51ca
+ * This Program is free software; you can redistribute it and/or modify
3f51ca
+ * it under the terms of the GNU General Public License as published by
3f51ca
+ * the Free Software Foundation; version 2 of the License.
3f51ca
+ *
3f51ca
+ * This Program is distributed in the hope that it will be useful, but
3f51ca
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
3f51ca
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3f51ca
+ * General Public License for more details.
3f51ca
+ *
3f51ca
+ * You should have received a copy of the GNU General Public License
3f51ca
+ * along with this Program; if not, write to the
3f51ca
+ *
3f51ca
+ *   Free Software Foundation, Inc.
3f51ca
+ *   59 Temple Place, Suite 330
3f51ca
+ *   Boston, MA 02111-1307 USA
3f51ca
+ *
3f51ca
+ */
3f51ca
+
3f51ca
+#ifndef BACK_EXTDOM_H
3f51ca
+#define BACK_EXTDOM_H
3f51ca
+#include <unistd.h>
3f51ca
+#include <pwd.h>
3f51ca
+#include <grp.h>
3f51ca
+
3f51ca
+/* Possible results of lookup using a nss_* function.
3f51ca
+ * Note: don't include nss.h as its path gets overriden by NSS library */
3f51ca
+enum nss_status {
3f51ca
+    NSS_STATUS_TRYAGAIN = -2,
3f51ca
+    NSS_STATUS_UNAVAIL,
3f51ca
+    NSS_STATUS_NOTFOUND,
3f51ca
+    NSS_STATUS_SUCCESS,
3f51ca
+    NSS_STATUS_RETURN
3f51ca
+};
3f51ca
+
3f51ca
+/* NSS backend operations implemented using either nss_sss.so.2 or libsss_nss_idmap API */
3f51ca
+struct nss_ops_ctx;
3f51ca
+
3f51ca
+int back_extdom_init_context(struct nss_ops_ctx **nss_context);
3f51ca
+void back_extdom_free_context(struct nss_ops_ctx **nss_context);
3f51ca
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
3f51ca
+                             unsigned int timeout);
3f51ca
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
3f51ca
+                            const char *name);
3f51ca
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
3f51ca
+                             const char *name);
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno);
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     uid_t uid, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno);
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno);
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     gid_t gid, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno);
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
3f51ca
+                                         const char *name, gid_t group,
3f51ca
+                                         gid_t *groups, int *ngroups,
3f51ca
+                                         int *lerrno);
3f51ca
+
3f51ca
+#endif /* BACK_EXTDOM_H */
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
3f51ca
new file mode 100644
3f51ca
index 0000000000000000000000000000000000000000..346c7d4301a607c7bc07ca5a9c53fe84618ac8ad
3f51ca
--- /dev/null
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
3f51ca
@@ -0,0 +1,276 @@
3f51ca
+/*
3f51ca
+ * Copyright 2013-2017 Red Hat, Inc.
3f51ca
+ *
3f51ca
+ * This Program is free software; you can redistribute it and/or modify
3f51ca
+ * it under the terms of the GNU General Public License as published by
3f51ca
+ * the Free Software Foundation; version 2 of the License.
3f51ca
+ *
3f51ca
+ * This Program is distributed in the hope that it will be useful, but
3f51ca
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
3f51ca
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3f51ca
+ * General Public License for more details.
3f51ca
+ *
3f51ca
+ * You should have received a copy of the GNU General Public License
3f51ca
+ * along with this Program; if not, write to the
3f51ca
+ *
3f51ca
+ *   Free Software Foundation, Inc.
3f51ca
+ *   59 Temple Place, Suite 330
3f51ca
+ *   Boston, MA 02111-1307 USA
3f51ca
+ *
3f51ca
+ */
3f51ca
+
3f51ca
+#ifdef HAVE_CONFIG_H
3f51ca
+#include "config.h"
3f51ca
+#endif
3f51ca
+
3f51ca
+#include <sys/types.h>
3f51ca
+#include <stdlib.h>
3f51ca
+#include <string.h>
3f51ca
+#include <time.h>
3f51ca
+#include <unistd.h>
3f51ca
+#include <dlfcn.h>
3f51ca
+#include <errno.h>
3f51ca
+#include <pwd.h>
3f51ca
+#include <grp.h>
3f51ca
+#include <sys/param.h>
3f51ca
+#include "back_extdom.h"
3f51ca
+
3f51ca
+struct nss_ops_ctx {
3f51ca
+    void *dl_handle;
3f51ca
+    long int initgroups_start;
3f51ca
+
3f51ca
+    enum nss_status (*getpwnam_r)(const char *name, struct passwd *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getgrnam_r)(const char *name, struct group *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getgrgid_r)(gid_t gid, struct group *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*initgroups_dyn)(const char *user, gid_t group,
3f51ca
+                                      long int *start, long int *size,
3f51ca
+                                      gid_t **groups, long int limit,
3f51ca
+                                      int *errnop);
3f51ca
+};
3f51ca
+
3f51ca
+void back_extdom_free_context(struct nss_ops_ctx **nss_context)
3f51ca
+{
3f51ca
+    if ((nss_context == NULL) || (*nss_context == NULL)) {
3f51ca
+        return;
3f51ca
+    }
3f51ca
+
3f51ca
+    if ((*nss_context)->dl_handle != NULL) {
3f51ca
+        dlclose((*nss_context)->dl_handle);
3f51ca
+    }
3f51ca
+
3f51ca
+    free((*nss_context));
3f51ca
+    *nss_context = NULL;
3f51ca
+}
3f51ca
+
3f51ca
+int back_extdom_init_context(struct nss_ops_ctx **nss_context)
3f51ca
+{
3f51ca
+    struct nss_ops_ctx *ctx = NULL;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return EINVAL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
3f51ca
+    if (ctx == NULL) {
3f51ca
+        return ENOMEM;
3f51ca
+    }
3f51ca
+    *nss_context = ctx;
3f51ca
+
3f51ca
+    ctx->dl_handle = dlopen("libnss_sss.so.2", RTLD_NOW);
3f51ca
+    if (ctx->dl_handle == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getpwnam_r = dlsym(ctx->dl_handle, "_nss_sss_getpwnam_r");
3f51ca
+    if (ctx->getpwnam_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getpwuid_r = dlsym(ctx->dl_handle, "_nss_sss_getpwuid_r");
3f51ca
+    if (ctx->getpwuid_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getgrnam_r = dlsym(ctx->dl_handle, "_nss_sss_getgrnam_r");
3f51ca
+    if (ctx->getgrnam_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getgrgid_r = dlsym(ctx->dl_handle, "_nss_sss_getgrgid_r");
3f51ca
+    if (ctx->getgrgid_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->initgroups_dyn = dlsym(ctx->dl_handle, "_nss_sss_initgroups_dyn");
3f51ca
+    if (ctx->initgroups_dyn == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    return 0;
3f51ca
+
3f51ca
+fail:
3f51ca
+    back_extdom_free_context(nss_context);
3f51ca
+
3f51ca
+    return EINVAL;
3f51ca
+}
3f51ca
+
3f51ca
+
3f51ca
+/* Following three functions cannot be implemented with nss_sss.so.2
3f51ca
+ * As result, we simply do nothing here */
3f51ca
+
3f51ca
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
3f51ca
+                             unsigned int timeout) {
3f51ca
+        /* no operation */
3f51ca
+}
3f51ca
+
3f51ca
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
3f51ca
+                            const char *name) {
3f51ca
+        /* no operation */
3f51ca
+}
3f51ca
+
3f51ca
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
3f51ca
+                             const char *name) {
3f51ca
+        /* no operation */
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+    enum nss_status ret;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = nss_context->getpwnam_r(name, pwd,
3f51ca
+                                  buffer, buflen,
3f51ca
+                                  lerrno);
3f51ca
+
3f51ca
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
3f51ca
+        *result = pwd;
3f51ca
+        *lerrno = 0;
3f51ca
+    }
3f51ca
+
3f51ca
+    return ret;
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     uid_t uid, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+    enum nss_status ret;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = nss_context->getpwuid_r(uid, pwd,
3f51ca
+                                  buffer, buflen,
3f51ca
+                                  lerrno);
3f51ca
+
3f51ca
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
3f51ca
+        *result = pwd;
3f51ca
+        *lerrno = 0;
3f51ca
+    }
3f51ca
+
3f51ca
+    return ret;
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+    enum nss_status ret;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = nss_context->getgrnam_r(name, grp,
3f51ca
+                                  buffer, buflen,
3f51ca
+                                  lerrno);
3f51ca
+
3f51ca
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
3f51ca
+        *result = grp;
3f51ca
+        *lerrno = 0;
3f51ca
+    }
3f51ca
+
3f51ca
+    return ret;
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     gid_t gid, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+
3f51ca
+    enum nss_status ret;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = nss_context->getgrgid_r(gid, grp,
3f51ca
+                                  buffer, buflen,
3f51ca
+                                  lerrno);
3f51ca
+
3f51ca
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
3f51ca
+        *result = grp;
3f51ca
+        *lerrno = 0;
3f51ca
+    }
3f51ca
+
3f51ca
+    return ret;
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
3f51ca
+                                         const char *name, gid_t group,
3f51ca
+                                         gid_t *groups, int *ngroups,
3f51ca
+                                         int *lerrno) {
3f51ca
+
3f51ca
+    enum nss_status ret = NSS_STATUS_UNAVAIL;
3f51ca
+    long int tsize = MAX (1, *ngroups);
3f51ca
+    gid_t *newgroups = NULL;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    newgroups = (gid_t *) calloc (tsize, sizeof (gid_t));
3f51ca
+    if (newgroups == NULL) {
3f51ca
+        *lerrno = ENOMEM;
3f51ca
+        return NSS_STATUS_TRYAGAIN;
3f51ca
+    }
3f51ca
+
3f51ca
+    newgroups[0] = group;
3f51ca
+    nss_context->initgroups_start = 1;
3f51ca
+
3f51ca
+    ret = nss_context->initgroups_dyn(name, group,
3f51ca
+                                      &nss_context->initgroups_start,
3f51ca
+                                      &tsize, &newgroups,
3f51ca
+                                      -1, lerrno);
3f51ca
+
3f51ca
+    (void) memcpy(groups, newgroups,
3f51ca
+                  MIN(*ngroups, nss_context->initgroups_start) * sizeof(gid_t));
3f51ca
+    free(newgroups);
3f51ca
+
3f51ca
+    if (*ngroups < nss_context->initgroups_start) {
3f51ca
+        ret = NSS_STATUS_TRYAGAIN;
3f51ca
+        *lerrno = ERANGE;
3f51ca
+    }
3f51ca
+
3f51ca
+    *ngroups = (int) nss_context->initgroups_start;
3f51ca
+
3f51ca
+    nss_context->initgroups_start = 0;
3f51ca
+
3f51ca
+    return ret;
3f51ca
+}
3f51ca
+
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
3f51ca
new file mode 100644
3f51ca
index 0000000000000000000000000000000000000000..89c58ca2de333b26954d916836b57aed5d7e18fb
3f51ca
--- /dev/null
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
3f51ca
@@ -0,0 +1,260 @@
3f51ca
+/*
3f51ca
+ * Copyright 2013-2017 Red Hat, Inc.
3f51ca
+ *
3f51ca
+ * This Program is free software; you can redistribute it and/or modify
3f51ca
+ * it under the terms of the GNU General Public License as published by
3f51ca
+ * the Free Software Foundation; version 2 of the License.
3f51ca
+ *
3f51ca
+ * This Program is distributed in the hope that it will be useful, but
3f51ca
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
3f51ca
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3f51ca
+ * General Public License for more details.
3f51ca
+ *
3f51ca
+ * You should have received a copy of the GNU General Public License
3f51ca
+ * along with this Program; if not, write to the
3f51ca
+ *
3f51ca
+ *   Free Software Foundation, Inc.
3f51ca
+ *   59 Temple Place, Suite 330
3f51ca
+ *   Boston, MA 02111-1307 USA
3f51ca
+ *
3f51ca
+ */
3f51ca
+
3f51ca
+#ifdef HAVE_CONFIG_H
3f51ca
+#include "config.h"
3f51ca
+#endif
3f51ca
+
3f51ca
+#include <sys/types.h>
3f51ca
+#include <stdlib.h>
3f51ca
+#include <string.h>
3f51ca
+#include <time.h>
3f51ca
+#include <unistd.h>
3f51ca
+#include <errno.h>
3f51ca
+#include <pwd.h>
3f51ca
+#include <grp.h>
3f51ca
+#include "back_extdom.h"
3f51ca
+
3f51ca
+/* SSSD only exposes *_timeout() variants if the following symbol is defined */
3f51ca
+#define IPA_389DS_PLUGIN_HELPER_CALLS
3f51ca
+#include <sss_nss_idmap.h>
3f51ca
+
3f51ca
+struct nss_ops_ctx {
3f51ca
+    unsigned int timeout;
3f51ca
+};
3f51ca
+
3f51ca
+static enum nss_status __convert_sss_nss2nss_status(int errcode) {
3f51ca
+    switch(errcode) {
3f51ca
+    case 0:
3f51ca
+        return NSS_STATUS_SUCCESS;
3f51ca
+    case ENOENT:
3f51ca
+        return NSS_STATUS_NOTFOUND;
3f51ca
+    case ETIME:
3f51ca
+        /* fall-through */
3f51ca
+    case ERANGE:
3f51ca
+        return NSS_STATUS_TRYAGAIN;
3f51ca
+    case ETIMEDOUT:
3f51ca
+        /* fall-through */
3f51ca
+    default:
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+    return NSS_STATUS_UNAVAIL;
3f51ca
+}
3f51ca
+
3f51ca
+int back_extdom_init_context(struct nss_ops_ctx **nss_context)
3f51ca
+{
3f51ca
+    struct nss_ops_ctx *ctx = NULL;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return EINVAL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
3f51ca
+
3f51ca
+    if (ctx == NULL) {
3f51ca
+        return ENOMEM;
3f51ca
+    }
3f51ca
+    *nss_context = ctx;
3f51ca
+    return 0;
3f51ca
+}
3f51ca
+
3f51ca
+void back_extdom_free_context(struct nss_ops_ctx **nss_context)
3f51ca
+{
3f51ca
+    if ((nss_context == NULL) || (*nss_context == NULL)) {
3f51ca
+        return;
3f51ca
+    }
3f51ca
+
3f51ca
+    free((*nss_context));
3f51ca
+    *nss_context = NULL;
3f51ca
+}
3f51ca
+
3f51ca
+
3f51ca
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
3f51ca
+                             unsigned int timeout) {
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return;
3f51ca
+    }
3f51ca
+
3f51ca
+    nss_context->timeout = timeout;
3f51ca
+}
3f51ca
+
3f51ca
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
3f51ca
+                            const char *name) {
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return;
3f51ca
+    }
3f51ca
+
3f51ca
+    (void) sss_nss_getpwnam_timeout(name, NULL,
3f51ca
+                                    NULL, 0,
3f51ca
+                                    NULL,
3f51ca
+                                    SSS_NSS_EX_FLAG_INVALIDATE_CACHE,
3f51ca
+                                    nss_context->timeout);
3f51ca
+}
3f51ca
+
3f51ca
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
3f51ca
+                             const char *name) {
3f51ca
+    if (nss_context == NULL) {
3f51ca
+            return;
3f51ca
+    }
3f51ca
+
3f51ca
+    (void) sss_nss_getgrnam_timeout(name, NULL,
3f51ca
+                                    NULL, 0,
3f51ca
+                                    NULL,
3f51ca
+                                    SSS_NSS_EX_FLAG_INVALIDATE_CACHE,
3f51ca
+                                    nss_context->timeout);
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+    int ret = 0;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = sss_nss_getpwnam_timeout(name, pwd,
3f51ca
+                                   buffer, buflen,
3f51ca
+                                   result,
3f51ca
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
3f51ca
+                                   nss_context->timeout);
3f51ca
+
3f51ca
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
3f51ca
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
3f51ca
+     * but writes down errno into return code so we propagate it in case
3f51ca
+     * of error and translate the return code */
3f51ca
+    if (lerrno != NULL) {
3f51ca
+        *lerrno = ret;
3f51ca
+    }
3f51ca
+    return __convert_sss_nss2nss_status(ret);
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     uid_t uid, struct passwd *pwd,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct passwd **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+
3f51ca
+    int ret = 0;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = sss_nss_getpwuid_timeout(uid, pwd,
3f51ca
+                                   buffer, buflen,
3f51ca
+                                   result,
3f51ca
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
3f51ca
+                                   nss_context->timeout);
3f51ca
+
3f51ca
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
3f51ca
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
3f51ca
+     * but writes down errno into return code so we propagate it in case
3f51ca
+     * of error and translate the return code */
3f51ca
+    if (lerrno != NULL) {
3f51ca
+        *lerrno = ret;
3f51ca
+    }
3f51ca
+    return __convert_sss_nss2nss_status(ret);
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
3f51ca
+                                     const char *name, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+
3f51ca
+    int ret = 0;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = sss_nss_getgrnam_timeout(name, grp,
3f51ca
+                                   buffer, buflen,
3f51ca
+                                   result,
3f51ca
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
3f51ca
+                                   nss_context->timeout);
3f51ca
+
3f51ca
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
3f51ca
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
3f51ca
+     * but writes down errno into return code so we propagate it in case
3f51ca
+     * of error and translate the return code */
3f51ca
+    if (lerrno != NULL) {
3f51ca
+        *lerrno = ret;
3f51ca
+    }
3f51ca
+    return __convert_sss_nss2nss_status(ret);
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
3f51ca
+                                     gid_t gid, struct group *grp,
3f51ca
+                                     char *buffer, size_t buflen,
3f51ca
+                                     struct group **result,
3f51ca
+                                     int *lerrno) {
3f51ca
+
3f51ca
+    int ret = 0;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = sss_nss_getgrgid_timeout(gid, grp,
3f51ca
+                                   buffer, buflen,
3f51ca
+                                   result,
3f51ca
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
3f51ca
+                                   nss_context->timeout);
3f51ca
+
3f51ca
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
3f51ca
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
3f51ca
+     * but writes down errno into return code so we propagate it in case
3f51ca
+     * of error and translate the return code */
3f51ca
+    if (lerrno != NULL) {
3f51ca
+        *lerrno = ret;
3f51ca
+    }
3f51ca
+    return __convert_sss_nss2nss_status(ret);
3f51ca
+}
3f51ca
+
3f51ca
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
3f51ca
+                                         const char *name, gid_t group,
3f51ca
+                                         gid_t *groups, int *ngroups,
3f51ca
+                                         int *lerrno) {
3f51ca
+    int ret = 0;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return NSS_STATUS_UNAVAIL;
3f51ca
+    }
3f51ca
+
3f51ca
+    ret = sss_nss_getgrouplist_timeout(name, group,
3f51ca
+                                       groups, ngroups,
3f51ca
+                                       SSS_NSS_EX_FLAG_NO_FLAGS,
3f51ca
+                                       nss_context->timeout);
3f51ca
+
3f51ca
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
3f51ca
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
3f51ca
+     * but writes down errno into return code so we propagate it in case
3f51ca
+     * of error and translate the return code */
3f51ca
+    if (lerrno != NULL) {
3f51ca
+        *lerrno = ret;
3f51ca
+    }
3f51ca
+    return __convert_sss_nss2nss_status(ret);
3f51ca
+}
3f51ca
+
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
3f51ca
index bc29f069816b0ce13578c9ae14c61edb832d44e4..bbc574747e8bbe045dfc9882198cb34b0bb8cab9 100644
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
3f51ca
@@ -150,10 +150,13 @@ struct extdom_res {
3f51ca
     } data;
3f51ca
 };
3f51ca
 
3f51ca
+struct nss_ops_ctx;
3f51ca
+
3f51ca
 struct ipa_extdom_ctx {
3f51ca
     Slapi_ComponentId *plugin_id;
3f51ca
     char *base_dn;
3f51ca
     size_t max_nss_buf_size;
3f51ca
+    struct nss_ops_ctx *nss_ctx;
3f51ca
 };
3f51ca
 
3f51ca
 struct domain_info {
3f51ca
@@ -179,15 +182,15 @@ int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req,
3f51ca
                    struct berval **berval);
3f51ca
 int pack_response(struct extdom_res *res, struct berval **ret_val);
3f51ca
 int get_buffer(size_t *_buf_len, char **_buf);
3f51ca
-int getpwnam_r_wrapper(size_t buf_max, const char *name,
3f51ca
+int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
3f51ca
                        struct passwd *pwd, char **_buf, size_t *_buf_len);
3f51ca
-int getpwuid_r_wrapper(size_t buf_max, uid_t uid,
3f51ca
+int getpwuid_r_wrapper(struct ipa_extdom_ctx *ctx, uid_t uid,
3f51ca
                        struct passwd *pwd, char **_buf, size_t *_buf_len);
3f51ca
-int getgrnam_r_wrapper(size_t buf_max, const char *name,
3f51ca
+int getgrnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
3f51ca
                        struct group *grp, char **_buf, size_t *_buf_len);
3f51ca
-int getgrgid_r_wrapper(size_t buf_max, gid_t gid,
3f51ca
+int getgrgid_r_wrapper(struct ipa_extdom_ctx *ctx, gid_t gid,
3f51ca
                        struct group *grp, char **_buf, size_t *_buf_len);
3f51ca
-int get_user_grouplist(const char *name, gid_t gid,
3f51ca
+int get_user_grouplist(struct ipa_extdom_ctx *ctx, const char *name, gid_t gid,
3f51ca
                        size_t *_ngroups, gid_t **_groups);
3f51ca
 int pack_ber_sid(const char *sid, struct berval **berval);
3f51ca
 int pack_ber_name(const char *domain_name, const char *name,
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c
3f51ca
index 526f903d2416e62ee5781909c234bd5ee2d89183..29699cfa390f5469d7c009388b90e68616cbf984 100644
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_cmocka_tests.c
3f51ca
@@ -19,6 +19,7 @@
3f51ca
     You should have received a copy of the GNU General Public License
3f51ca
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
3f51ca
 */
3f51ca
+#define _GNU_SOURCE
3f51ca
 
3f51ca
 #include <errno.h>
3f51ca
 #include <stdarg.h>
3f51ca
@@ -31,24 +32,166 @@
3f51ca
 
3f51ca
 
3f51ca
 #include "ipa_extdom.h"
3f51ca
+#include "back_extdom.h"
3f51ca
+#include <stdio.h>
3f51ca
+#include <dlfcn.h>
3f51ca
 
3f51ca
 #define MAX_BUF (1024*1024*1024)
3f51ca
+struct test_data {
3f51ca
+    struct extdom_req *req;
3f51ca
+    struct ipa_extdom_ctx *ctx;
3f51ca
+};
3f51ca
+
3f51ca
+/*
3f51ca
+ * redefine logging for mocks
3f51ca
+ */
3f51ca
+#ifdef __GNUC__
3f51ca
+    __attribute__((format(printf, 3, 4)))
3f51ca
+#endif
3f51ca
+int slapi_log_error(int loglevel, char *subsystem, char *fmt, ...)
3f51ca
+{
3f51ca
+    va_list ap;
3f51ca
+    va_start(ap, fmt);
3f51ca
+    vprint_error(fmt, ap);
3f51ca
+    va_end(ap);
3f51ca
+    return 0;
3f51ca
+}
3f51ca
+
3f51ca
+
3f51ca
+/*
3f51ca
+ * We cannot run cmocka tests against SSSD as that would require to set up SSSD
3f51ca
+ * and the rest of environment. Instead, we compile cmocka tests against
3f51ca
+ * back_extdom_nss_sss.c and re-define context initialization to use
3f51ca
+ * nsswrapper with our test data.
3f51ca
+ *
3f51ca
+ * This means we have to keep struct nss_ops_ctx definition in sync with tests!
3f51ca
+ */
3f51ca
+
3f51ca
+struct nss_ops_ctx {
3f51ca
+    void *dl_handle;
3f51ca
+    long int initgroups_start;
3f51ca
+
3f51ca
+    enum nss_status (*getpwnam_r)(const char *name, struct passwd *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getgrnam_r)(const char *name, struct group *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*getgrgid_r)(gid_t gid, struct group *result,
3f51ca
+                                  char *buffer, size_t buflen, int *errnop);
3f51ca
+    enum nss_status (*initgroups_dyn)(const char *user, gid_t group,
3f51ca
+                                      long int *start, long int *size,
3f51ca
+                                      gid_t **groups, long int limit,
3f51ca
+                                      int *errnop);
3f51ca
+};
3f51ca
+
3f51ca
+int cmocka_extdom_init_context(struct nss_ops_ctx **nss_context)
3f51ca
+{
3f51ca
+    struct nss_ops_ctx *ctx = NULL;
3f51ca
+
3f51ca
+    if (nss_context == NULL) {
3f51ca
+        return -1;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
3f51ca
+
3f51ca
+    if (ctx == NULL) {
3f51ca
+        return ENOMEM;
3f51ca
+    }
3f51ca
+    *nss_context = ctx;
3f51ca
+
3f51ca
+    ctx->dl_handle = dlopen("libnss_files.so.2", RTLD_NOW);
3f51ca
+    if (ctx->dl_handle == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getpwnam_r = dlsym(ctx->dl_handle, "_nss_files_getpwnam_r");
3f51ca
+    if (ctx->getpwnam_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getpwuid_r = dlsym(ctx->dl_handle, "_nss_files_getpwuid_r");
3f51ca
+    if (ctx->getpwuid_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getgrnam_r = dlsym(ctx->dl_handle, "_nss_files_getgrnam_r");
3f51ca
+    if (ctx->getgrnam_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->getgrgid_r = dlsym(ctx->dl_handle, "_nss_files_getgrgid_r");
3f51ca
+    if (ctx->getgrgid_r == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    ctx->initgroups_dyn = dlsym(ctx->dl_handle, "_nss_files_initgroups_dyn");
3f51ca
+    if (ctx->initgroups_dyn == NULL) {
3f51ca
+        goto fail;
3f51ca
+    }
3f51ca
+
3f51ca
+    return 0;
3f51ca
+
3f51ca
+fail:
3f51ca
+    back_extdom_free_context(nss_context);
3f51ca
+
3f51ca
+    return -1;
3f51ca
+}
3f51ca
+
3f51ca
+struct {
3f51ca
+    const char *o, *n;
3f51ca
+} path_table[] = {
3f51ca
+    { .o = "/etc/passwd", .n = "./test_data/passwd"},
3f51ca
+    { .o = "/etc/group",  .n = "./test_data/group"},
3f51ca
+    { .o = NULL, .n = NULL}};
3f51ca
+
3f51ca
+FILE *(*original_fopen)(const char*, const char*) = NULL;
3f51ca
+
3f51ca
+FILE *fopen(const char *path, const char *mode) {
3f51ca
+    const char *_path = NULL;
3f51ca
+
3f51ca
+    /* Do not handle before-main() cases */
3f51ca
+    if (original_fopen == NULL) {
3f51ca
+        return NULL;
3f51ca
+    }
3f51ca
+    for(int i=0; path_table[i].o != NULL; i++) {
3f51ca
+        if (strcmp(path, path_table[i].o) == 0) {
3f51ca
+                _path = path_table[i].n;
3f51ca
+                break;
3f51ca
+        }
3f51ca
+    }
3f51ca
+    return (*original_fopen)(_path ? _path : path, mode);
3f51ca
+}
3f51ca
+
3f51ca
+/* Attempt to initialize original_fopen before main()
3f51ca
+ * There is no explicit order when all initializers are called,
3f51ca
+ * so we might still be late here compared to a code in a shared
3f51ca
+ * library initializer, like libselinux */
3f51ca
+void redefined_fopen_ctor (void) __attribute__ ((constructor));
3f51ca
+void redefined_fopen_ctor(void) {
3f51ca
+    original_fopen = dlsym(RTLD_NEXT, "fopen");
3f51ca
+}
3f51ca
 
3f51ca
 void test_getpwnam_r_wrapper(void **state)
3f51ca
 {
3f51ca
     int ret;
3f51ca
     struct passwd pwd;
3f51ca
     char *buf;
3f51ca
-    size_t buf_len;
3f51ca
+    size_t buf_len, max_big_buf_len;
3f51ca
+    struct test_data *test_data;
3f51ca
+
3f51ca
+    test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwnam_r_wrapper(MAX_BUF, "non_exisiting_user", &pwd, &buf,
3f51ca
-                             &buf_len);
3f51ca
+    ret = getpwnam_r_wrapper(test_data->ctx,
3f51ca
+                             "non_exisiting_user", &pwd,
3f51ca
+                             &buf, &buf_len);
3f51ca
     assert_int_equal(ret, ENOENT);
3f51ca
 
3f51ca
-    ret = getpwnam_r_wrapper(MAX_BUF, "user", &pwd, &buf, &buf_len);
3f51ca
+    ret = getpwnam_r_wrapper(test_data->ctx,
3f51ca
+                             "user", &pwd, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(pwd.pw_name, "user");
3f51ca
     assert_string_equal(pwd.pw_passwd, "x");
3f51ca
@@ -62,7 +205,8 @@ void test_getpwnam_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwnam_r_wrapper(MAX_BUF, "user_big", &pwd, &buf, &buf_len);
3f51ca
+    ret = getpwnam_r_wrapper(test_data->ctx,
3f51ca
+                             "user_big", &pwd, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(pwd.pw_name, "user_big");
3f51ca
     assert_string_equal(pwd.pw_passwd, "x");
3f51ca
@@ -76,7 +220,11 @@ void test_getpwnam_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwnam_r_wrapper(1024, "user_big", &pwd, &buf, &buf_len);
3f51ca
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
3f51ca
+    test_data->ctx->max_nss_buf_size = 1024;
3f51ca
+    ret = getpwnam_r_wrapper(test_data->ctx,
3f51ca
+                             "user_big", &pwd, &buf, &buf_len);
3f51ca
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
3f51ca
     assert_int_equal(ret, ERANGE);
3f51ca
     free(buf);
3f51ca
 }
3f51ca
@@ -86,15 +234,18 @@ void test_getpwuid_r_wrapper(void **state)
3f51ca
     int ret;
3f51ca
     struct passwd pwd;
3f51ca
     char *buf;
3f51ca
-    size_t buf_len;
3f51ca
+    size_t buf_len, max_big_buf_len;
3f51ca
+    struct test_data *test_data;
3f51ca
+
3f51ca
+    test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwuid_r_wrapper(MAX_BUF, 99999, &pwd, &buf, &buf_len);
3f51ca
+    ret = getpwuid_r_wrapper(test_data->ctx, 99999, &pwd, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, ENOENT);
3f51ca
 
3f51ca
-    ret = getpwuid_r_wrapper(MAX_BUF, 12345, &pwd, &buf, &buf_len);
3f51ca
+    ret = getpwuid_r_wrapper(test_data->ctx, 12345, &pwd, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(pwd.pw_name, "user");
3f51ca
     assert_string_equal(pwd.pw_passwd, "x");
3f51ca
@@ -108,7 +259,7 @@ void test_getpwuid_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwuid_r_wrapper(MAX_BUF, 12346, &pwd, &buf, &buf_len);
3f51ca
+    ret = getpwuid_r_wrapper(test_data->ctx, 12346, &pwd, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(pwd.pw_name, "user_big");
3f51ca
     assert_string_equal(pwd.pw_passwd, "x");
3f51ca
@@ -122,7 +273,10 @@ void test_getpwuid_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getpwuid_r_wrapper(1024, 12346, &pwd, &buf, &buf_len);
3f51ca
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
3f51ca
+    test_data->ctx->max_nss_buf_size = 1024;
3f51ca
+    ret = getpwuid_r_wrapper(test_data->ctx, 12346, &pwd, &buf, &buf_len);
3f51ca
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
3f51ca
     assert_int_equal(ret, ERANGE);
3f51ca
     free(buf);
3f51ca
 }
3f51ca
@@ -132,15 +286,19 @@ void test_getgrnam_r_wrapper(void **state)
3f51ca
     int ret;
3f51ca
     struct group grp;
3f51ca
     char *buf;
3f51ca
-    size_t buf_len;
3f51ca
+    size_t buf_len, max_big_buf_len;
3f51ca
+    struct test_data *test_data;
3f51ca
+
3f51ca
+    test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrnam_r_wrapper(MAX_BUF, "non_exisiting_group", &grp, &buf, &buf_len);
3f51ca
+    ret = getgrnam_r_wrapper(test_data->ctx,
3f51ca
+                             "non_exisiting_group", &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, ENOENT);
3f51ca
 
3f51ca
-    ret = getgrnam_r_wrapper(MAX_BUF, "group", &grp, &buf, &buf_len);
3f51ca
+    ret = getgrnam_r_wrapper(test_data->ctx, "group", &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(grp.gr_name, "group");
3f51ca
     assert_string_equal(grp.gr_passwd, "x");
3f51ca
@@ -153,7 +311,7 @@ void test_getgrnam_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrnam_r_wrapper(MAX_BUF, "group_big", &grp, &buf, &buf_len);
3f51ca
+    ret = getgrnam_r_wrapper(test_data->ctx, "group_big", &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(grp.gr_name, "group_big");
3f51ca
     assert_string_equal(grp.gr_passwd, "x");
3f51ca
@@ -165,7 +323,10 @@ void test_getgrnam_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrnam_r_wrapper(1024, "group_big", &grp, &buf, &buf_len);
3f51ca
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
3f51ca
+    test_data->ctx->max_nss_buf_size = 1024;
3f51ca
+    ret = getgrnam_r_wrapper(test_data->ctx, "group_big", &grp, &buf, &buf_len);
3f51ca
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
3f51ca
     assert_int_equal(ret, ERANGE);
3f51ca
     free(buf);
3f51ca
 }
3f51ca
@@ -175,15 +336,18 @@ void test_getgrgid_r_wrapper(void **state)
3f51ca
     int ret;
3f51ca
     struct group grp;
3f51ca
     char *buf;
3f51ca
-    size_t buf_len;
3f51ca
+    size_t buf_len, max_big_buf_len;
3f51ca
+    struct test_data *test_data;
3f51ca
+
3f51ca
+    test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrgid_r_wrapper(MAX_BUF, 99999, &grp, &buf, &buf_len);
3f51ca
+    ret = getgrgid_r_wrapper(test_data->ctx, 99999, &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, ENOENT);
3f51ca
 
3f51ca
-    ret = getgrgid_r_wrapper(MAX_BUF, 11111, &grp, &buf, &buf_len);
3f51ca
+    ret = getgrgid_r_wrapper(test_data->ctx, 11111, &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(grp.gr_name, "group");
3f51ca
     assert_string_equal(grp.gr_passwd, "x");
3f51ca
@@ -196,7 +360,7 @@ void test_getgrgid_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrgid_r_wrapper(MAX_BUF, 22222, &grp, &buf, &buf_len);
3f51ca
+    ret = getgrgid_r_wrapper(test_data->ctx, 22222, &grp, &buf, &buf_len);
3f51ca
     assert_int_equal(ret, 0);
3f51ca
     assert_string_equal(grp.gr_name, "group_big");
3f51ca
     assert_string_equal(grp.gr_passwd, "x");
3f51ca
@@ -208,7 +372,10 @@ void test_getgrgid_r_wrapper(void **state)
3f51ca
     ret = get_buffer(&buf_len, &buf;;
3f51ca
     assert_int_equal(ret, 0);
3f51ca
 
3f51ca
-    ret = getgrgid_r_wrapper(1024, 22222, &grp, &buf, &buf_len);
3f51ca
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
3f51ca
+    test_data->ctx->max_nss_buf_size = 1024;
3f51ca
+    ret = getgrgid_r_wrapper(test_data->ctx, 22222, &grp, &buf, &buf_len);
3f51ca
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
3f51ca
     assert_int_equal(ret, ERANGE);
3f51ca
     free(buf);
3f51ca
 }
3f51ca
@@ -219,16 +386,21 @@ void test_get_user_grouplist(void **state)
3f51ca
     size_t ngroups;
3f51ca
     gid_t *groups;
3f51ca
     size_t c;
3f51ca
+    struct test_data *test_data;
3f51ca
+
3f51ca
+    test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     /* This is a bit odd behaviour of getgrouplist() it does not check if the
3f51ca
      * user exists, only if memberships of the user can be found. */
3f51ca
-    ret = get_user_grouplist("non_exisiting_user", 23456, &ngroups, &groups);
3f51ca
+    ret = get_user_grouplist(test_data->ctx,
3f51ca
+                             "non_exisiting_user", 23456, &ngroups, &groups);
3f51ca
     assert_int_equal(ret, LDAP_SUCCESS);
3f51ca
     assert_int_equal(ngroups, 1);
3f51ca
     assert_int_equal(groups[0], 23456);
3f51ca
     free(groups);
3f51ca
 
3f51ca
-    ret = get_user_grouplist("member0001", 23456, &ngroups, &groups);
3f51ca
+    ret = get_user_grouplist(test_data->ctx,
3f51ca
+                             "member0001", 23456, &ngroups, &groups);
3f51ca
     assert_int_equal(ret, LDAP_SUCCESS);
3f51ca
     assert_int_equal(ngroups, 3);
3f51ca
     assert_int_equal(groups[0], 23456);
3f51ca
@@ -236,14 +408,16 @@ void test_get_user_grouplist(void **state)
3f51ca
     assert_int_equal(groups[2], 22222);
3f51ca
     free(groups);
3f51ca
 
3f51ca
-    ret = get_user_grouplist("member0003", 23456, &ngroups, &groups);
3f51ca
+    ret = get_user_grouplist(test_data->ctx,
3f51ca
+                             "member0003", 23456, &ngroups, &groups);
3f51ca
     assert_int_equal(ret, LDAP_SUCCESS);
3f51ca
     assert_int_equal(ngroups, 2);
3f51ca
     assert_int_equal(groups[0], 23456);
3f51ca
     assert_int_equal(groups[1], 22222);
3f51ca
     free(groups);
3f51ca
 
3f51ca
-    ret = get_user_grouplist("user_big", 23456, &ngroups, &groups);
3f51ca
+    ret = get_user_grouplist(test_data->ctx,
3f51ca
+                             "user_big", 23456, &ngroups, &groups);
3f51ca
     assert_int_equal(ret, LDAP_SUCCESS);
3f51ca
     assert_int_equal(ngroups, 1001);
3f51ca
     assert_int_equal(groups[0], 23456);
3f51ca
@@ -253,11 +427,6 @@ void test_get_user_grouplist(void **state)
3f51ca
     free(groups);
3f51ca
 }
3f51ca
 
3f51ca
-struct test_data {
3f51ca
-    struct extdom_req *req;
3f51ca
-    struct ipa_extdom_ctx *ctx;
3f51ca
-};
3f51ca
-
3f51ca
 static int  extdom_req_setup(void **state)
3f51ca
 {
3f51ca
     struct test_data *test_data;
3f51ca
@@ -269,8 +438,14 @@ static int  extdom_req_setup(void **state)
3f51ca
     assert_non_null(test_data->req);
3f51ca
 
3f51ca
     test_data->ctx = calloc(sizeof(struct ipa_extdom_ctx), 1);
3f51ca
-    assert_non_null(test_data->req);
3f51ca
+    assert_non_null(test_data->ctx);
3f51ca
+
3f51ca
+    test_data->ctx->max_nss_buf_size = MAX_BUF;
3f51ca
+
3f51ca
+    assert_int_equal(cmocka_extdom_init_context(&test_data->ctx->nss_ctx), 0);
3f51ca
+    assert_non_null(test_data->ctx->nss_ctx);
3f51ca
 
3f51ca
+    back_extdom_set_timeout(test_data->ctx->nss_ctx, 10000);
3f51ca
     *state = test_data;
3f51ca
 
3f51ca
     return 0;
3f51ca
@@ -283,6 +458,7 @@ static int  extdom_req_teardown(void **state)
3f51ca
     test_data = (struct test_data *) *state;
3f51ca
 
3f51ca
     free_req_data(test_data->req);
3f51ca
+    back_extdom_free_context(&test_data->ctx->nss_ctx);
3f51ca
     free(test_data->ctx);
3f51ca
     free(test_data);
3f51ca
 
3f51ca
@@ -450,5 +626,6 @@ int main(int argc, const char *argv[])
3f51ca
         cmocka_unit_test(test_decode),
3f51ca
     };
3f51ca
 
3f51ca
-    return cmocka_run_group_tests(tests, NULL, NULL);
3f51ca
+    assert_non_null(original_fopen);
3f51ca
+    return cmocka_run_group_tests(tests, extdom_req_setup, extdom_req_teardown);
3f51ca
 }
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
3f51ca
index fe225fa86669a6728bec5014be41d80275f10717..86c6638ba73c2f59aff29191e3e68dc5c85d50fc 100644
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
3f51ca
@@ -43,11 +43,12 @@
3f51ca
 
3f51ca
 #include <errno.h>
3f51ca
 #include <stdio.h>
3f51ca
+#include <sys/param.h>
3f51ca
 
3f51ca
 #include "ipa_extdom.h"
3f51ca
+#include "back_extdom.h"
3f51ca
 #include "util.h"
3f51ca
 
3f51ca
-#define MAX(a,b) (((a)>(b))?(a):(b))
3f51ca
 #define SSSD_DOMAIN_SEPARATOR '@'
3f51ca
 
3f51ca
 int get_buffer(size_t *_buf_len, char **_buf)
3f51ca
@@ -97,134 +98,137 @@ static int inc_buffer(size_t buf_max, size_t *_buf_len, char **_buf)
3f51ca
     return 0;
3f51ca
 }
3f51ca
 
3f51ca
-int getpwnam_r_wrapper(size_t buf_max, const char *name,
3f51ca
-                       struct passwd *pwd, char **_buf, size_t *_buf_len)
3f51ca
+int __nss_to_err(enum nss_status errcode)
3f51ca
 {
3f51ca
-    char *buf = NULL;
3f51ca
-    size_t buf_len = 0;
3f51ca
-    int ret;
3f51ca
-    struct passwd *result = NULL;
3f51ca
+    switch(errcode) {
3f51ca
+    case NSS_STATUS_SUCCESS:
3f51ca
+        return 0;
3f51ca
+    case NSS_STATUS_NOTFOUND:
3f51ca
+        return ENOENT;
3f51ca
+    case NSS_STATUS_TRYAGAIN:
3f51ca
+        return ERANGE;
3f51ca
+    case NSS_STATUS_UNAVAIL:
3f51ca
+        return ETIMEDOUT;
3f51ca
+    }
3f51ca
 
3f51ca
-    buf = *_buf;
3f51ca
-    buf_len = *_buf_len;
3f51ca
+    return -1;
3f51ca
+}
3f51ca
 
3f51ca
-    while (buf != NULL
3f51ca
-            && (ret = getpwnam_r(name, pwd, buf, buf_len, &result)) == ERANGE) {
3f51ca
-        ret = inc_buffer(buf_max, &buf_len, &buf;;
3f51ca
-        if (ret != 0) {
3f51ca
-            if (ret == ERANGE) {
3f51ca
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
-            }
3f51ca
-            goto done;
3f51ca
+int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
3f51ca
+                       struct passwd *pwd, char **buf, size_t *buf_len)
3f51ca
+{
3f51ca
+    int ret, lerrno = 0;
3f51ca
+    struct passwd *result = NULL;
3f51ca
+    enum nss_status rc;
3f51ca
+
3f51ca
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
3f51ca
+        rc = back_extdom_getpwnam(ctx->nss_ctx, name, pwd, *buf, *buf_len, &result, &lerrno);
3f51ca
+        ret = __nss_to_err(rc);
3f51ca
+        if (ret == ERANGE) {
3f51ca
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
3f51ca
+            if (ret != 0) goto done;
3f51ca
         }
3f51ca
     }
3f51ca
 
3f51ca
-    if (ret == 0 && result == NULL) {
3f51ca
-        ret = ENOENT;
3f51ca
-    }
3f51ca
-
3f51ca
 done:
3f51ca
-    *_buf = buf;
3f51ca
-    *_buf_len = buf_len;
3f51ca
-
3f51ca
+    switch(ret) {
3f51ca
+    case 0:
3f51ca
+        if (result == NULL) ret = ENOENT;
3f51ca
+        break;
3f51ca
+    case ERANGE:
3f51ca
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
+    default:
3f51ca
+        break;
3f51ca
+    }
3f51ca
     return ret;
3f51ca
 }
3f51ca
 
3f51ca
-int getpwuid_r_wrapper(size_t buf_max, uid_t uid,
3f51ca
-                       struct passwd *pwd, char **_buf, size_t *_buf_len)
3f51ca
+int getpwuid_r_wrapper(struct ipa_extdom_ctx *ctx, uid_t uid,
3f51ca
+                       struct passwd *pwd, char **buf, size_t *buf_len)
3f51ca
 {
3f51ca
-    char *buf = NULL;
3f51ca
-    size_t buf_len = 0;
3f51ca
-    int ret;
3f51ca
+    int ret, lerrno;
3f51ca
     struct passwd *result = NULL;
3f51ca
-
3f51ca
-    buf = *_buf;
3f51ca
-    buf_len = *_buf_len;
3f51ca
-
3f51ca
-    while (buf != NULL
3f51ca
-            && (ret = getpwuid_r(uid, pwd, buf, buf_len, &result)) == ERANGE) {
3f51ca
-        ret = inc_buffer(buf_max, &buf_len, &buf;;
3f51ca
-        if (ret != 0) {
3f51ca
-            if (ret == ERANGE) {
3f51ca
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
-            }
3f51ca
-            goto done;
3f51ca
+    enum nss_status rc;
3f51ca
+
3f51ca
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
3f51ca
+        rc = back_extdom_getpwuid(ctx->nss_ctx, uid, pwd, *buf, *buf_len, &result, &lerrno);
3f51ca
+        ret = __nss_to_err(rc);
3f51ca
+        if (ret == ERANGE) {
3f51ca
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
3f51ca
+            if (ret != 0) goto done;
3f51ca
         }
3f51ca
     }
3f51ca
 
3f51ca
-    if (ret == 0 && result == NULL) {
3f51ca
-        ret = ENOENT;
3f51ca
-    }
3f51ca
-
3f51ca
 done:
3f51ca
-    *_buf = buf;
3f51ca
-    *_buf_len = buf_len;
3f51ca
+    switch(ret) {
3f51ca
+    case 0:
3f51ca
+        if (result == NULL) ret = ENOENT;
3f51ca
+        break;
3f51ca
+    case ERANGE:
3f51ca
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
+    default:
3f51ca
+        break;
3f51ca
+    }
3f51ca
 
3f51ca
     return ret;
3f51ca
 }
3f51ca
 
3f51ca
-int getgrnam_r_wrapper(size_t buf_max, const char *name,
3f51ca
-                       struct group *grp, char **_buf, size_t *_buf_len)
3f51ca
+int getgrnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
3f51ca
+                       struct group *grp, char **buf, size_t *buf_len)
3f51ca
 {
3f51ca
-    char *buf = NULL;
3f51ca
-    size_t buf_len = 0;
3f51ca
-    int ret;
3f51ca
+    int ret, lerrno;
3f51ca
     struct group *result = NULL;
3f51ca
-
3f51ca
-    buf = *_buf;
3f51ca
-    buf_len = *_buf_len;
3f51ca
-
3f51ca
-    while (buf != NULL
3f51ca
-            && (ret = getgrnam_r(name, grp, buf, buf_len, &result)) == ERANGE) {
3f51ca
-        ret = inc_buffer(buf_max, &buf_len, &buf;;
3f51ca
-        if (ret != 0) {
3f51ca
-            if (ret == ERANGE) {
3f51ca
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
-            }
3f51ca
-            goto done;
3f51ca
+    enum nss_status rc;
3f51ca
+
3f51ca
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
3f51ca
+        rc = back_extdom_getgrnam(ctx->nss_ctx, name, grp, *buf, *buf_len, &result, &lerrno);
3f51ca
+        ret = __nss_to_err(rc);
3f51ca
+        if (ret == ERANGE) {
3f51ca
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
3f51ca
+            if (ret != 0) goto done;
3f51ca
         }
3f51ca
     }
3f51ca
 
3f51ca
-    if (ret == 0 && result == NULL) {
3f51ca
-        ret = ENOENT;
3f51ca
-    }
3f51ca
-
3f51ca
 done:
3f51ca
-    *_buf = buf;
3f51ca
-    *_buf_len = buf_len;
3f51ca
+    switch(ret) {
3f51ca
+    case 0:
3f51ca
+        if (result == NULL) ret = ENOENT;
3f51ca
+        break;
3f51ca
+    case ERANGE:
3f51ca
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
+    default:
3f51ca
+        break;
3f51ca
+    }
3f51ca
 
3f51ca
     return ret;
3f51ca
 }
3f51ca
 
3f51ca
-int getgrgid_r_wrapper(size_t buf_max, gid_t gid,
3f51ca
-                       struct group *grp, char **_buf, size_t *_buf_len)
3f51ca
+int getgrgid_r_wrapper(struct ipa_extdom_ctx *ctx, gid_t gid,
3f51ca
+                       struct group *grp, char **buf, size_t *buf_len)
3f51ca
 {
3f51ca
-    char *buf = NULL;
3f51ca
-    size_t buf_len = 0;
3f51ca
-    int ret;
3f51ca
+    int ret, lerrno;
3f51ca
     struct group *result = NULL;
3f51ca
-
3f51ca
-    buf = *_buf;
3f51ca
-    buf_len = *_buf_len;
3f51ca
-
3f51ca
-    while (buf != NULL
3f51ca
-            && (ret = getgrgid_r(gid, grp, buf, buf_len, &result)) == ERANGE) {
3f51ca
-        ret = inc_buffer(buf_max, &buf_len, &buf;;
3f51ca
-        if (ret != 0) {
3f51ca
-            if (ret == ERANGE) {
3f51ca
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
-            }
3f51ca
-            goto done;
3f51ca
+    enum nss_status rc;
3f51ca
+
3f51ca
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
3f51ca
+        rc = back_extdom_getgrgid(ctx->nss_ctx, gid, grp, *buf, *buf_len, &result, &lerrno);
3f51ca
+        ret = __nss_to_err(rc);
3f51ca
+        if (ret == ERANGE) {
3f51ca
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
3f51ca
+            if (ret != 0) goto done;
3f51ca
         }
3f51ca
     }
3f51ca
 
3f51ca
-    if (ret == 0 && result == NULL) {
3f51ca
-        ret = ENOENT;
3f51ca
-    }
3f51ca
-
3f51ca
 done:
3f51ca
-    *_buf = buf;
3f51ca
-    *_buf_len = buf_len;
3f51ca
+    switch(ret) {
3f51ca
+    case 0:
3f51ca
+        if (result == NULL) ret = ENOENT;
3f51ca
+        break;
3f51ca
+    case ERANGE:
3f51ca
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
3f51ca
+    default:
3f51ca
+        break;
3f51ca
+    }
3f51ca
 
3f51ca
     return ret;
3f51ca
 }
3f51ca
@@ -406,13 +410,14 @@ int check_request(struct extdom_req *req, enum extdom_version version)
3f51ca
     return LDAP_SUCCESS;
3f51ca
 }
3f51ca
 
3f51ca
-int get_user_grouplist(const char *name, gid_t gid,
3f51ca
+int get_user_grouplist(struct ipa_extdom_ctx *ctx, const char *name, gid_t gid,
3f51ca
                        size_t *_ngroups, gid_t **_groups)
3f51ca
 {
3f51ca
-    int ret;
3f51ca
+    int lerrno;
3f51ca
     int ngroups;
3f51ca
     gid_t *groups;
3f51ca
     gid_t *new_groups;
3f51ca
+    enum nss_status rc;
3f51ca
 
3f51ca
     ngroups = 128;
3f51ca
     groups = malloc(ngroups * sizeof(gid_t));
3f51ca
@@ -420,19 +425,18 @@ int get_user_grouplist(const char *name, gid_t gid,
3f51ca
         return LDAP_OPERATIONS_ERROR;
3f51ca
     }
3f51ca
 
3f51ca
-    ret = getgrouplist(name, gid, groups, &ngroups);
3f51ca
-    if (ret == -1) {
3f51ca
-        new_groups = realloc(groups, ngroups * sizeof(gid_t));
3f51ca
-        if (new_groups == NULL) {
3f51ca
-            free(groups);
3f51ca
-            return LDAP_OPERATIONS_ERROR;
3f51ca
-        }
3f51ca
-        groups = new_groups;
3f51ca
-
3f51ca
-        ret = getgrouplist(name, gid, groups, &ngroups);
3f51ca
-        if (ret == -1) {
3f51ca
-            free(groups);
3f51ca
-            return LDAP_OPERATIONS_ERROR;
3f51ca
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
3f51ca
+        rc = back_extdom_getgrouplist(ctx->nss_ctx, name, gid, groups, &ngroups, &lerrno);
3f51ca
+        if (rc == NSS_STATUS_TRYAGAIN) {
3f51ca
+            new_groups = NULL;
3f51ca
+            if (lerrno == ERANGE) {
3f51ca
+                new_groups = realloc(groups, ngroups * sizeof(gid_t));
3f51ca
+            }
3f51ca
+            if ((new_groups == NULL) || (lerrno == ENOMEM)) {
3f51ca
+                free(groups);
3f51ca
+                return LDAP_OPERATIONS_ERROR;
3f51ca
+            }
3f51ca
+            groups = new_groups;
3f51ca
         }
3f51ca
     }
3f51ca
 
3f51ca
@@ -538,7 +542,7 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx,
3f51ca
     }
3f51ca
 
3f51ca
     if (response_type == RESP_USER_GROUPLIST) {
3f51ca
-        ret = get_user_grouplist(user_name, gid, &ngroups, &groups);
3f51ca
+        ret = get_user_grouplist(ctx, user_name, gid, &ngroups, &groups);
3f51ca
         if (ret != LDAP_SUCCESS) {
3f51ca
             goto done;
3f51ca
         }
3f51ca
@@ -561,7 +565,7 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx,
3f51ca
         }
3f51ca
 
3f51ca
         for (c = 0; c < ngroups; c++) {
3f51ca
-            ret = getgrgid_r_wrapper(ctx->max_nss_buf_size,
3f51ca
+            ret = getgrgid_r_wrapper(ctx,
3f51ca
                                      groups[c], &grp, &buf, &buf_len);
3f51ca
             if (ret != 0) {
3f51ca
                 if (ret == ENOMEM || ret == ERANGE) {
3f51ca
@@ -841,8 +845,7 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx,
3f51ca
 
3f51ca
         ret = pack_ber_sid(sid_str, berval);
3f51ca
     } else {
3f51ca
-        ret = getpwuid_r_wrapper(ctx->max_nss_buf_size, uid, &pwd, &buf,
3f51ca
-                                 &buf_len);
3f51ca
+        ret = getpwuid_r_wrapper(ctx, uid, &pwd, &buf, &buf_len);
3f51ca
         if (ret != 0) {
3f51ca
             if (ret == ENOMEM || ret == ERANGE) {
3f51ca
                 ret = LDAP_OPERATIONS_ERROR;
3f51ca
@@ -913,8 +916,7 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx,
3f51ca
 
3f51ca
         ret = pack_ber_sid(sid_str, berval);
3f51ca
     } else {
3f51ca
-        ret = getgrgid_r_wrapper(ctx->max_nss_buf_size, gid, &grp, &buf,
3f51ca
-                                 &buf_len);
3f51ca
+        ret = getgrgid_r_wrapper(ctx, gid, &grp, &buf, &buf_len);
3f51ca
         if (ret != 0) {
3f51ca
             if (ret == ENOMEM || ret == ERANGE) {
3f51ca
                 ret = LDAP_OPERATIONS_ERROR;
3f51ca
@@ -1053,8 +1055,7 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
3f51ca
     switch(id_type) {
3f51ca
     case SSS_ID_TYPE_UID:
3f51ca
     case SSS_ID_TYPE_BOTH:
3f51ca
-        ret = getpwnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &pwd, &buf,
3f51ca
-                                 &buf_len);
3f51ca
+        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
3f51ca
         if (ret != 0) {
3f51ca
             if (ret == ENOMEM || ret == ERANGE) {
3f51ca
                 ret = LDAP_OPERATIONS_ERROR;
3f51ca
@@ -1086,8 +1087,7 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
3f51ca
                             pwd.pw_shell, kv_list, berval);
3f51ca
         break;
3f51ca
     case SSS_ID_TYPE_GID:
3f51ca
-        ret = getgrnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &grp, &buf,
3f51ca
-                                 &buf_len);
3f51ca
+        ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
3f51ca
         if (ret != 0) {
3f51ca
             if (ret == ENOMEM || ret == ERANGE) {
3f51ca
                 ret = LDAP_OPERATIONS_ERROR;
3f51ca
@@ -1181,8 +1181,7 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx,
3f51ca
             goto done;
3f51ca
         }
3f51ca
 
3f51ca
-        ret = getpwnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &pwd, &buf,
3f51ca
-                                 &buf_len);
3f51ca
+        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
3f51ca
         if (ret == 0) {
3f51ca
             if (request_type == REQ_FULL_WITH_GROUPS) {
3f51ca
                 ret = sss_nss_getorigbyname(pwd.pw_name, &kv_list, &id_type);
3f51ca
@@ -1211,8 +1210,7 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx,
3f51ca
              * error codes which can indicate that the user was not found. To
3f51ca
              * be on the safe side we fail back to the group lookup on all
3f51ca
              * errors. */
3f51ca
-            ret = getgrnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &grp, &buf,
3f51ca
-                                     &buf_len);
3f51ca
+            ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
3f51ca
             if (ret != 0) {
3f51ca
                 if (ret == ENOMEM || ret == ERANGE) {
3f51ca
                     ret = LDAP_OPERATIONS_ERROR;
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
3f51ca
index 5bc8c2f571e311c5ae4cc56e2e1ae7d4e2f77ee6..83c30e7e6aad72af603c0b4ed1c49b80fa57560f 100644
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
3f51ca
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
3f51ca
@@ -38,9 +38,11 @@
3f51ca
  * END COPYRIGHT BLOCK **/
3f51ca
 
3f51ca
 #include "ipa_extdom.h"
3f51ca
+#include "back_extdom.h"
3f51ca
 #include "util.h"
3f51ca
 
3f51ca
 #define DEFAULT_MAX_NSS_BUFFER (128*1024*1024)
3f51ca
+#define DEFAULT_MAX_NSS_TIMEOUT (10*1000)
3f51ca
 
3f51ca
 Slapi_PluginDesc ipa_extdom_plugin_desc = {
3f51ca
     IPA_EXTDOM_FEATURE_DESC,
3f51ca
@@ -166,6 +168,7 @@ static int ipa_extdom_init_ctx(Slapi_PBlock *pb, struct ipa_extdom_ctx **_ctx)
3f51ca
     struct ipa_extdom_ctx *ctx;
3f51ca
     Slapi_Entry *e;
3f51ca
     int ret;
3f51ca
+    unsigned int timeout;
3f51ca
 
3f51ca
     ctx = calloc(1, sizeof(struct ipa_extdom_ctx));
3f51ca
     if (!ctx) {
3f51ca
@@ -202,6 +205,20 @@ static int ipa_extdom_init_ctx(Slapi_PBlock *pb, struct ipa_extdom_ctx **_ctx)
3f51ca
     }
3f51ca
     LOG("Maximal nss buffer size set to [%zu]!\n", ctx->max_nss_buf_size);
3f51ca
 
3f51ca
+
3f51ca
+    ret = back_extdom_init_context(&ctx->nss_ctx);
3f51ca
+    if (ret != 0) {
3f51ca
+        LOG("Unable to initialize nss interface: returned [%d]!\n", ret);
3f51ca
+        goto done;
3f51ca
+    }
3f51ca
+
3f51ca
+    timeout = slapi_entry_attr_get_uint(e, "ipaExtdomMaxNssTimeout");
3f51ca
+    if (timeout == 0) {
3f51ca
+        timeout = DEFAULT_MAX_NSS_TIMEOUT;
3f51ca
+    }
3f51ca
+    back_extdom_set_timeout(ctx->nss_ctx, timeout);
3f51ca
+    LOG("Maximal nss timeout (in ms) set to [%u]!\n", timeout);
3f51ca
+
3f51ca
     ret = 0;
3f51ca
 
3f51ca
 done:
3f51ca
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh b/daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh
3f51ca
deleted file mode 100644
3f51ca
index ad839f340efe989a91cd6902f59c9a41483f68e0..0000000000000000000000000000000000000000
3f51ca
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh
3f51ca
+++ /dev/null
3f51ca
@@ -1,3 +0,0 @@
3f51ca
-export LD_PRELOAD=$(pkg-config --libs nss_wrapper)
3f51ca
-export NSS_WRAPPER_PASSWD=./test_data/passwd
3f51ca
-export NSS_WRAPPER_GROUP=./test_data/group
3f51ca
diff --git a/freeipa.spec.in b/freeipa.spec.in
3f51ca
index a8b5ce81fcf9bdb61cd3707e6b68b6f2196e0776..80ae98c5515f64a8df8d981ad5e91b05c84e31c1 100644
3f51ca
--- a/freeipa.spec.in
3f51ca
+++ b/freeipa.spec.in
3f51ca
@@ -246,7 +246,6 @@ BuildRequires:  python3-augeas
3f51ca
 #
3f51ca
 %if ! %{ONLY_CLIENT}
3f51ca
 BuildRequires:  libcmocka-devel
3f51ca
-BuildRequires:  nss_wrapper
3f51ca
 # Required by ipa_kdb_tests
3f51ca
 BuildRequires:  %{_libdir}/krb5/plugins/kdb/db2.so
3f51ca
 %endif # ONLY_CLIENT
3f51ca
diff --git a/server.m4 b/server.m4
3f51ca
index a9670c87372bb7b92d08dad634c0bda123a02597..f0a8bbcc778596dade89d9332abb2939b8a44143 100644
3f51ca
--- a/server.m4
3f51ca
+++ b/server.m4
3f51ca
@@ -35,6 +35,16 @@ AC_CHECK_LIB([sss_nss_idmap],
3f51ca
              [AC_MSG_ERROR([Required sss_nss_getlistbycert symbol in sss_nss_idmap not found])],
3f51ca
              [])
3f51ca
 
3f51ca
+dnl --- if sss_nss_idmap provides _timeout() API, use it
3f51ca
+bck_cflags="$CFLAGS"
3f51ca
+CFLAGS="$CFLAGS -DIPA_389DS_PLUGIN_HELPER_CALLS"
3f51ca
+AC_CHECK_DECLS([sss_nss_getpwnam_timeout], [], [], [[#include <sss_nss_idmap.h>]])
3f51ca
+CFLAGS="$bck_cflags"
3f51ca
+
3f51ca
+if test "x$ac_cv_have_decl_sss_nss_getpwnam_timeout" = xyes ; then
3f51ca
+    AC_DEFINE(USE_SSS_NSS_TIMEOUT,1,[Use extended NSS API provided by SSSD])
3f51ca
+fi
3f51ca
+
3f51ca
 dnl -- sss_certmap and certauth.h are needed by the IPA KDB certauth plugin --
3f51ca
 PKG_CHECK_EXISTS([sss_certmap],
3f51ca
                  [PKG_CHECK_MODULES([SSSCERTMAP], [sss_certmap])],
3f51ca
-- 
3f51ca
2.14.3
3f51ca