URL: https://github.com/freeipa/freeipa/pull/1302
Author: abbra
 Title: #1302: ipa-extdom-extop: refactor nsswitch operations
Action: opened

PR body:
"""
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.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/1302/head:pr1302
git checkout pr1302
From 2ec66f0f2f527d85a0bae1ee2635f17a3202e344 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.
---
 configure.ac                                       |   2 -
 .../ipa-slapi-plugins/ipa-extdom-extop/Makefile.am |  17 +-
 .../ipa-extdom-extop/back_extdom.h                 |  80 ++++++
 .../ipa-extdom-extop/back_extdom_nss_sss.c         | 279 +++++++++++++++++++++
 .../ipa-extdom-extop/back_extdom_sss_idmap.c       | 239 ++++++++++++++++++
 .../ipa-extdom-extop/ipa_extdom.h                  |  13 +-
 .../ipa-extdom-extop/ipa_extdom_cmocka_tests.c     | 213 +++++++++++++---
 .../ipa-extdom-extop/ipa_extdom_common.c           | 175 ++++++++-----
 .../ipa-extdom-extop/ipa_extdom_extop.c            |  13 +
 .../ipa-extdom-extop/test_data/test_setup.sh       |   3 -
 server.m4                                          |  11 +
 11 files changed, 931 insertions(+), 114 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..8f28fb4068 100644
--- a/configure.ac
+++ b/configure.ac
@@ -162,8 +162,6 @@ AC_DEFUN([AM_CHECK_WRAPPER],
     AM_CONDITIONAL($2, [ test x$FOUND_WRAPPER = x1])
 ])
 
-AM_CHECK_WRAPPER(nss_wrapper, HAVE_NSS_WRAPPER)
-
 dnl ---------------------------------------------------------------------------
 dnl - Check for POPT
 dnl ---------------------------------------------------------------------------
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..8759e77ce2
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom.h
@@ -0,0 +1,80 @@
+/*
+ * 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_sch_nss_h
+#define back_sch_nss_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;
+
+void 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_sch_nss_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..ff4e377fb0
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_nss_sss.c
@@ -0,0 +1,279 @@
+/*
+ * 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) {
+		return;
+	}
+
+	if ((*nss_context)->dl_handle != NULL) {
+		dlclose((*nss_context)->dl_handle);
+	}
+
+	free((*nss_context));
+	*nss_context = NULL;
+}
+
+void back_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+	struct nss_ops_ctx *ctx = NULL;
+
+	if (nss_context == NULL) {
+		return;
+	}
+
+	ctx = calloc(1, sizeof(struct nss_ops_ctx));
+
+	*nss_context = ctx;
+	if (ctx == NULL) {
+		return;
+	}
+
+	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;
+
+fail:
+	back_extdom_free_context(nss_context);
+
+	return;
+}
+
+
+/* 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 */
+}
+
+#include <stdio.h>
+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 = (enum nss_status)
+		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 = (enum nss_status)
+		nss_context->getpwuid_r(uid, pwd,
+					buffer, buflen,
+					lerrno);
+
+	if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+		*result = pwd;
+	}
+
+	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 = (enum nss_status)
+		nss_context->getgrnam_r(name, grp,
+					buffer, buflen,
+					lerrno);
+
+	if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+		*result = grp;
+	}
+
+	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 = (enum nss_status)
+		nss_context->getgrgid_r(gid, grp,
+					buffer, buflen,
+					lerrno);
+
+	if ((ret == NSS_STATUS_SUCCESS) && (result != NULL)) {
+		*result = grp;
+	}
+
+	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 = (gid_t *) malloc (tsize * sizeof (gid_t));
+
+
+	if (nss_context == NULL) {
+		return NSS_STATUS_UNAVAIL;
+	}
+
+	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..b9a2256593
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/back_extdom_sss_idmap.c
@@ -0,0 +1,239 @@
+/*
+ * 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) {
+	enum nss_status ret = NSS_STATUS_UNAVAIL;
+
+	if (errcode == 0) {
+		ret = NSS_STATUS_SUCCESS;
+	} else if (errcode == ENOENT) {
+		ret = NSS_STATUS_NOTFOUND;
+	} else if(errcode == ERANGE) {
+		ret = NSS_STATUS_TRYAGAIN;
+	} else if(errcode == ETIMEDOUT) {
+		ret = NSS_STATUS_UNAVAIL;
+	} else if(errcode == ETIME) {
+		ret = NSS_STATUS_TRYAGAIN;
+	};
+
+	return ret;
+}
+
+void back_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+	struct nss_ops_ctx *ctx = NULL;
+
+	if (nss_context == NULL) {
+		return;
+	}
+
+	ctx = calloc(1, sizeof(struct nss_ops_ctx));
+
+	*nss_context = ctx;
+}
+
+void back_extdom_free_context(struct nss_ops_ctx **nss_context)
+{
+	if (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;
+}
+
+/* TODO: handle buffers and memory allocation in this function */
+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);
+}
+
+/* TODO: handle buffers and memory allocation in this function */
+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);
+
+	if (ret != 0 && lerrno != NULL) {
+		/* SSSD translates errno into return code */
+		*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);
+	if (ret != 0 && lerrno != NULL) {
+		/* SSSD translates errno into return code */
+		*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);
+	if (ret != 0 && lerrno != NULL) {
+		/* SSSD translates errno into return code */
+		*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);
+	if (ret != 0 && lerrno != NULL) {
+		/* SSSD translates errno into return code */
+		*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);
+	if (ret != 0 && lerrno != NULL) {
+		/* SSSD translates errno into return code */
+		*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..2c4f9eb0e6 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,138 @@
 
 
 #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;
+};
+
+/*
+ * 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);
+};
+
+void cmocka_extdom_init_context(struct nss_ops_ctx **nss_context)
+{
+	struct nss_ops_ctx *ctx = NULL;
+
+	if (nss_context == NULL) {
+		return;
+	}
+
+	ctx = calloc(1, sizeof(struct nss_ops_ctx));
+
+	*nss_context = ctx;
+	if (ctx == NULL) {
+		return;
+	}
+
+	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;
+
+fail:
+	back_extdom_free_context(nss_context);
+
+	return;
+}
+
+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;
+    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);
+}
+
+
 
 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 +177,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 +192,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 +206,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 +231,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 +245,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 +258,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 +283,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 +295,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 +308,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 +332,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 +344,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 +358,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 +380,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 +399,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 +410,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;
+
+    cmocka_extdom_init_context(&test_data->ctx->nss_ctx);
+    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 +430,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 +598,6 @@ int main(int argc, const char *argv[])
         cmocka_unit_test(test_decode),
     };
 
-    return cmocka_run_group_tests(tests, NULL, NULL);
+    original_fopen = dlsym(RTLD_NEXT, "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..bbe7782a75 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
@@ -45,6 +45,7 @@
 #include <stdio.h>
 
 #include "ipa_extdom.h"
+#include "back_extdom.h"
 #include "util.h"
 
 #define MAX(a,b) (((a)>(b))?(a):(b))
@@ -97,26 +98,49 @@ 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,
+int __nss_to_err(enum nss_status errcode)
+{
+    int ret = -1;
+
+    if (errcode == NSS_STATUS_SUCCESS) {
+        ret = 0;
+    } else if (errcode == NSS_STATUS_NOTFOUND) {
+        ret = ENOENT;
+    } else if(errcode == NSS_STATUS_TRYAGAIN) {
+        ret = ERANGE;
+    } else if(errcode == NSS_STATUS_UNAVAIL) {
+        ret = ETIMEDOUT;
+    };
+
+    return ret;
+}
+
+int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
                        struct passwd *pwd, char **_buf, size_t *_buf_len)
 {
     char *buf = NULL;
     size_t buf_len = 0;
-    int ret;
+    int ret, lerrno = 0;
     struct passwd *result = NULL;
+    enum nss_status rc;
 
     buf = *_buf;
     buf_len = *_buf_len;
 
-    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");
+    while (buf != NULL) {
+        rc = back_extdom_getpwnam(ctx->nss_ctx, name, pwd, buf, buf_len, &result, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            ret = inc_buffer(ctx->max_nss_buf_size, &buf_len, &buf);
+            if (ret != 0) {
+                if (ret == ERANGE) {
+                    LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+                }
+                goto done;
             }
-            goto done;
-        }
+        } else {
+	    ret = __nss_to_err(rc);
+	    break;
+	}
     }
 
     if (ret == 0 && result == NULL) {
@@ -130,26 +154,32 @@ int getpwnam_r_wrapper(size_t buf_max, const char *name,
     return ret;
 }
 
-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)
 {
     char *buf = NULL;
     size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct passwd *result = NULL;
+    enum nss_status rc;
 
     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");
+    while (buf != NULL) {
+        rc = back_extdom_getpwuid(ctx->nss_ctx, uid, pwd, buf, buf_len, &result, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            ret = inc_buffer(ctx->max_nss_buf_size, &buf_len, &buf);
+            if (ret != 0) {
+                if (ret == ERANGE) {
+                    LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+                }
+                goto done;
             }
-            goto done;
-        }
+        } else {
+	    ret = __nss_to_err(rc);
+	    break;
+	}
     }
 
     if (ret == 0 && result == NULL) {
@@ -163,26 +193,32 @@ int getpwuid_r_wrapper(size_t buf_max, uid_t uid,
     return ret;
 }
 
-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)
 {
     char *buf = NULL;
     size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct group *result = NULL;
+    enum nss_status rc;
 
     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");
+    while (buf != NULL) {
+        rc = back_extdom_getgrnam(ctx->nss_ctx, name, grp, buf, buf_len, &result, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            ret = inc_buffer(ctx->max_nss_buf_size, &buf_len, &buf);
+            if (ret != 0) {
+                if (ret == ERANGE) {
+                    LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+                }
+                goto done;
             }
-            goto done;
-        }
+        } else {
+	    ret = __nss_to_err(rc);
+	    break;
+	}
     }
 
     if (ret == 0 && result == NULL) {
@@ -196,26 +232,32 @@ int getgrnam_r_wrapper(size_t buf_max, const char *name,
     return ret;
 }
 
-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)
 {
     char *buf = NULL;
     size_t buf_len = 0;
-    int ret;
+    int ret, lerrno;
     struct group *result = NULL;
+    enum nss_status rc;
 
     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");
+    while (buf != NULL) {
+        rc = back_extdom_getgrgid(ctx->nss_ctx, gid, grp, buf, buf_len, &result, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            ret = inc_buffer(ctx->max_nss_buf_size, &buf_len, &buf);
+            if (ret != 0) {
+                if (ret == ERANGE) {
+                    LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
+                }
+                goto done;
             }
-            goto done;
-        }
+        } else {
+	    ret = __nss_to_err(rc);
+	    break;
+	}
     }
 
     if (ret == 0 && result == NULL) {
@@ -406,13 +448,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,21 +463,21 @@ 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;
+    do {
+        rc = back_extdom_getgrouplist(ctx->nss_ctx, name, gid, groups, &ngroups, &lerrno);
+        if (rc == NSS_STATUS_TRYAGAIN) {
+            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;
+        if (rc == NSS_STATUS_NOTFOUND) {
+            break;
         }
-    }
+    } while (rc != NSS_STATUS_SUCCESS);
 
     *_ngroups = ngroups;
     *_groups = groups;
@@ -538,7 +581,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 +604,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 +884,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 +955,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 +1094,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 +1126,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 +1220,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 +1249,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..4c1df1a647 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,16 @@ 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);
 
+
+    back_extdom_init_context(&ctx->nss_ctx);
+    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/server.m4 b/server.m4
index a9670c8737..ac8bade3cd 100644
--- a/server.m4
+++ b/server.m4
@@ -35,6 +35,17 @@ 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
+AM_CONDITIONAL([USE_SSS_NSS_TIMEOUT], [test "x$ac_cv_have_decl_sss_nss_getpwnam_timeout" = xyes])
+
 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