URL: https://github.com/freeipa/freeipa/pull/1348
Author: abbra
 Title: #1348: [Backport][ipa-4-6] ipa-extdom-extop: refactor nsswitch 
operations
Action: opened

PR body:
"""
This PR was opened automatically because PR #1302 was pushed to master and 
backport to ipa-4-6 is required.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/1348/head:pr1348
git checkout pr1348
From fadfe7228c68725d7a60ca60a62b5216c299ec30 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Tue, 14 Nov 2017 12:44:02 +0200
Subject: [PATCH] ipa-extdom-extop: refactor nsswitch operations

Refactor nsswitch operations in ipa-extdom-extop plugin to allow use
of timeout-enabled nsswitch calls provided by libsss_nss_idmap.

Standard POSIX nsswitch API has no way to cancel requests which may
cause ipa-extdom-extop requests to hang far too long and potentially
exhaust LDAP server workers. In addition, glibc nsswitch API iterates
through all nsswitch modules one by one and with multiple parallel
requests a lock up may happen in an unrelated nsswitch module like
nss_files.so.2.

A solution to the latter issue is to directly load nss_sss.so.2 plugin
and utilize it. This, however, does not solve a problem with lack of
cancellable API.

With SSSD 1.16.1, libsss_nss_idmap provides a timeout-enabled variant of
nsswitch API that is directly integrated with SSSD client side machinery
used by nss_sss.so.2. As result, this API can be used instead of loading
nss_sss.so.2 directly.

To support older SSSD version, both direct loading of nss_sss.so.2 and
new timeout-enabled API are supported by this changeset. An API to
abstract both is designed to be a mix between internal glibc nsswitch
API and external nsswitch API that libsss_nss_idmap mimics. API does not
expose per-call timeout. Instead, it allows to set a timeout per
nsswitch operation context to reduce requirements on information
a caller has to maintain.

A choice which API to use is made at configure time.

In order to test the API, a cmocka test is updated to explicitly load
nss_files.so.2 as a backend. Since use of nss_sss.so.2 would always
depend on availablility of SSSD, predictable testing would not be
possible without it otherwise. Also, cmocka test does not use
nss_wrapper anymore because nss_wrapper overrides higher level glibc
nsswitch API while we are loading an individual nsswitch module
directly.

As result, cmocka test overrides fopen() call used by nss_files.so.2 to
load /etc/passwd and /etc/group. An overridden version changes paths to
/etc/passwd and /etc/group to a local test_data/passwd and
test_data/group. This way we can continue testing a backend API for
ipa-extdom-extop with the same data as with nss_wrapper.

Fixes https://pagure.io/freeipa/issue/5464
---
 configure.ac                                       |  25 +-
 .../ipa-slapi-plugins/ipa-extdom-extop/Makefile.am |  17 +-
 .../ipa-extdom-extop/back_extdom.h                 |  79 ++++++
 .../ipa-extdom-extop/back_extdom_nss_sss.c         | 276 +++++++++++++++++++++
 .../ipa-extdom-extop/back_extdom_sss_idmap.c       | 260 +++++++++++++++++++
 .../ipa-extdom-extop/ipa_extdom.h                  |  13 +-
 .../ipa-extdom-extop/ipa_extdom_cmocka_tests.c     | 241 +++++++++++++++---
 .../ipa-extdom-extop/ipa_extdom_common.c           | 242 +++++++++---------
 .../ipa-extdom-extop/ipa_extdom_extop.c            |  17 ++
 .../ipa-extdom-extop/test_data/test_setup.sh       |   3 -
 freeipa.spec.in                                    |   1 -
 server.m4                                          |  10 +
 12 files changed, 994 insertions(+), 190 deletions(-)
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
 create mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
 delete mode 100644 daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh

diff --git a/configure.ac b/configure.ac
index 699fd64cba..a89d69c461 100644
--- a/configure.ac
+++ b/configure.ac
@@ -140,30 +140,6 @@ PKG_CHECK_EXISTS(cmocka,
 )
 AM_CONDITIONAL([HAVE_CMOCKA], [test x$have_cmocka = xyes])
 
-dnl A macro to check presence of a cwrap (http://cwrap.org) wrapper on the system
-dnl Usage:
-dnl     AM_CHECK_WRAPPER(name, conditional)
-dnl If the cwrap library is found, sets the HAVE_$name conditional
-AC_DEFUN([AM_CHECK_WRAPPER],
-[
-    FOUND_WRAPPER=0
-
-    AC_MSG_CHECKING([for $1])
-    PKG_CHECK_EXISTS([$1],
-                     [
-                        AC_MSG_RESULT([yes])
-                        FOUND_WRAPPER=1
-                     ],
-                     [
-                        AC_MSG_RESULT([no])
-                        AC_MSG_WARN([cwrap library $1 not found, some tests will not run])
-                     ])
-
-    AM_CONDITIONAL($2, [ test x$FOUND_WRAPPER = x1])
-])
-
-AM_CHECK_WRAPPER(nss_wrapper, HAVE_NSS_WRAPPER)
-
 dnl ---------------------------------------------------------------------------
 dnl - Check for POPT
 dnl ---------------------------------------------------------------------------
@@ -235,6 +211,7 @@ dnl ---------------------------------------------------------------------------
 AM_COND_IF([ENABLE_SERVER], [
     m4_include(server.m4)
 ])
+AM_CONDITIONAL([USE_SSS_NSS_TIMEOUT], [test "x$ac_cv_have_decl_sss_nss_getpwnam_timeout" = xyes])
 
 dnl ---------------------------------------------------------------------------
 dnl - Check if IPA certauth plugin can be build
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
index 1213965c96..cbdd570eab 100644
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/Makefile.am
@@ -25,6 +25,7 @@ libipa_extdom_extop_la_SOURCES = 	\
 	ipa_extdom.h			\
 	ipa_extdom_extop.c		\
 	ipa_extdom_common.c		\
+	back_extdom.h			\
 	$(NULL)
 
 libipa_extdom_extop_la_LDFLAGS = -avoid-version
@@ -34,20 +35,29 @@ libipa_extdom_extop_la_LIBADD = 	\
 	$(SSSNSSIDMAP_LIBS)		\
 	$(NULL)
 
+# We have two backends for nss operations:
+# (1) directly loading nss_sss.so.2
+# (2) using timeout-enabled API from libsss_nss_idmap
+# We prefer (2) if available
+if USE_SSS_NSS_TIMEOUT
+libipa_extdom_extop_la_SOURCES += back_extdom_sss_idmap.c
+else
+libipa_extdom_extop_la_SOURCES += back_extdom_nss_sss.c
+endif
+
+
 TESTS =
 check_PROGRAMS =
 
 if HAVE_CMOCKA
-if HAVE_NSS_WRAPPER
-TESTS_ENVIRONMENT = . ./test_data/test_setup.sh;
 TESTS += extdom_cmocka_tests
 check_PROGRAMS += extdom_cmocka_tests
 endif
-endif
 
 extdom_cmocka_tests_SOURCES = 		\
 	ipa_extdom_cmocka_tests.c	\
 	ipa_extdom_common.c		\
+	back_extdom_nss_sss.c		\
 	$(NULL)
 extdom_cmocka_tests_CFLAGS = $(CMOCKA_CFLAGS)
 extdom_cmocka_tests_LDFLAGS = 	\
@@ -58,6 +68,7 @@ extdom_cmocka_tests_LDADD = 	\
 	$(LDAP_LIBS)		\
 	$(DIRSRV_LIBS)		\
 	$(SSSNSSIDMAP_LIBS)	\
+	-ldl			\
 	$(NULL)
 
 
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
new file mode 100644
index 0000000000..d2937c8c8e
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this Program; if not, write to the
+ *
+ *   Free Software Foundation, Inc.
+ *   59 Temple Place, Suite 330
+ *   Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef BACK_EXTDOM_H
+#define BACK_EXTDOM_H
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+/* Possible results of lookup using a nss_* function.
+ * Note: don't include nss.h as its path gets overriden by NSS library */
+enum nss_status {
+    NSS_STATUS_TRYAGAIN = -2,
+    NSS_STATUS_UNAVAIL,
+    NSS_STATUS_NOTFOUND,
+    NSS_STATUS_SUCCESS,
+    NSS_STATUS_RETURN
+};
+
+/* NSS backend operations implemented using either nss_sss.so.2 or libsss_nss_idmap API */
+struct nss_ops_ctx;
+
+int back_extdom_init_context(struct nss_ops_ctx **nss_context);
+void back_extdom_free_context(struct nss_ops_ctx **nss_context);
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
+                             unsigned int timeout);
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
+                            const char *name);
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
+                             const char *name);
+
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno);
+
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
+                                     uid_t uid, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno);
+
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno);
+
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
+                                     gid_t gid, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno);
+
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
+                                         const char *name, gid_t group,
+                                         gid_t *groups, int *ngroups,
+                                         int *lerrno);
+
+#endif /* BACK_EXTDOM_H */
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
new file mode 100644
index 0000000000..346c7d4301
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2013-2017 Red Hat, Inc.
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this Program; if not, write to the
+ *
+ *   Free Software Foundation, Inc.
+ *   59 Temple Place, Suite 330
+ *   Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/param.h>
+#include "back_extdom.h"
+
+struct nss_ops_ctx {
+    void *dl_handle;
+    long int initgroups_start;
+
+    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 (*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 (*initgroups_dyn)(const char *user, gid_t group,
+                                      long int *start, long int *size,
+                                      gid_t **groups, long int limit,
+                                      int *errnop);
+};
+
+void back_extdom_free_context(struct nss_ops_ctx **nss_context)
+{
+    if ((nss_context == NULL) || (*nss_context == NULL)) {
+        return;
+    }
+
+    if ((*nss_context)->dl_handle != NULL) {
+        dlclose((*nss_context)->dl_handle);
+    }
+
+    free((*nss_context));
+    *nss_context = NULL;
+}
+
+int back_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+    struct nss_ops_ctx *ctx = NULL;
+
+    if (nss_context == NULL) {
+        return EINVAL;
+    }
+
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
+    if (ctx == NULL) {
+        return ENOMEM;
+    }
+    *nss_context = ctx;
+
+    ctx->dl_handle = dlopen("libnss_sss.so.2", RTLD_NOW);
+    if (ctx->dl_handle == NULL) {
+        goto fail;
+    }
+
+    ctx->getpwnam_r = dlsym(ctx->dl_handle, "_nss_sss_getpwnam_r");
+    if (ctx->getpwnam_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getpwuid_r = dlsym(ctx->dl_handle, "_nss_sss_getpwuid_r");
+    if (ctx->getpwuid_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getgrnam_r = dlsym(ctx->dl_handle, "_nss_sss_getgrnam_r");
+    if (ctx->getgrnam_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getgrgid_r = dlsym(ctx->dl_handle, "_nss_sss_getgrgid_r");
+    if (ctx->getgrgid_r == NULL) {
+        goto fail;
+    }
+
+    ctx->initgroups_dyn = dlsym(ctx->dl_handle, "_nss_sss_initgroups_dyn");
+    if (ctx->initgroups_dyn == NULL) {
+        goto fail;
+    }
+
+    return 0;
+
+fail:
+    back_extdom_free_context(nss_context);
+
+    return EINVAL;
+}
+
+
+/* Following three functions cannot be implemented with nss_sss.so.2
+ * As result, we simply do nothing here */
+
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
+                             unsigned int timeout) {
+        /* no operation */
+}
+
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
+                            const char *name) {
+        /* no operation */
+}
+
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
+                             const char *name) {
+        /* no operation */
+}
+
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno) {
+    enum nss_status ret;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = nss_context->getpwnam_r(name, pwd,
+                                  buffer, buflen,
+                                  lerrno);
+
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+        *result = pwd;
+        *lerrno = 0;
+    }
+
+    return ret;
+}
+
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
+                                     uid_t uid, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno) {
+    enum nss_status ret;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = nss_context->getpwuid_r(uid, pwd,
+                                  buffer, buflen,
+                                  lerrno);
+
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+        *result = pwd;
+        *lerrno = 0;
+    }
+
+    return ret;
+}
+
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno) {
+    enum nss_status ret;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = nss_context->getgrnam_r(name, grp,
+                                  buffer, buflen,
+                                  lerrno);
+
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+        *result = grp;
+        *lerrno = 0;
+    }
+
+    return ret;
+}
+
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
+                                     gid_t gid, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno) {
+
+    enum nss_status ret;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = nss_context->getgrgid_r(gid, grp,
+                                  buffer, buflen,
+                                  lerrno);
+
+    if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+        *result = grp;
+        *lerrno = 0;
+    }
+
+    return ret;
+}
+
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
+                                         const char *name, gid_t group,
+                                         gid_t *groups, int *ngroups,
+                                         int *lerrno) {
+
+    enum nss_status ret = NSS_STATUS_UNAVAIL;
+    long int tsize = MAX (1, *ngroups);
+    gid_t *newgroups = NULL;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    newgroups = (gid_t *) calloc (tsize, sizeof (gid_t));
+    if (newgroups == NULL) {
+        *lerrno = ENOMEM;
+        return NSS_STATUS_TRYAGAIN;
+    }
+
+    newgroups[0] = group;
+    nss_context->initgroups_start = 1;
+
+    ret = nss_context->initgroups_dyn(name, group,
+                                      &nss_context->initgroups_start,
+                                      &tsize, &newgroups,
+                                      -1, lerrno);
+
+    (void) memcpy(groups, newgroups,
+                  MIN(*ngroups, nss_context->initgroups_start) * sizeof(gid_t));
+    free(newgroups);
+
+    if (*ngroups < nss_context->initgroups_start) {
+        ret = NSS_STATUS_TRYAGAIN;
+        *lerrno = ERANGE;
+    }
+
+    *ngroups = (int) nss_context->initgroups_start;
+
+    nss_context->initgroups_start = 0;
+
+    return ret;
+}
+
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
new file mode 100644
index 0000000000..89c58ca2de
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2013-2017 Red Hat, Inc.
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this Program; if not, write to the
+ *
+ *   Free Software Foundation, Inc.
+ *   59 Temple Place, Suite 330
+ *   Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include "back_extdom.h"
+
+/* SSSD only exposes *_timeout() variants if the following symbol is defined */
+#define IPA_389DS_PLUGIN_HELPER_CALLS
+#include <sss_nss_idmap.h>
+
+struct nss_ops_ctx {
+    unsigned int timeout;
+};
+
+static enum nss_status __convert_sss_nss2nss_status(int errcode) {
+    switch(errcode) {
+    case 0:
+        return NSS_STATUS_SUCCESS;
+    case ENOENT:
+        return NSS_STATUS_NOTFOUND;
+    case ETIME:
+        /* fall-through */
+    case ERANGE:
+        return NSS_STATUS_TRYAGAIN;
+    case ETIMEDOUT:
+        /* fall-through */
+    default:
+        return NSS_STATUS_UNAVAIL;
+    }
+    return NSS_STATUS_UNAVAIL;
+}
+
+int back_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+    struct nss_ops_ctx *ctx = NULL;
+
+    if (nss_context == NULL) {
+        return EINVAL;
+    }
+
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
+
+    if (ctx == NULL) {
+        return ENOMEM;
+    }
+    *nss_context = ctx;
+    return 0;
+}
+
+void back_extdom_free_context(struct nss_ops_ctx **nss_context)
+{
+    if ((nss_context == NULL) || (*nss_context == NULL)) {
+        return;
+    }
+
+    free((*nss_context));
+    *nss_context = NULL;
+}
+
+
+void back_extdom_set_timeout(struct nss_ops_ctx *nss_context,
+                             unsigned int timeout) {
+    if (nss_context == NULL) {
+        return;
+    }
+
+    nss_context->timeout = timeout;
+}
+
+void back_extdom_evict_user(struct nss_ops_ctx *nss_context,
+                            const char *name) {
+    if (nss_context == NULL) {
+        return;
+    }
+
+    (void) sss_nss_getpwnam_timeout(name, NULL,
+                                    NULL, 0,
+                                    NULL,
+                                    SSS_NSS_EX_FLAG_INVALIDATE_CACHE,
+                                    nss_context->timeout);
+}
+
+void back_extdom_evict_group(struct nss_ops_ctx *nss_context,
+                             const char *name) {
+    if (nss_context == NULL) {
+            return;
+    }
+
+    (void) sss_nss_getgrnam_timeout(name, NULL,
+                                    NULL, 0,
+                                    NULL,
+                                    SSS_NSS_EX_FLAG_INVALIDATE_CACHE,
+                                    nss_context->timeout);
+}
+
+enum nss_status back_extdom_getpwnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno) {
+    int ret = 0;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = sss_nss_getpwnam_timeout(name, pwd,
+                                   buffer, buflen,
+                                   result,
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
+                                   nss_context->timeout);
+
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
+     * but writes down errno into return code so we propagate it in case
+     * of error and translate the return code */
+    if (lerrno != NULL) {
+        *lerrno = ret;
+    }
+    return __convert_sss_nss2nss_status(ret);
+}
+
+enum nss_status back_extdom_getpwuid(struct nss_ops_ctx *nss_context,
+                                     uid_t uid, struct passwd *pwd,
+                                     char *buffer, size_t buflen,
+                                     struct passwd **result,
+                                     int *lerrno) {
+
+    int ret = 0;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = sss_nss_getpwuid_timeout(uid, pwd,
+                                   buffer, buflen,
+                                   result,
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
+                                   nss_context->timeout);
+
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
+     * but writes down errno into return code so we propagate it in case
+     * of error and translate the return code */
+    if (lerrno != NULL) {
+        *lerrno = ret;
+    }
+    return __convert_sss_nss2nss_status(ret);
+}
+
+enum nss_status back_extdom_getgrnam(struct nss_ops_ctx *nss_context,
+                                     const char *name, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno) {
+
+    int ret = 0;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = sss_nss_getgrnam_timeout(name, grp,
+                                   buffer, buflen,
+                                   result,
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
+                                   nss_context->timeout);
+
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
+     * but writes down errno into return code so we propagate it in case
+     * of error and translate the return code */
+    if (lerrno != NULL) {
+        *lerrno = ret;
+    }
+    return __convert_sss_nss2nss_status(ret);
+}
+
+enum nss_status back_extdom_getgrgid(struct nss_ops_ctx *nss_context,
+                                     gid_t gid, struct group *grp,
+                                     char *buffer, size_t buflen,
+                                     struct group **result,
+                                     int *lerrno) {
+
+    int ret = 0;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = sss_nss_getgrgid_timeout(gid, grp,
+                                   buffer, buflen,
+                                   result,
+                                   SSS_NSS_EX_FLAG_NO_FLAGS,
+                                   nss_context->timeout);
+
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
+     * but writes down errno into return code so we propagate it in case
+     * of error and translate the return code */
+    if (lerrno != NULL) {
+        *lerrno = ret;
+    }
+    return __convert_sss_nss2nss_status(ret);
+}
+
+enum nss_status back_extdom_getgrouplist(struct nss_ops_ctx *nss_context,
+                                         const char *name, gid_t group,
+                                         gid_t *groups, int *ngroups,
+                                         int *lerrno) {
+    int ret = 0;
+
+    if (nss_context == NULL) {
+        return NSS_STATUS_UNAVAIL;
+    }
+
+    ret = sss_nss_getgrouplist_timeout(name, group,
+                                       groups, ngroups,
+                                       SSS_NSS_EX_FLAG_NO_FLAGS,
+                                       nss_context->timeout);
+
+    /* SSSD uses the same infrastructure to handle sss_nss_get* calls
+     * as nss_sss.so.2 module where 'int *errno' is passed to the helper
+     * but writes down errno into return code so we propagate it in case
+     * of error and translate the return code */
+    if (lerrno != NULL) {
+        *lerrno = ret;
+    }
+    return __convert_sss_nss2nss_status(ret);
+}
+
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
index bc29f06981..bbc574747e 100644
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h
@@ -150,10 +150,13 @@ struct extdom_res {
     } data;
 };
 
+struct nss_ops_ctx;
+
 struct ipa_extdom_ctx {
     Slapi_ComponentId *plugin_id;
     char *base_dn;
     size_t max_nss_buf_size;
+    struct nss_ops_ctx *nss_ctx;
 };
 
 struct domain_info {
@@ -179,15 +182,15 @@ int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req,
                    struct berval **berval);
 int pack_response(struct extdom_res *res, struct berval **ret_val);
 int get_buffer(size_t *_buf_len, char **_buf);
-int getpwnam_r_wrapper(size_t buf_max, const char *name,
+int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
                        struct passwd *pwd, char **_buf, size_t *_buf_len);
-int getpwuid_r_wrapper(size_t buf_max, uid_t uid,
+int getpwuid_r_wrapper(struct ipa_extdom_ctx *ctx, uid_t uid,
                        struct passwd *pwd, char **_buf, size_t *_buf_len);
-int getgrnam_r_wrapper(size_t buf_max, const char *name,
+int getgrnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
                        struct group *grp, char **_buf, size_t *_buf_len);
-int getgrgid_r_wrapper(size_t buf_max, gid_t gid,
+int getgrgid_r_wrapper(struct ipa_extdom_ctx *ctx, gid_t gid,
                        struct group *grp, char **_buf, size_t *_buf_len);
-int get_user_grouplist(const char *name, gid_t gid,
+int get_user_grouplist(struct ipa_extdom_ctx *ctx, const char *name, gid_t gid,
                        size_t *_ngroups, gid_t **_groups);
 int pack_ber_sid(const char *sid, struct berval **berval);
 int pack_ber_name(const char *domain_name, const char *name,
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
index 526f903d24..29699cfa39 100644
--- 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
@@ -19,6 +19,7 @@
     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 <errno.h>
 #include <stdarg.h>
@@ -31,24 +32,166 @@
 
 
 #include "ipa_extdom.h"
+#include "back_extdom.h"
+#include <stdio.h>
+#include <dlfcn.h>
 
 #define MAX_BUF (1024*1024*1024)
+struct test_data {
+    struct extdom_req *req;
+    struct ipa_extdom_ctx *ctx;
+};
+
+/*
+ * redefine logging for mocks
+ */
+#ifdef __GNUC__
+    __attribute__((format(printf, 3, 4)))
+#endif
+int slapi_log_error(int loglevel, char *subsystem, char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vprint_error(fmt, ap);
+    va_end(ap);
+    return 0;
+}
+
+
+/*
+ * We cannot run cmocka tests against SSSD as that would require to set up SSSD
+ * and the rest of environment. Instead, we compile cmocka tests against
+ * back_extdom_nss_sss.c and re-define context initialization to use
+ * nsswrapper with our test data.
+ *
+ * This means we have to keep struct nss_ops_ctx definition in sync with tests!
+ */
+
+struct nss_ops_ctx {
+    void *dl_handle;
+    long int initgroups_start;
+
+    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 (*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 (*initgroups_dyn)(const char *user, gid_t group,
+                                      long int *start, long int *size,
+                                      gid_t **groups, long int limit,
+                                      int *errnop);
+};
+
+int cmocka_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+    struct nss_ops_ctx *ctx = NULL;
+
+    if (nss_context == NULL) {
+        return -1;
+    }
+
+    ctx = calloc(1, sizeof(struct nss_ops_ctx));
+
+    if (ctx == NULL) {
+        return ENOMEM;
+    }
+    *nss_context = ctx;
+
+    ctx->dl_handle = dlopen("libnss_files.so.2", RTLD_NOW);
+    if (ctx->dl_handle == NULL) {
+        goto fail;
+    }
+
+    ctx->getpwnam_r = dlsym(ctx->dl_handle, "_nss_files_getpwnam_r");
+    if (ctx->getpwnam_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getpwuid_r = dlsym(ctx->dl_handle, "_nss_files_getpwuid_r");
+    if (ctx->getpwuid_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getgrnam_r = dlsym(ctx->dl_handle, "_nss_files_getgrnam_r");
+    if (ctx->getgrnam_r == NULL) {
+        goto fail;
+    }
+
+    ctx->getgrgid_r = dlsym(ctx->dl_handle, "_nss_files_getgrgid_r");
+    if (ctx->getgrgid_r == NULL) {
+        goto fail;
+    }
+
+    ctx->initgroups_dyn = dlsym(ctx->dl_handle, "_nss_files_initgroups_dyn");
+    if (ctx->initgroups_dyn == NULL) {
+        goto fail;
+    }
+
+    return 0;
+
+fail:
+    back_extdom_free_context(nss_context);
+
+    return -1;
+}
+
+struct {
+    const char *o, *n;
+} path_table[] = {
+    { .o = "/etc/passwd", .n = "./test_data/passwd"},
+    { .o = "/etc/group",  .n = "./test_data/group"},
+    { .o = NULL, .n = NULL}};
+
+FILE *(*original_fopen)(const char*, const char*) = NULL;
+
+FILE *fopen(const char *path, const char *mode) {
+    const char *_path = NULL;
+
+    /* Do not handle before-main() cases */
+    if (original_fopen == NULL) {
+        return NULL;
+    }
+    for(int i=0; path_table[i].o != NULL; i++) {
+        if (strcmp(path, path_table[i].o) == 0) {
+                _path = path_table[i].n;
+                break;
+        }
+    }
+    return (*original_fopen)(_path ? _path : path, mode);
+}
+
+/* Attempt to initialize original_fopen before main()
+ * There is no explicit order when all initializers are called,
+ * so we might still be late here compared to a code in a shared
+ * library initializer, like libselinux */
+void redefined_fopen_ctor (void) __attribute__ ((constructor));
+void redefined_fopen_ctor(void) {
+    original_fopen = dlsym(RTLD_NEXT, "fopen");
+}
 
 void test_getpwnam_r_wrapper(void **state)
 {
     int ret;
     struct passwd pwd;
     char *buf;
-    size_t buf_len;
+    size_t buf_len, max_big_buf_len;
+    struct test_data *test_data;
+
+    test_data = (struct test_data *) *state;
 
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwnam_r_wrapper(MAX_BUF, "non_exisiting_user", &pwd, &buf,
-                             &buf_len);
+    ret = getpwnam_r_wrapper(test_data->ctx,
+                             "non_exisiting_user", &pwd,
+                             &buf, &buf_len);
     assert_int_equal(ret, ENOENT);
 
-    ret = getpwnam_r_wrapper(MAX_BUF, "user", &pwd, &buf, &buf_len);
+    ret = getpwnam_r_wrapper(test_data->ctx,
+                             "user", &pwd, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(pwd.pw_name, "user");
     assert_string_equal(pwd.pw_passwd, "x");
@@ -62,7 +205,8 @@ void test_getpwnam_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwnam_r_wrapper(MAX_BUF, "user_big", &pwd, &buf, &buf_len);
+    ret = getpwnam_r_wrapper(test_data->ctx,
+                             "user_big", &pwd, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(pwd.pw_name, "user_big");
     assert_string_equal(pwd.pw_passwd, "x");
@@ -76,7 +220,11 @@ void test_getpwnam_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwnam_r_wrapper(1024, "user_big", &pwd, &buf, &buf_len);
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
+    test_data->ctx->max_nss_buf_size = 1024;
+    ret = getpwnam_r_wrapper(test_data->ctx,
+                             "user_big", &pwd, &buf, &buf_len);
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
     assert_int_equal(ret, ERANGE);
     free(buf);
 }
@@ -86,15 +234,18 @@ void test_getpwuid_r_wrapper(void **state)
     int ret;
     struct passwd pwd;
     char *buf;
-    size_t buf_len;
+    size_t buf_len, max_big_buf_len;
+    struct test_data *test_data;
+
+    test_data = (struct test_data *) *state;
 
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwuid_r_wrapper(MAX_BUF, 99999, &pwd, &buf, &buf_len);
+    ret = getpwuid_r_wrapper(test_data->ctx, 99999, &pwd, &buf, &buf_len);
     assert_int_equal(ret, ENOENT);
 
-    ret = getpwuid_r_wrapper(MAX_BUF, 12345, &pwd, &buf, &buf_len);
+    ret = getpwuid_r_wrapper(test_data->ctx, 12345, &pwd, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(pwd.pw_name, "user");
     assert_string_equal(pwd.pw_passwd, "x");
@@ -108,7 +259,7 @@ void test_getpwuid_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwuid_r_wrapper(MAX_BUF, 12346, &pwd, &buf, &buf_len);
+    ret = getpwuid_r_wrapper(test_data->ctx, 12346, &pwd, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(pwd.pw_name, "user_big");
     assert_string_equal(pwd.pw_passwd, "x");
@@ -122,7 +273,10 @@ void test_getpwuid_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getpwuid_r_wrapper(1024, 12346, &pwd, &buf, &buf_len);
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
+    test_data->ctx->max_nss_buf_size = 1024;
+    ret = getpwuid_r_wrapper(test_data->ctx, 12346, &pwd, &buf, &buf_len);
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
     assert_int_equal(ret, ERANGE);
     free(buf);
 }
@@ -132,15 +286,19 @@ void test_getgrnam_r_wrapper(void **state)
     int ret;
     struct group grp;
     char *buf;
-    size_t buf_len;
+    size_t buf_len, max_big_buf_len;
+    struct test_data *test_data;
+
+    test_data = (struct test_data *) *state;
 
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrnam_r_wrapper(MAX_BUF, "non_exisiting_group", &grp, &buf, &buf_len);
+    ret = getgrnam_r_wrapper(test_data->ctx,
+                             "non_exisiting_group", &grp, &buf, &buf_len);
     assert_int_equal(ret, ENOENT);
 
-    ret = getgrnam_r_wrapper(MAX_BUF, "group", &grp, &buf, &buf_len);
+    ret = getgrnam_r_wrapper(test_data->ctx, "group", &grp, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(grp.gr_name, "group");
     assert_string_equal(grp.gr_passwd, "x");
@@ -153,7 +311,7 @@ void test_getgrnam_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrnam_r_wrapper(MAX_BUF, "group_big", &grp, &buf, &buf_len);
+    ret = getgrnam_r_wrapper(test_data->ctx, "group_big", &grp, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(grp.gr_name, "group_big");
     assert_string_equal(grp.gr_passwd, "x");
@@ -165,7 +323,10 @@ void test_getgrnam_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrnam_r_wrapper(1024, "group_big", &grp, &buf, &buf_len);
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
+    test_data->ctx->max_nss_buf_size = 1024;
+    ret = getgrnam_r_wrapper(test_data->ctx, "group_big", &grp, &buf, &buf_len);
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
     assert_int_equal(ret, ERANGE);
     free(buf);
 }
@@ -175,15 +336,18 @@ void test_getgrgid_r_wrapper(void **state)
     int ret;
     struct group grp;
     char *buf;
-    size_t buf_len;
+    size_t buf_len, max_big_buf_len;
+    struct test_data *test_data;
+
+    test_data = (struct test_data *) *state;
 
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrgid_r_wrapper(MAX_BUF, 99999, &grp, &buf, &buf_len);
+    ret = getgrgid_r_wrapper(test_data->ctx, 99999, &grp, &buf, &buf_len);
     assert_int_equal(ret, ENOENT);
 
-    ret = getgrgid_r_wrapper(MAX_BUF, 11111, &grp, &buf, &buf_len);
+    ret = getgrgid_r_wrapper(test_data->ctx, 11111, &grp, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(grp.gr_name, "group");
     assert_string_equal(grp.gr_passwd, "x");
@@ -196,7 +360,7 @@ void test_getgrgid_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrgid_r_wrapper(MAX_BUF, 22222, &grp, &buf, &buf_len);
+    ret = getgrgid_r_wrapper(test_data->ctx, 22222, &grp, &buf, &buf_len);
     assert_int_equal(ret, 0);
     assert_string_equal(grp.gr_name, "group_big");
     assert_string_equal(grp.gr_passwd, "x");
@@ -208,7 +372,10 @@ void test_getgrgid_r_wrapper(void **state)
     ret = get_buffer(&buf_len, &buf);
     assert_int_equal(ret, 0);
 
-    ret = getgrgid_r_wrapper(1024, 22222, &grp, &buf, &buf_len);
+    max_big_buf_len = test_data->ctx->max_nss_buf_size;
+    test_data->ctx->max_nss_buf_size = 1024;
+    ret = getgrgid_r_wrapper(test_data->ctx, 22222, &grp, &buf, &buf_len);
+    test_data->ctx->max_nss_buf_size = max_big_buf_len;
     assert_int_equal(ret, ERANGE);
     free(buf);
 }
@@ -219,16 +386,21 @@ void test_get_user_grouplist(void **state)
     size_t ngroups;
     gid_t *groups;
     size_t c;
+    struct test_data *test_data;
+
+    test_data = (struct test_data *) *state;
 
     /* This is a bit odd behaviour of getgrouplist() it does not check if the
      * user exists, only if memberships of the user can be found. */
-    ret = get_user_grouplist("non_exisiting_user", 23456, &ngroups, &groups);
+    ret = get_user_grouplist(test_data->ctx,
+                             "non_exisiting_user", 23456, &ngroups, &groups);
     assert_int_equal(ret, LDAP_SUCCESS);
     assert_int_equal(ngroups, 1);
     assert_int_equal(groups[0], 23456);
     free(groups);
 
-    ret = get_user_grouplist("member0001", 23456, &ngroups, &groups);
+    ret = get_user_grouplist(test_data->ctx,
+                             "member0001", 23456, &ngroups, &groups);
     assert_int_equal(ret, LDAP_SUCCESS);
     assert_int_equal(ngroups, 3);
     assert_int_equal(groups[0], 23456);
@@ -236,14 +408,16 @@ void test_get_user_grouplist(void **state)
     assert_int_equal(groups[2], 22222);
     free(groups);
 
-    ret = get_user_grouplist("member0003", 23456, &ngroups, &groups);
+    ret = get_user_grouplist(test_data->ctx,
+                             "member0003", 23456, &ngroups, &groups);
     assert_int_equal(ret, LDAP_SUCCESS);
     assert_int_equal(ngroups, 2);
     assert_int_equal(groups[0], 23456);
     assert_int_equal(groups[1], 22222);
     free(groups);
 
-    ret = get_user_grouplist("user_big", 23456, &ngroups, &groups);
+    ret = get_user_grouplist(test_data->ctx,
+                             "user_big", 23456, &ngroups, &groups);
     assert_int_equal(ret, LDAP_SUCCESS);
     assert_int_equal(ngroups, 1001);
     assert_int_equal(groups[0], 23456);
@@ -253,11 +427,6 @@ void test_get_user_grouplist(void **state)
     free(groups);
 }
 
-struct test_data {
-    struct extdom_req *req;
-    struct ipa_extdom_ctx *ctx;
-};
-
 static int  extdom_req_setup(void **state)
 {
     struct test_data *test_data;
@@ -269,8 +438,14 @@ static int  extdom_req_setup(void **state)
     assert_non_null(test_data->req);
 
     test_data->ctx = calloc(sizeof(struct ipa_extdom_ctx), 1);
-    assert_non_null(test_data->req);
+    assert_non_null(test_data->ctx);
+
+    test_data->ctx->max_nss_buf_size = MAX_BUF;
+
+    assert_int_equal(cmocka_extdom_init_context(&test_data->ctx->nss_ctx), 0);
+    assert_non_null(test_data->ctx->nss_ctx);
 
+    back_extdom_set_timeout(test_data->ctx->nss_ctx, 10000);
     *state = test_data;
 
     return 0;
@@ -283,6 +458,7 @@ static int  extdom_req_teardown(void **state)
     test_data = (struct test_data *) *state;
 
     free_req_data(test_data->req);
+    back_extdom_free_context(&test_data->ctx->nss_ctx);
     free(test_data->ctx);
     free(test_data);
 
@@ -450,5 +626,6 @@ int main(int argc, const char *argv[])
         cmocka_unit_test(test_decode),
     };
 
-    return cmocka_run_group_tests(tests, NULL, NULL);
+    assert_non_null(original_fopen);
+    return cmocka_run_group_tests(tests, extdom_req_setup, extdom_req_teardown);
 }
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
index 750f1ec7d3..1b93dce186 100644
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c
@@ -43,11 +43,12 @@
 
 #include <errno.h>
 #include <stdio.h>
+#include <sys/param.h>
 
 #include "ipa_extdom.h"
+#include "back_extdom.h"
 #include "util.h"
 
-#define MAX(a,b) (((a)>(b))?(a):(b))
 #define SSSD_DOMAIN_SEPARATOR '@'
 
 int get_buffer(size_t *_buf_len, char **_buf)
@@ -97,134 +98,137 @@ static int inc_buffer(size_t buf_max, size_t *_buf_len, char **_buf)
     return 0;
 }
 
-int getpwnam_r_wrapper(size_t buf_max, const char *name,
-                       struct passwd *pwd, char **_buf, size_t *_buf_len)
+int __nss_to_err(enum nss_status errcode)
 {
-    char *buf = NULL;
-    size_t buf_len = 0;
-    int ret;
-    struct passwd *result = NULL;
+    switch(errcode) {
+    case NSS_STATUS_SUCCESS:
+        return 0;
+    case NSS_STATUS_NOTFOUND:
+        return ENOENT;
+    case NSS_STATUS_TRYAGAIN:
+        return ERANGE;
+    case NSS_STATUS_UNAVAIL:
+        return ETIMEDOUT;
+    }
 
-    buf = *_buf;
-    buf_len = *_buf_len;
+    return -1;
+}
 
-    while (buf != NULL
-            && (ret = getpwnam_r(name, pwd, buf, buf_len, &result)) == ERANGE) {
-        ret = inc_buffer(buf_max, &buf_len, &buf);
-        if (ret != 0) {
-            if (ret == ERANGE) {
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
-            }
-            goto done;
+int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
+                       struct passwd *pwd, char **buf, size_t *buf_len)
+{
+    int ret, lerrno = 0;
+    struct passwd *result = NULL;
+    enum nss_status rc;
+
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
+        rc = back_extdom_getpwnam(ctx->nss_ctx, name, pwd, *buf, *buf_len, &result, &lerrno);
+        ret = __nss_to_err(rc);
+        if (ret == ERANGE) {
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
+            if (ret != 0) goto done;
         }
     }
 
-    if (ret == 0 && result == NULL) {
-        ret = ENOENT;
-    }
-
 done:
-    *_buf = buf;
-    *_buf_len = buf_len;
-
+    switch(ret) {
+    case 0:
+        if (result == NULL) ret = ENOENT;
+        break;
+    case ERANGE:
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+    default:
+        break;
+    }
     return ret;
 }
 
-int getpwuid_r_wrapper(size_t buf_max, uid_t uid,
-                       struct passwd *pwd, char **_buf, size_t *_buf_len)
+int getpwuid_r_wrapper(struct ipa_extdom_ctx *ctx, uid_t uid,
+                       struct passwd *pwd, char **buf, size_t *buf_len)
 {
-    char *buf = NULL;
-    size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct passwd *result = NULL;
-
-    buf = *_buf;
-    buf_len = *_buf_len;
-
-    while (buf != NULL
-            && (ret = getpwuid_r(uid, pwd, buf, buf_len, &result)) == ERANGE) {
-        ret = inc_buffer(buf_max, &buf_len, &buf);
-        if (ret != 0) {
-            if (ret == ERANGE) {
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
-            }
-            goto done;
+    enum nss_status rc;
+
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
+        rc = back_extdom_getpwuid(ctx->nss_ctx, uid, pwd, *buf, *buf_len, &result, &lerrno);
+        ret = __nss_to_err(rc);
+        if (ret == ERANGE) {
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
+            if (ret != 0) goto done;
         }
     }
 
-    if (ret == 0 && result == NULL) {
-        ret = ENOENT;
-    }
-
 done:
-    *_buf = buf;
-    *_buf_len = buf_len;
+    switch(ret) {
+    case 0:
+        if (result == NULL) ret = ENOENT;
+        break;
+    case ERANGE:
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+    default:
+        break;
+    }
 
     return ret;
 }
 
-int getgrnam_r_wrapper(size_t buf_max, const char *name,
-                       struct group *grp, char **_buf, size_t *_buf_len)
+int getgrnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
+                       struct group *grp, char **buf, size_t *buf_len)
 {
-    char *buf = NULL;
-    size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct group *result = NULL;
-
-    buf = *_buf;
-    buf_len = *_buf_len;
-
-    while (buf != NULL
-            && (ret = getgrnam_r(name, grp, buf, buf_len, &result)) == ERANGE) {
-        ret = inc_buffer(buf_max, &buf_len, &buf);
-        if (ret != 0) {
-            if (ret == ERANGE) {
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
-            }
-            goto done;
+    enum nss_status rc;
+
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
+        rc = back_extdom_getgrnam(ctx->nss_ctx, name, grp, *buf, *buf_len, &result, &lerrno);
+        ret = __nss_to_err(rc);
+        if (ret == ERANGE) {
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
+            if (ret != 0) goto done;
         }
     }
 
-    if (ret == 0 && result == NULL) {
-        ret = ENOENT;
-    }
-
 done:
-    *_buf = buf;
-    *_buf_len = buf_len;
+    switch(ret) {
+    case 0:
+        if (result == NULL) ret = ENOENT;
+        break;
+    case ERANGE:
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+    default:
+        break;
+    }
 
     return ret;
 }
 
-int getgrgid_r_wrapper(size_t buf_max, gid_t gid,
-                       struct group *grp, char **_buf, size_t *_buf_len)
+int getgrgid_r_wrapper(struct ipa_extdom_ctx *ctx, gid_t gid,
+                       struct group *grp, char **buf, size_t *buf_len)
 {
-    char *buf = NULL;
-    size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct group *result = NULL;
-
-    buf = *_buf;
-    buf_len = *_buf_len;
-
-    while (buf != NULL
-            && (ret = getgrgid_r(gid, grp, buf, buf_len, &result)) == ERANGE) {
-        ret = inc_buffer(buf_max, &buf_len, &buf);
-        if (ret != 0) {
-            if (ret == ERANGE) {
-                LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
-            }
-            goto done;
+    enum nss_status rc;
+
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
+        rc = back_extdom_getgrgid(ctx->nss_ctx, gid, grp, *buf, *buf_len, &result, &lerrno);
+        ret = __nss_to_err(rc);
+        if (ret == ERANGE) {
+            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
+            if (ret != 0) goto done;
         }
     }
 
-    if (ret == 0 && result == NULL) {
-        ret = ENOENT;
-    }
-
 done:
-    *_buf = buf;
-    *_buf_len = buf_len;
+    switch(ret) {
+    case 0:
+        if (result == NULL) ret = ENOENT;
+        break;
+    case ERANGE:
+        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+    default:
+        break;
+    }
 
     return ret;
 }
@@ -406,13 +410,14 @@ int check_request(struct extdom_req *req, enum extdom_version version)
     return LDAP_SUCCESS;
 }
 
-int get_user_grouplist(const char *name, gid_t gid,
+int get_user_grouplist(struct ipa_extdom_ctx *ctx, const char *name, gid_t gid,
                        size_t *_ngroups, gid_t **_groups)
 {
-    int ret;
+    int lerrno;
     int ngroups;
     gid_t *groups;
     gid_t *new_groups;
+    enum nss_status rc;
 
     ngroups = 128;
     groups = malloc(ngroups * sizeof(gid_t));
@@ -420,19 +425,18 @@ int get_user_grouplist(const char *name, gid_t gid,
         return LDAP_OPERATIONS_ERROR;
     }
 
-    ret = getgrouplist(name, gid, groups, &ngroups);
-    if (ret == -1) {
-        new_groups = realloc(groups, ngroups * sizeof(gid_t));
-        if (new_groups == NULL) {
-            free(groups);
-            return LDAP_OPERATIONS_ERROR;
-        }
-        groups = new_groups;
-
-        ret = getgrouplist(name, gid, groups, &ngroups);
-        if (ret == -1) {
-            free(groups);
-            return LDAP_OPERATIONS_ERROR;
+    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
+        rc = back_extdom_getgrouplist(ctx->nss_ctx, name, gid, groups, &ngroups, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            new_groups = NULL;
+            if (lerrno == ERANGE) {
+                new_groups = realloc(groups, ngroups * sizeof(gid_t));
+            }
+            if ((new_groups == NULL) || (lerrno == ENOMEM)) {
+                free(groups);
+                return LDAP_OPERATIONS_ERROR;
+            }
+            groups = new_groups;
         }
     }
 
@@ -538,7 +542,7 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx,
     }
 
     if (response_type == RESP_USER_GROUPLIST) {
-        ret = get_user_grouplist(user_name, gid, &ngroups, &groups);
+        ret = get_user_grouplist(ctx, user_name, gid, &ngroups, &groups);
         if (ret != LDAP_SUCCESS) {
             goto done;
         }
@@ -561,7 +565,7 @@ int pack_ber_user(struct ipa_extdom_ctx *ctx,
         }
 
         for (c = 0; c < ngroups; c++) {
-            ret = getgrgid_r_wrapper(ctx->max_nss_buf_size,
+            ret = getgrgid_r_wrapper(ctx,
                                      groups[c], &grp, &buf, &buf_len);
             if (ret != 0) {
                 if (ret == ENOMEM || ret == ERANGE) {
@@ -841,8 +845,7 @@ static int handle_uid_request(struct ipa_extdom_ctx *ctx,
 
         ret = pack_ber_sid(sid_str, berval);
     } else {
-        ret = getpwuid_r_wrapper(ctx->max_nss_buf_size, uid, &pwd, &buf,
-                                 &buf_len);
+        ret = getpwuid_r_wrapper(ctx, uid, &pwd, &buf, &buf_len);
         if (ret != 0) {
             if (ret == ENOMEM || ret == ERANGE) {
                 ret = LDAP_OPERATIONS_ERROR;
@@ -913,8 +916,7 @@ static int handle_gid_request(struct ipa_extdom_ctx *ctx,
 
         ret = pack_ber_sid(sid_str, berval);
     } else {
-        ret = getgrgid_r_wrapper(ctx->max_nss_buf_size, gid, &grp, &buf,
-                                 &buf_len);
+        ret = getgrgid_r_wrapper(ctx, gid, &grp, &buf, &buf_len);
         if (ret != 0) {
             if (ret == ENOMEM || ret == ERANGE) {
                 ret = LDAP_OPERATIONS_ERROR;
@@ -1053,8 +1055,7 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
     switch(id_type) {
     case SSS_ID_TYPE_UID:
     case SSS_ID_TYPE_BOTH:
-        ret = getpwnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &pwd, &buf,
-                                 &buf_len);
+        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
         if (ret != 0) {
             if (ret == ENOMEM || ret == ERANGE) {
                 ret = LDAP_OPERATIONS_ERROR;
@@ -1086,8 +1087,7 @@ static int handle_sid_request(struct ipa_extdom_ctx *ctx,
                             pwd.pw_shell, kv_list, berval);
         break;
     case SSS_ID_TYPE_GID:
-        ret = getgrnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &grp, &buf,
-                                 &buf_len);
+        ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
         if (ret != 0) {
             if (ret == ENOMEM || ret == ERANGE) {
                 ret = LDAP_OPERATIONS_ERROR;
@@ -1181,8 +1181,7 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx,
             goto done;
         }
 
-        ret = getpwnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &pwd, &buf,
-                                 &buf_len);
+        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
         if (ret == 0) {
             if (request_type == REQ_FULL_WITH_GROUPS) {
                 ret = sss_nss_getorigbyname(pwd.pw_name, &kv_list, &id_type);
@@ -1211,8 +1210,7 @@ static int handle_name_request(struct ipa_extdom_ctx *ctx,
              * error codes which can indicate that the user was not found. To
              * be on the safe side we fail back to the group lookup on all
              * errors. */
-            ret = getgrnam_r_wrapper(ctx->max_nss_buf_size, fq_name, &grp, &buf,
-                                     &buf_len);
+            ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
             if (ret != 0) {
                 if (ret == ENOMEM || ret == ERANGE) {
                     ret = LDAP_OPERATIONS_ERROR;
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
index 5bc8c2f571..83c30e7e6a 100644
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
@@ -38,9 +38,11 @@
  * END COPYRIGHT BLOCK **/
 
 #include "ipa_extdom.h"
+#include "back_extdom.h"
 #include "util.h"
 
 #define DEFAULT_MAX_NSS_BUFFER (128*1024*1024)
+#define DEFAULT_MAX_NSS_TIMEOUT (10*1000)
 
 Slapi_PluginDesc ipa_extdom_plugin_desc = {
     IPA_EXTDOM_FEATURE_DESC,
@@ -166,6 +168,7 @@ static int ipa_extdom_init_ctx(Slapi_PBlock *pb, struct ipa_extdom_ctx **_ctx)
     struct ipa_extdom_ctx *ctx;
     Slapi_Entry *e;
     int ret;
+    unsigned int timeout;
 
     ctx = calloc(1, sizeof(struct ipa_extdom_ctx));
     if (!ctx) {
@@ -202,6 +205,20 @@ static int ipa_extdom_init_ctx(Slapi_PBlock *pb, struct ipa_extdom_ctx **_ctx)
     }
     LOG("Maximal nss buffer size set to [%zu]!\n", ctx->max_nss_buf_size);
 
+
+    ret = back_extdom_init_context(&ctx->nss_ctx);
+    if (ret != 0) {
+        LOG("Unable to initialize nss interface: returned [%d]!\n", ret);
+        goto done;
+    }
+
+    timeout = slapi_entry_attr_get_uint(e, "ipaExtdomMaxNssTimeout");
+    if (timeout == 0) {
+        timeout = DEFAULT_MAX_NSS_TIMEOUT;
+    }
+    back_extdom_set_timeout(ctx->nss_ctx, timeout);
+    LOG("Maximal nss timeout (in ms) set to [%u]!\n", timeout);
+
     ret = 0;
 
 done:
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
deleted file mode 100644
index ad839f340e..0000000000
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/test_data/test_setup.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-export LD_PRELOAD=$(pkg-config --libs nss_wrapper)
-export NSS_WRAPPER_PASSWD=./test_data/passwd
-export NSS_WRAPPER_GROUP=./test_data/group
diff --git a/freeipa.spec.in b/freeipa.spec.in
index e47a084757..011f03eda4 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -257,7 +257,6 @@ BuildRequires:  python3-pyldap
 #
 %if ! %{ONLY_CLIENT}
 BuildRequires:  libcmocka-devel
-BuildRequires:  nss_wrapper
 # Required by ipa_kdb_tests
 BuildRequires:  %{_libdir}/krb5/plugins/kdb/db2.so
 %endif # ONLY_CLIENT
diff --git a/server.m4 b/server.m4
index a9670c8737..f0a8bbcc77 100644
--- a/server.m4
+++ b/server.m4
@@ -35,6 +35,16 @@ AC_CHECK_LIB([sss_nss_idmap],
              [AC_MSG_ERROR([Required sss_nss_getlistbycert symbol in sss_nss_idmap not found])],
              [])
 
+dnl --- if sss_nss_idmap provides _timeout() API, use it
+bck_cflags="$CFLAGS"
+CFLAGS="$CFLAGS -DIPA_389DS_PLUGIN_HELPER_CALLS"
+AC_CHECK_DECLS([sss_nss_getpwnam_timeout], [], [], [[#include <sss_nss_idmap.h>]])
+CFLAGS="$bck_cflags"
+
+if test "x$ac_cv_have_decl_sss_nss_getpwnam_timeout" = xyes ; then
+    AC_DEFINE(USE_SSS_NSS_TIMEOUT,1,[Use extended NSS API provided by SSSD])
+fi
+
 dnl -- sss_certmap and certauth.h are needed by the IPA KDB certauth plugin --
 PKG_CHECK_EXISTS([sss_certmap],
                  [PKG_CHECK_MODULES([SSSCERTMAP], [sss_certmap])],
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org

Reply via email to