Browse Source

checkin first version of pam plugin

Rich Megginson 21 years ago
parent
commit
9eb2b56ec6

+ 93 - 0
ldap/servers/plugins/pam_passthru/Makefile

@@ -0,0 +1,93 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2005 Red Hat
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+#
+# GNU Makefile for Directory Server "PAM Pass Through Authentication" plugin
+#
+#
+
+LDAP_SRC = ../../..
+BUILD_ROOT = ../../../..
+
+NOSTDCLEAN=true # don't let nsconfig.mk define target clean
+NOSTDSTRIP=true # don't let nsconfig.mk define target strip
+NSPR20=true	# probably should be defined somewhere else (not sure where)
+
+OBJDEST = $(OBJDIR)/lib/libpam_passthru
+LIBDIR = $(LIB_RELDIR)
+
+include $(BUILD_ROOT)/nsconfig.mk
+include $(LDAP_SRC)/nsldap.mk
+
+ifeq ($(ARCH), WINNT)
+DEF_FILE:=./libpam_passthru.def
+endif
+
+CFLAGS+=$(SLCFLAGS)
+
+INCLUDES += -I$(LDAP_SRC)/servers/slapd
+
+PAM_PASSTHRU_OBJS=	pam_ptimpl.o pam_ptconfig.o pam_ptdebug.o pam_ptpreop.o
+
+OBJS = $(addprefix $(OBJDEST)/, $(PAM_PASSTHRU_OBJS)) 
+
+ifeq ($(ARCH), WINNT)
+LIBPAM_PASSTHRU_DLL_OBJ = $(addprefix $(OBJDEST)/, pam_ptdllmain.o)
+endif
+
+LIBPAM_PASSTHRU=	$(addprefix $(LIBDIR)/, $(PAM_PASSTHRU_DLL).$(DLL_SUFFIX))
+
+EXTRA_LIBS += -lpam
+#LD += -Xlinker --no-undefined -Xlinker --no-allow-shlib-undefined
+#LD += -Xlinker --export-dynamic
+
+ifeq ($(ARCH), WINNT)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPD) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK)
+endif
+
+
+ifeq ($(ARCH), WINNT)
+DLL_LDFLAGS += -def:"./libpam_passthru.def"
+endif # WINNT
+
+ifeq ($(ARCH), AIX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP)
+EXTRA_LIBS_DEP += $(LDAPSDK_DEP) $(NSPR_DEP)
+EXTRA_LIBS += $(LIBSLAPDLINK) $(LDAP_SDK_LIBLDAP_DLL) $(NSPRLINK)
+EXTRA_LIBS += $(DLL_EXTRA_LIBS) 
+LD=ld
+endif
+
+ifeq ($(ARCH), HPUX)
+EXTRA_LIBS_DEP += $(LIBSLAPD_DEP) $(LDAPSDK_DEP) $(NSPR_DEP) $(SECURITY_DEP)
+EXTRA_LIBS += $(DYN_NSHTTPD) $(ADMINUTIL_LINK) $(LDAPLINK) $(SECURITYLINK) $(NSPRLINK) $(ICULINK)
+endif
+
+clientSDK: 
+
+all:	$(OBJDEST) $(LIBDIR) $(LIBPAM_PASSTHRU)
+
+$(LIBPAM_PASSTHRU): $(OBJS) $(LIBPAM_PASSTHRU_DLL_OBJ) $(DEF_FILE)
+	$(LINK_DLL) $(LIBPAM_PASSTHRU_DLL_OBJ) $(PLATFORMLIBS) $(EXTRA_LIBS)
+
+veryclean: clean
+
+clean:
+	$(RM) $(OBJS)
+ifeq ($(ARCH), WINNT)
+	$(RM) $(LIBPAM_PASSTHRU_DLL_OBJ)
+endif
+	$(RM) $(LIBPAM_PASSTHRU)
+
+$(OBJDEST):
+	$(MKDIR) $(OBJDEST)
+
+#
+# header file dependencies (incomplete)
+#
+$(OBJS):	pam_passthru.h

+ 184 - 0
ldap/servers/plugins/pam_passthru/README

@@ -0,0 +1,184 @@
+#
+# BEGIN COPYRIGHT BLOCK
+# Copyright 2005 Red Hat
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+PAM pass through authentication plugin for Directory Server
+
+Introduction
+
+Many organizations have authentication mechanisms
+already in place. They may not want to have the LDAP server be the
+central repository for authentication credentials and the
+authentication mechanism. The typical deployment would use PAM as the
+gateway to authentication. They do want to have many apps use the LDAP
+server for authentication and for authorization, user information,
+etc., just not as the authoritative data source for
+credentials. GSS/SASL is typically used for this e.g. for Kerberos,
+you can use your ticket to authenticate to the DS - the DS "passes
+through" the authentication to Kerberos. But many apps cannot (or will
+not) use SASL as their authentication mechanism - they must use simple
+cleartext password BINDs. For these applications, it would be very
+useful to have the DS pass through the auth creds to PAM.
+
+BIND Preoperation Plugin for PAM
+
+The PAM BIND Preoperation Plugin intercepts the BIND request and uses
+the PAM API to authenticate the user.
+
+If PAM supports password expiration or creation, how to handle that
+with LDAP? Use standard LDAP mechanisms for password expiration
+handling? Should probably make this configurable - ignore and error
+out vs. sending back an appropriate control or error code.
+
+Configuration
+
+The administrator must be able to configure the following options in
+the plugin. These are the attributes of the objectclass pamConfig
+which is one of the objectclasses of the plugin entry:
+
+* subtrees (list of DNs) - suffixes and/or subtrees to which this applies
+      o pamExcludeSuffix - suffixes to be excluded from checking
+      o pamIncludeSuffix - suffixes to be included in checking and exclude all others
+      o excludes "win" in case of duplicates
+      o default is to apply to all suffixes if no includes or excludes are specified 
+* pamMissingSuffix (string)
+      o ERROR: error and abort if excluded or included suffix does not exist
+      o ALLOW (default): warn if exclude or include is missing, but continue
+      o IGNORE: allow missing suffixes and don't log error 
+* pamFallback (boolean) - if false, if PAM auth fails, the BIND
+	operation fails. If true, if PAM auth fails, the DS will attempt other
+	BIND mechanisms e.g. userPassword.
+* pamSecure (boolean) - if true, a secure connection is required
+* pamIDAttr (string) - The value of this attribute, present in the
+	user's entry, holds the PAM identity of the user - it maps the LDAP
+	identity to the PAM identity
+* pamMapMethod (string)
+      o RDN (default) - uses the value from the leftmost RDN in the BIND DN
+      o ENTRY - gets the value of the PAM identity attribute from the BIND DN entry
+      o DN - uses the full DN string
+      o NOTE: if ENTRY is specified as the method, pamIDAttr must
+		be set in the plugin config entry, and user entries should have the
+		named attribute
+* pamService (string) - the service argument to pam_start()
+* others to keep statistics - TBD 
+
+Design
+
+BIND -> this plugin -> get config -> make sure arguments and state
+conform to config settings -> pam_start() -> pam handshakes -> get
+auth status -> pam_end() -> report BIND status back to LDAP client
+
+The big problem is - how to map the BIND DN to the user name given to
+pam_start(). There are a couple of different ways to do this. One way
+is to just use the value part of the leftmost RDN in the BIND DN
+e.g. if you bound as uid=richm,ou=people,dc=redhat,dc=com, you would
+pass "richm" to PAM. Another way is to specify some attribute that
+must exist in the user's entry and use that value e.g. if my entry
+looks like this:
+
+dn: uid=richm,ou=people,dc=redhat,dc=com
+...
+objectclass: inetOrgPerson
+objectclass: redHatOrgPerson # has the extra attr
+...
+rhuid: rmeggins
+...
+
+"rhuid" would be specified in the PAM plugin config. When I bind as
+uid=richm, the plugin would look up my entry, get the value of the
+"rhuid" attribute, and use that value for PAM.
+
+The password is the same password passed in as the BIND credentials.
+
+We do not need to worry about PAM sessions - all we want to do is use
+PAM to verify the auth creds. However, we could implement sessions -
+we could do a pam_open_session() upon bind success and a
+pam_close_session() upon rebind, unbind, closure, or
+shutdown. However, this adds considerable complexity - must persist
+the pam_handle_t throughout the connection (probably in a connection
+extension), must ensure thread safe access to connection extension
+resources, must ensure clean up in a variety of situations. So, best
+to avoid it if possible.
+
+We may have to worry about different PAM policy in different subtrees
+e.g. maybe for dc=coke,dc=com you want to use the ENTRY map method,
+but for dc=pepsi,dc=com you want to use the RDN method. We could
+probably do this by having the pamMapMethod attr be multivalued, and
+have it's value like this:
+
+pamMapMethod: RDN dc=coke,dc=com
+pamMapMethod: RDN dc=sprite,dc=com
+pamMapMethod: ENTRY dc=pepsi,dc=com
+pamMapMethod: DN (the default for all other suffixes)
+
+The suffix that uses that map method would follow the map method used.
+
+We need to worry about account expiration or lockout e.g. the user's
+credentials are valid but the user has been locked out of his/her
+account, or the password has expired, or something like that. Some of
+this can be handled by LDAP e.g. returning password policy control
+values when the password has expired. So we need to call
+pam_acct_mgmt() somewhere during the pam handshakes and before
+pam_end() to get this information. We also try to return an
+appropriate LDAP error code.
+PAM Error Code			LDAP Error Code				Meaning
+PAM_USER_UNKNOWN		LDAP_NO_SUCH_OBJECT			User ID does not exist
+PAM_AUTH_ERROR			LDAP_INVALID_CREDENTIALS	Password is not correct
+PAM_ACCT_EXPIRED		LDAP_INVALID_CREDENTIALS	User's password is expired
+PAM_PERM_DENIED			LDAP_UNWILLING_TO_PERFORM	User's account is locked out
+PAM_NEW_AUTHTOK_REQD	LDAP_INVALID_CREDENTIALS	User's password has expired and must be renewed
+PAM_MAXTRIES			LDAP_CONSTRAINT_VIOLATION	Max retry count has been exceeded
+Other codes				LDAP_OPERATIONS_ERROR		PAM config is incorrect, machine problem, etc.
+There are three controls we might possibly add to the response:
+* the auth response control - returned upon success - contains the BIND DN (u: not currently supported)
+* LDAP_CONTROL_PWEXPIRED - returned when PAM reports ACCT_EXPIRED or NEW_AUTHTOK_REQD
+* the new password policy control - returned when PAM reports
+	ACCT_EXPIRED, NEW_AUTHTOK_REQD, PERM_DENIED, MAXTRIES The controls can
+	be used to get more information in the case of error (password
+	incorrect or expired?).
+
+The latter two must be requested by the client.
+
+The plugin should report status in attributes of the plugin entry
+e.g. successfuls auth attempts, failed attempts, last pam error code
+and message, etc. We could also do this in an entry under
+cn=monitor. TBD.
+
+Configuration
+
+1. Shutdown the server
+2. Make sure the slapd-instance/config/schema contains the 60pam-config.ldif file
+3. Make sure serverroot/lib/pam-passthru-plugin.so exists
+4. Make sure /etc/pam.d/ldapserver exists and is configured correctly
+5. If the configuration is not already in dse.ldif, append the following to slapd-instance/config/dse.ldif
+
+dn: cn=PAM Pass Through Auth,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+objectclass: pamConfig
+cn: PAM Pass Through Auth
+nsslapd-pluginpath: /opt/ldapserver/lib/pam-passthru-plugin.so
+nsslapd-plugininitfunc: pam_passthruauth_init
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginLoadGlobal: true
+nsslapd-plugin-depends-on-type: database
+pamMissingSuffix: ALLOW
+pamExcludeSuffix: o=NetscapeRoot
+pamExcludeSuffix: cn=config
+pamMapMethod: RDN
+pamFallback: FALSE
+pamSecure: TRUE
+pamService: ldapserver
+
+Make sure there is a blank line at the end. The line with
+o=NetscapeRoot may be omitted if this is not a configuration DS. Then
+restart slapd.
+
+See Also
+PAM API for Linux http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_appl.html
+PAM API for Solaris Writing PAM Applications and Services from the Solaris Security for Developers Guide http://docs.sun.com/app/docs/doc/816-4863/6mb20lvfh?a=view
+PAM API for HP-UX http://docs.hp.com/en/B2355-60103/pam.3.html

+ 20 - 0
ldap/servers/plugins/pam_passthru/config.ldif

@@ -0,0 +1,20 @@
+dn: cn=PAM Pass Through Auth,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+objectclass: pamConfig
+cn: PAM Pass Through Auth
+nsslapd-pluginpath: /opt/ldapserver/lib/pam-passthru-plugin.so
+nsslapd-plugininitfunc: pam_passthruauth_init
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginLoadGlobal: true
+nsslapd-plugin-depends-on-type: database
+pamMissingSuffix: ALLOW
+pamExcludeSuffix: o=NetscapeRoot
+pamExcludeSuffix: cn=config
+pamMapMethod: RDN
+pamFallback: FALSE
+pamSecure: TRUE
+pamService: ldapserver
+

+ 13 - 0
ldap/servers/plugins/pam_passthru/libpam_passthru.def

@@ -0,0 +1,13 @@
+; BEGIN COPYRIGHT BLOCK
+; Copyright 2005 Red Hat
+; All rights reserved.
+; END COPYRIGHT BLOCK
+;
+;
+;
+DESCRIPTION     'Directory Server Pass Through Authentication Plugin'
+;CODE		SHARED READ EXECUTE
+;DATA		SHARED READ WRITE
+EXPORTS
+	passthruauth_init		@1
+	plugin_init_debug_level		@2

+ 104 - 0
ldap/servers/plugins/pam_passthru/pam_passthru.h

@@ -0,0 +1,104 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * pam_passthru.h - Pass Through Authentication shared definitions
+ *
+ */
+
+#ifndef _PAM_PASSTHRU_H_
+#define _PAM_PASSTHRU_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include "portable.h"
+#include "slapi-plugin.h"
+#include <dirlite_strings.h> /* PLUGIN_MAGIC_VENDOR_STR */
+#include "dirver.h"
+#include <nspr.h>
+
+/* Private API: to get slapd_pr_strerror() and SLAPI_COMPONENT_NAME_NSPR */
+#include "slapi-private.h"
+
+/*
+ * macros
+ */
+#define PAM_PASSTHRU_PLUGIN_SUBSYSTEM	"pam_passthru-plugin"   /* for logging */
+
+#define PAM_PASSTHRU_ASSERT( expr )		PR_ASSERT( expr )
+
+#define PAM_PASSTHRU_OP_NOT_HANDLED		0
+#define PAM_PASSTHRU_OP_HANDLED		1
+
+/* #define	PAM_PASSTHRU_VERBOSE_LOGGING	*/
+
+/*
+ * structs
+ */
+typedef struct pam_passthrusuffix {
+    Slapi_DN *pamptsuffix_dn;
+    struct pam_passthrusuffix *pamptsuffix_next;
+} Pam_PassthruSuffix;
+
+#define PAMPT_MISSING_SUFFIX_ERROR  0 /* error out if an included or excluded suffix is missing */
+#define PAMPT_MISSING_SUFFIX_ALLOW  1 /* allow but log missing suffixes */
+#define PAMPT_MISSING_SUFFIX_IGNORE 2 /* allow and don't log missing suffixes */
+
+#define PAMPT_MISSING_SUFFIX_ERROR_STRING "ERROR"
+#define PAMPT_MISSING_SUFFIX_ALLOW_STRING "ALLOW"
+#define PAMPT_MISSING_SUFFIX_IGNORE_STRING "IGNORE"
+
+typedef struct pam_passthruconfig {
+	Slapi_Mutex *lock; /* for config access */
+    Pam_PassthruSuffix *pamptconfig_includes; /* list of suffixes to include in this op */
+    Pam_PassthruSuffix *pamptconfig_excludes; /* list of suffixes to exclude in this op */
+	PRBool pamptconfig_fallback; /* if false, failure here fails entire bind */
+	                             /* if true, failure here falls through to regular bind */
+    PRBool pamptconfig_secure; /* if true, plugin only operates on secure connections */
+	char *pamptconfig_pam_ident_attr; /* name of attribute in user entry for ENTRY map method */
+	int pamptconfig_map_method1; /* how to map the BIND DN to the PAM identity */
+	int pamptconfig_map_method2; /* how to map the BIND DN to the PAM identity */
+	int pamptconfig_map_method3; /* how to map the BIND DN to the PAM identity */
+#define PAMPT_MAP_METHOD_NONE -1 /* do not map */
+#define PAMPT_MAP_METHOD_DN 0 /* use the full DN as the PAM identity */
+#define PAMPT_MAP_METHOD_RDN 1 /* use the leftmost RDN value as the PAM identity */
+#define PAMPT_MAP_METHOD_ENTRY 2 /* use the PAM identity attribute in the entry */
+	char *pamptconfig_service; /* the PAM service name for pam_start() */
+} Pam_PassthruConfig;
+
+#define PAMPT_MAP_METHOD_DN_STRING "DN"
+#define PAMPT_MAP_METHOD_RDN_STRING "RDN"
+#define PAMPT_MAP_METHOD_ENTRY_STRING "ENTRY"
+
+#define PAMPT_MISSING_SUFFIX_ATTR "pamMissingSuffix" /* single valued */
+#define PAMPT_EXCLUDES_ATTR "pamExcludeSuffix" /* multi valued */
+#define PAMPT_INCLUDES_ATTR "pamIncludeSuffix" /* multi valued */
+#define PAMPT_PAM_IDENT_ATTR "pamIDAttr" /* single valued (for now) */
+#define PAMPT_MAP_METHOD_ATTR "pamIDMapMethod" /* single valued */
+#define PAMPT_FALLBACK_ATTR "pamFallback" /* single */
+#define PAMPT_SECURE_ATTR "pamSecure" /* single */
+#define PAMPT_SERVICE_ATTR "pamService" /* single */
+
+/*
+ * public functions
+ */
+
+void pam_passthruauth_set_plugin_identity(void * identity);
+void * pam_passthruauth_get_plugin_identity();
+
+/*
+ * pam_ptconfig.c:
+ */
+int pam_passthru_config( Slapi_Entry *config_e );
+Pam_PassthruConfig *pam_passthru_get_config( void );
+int pam_passthru_check_suffix(Pam_PassthruConfig *cfg, char *binddn);
+
+/*
+ * pam_ptimpl.c
+ */
+int pam_passthru_do_pam_auth(Slapi_PBlock *pb, Pam_PassthruConfig *cfg);
+
+#endif	/* _PAM_PASSTHRU_H_ */

+ 539 - 0
ldap/servers/plugins/pam_passthru/pam_ptconfig.c

@@ -0,0 +1,539 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * ptconfig.c - configuration-related code for Pass Through Authentication
+ *
+ */
+
+#include <plstr.h>
+
+#include "pam_passthru.h"
+
+#define PAM_PT_CONFIG_FILTER "(objectclass=*)"
+#ifndef SLAPI_DSE_RETURNTEXT_SIZE
+#define SLAPI_DSE_RETURNTEXT_SIZE 512	/* for use by callback functions */
+#endif /* SLAPI_DSE_RETURNTEXT_SIZE */
+
+/*
+ * The configuration attributes are contained in the plugin entry e.g.
+ * cn=PAM Pass Through,cn=plugins,cn=config
+ *
+ * Configuration is a two step process.  The first pass is a validation step which
+ * occurs pre-op - check inputs and error out if bad.  The second pass actually
+ * applies the changes to the run time config.
+ */
+
+
+/*
+ * function prototypes
+ */ 
+static int pam_passthru_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+										 int *returncode, char *returntext, void *arg);
+static int pam_passthru_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+										 int *returncode, char *returntext, void *arg);
+static int pam_passthru_search (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+								int *returncode, char *returntext, void *arg)
+{
+	return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+ * static variables
+ */
+/* for now, there is only one configuration and it is global to the plugin  */
+static Pam_PassthruConfig	theConfig;
+static int		inited = 0;
+
+
+static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+						   int *returncode, char *returntext, void *arg)
+{
+	*returncode = LDAP_UNWILLING_TO_PERFORM;
+    return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+/*
+ * Read configuration and create a configuration data structure.
+ * This is called after the server has configured itself so we can check
+ *   for things like collisions between our suffixes and backend's suffixes.
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well).
+ */
+int
+pam_passthru_config(Slapi_Entry *config_e)
+{
+	int returncode = LDAP_SUCCESS;
+	char returntext[SLAPI_DSE_RETURNTEXT_SIZE];
+
+    if ( inited ) {
+		slapi_log_error( SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "only one PAM pass through plugin instance can be used\n" );
+		return( LDAP_PARAM_ERROR );
+    }
+
+	/* initialize fields */
+	if ((theConfig.lock = slapi_new_mutex()) == NULL) {
+	    return( LDAP_LOCAL_ERROR );
+	}
+	/* do not fallback to regular bind */
+	theConfig.pamptconfig_fallback = PR_FALSE;
+	/* require TLS/SSL security */
+	theConfig.pamptconfig_secure = PR_TRUE;
+	/* use the RDN method to derive the PAM identity */
+	theConfig.pamptconfig_map_method1 = PAMPT_MAP_METHOD_RDN;
+	theConfig.pamptconfig_map_method2 = PAMPT_MAP_METHOD_NONE;
+	theConfig.pamptconfig_map_method3 = PAMPT_MAP_METHOD_NONE;
+
+	if (SLAPI_DSE_CALLBACK_OK == pam_passthru_validate_config(NULL, NULL, config_e,
+															  &returncode, returntext, NULL)) {
+		pam_passthru_apply_config(NULL, NULL, config_e,
+								  &returncode, returntext, NULL);
+	}
+
+	/* config DSE must be initialized before we get here */
+	if (returncode == LDAP_SUCCESS) {
+		const char *config_dn = slapi_entry_get_dn_const(config_e);
+		slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+									   PAM_PT_CONFIG_FILTER, pam_passthru_validate_config,NULL);
+		slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, config_dn, LDAP_SCOPE_BASE,
+									   PAM_PT_CONFIG_FILTER, pam_passthru_apply_config,NULL);
+		slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+									   PAM_PT_CONFIG_FILTER, dont_allow_that, NULL);
+		slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+									   PAM_PT_CONFIG_FILTER, dont_allow_that, NULL);
+		slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+									   PAM_PT_CONFIG_FILTER, pam_passthru_search,NULL);
+	}
+
+    inited = 1;
+
+	if (returncode != LDAP_SUCCESS) {
+		slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"Error %d: %s\n", returncode, returntext);
+	}
+
+    return returncode;
+}
+
+static int
+missing_suffix_to_int(char *missing_suffix)
+{
+	int retval = -1; /* -1 is error */
+	if (!PL_strcasecmp(missing_suffix, PAMPT_MISSING_SUFFIX_ERROR_STRING)) {
+		retval = PAMPT_MISSING_SUFFIX_ERROR;
+	} else if (!PL_strcasecmp(missing_suffix, PAMPT_MISSING_SUFFIX_ALLOW_STRING)) {
+		retval = PAMPT_MISSING_SUFFIX_ALLOW;
+	} else if (!PL_strcasecmp(missing_suffix, PAMPT_MISSING_SUFFIX_IGNORE_STRING)) {
+		retval = PAMPT_MISSING_SUFFIX_IGNORE;
+	}
+
+	return retval;
+}
+
+static PRBool
+check_missing_suffix_flag(int val) {
+	if (val == PAMPT_MISSING_SUFFIX_ERROR ||
+		val == PAMPT_MISSING_SUFFIX_ALLOW ||
+		val == PAMPT_MISSING_SUFFIX_IGNORE) {
+		return PR_TRUE;
+	}
+
+	return PR_FALSE;
+}
+
+#define MAKE_STR(x) #x
+static char *get_missing_suffix_values()
+{
+	return MAKE_STR(PAMPT_MISSING_SUFFIX_ERROR) ", " MAKE_STR(PAMPT_MISSING_SUFFIX_ALLOW) ", "
+		MAKE_STR(PAMPT_MISSING_SUFFIX_IGNORE);
+}
+
+static char *get_map_method_values()
+{
+	return PAMPT_MAP_METHOD_DN_STRING " or " PAMPT_MAP_METHOD_RDN_STRING " or " PAMPT_MAP_METHOD_ENTRY_STRING;
+}
+
+static int
+meth_to_int(char **map_method, int *err)
+{
+	char *end;
+	int len;
+	int ret;
+
+	*err = 0;
+	if (!map_method || !*map_method) {
+		return PAMPT_MAP_METHOD_NONE;
+	}
+
+	end = strchr(*map_method, ' ');
+	if (!end) {
+		len = strlen(*map_method);
+	} else {
+		len = end - *map_method;
+	}
+	if (!PL_strncasecmp(*map_method, PAMPT_MAP_METHOD_DN_STRING, len)) {
+		ret = PAMPT_MAP_METHOD_DN;
+	} else if (!PL_strncasecmp(*map_method, PAMPT_MAP_METHOD_RDN_STRING, len)) {
+		ret = PAMPT_MAP_METHOD_RDN;
+	} else if (!PL_strncasecmp(*map_method, PAMPT_MAP_METHOD_ENTRY_STRING, len)) {
+		ret = PAMPT_MAP_METHOD_ENTRY;
+	} else {
+		*err = 1;
+	}
+
+	if (!err) {
+		if (end && *end) {
+			*map_method = end + 1;
+		} else {
+			*map_method = NULL;
+		}
+	}
+
+	return ret;
+}
+
+static int
+parse_map_method(char *map_method, int *one, int *two, int *three, char *returntext)
+{
+	int err = 0;
+	int extra;
+
+	*one = *two = *three = PAMPT_MAP_METHOD_NONE;
+	*one = meth_to_int(&map_method, &err);
+	if (err) {
+		PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+					"The map method in the string [%s] is invalid: must be "
+					"one of %s", map_method, get_map_method_values());
+		return LDAP_UNWILLING_TO_PERFORM;
+	}
+	*two = meth_to_int(&map_method, &err);
+	if (err) {
+		PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+					"The map method in the string [%s] is invalid: must be "
+					"one of %s", map_method, get_map_method_values());
+		return LDAP_UNWILLING_TO_PERFORM;
+	}
+	*three = meth_to_int(&map_method, &err);
+	if (err) {
+		PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+					"The map method in the string [%s] is invalid: must be "
+					"one of %s", map_method, get_map_method_values());
+		return LDAP_UNWILLING_TO_PERFORM;
+	}
+	if (((extra = meth_to_int(&map_method, &err)) != PAMPT_MAP_METHOD_NONE) ||
+		err) {
+		PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+					"Invalid extra text [%s] after last map method",
+					map_method);
+		return LDAP_UNWILLING_TO_PERFORM;		
+	}
+
+	return err;
+}
+		
+/*
+  Validate the pending changes in the e entry.
+*/
+static int 
+pam_passthru_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+	int *returncode, char *returntext, void *arg)
+{
+	char *missing_suffix_str = NULL;
+	int missing_suffix;
+	int ii;
+	char **excludes = NULL;
+	char **includes = NULL;
+	char *pam_ident_attr = NULL;
+	char *map_method = NULL;
+
+	*returncode = LDAP_UNWILLING_TO_PERFORM; /* be pessimistic */
+	/* first, get the missing_suffix flag and validate it */
+	missing_suffix_str = slapi_entry_attr_get_charptr(e, PAMPT_MISSING_SUFFIX_ATTR);
+	if ((missing_suffix = missing_suffix_to_int(missing_suffix_str)) < 0 ||
+		!check_missing_suffix_flag(missing_suffix)) {
+		PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+					"Error: valid values for %s are %s",
+					PAMPT_MISSING_SUFFIX_ATTR, get_missing_suffix_values());
+		goto done;
+	}
+
+	if (missing_suffix != PAMPT_MISSING_SUFFIX_IGNORE) {
+		char **missing_list = NULL;
+		Slapi_DN *comp_dn = slapi_sdn_new();
+
+		/* get the list of excluded suffixes */
+		excludes = slapi_entry_attr_get_charray(e, PAMPT_EXCLUDES_ATTR);
+		for (ii = 0; excludes && excludes[ii]; ++ii) {
+			slapi_sdn_init_dn_byref(comp_dn, excludes[ii]);
+			if (!slapi_be_exist(comp_dn)) {
+				charray_add(&missing_list, slapi_ch_strdup(excludes[ii]));
+			}
+			slapi_sdn_done(comp_dn);
+		}
+
+		/* get the list of included suffixes */
+		includes = slapi_entry_attr_get_charray(e, PAMPT_INCLUDES_ATTR);
+		for (ii = 0; includes && includes[ii]; ++ii) {
+			slapi_sdn_init_dn_byref(comp_dn, includes[ii]);
+			if (!slapi_be_exist(comp_dn)) {
+				charray_add(&missing_list, slapi_ch_strdup(includes[ii]));
+			}
+			slapi_sdn_done(comp_dn);
+		}
+
+		slapi_sdn_free(&comp_dn);
+
+		if (missing_list) {
+			PRUint32 size =
+				PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+							"The following suffixes listed in %s or %s are not present in this "
+							"server: ", PAMPT_EXCLUDES_ATTR, PAMPT_INCLUDES_ATTR);
+			for (ii = 0; missing_list[ii]; ++ii) {
+				if (size < SLAPI_DSE_RETURNTEXT_SIZE) {
+					size += PR_snprintf(returntext+size, SLAPI_DSE_RETURNTEXT_SIZE-size,
+										"%s%s", (ii > 0) ? "; " : "",
+										missing_list[ii]);
+				}
+			}
+			slapi_ch_array_free(missing_list);
+			missing_list = NULL;
+			if (missing_suffix != PAMPT_MISSING_SUFFIX_ERROR) {
+				slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+								"Warning: %s\n", returntext);
+				*returntext = 0; /* log error, don't report back to user */
+			} else {
+				goto done;
+			}
+		}
+	}
+
+	pam_ident_attr = slapi_entry_attr_get_charptr(e, PAMPT_PAM_IDENT_ATTR);
+	map_method = slapi_entry_attr_get_charptr(e, PAMPT_MAP_METHOD_ATTR);
+	if (map_method) {
+		int one, two, three;
+		*returncode = parse_map_method(map_method, &one, &two, &three, returntext);
+		if (!pam_ident_attr &&
+			((one == PAMPT_MAP_METHOD_ENTRY) || (two == PAMPT_MAP_METHOD_ENTRY) ||
+			 (three == PAMPT_MAP_METHOD_ENTRY))) {
+			PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "Error: the %s method"
+						" was specified, but no %s was given",
+						PAMPT_MAP_METHOD_ENTRY_STRING, PAMPT_PAM_IDENT_ATTR);
+			*returncode = LDAP_UNWILLING_TO_PERFORM;
+			goto done;
+		}
+		if (one == two == three == PAMPT_MAP_METHOD_NONE) {
+			PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "Error: no method(s)"
+						" specified for %s, should be one or more of %s",
+						PAMPT_MAP_METHOD_ATTR, get_map_method_values());
+			*returncode = LDAP_UNWILLING_TO_PERFORM;
+			goto done;
+		}
+	}
+
+	/* success */
+	*returncode = LDAP_SUCCESS;
+
+done:
+	slapi_ch_free_string(&map_method);
+	slapi_ch_free_string(&pam_ident_attr);
+	slapi_ch_array_free(excludes);
+	excludes = NULL;
+	slapi_ch_array_free(includes);
+	includes = NULL;
+	slapi_ch_free_string(&missing_suffix_str);
+
+	if (*returncode != LDAP_SUCCESS)
+	{
+		return SLAPI_DSE_CALLBACK_ERROR;
+	}
+	else
+    {
+	    return SLAPI_DSE_CALLBACK_OK;
+    }
+}
+
+static Pam_PassthruSuffix *
+New_Pam_PassthruSuffix(char *suffix)
+{
+	Pam_PassthruSuffix *newone = NULL;
+	if (suffix) {
+		newone = (Pam_PassthruSuffix *)slapi_ch_malloc(sizeof(Pam_PassthruSuffix));
+		newone->pamptsuffix_dn = slapi_sdn_new();
+		slapi_sdn_init_dn_byval(newone->pamptsuffix_dn, suffix);
+		newone->pamptsuffix_next = NULL;
+	}
+	return newone;
+}
+
+static Pam_PassthruSuffix *
+pam_ptconfig_add_suffixes(char **str_list)
+{
+	Pam_PassthruSuffix *head = NULL;
+	Pam_PassthruSuffix *suffixent = NULL;
+
+	if (str_list && *str_list) {
+		int ii;
+		for (ii = 0; str_list[ii]; ++ii) {
+			Pam_PassthruSuffix *tmp = New_Pam_PassthruSuffix(str_list[ii]);
+			if (!suffixent) {
+				head = suffixent = tmp;
+			} else {
+				suffixent->pamptsuffix_next = tmp;
+				suffixent = suffixent->pamptsuffix_next;
+			}
+		}
+	}
+	return head;
+}
+
+static void
+Delete_Pam_PassthruSuffix(Pam_PassthruSuffix *one)
+{
+	if (one) {
+		slapi_sdn_free(&one->pamptsuffix_dn);
+		slapi_ch_free((void **)&one);
+	}
+}
+
+static void
+pam_ptconfig_free_suffixes(Pam_PassthruSuffix *list)
+{
+	while (list) {
+		Pam_PassthruSuffix *next = list->pamptsuffix_next;
+		Delete_Pam_PassthruSuffix(list);
+		list = next;
+	}
+}
+
+/*
+  Apply the pending changes in the e entry to our config struct.
+  validate must have already been called
+*/
+static int 
+pam_passthru_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, 
+	int *returncode, char *returntext, void *arg)
+{
+	char **excludes = NULL;
+	char **includes = NULL;
+	char *new_service = NULL;
+	char *pam_ident_attr = NULL;
+	char *map_method = NULL;
+	int fallback;
+	int secure;
+
+	*returncode = LDAP_SUCCESS;
+
+	pam_ident_attr = slapi_entry_attr_get_charptr(e, PAMPT_PAM_IDENT_ATTR);
+	map_method = slapi_entry_attr_get_charptr(e, PAMPT_MAP_METHOD_ATTR);
+	new_service = slapi_entry_attr_get_charptr(e, PAMPT_SERVICE_ATTR);
+	excludes = slapi_entry_attr_get_charray(e, PAMPT_EXCLUDES_ATTR);
+	includes = slapi_entry_attr_get_charray(e, PAMPT_INCLUDES_ATTR);
+	fallback = slapi_entry_attr_get_int(e, PAMPT_FALLBACK_ATTR);
+	secure = slapi_entry_attr_get_int(e, PAMPT_SECURE_ATTR);
+
+	/* lock config here */
+	slapi_lock_mutex(theConfig.lock);
+
+	theConfig.pamptconfig_fallback = fallback;
+	theConfig.pamptconfig_secure = secure;
+	if (!theConfig.pamptconfig_service ||
+		(new_service && PL_strcmp(theConfig.pamptconfig_service, new_service))) {
+		slapi_ch_free_string(&theConfig.pamptconfig_service);
+		theConfig.pamptconfig_service = new_service;
+		new_service = NULL; /* config now owns memory */
+	}
+
+	/* get the list of excluded suffixes */
+	pam_ptconfig_free_suffixes(theConfig.pamptconfig_excludes);
+	theConfig.pamptconfig_excludes = pam_ptconfig_add_suffixes(excludes);
+
+	/* get the list of included suffixes */
+	pam_ptconfig_free_suffixes(theConfig.pamptconfig_includes);
+	theConfig.pamptconfig_includes = pam_ptconfig_add_suffixes(includes);
+
+	if (!theConfig.pamptconfig_pam_ident_attr ||
+		(pam_ident_attr && PL_strcmp(theConfig.pamptconfig_pam_ident_attr, pam_ident_attr))) {
+		slapi_ch_free_string(&theConfig.pamptconfig_pam_ident_attr);
+		theConfig.pamptconfig_pam_ident_attr = pam_ident_attr;
+		pam_ident_attr = NULL; /* config now owns memory */
+	}
+
+	if (map_method) {
+		parse_map_method(map_method,
+						 &theConfig.pamptconfig_map_method1,
+						 &theConfig.pamptconfig_map_method2,
+						 &theConfig.pamptconfig_map_method3,
+						 NULL);
+	}
+
+	/* unlock config here */
+	slapi_unlock_mutex(theConfig.lock);
+
+	slapi_ch_free_string(&new_service);
+	slapi_ch_free_string(&map_method);
+	slapi_ch_free_string(&pam_ident_attr);
+	slapi_ch_array_free(excludes);
+	slapi_ch_array_free(includes);
+
+	if (*returncode != LDAP_SUCCESS)
+	{
+		return SLAPI_DSE_CALLBACK_ERROR;
+	}
+	else
+    {
+	    return SLAPI_DSE_CALLBACK_OK;
+    }
+}
+
+int
+pam_passthru_check_suffix(Pam_PassthruConfig *cfg, char *binddn)
+{
+	Slapi_DN *comp_dn;
+	Pam_PassthruSuffix *try;
+	int ret = LDAP_SUCCESS;
+
+	comp_dn = slapi_sdn_new();
+	slapi_sdn_init_dn_byref(comp_dn, binddn);
+
+	slapi_lock_mutex(cfg->lock);
+	if (!cfg->pamptconfig_includes && !cfg->pamptconfig_excludes) {
+		goto done; /* NULL means allow */
+	}
+
+	/* exclude trumps include - if suffix is on exclude list, then
+	   deny */
+	for (try = cfg->pamptconfig_excludes; try; try = try->pamptsuffix_next) {
+		if (slapi_sdn_issuffix(comp_dn, try->pamptsuffix_dn)) {
+			ret = LDAP_UNWILLING_TO_PERFORM; /* suffix is excluded */
+			goto done;
+		}
+	}
+
+	/* ok, now flip it - deny access unless dn is on include list */
+	if (cfg->pamptconfig_includes) {
+		ret = LDAP_UNWILLING_TO_PERFORM; /* suffix is excluded */
+		for (try = cfg->pamptconfig_includes; try; try = try->pamptsuffix_next) {
+			if (slapi_sdn_issuffix(comp_dn, try->pamptsuffix_dn)) {
+				ret = LDAP_SUCCESS; /* suffix is included */
+				goto done;
+			}
+		}
+	}
+		
+done:
+	slapi_unlock_mutex(cfg->lock);
+	slapi_sdn_free(&comp_dn);
+
+	return ret;
+}
+
+/*
+ * Get the pass though configuration data.  For now, there is only one
+ * configuration and it is global to the plugin.
+ */
+Pam_PassthruConfig *
+pam_passthru_get_config( void )
+{
+    return( &theConfig );
+}

+ 22 - 0
ldap/servers/plugins/pam_passthru/pam_ptdebug.c

@@ -0,0 +1,22 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * pam_ptdebug.c - debugging-related code for PAM Pass Through Authentication
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "pam_passthru.h"
+
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+	module_ldap_debug = level_ptr;
+}
+#endif

+ 130 - 0
ldap/servers/plugins/pam_passthru/pam_ptdllmain.c

@@ -0,0 +1,130 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+#include "ldap.h"
+#include "lber.h"
+#include "passthru.h"
+
+
+#ifdef _WIN32
+/* Lifted from Q125688
+ * How to Port a 16-bit DLL to a Win32 DLL
+ * on the MSVC 4.0 CD
+ */
+BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
+{
+	WSADATA	wsadata;
+
+	switch (fdwReason)
+	{
+	case DLL_PROCESS_ATTACH:
+		 /* Code from LibMain inserted here.  Return TRUE to keep the
+		    DLL loaded or return FALSE to fail loading the DLL.
+
+		    You may have to modify the code in your original LibMain to
+		    account for the fact that it may be called more than once.
+		    You will get one DLL_PROCESS_ATTACH for each process that
+		    loads the DLL. This is different from LibMain which gets
+		    called only once when the DLL is loaded. The only time this
+		    is critical is when you are using shared data sections.
+		    If you are using shared data sections for statically
+		    allocated data, you will need to be careful to initialize it
+		    only once. Check your code carefully.
+
+		    Certain one-time initializations may now need to be done for
+		    each process that attaches. You may also not need code from
+		    your original LibMain because the operating system may now
+		    be doing it for you.
+		 */
+		/*
+		 * 16 bit code calls UnlockData()
+		 * which is mapped to UnlockSegment in windows.h
+		 * in 32 bit world UnlockData is not defined anywhere
+		 * UnlockSegment is mapped to GlobalUnfix in winbase.h
+		 * and the docs for both UnlockSegment and GlobalUnfix say 
+		 * ".. function is oboslete.  Segments have no meaning 
+		 *  in the 32-bit environment".  So we do nothing here.
+		 */
+
+		if( errno = WSAStartup(0x0101, &wsadata ) != 0 )
+			return FALSE;
+
+		break;
+
+	case DLL_THREAD_ATTACH:
+		/* Called each time a thread is created in a process that has
+		   already loaded (attached to) this DLL. Does not get called
+		   for each thread that exists in the process before it loaded
+		   the DLL.
+
+		   Do thread-specific initialization here.
+		*/
+		break;
+
+	case DLL_THREAD_DETACH:
+		/* Same as above, but called when a thread in the process
+		   exits.
+
+		   Do thread-specific cleanup here.
+		*/
+		break;
+
+	case DLL_PROCESS_DETACH:
+		/* Code from _WEP inserted here.  This code may (like the
+		   LibMain) not be necessary.  Check to make certain that the
+		   operating system is not doing it for you.
+		*/
+		WSACleanup();
+
+		break;
+	}
+	/* The return value is only used for DLL_PROCESS_ATTACH; all other
+	conditions are ignored.  */
+	return TRUE;   // successful DLL_PROCESS_ATTACH
+}
+#else
+int CALLBACK
+LibMain( HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine )
+{
+	/*UnlockData( 0 );*/
+ 	return( 1 );
+}
+#endif
+
+#ifdef LDAP_DEBUG
+#ifndef _WIN32
+#include <stdarg.h>
+#include <stdio.h>
+
+void LDAPDebug( int level, char* fmt, ... )
+{
+	static char debugBuf[1024];
+
+	if (module_ldap_debug && (*module_ldap_debug & level))
+	{
+		va_list ap;
+		va_start (ap, fmt);
+		_snprintf (debugBuf, sizeof(debugBuf), fmt, ap);
+		va_end (ap);
+
+		OutputDebugString (debugBuf);
+	}
+}
+#endif
+#endif
+
+#ifndef _WIN32
+
+/* The 16-bit version of the RTL does not implement perror() */
+
+#include <stdio.h>
+
+void perror( const char *msg )
+{
+	char buf[128];
+	wsprintf( buf, "%s: error %d\n", msg, WSAGetLastError()) ;
+	OutputDebugString( buf );
+}
+
+#endif

+ 397 - 0
ldap/servers/plugins/pam_passthru/pam_ptimpl.c

@@ -0,0 +1,397 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include <security/pam_appl.h>
+
+#include "pam_passthru.h"
+
+
+/* Utility struct to wrap strings to avoid mallocs if possible - use
+   stack allocated string space */
+#define MY_STATIC_BUF_SIZE 256
+typedef struct my_str_buf {
+	char fixbuf[MY_STATIC_BUF_SIZE];
+	char *str;
+} MyStrBuf;
+
+static char *
+init_my_str_buf(MyStrBuf *buf, const char *s)
+{
+	if (s && (strlen(s) < MY_STATIC_BUF_SIZE)) {
+		strcpy(buf->fixbuf, s);
+		buf->str = buf->fixbuf;
+	} else {
+		buf->str = slapi_ch_strdup(s);
+	}
+
+	return buf->str;
+}
+
+static void
+delete_my_str_buf(MyStrBuf *buf)
+{
+	if (buf->str != buf->fixbuf) {
+		slapi_ch_free_string(&buf->str);
+	}
+}
+
+/* for third arg to pam_start */
+struct my_pam_conv_str {
+	Slapi_PBlock *pb;
+	char *pam_identity;
+};
+
+/*
+ * Get the PAM identity from the value of the leftmost RDN in the BIND DN.
+ */
+static char *
+derive_from_bind_dn(Slapi_PBlock *pb, char *binddn, MyStrBuf *pam_id)
+{
+	Slapi_RDN *rdn;
+	char *type = NULL;
+	char *value = NULL;
+
+	rdn = slapi_rdn_new_dn(binddn);
+	slapi_rdn_get_first(rdn, &type, &value);
+	init_my_str_buf(pam_id, value);
+	slapi_rdn_free(&rdn);
+
+	return pam_id->str;
+}
+
+static char *
+derive_from_bind_entry(Slapi_PBlock *pb, char *binddn, MyStrBuf *pam_id, char *map_ident_attr)
+{
+	char buf[BUFSIZ];
+	Slapi_Entry *entry = NULL;
+	Slapi_DN *sdn = slapi_sdn_new_dn_byref(binddn);
+	char *attrs[] = { map_ident_attr, NULL };
+	int rc = slapi_search_internal_get_entry(sdn, attrs, &entry,
+											 pam_passthruauth_get_plugin_identity());
+
+	slapi_sdn_free(&sdn);
+
+	if (rc != LDAP_SUCCESS) {
+		slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"Could not find BIND dn %s (error %d - %s)\n",
+						escape_string(binddn, buf), rc, ldap_err2string(rc));
+		init_my_str_buf(pam_id, NULL);
+   	} else if (NULL == entry) {
+		slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"Could not find entry for BIND dn %s\n",
+						escape_string(binddn, buf));
+		init_my_str_buf(pam_id, NULL);
+	} else {
+		char *val = slapi_entry_attr_get_charptr(entry, map_ident_attr);
+		init_my_str_buf(pam_id, val);
+		slapi_ch_free_string(&val);
+	}
+
+	slapi_entry_free(entry);
+
+	return pam_id->str;
+}
+
+static void
+report_pam_error(char *str, int rc, pam_handle_t *pam_handle)
+{
+	if (rc != PAM_SUCCESS) {
+		slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"Error from PAM %s (%d: %s)\n",
+						str, rc, pam_strerror(pam_handle, rc));
+	}
+}
+
+/* returns a berval value as a null terminated string */
+static char *strdupbv(struct berval *bv)
+{
+	char *str = malloc(bv->bv_len+1);
+	memcpy(str, bv->bv_val, bv->bv_len);
+	str[bv->bv_len] = 0;
+	return str;
+}
+
+static void
+free_pam_response(int nresp, struct pam_response *resp)
+{
+	int ii;
+	for (ii = 0; ii < nresp; ++ii) {
+		if (resp[ii].resp) {
+			free(resp[ii].resp);
+		}
+	}
+	free(resp);
+}
+
+/*
+ * This is the conversation function passed into pam_start().  This is what sets the password
+ * that PAM uses to authenticate.  This function is sort of stupid - it assumes all echo off
+ * or binary prompts are for the password, and other prompts are for the username.  Time will
+ * tell if this is actually the case.
+ */
+static int
+pam_conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *mydata)
+{
+	int ii;
+	struct berval *creds;
+	struct my_pam_conv_str *my_data = (struct my_pam_conv_str *)mydata;
+    struct pam_response *reply;
+	int ret = PAM_SUCCESS;
+
+    if (num_msg <= 0) {
+		return PAM_CONV_ERR;
+	}
+
+	/* empty reply structure */
+    reply = (struct pam_response *)calloc(num_msg,
+										  sizeof(struct pam_response));
+	slapi_pblock_get( my_data->pb, SLAPI_BIND_CREDENTIALS, &creds ); /* the password */
+	for (ii = 0; ii < num_msg; ++ii) {
+		slapi_log_error(SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"pam msg [%d] = %d %s\n", ii, msg[ii]->msg_style,
+						msg[ii]->msg);
+		/* hard to tell what prompt is for . . . */
+		/* assume prompts for password are either BINARY or ECHO_OFF */
+		if ((msg[ii]->msg_style == PAM_PROMPT_ECHO_OFF) ||
+			(msg[ii]->msg_style == PAM_BINARY_PROMPT)) {
+			reply[ii].resp = strdupbv(creds);
+		} else if (msg[ii]->msg_style == PAM_PROMPT_ECHO_ON) { /* assume username */
+			reply[ii].resp = strdup(my_data->pam_identity);
+		} else if (msg[ii]->msg_style == PAM_ERROR_MSG) {
+			slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+							"pam msg [%d] error [%s]\n", ii, msg[ii]->msg);
+		} else if (msg[ii]->msg_style == PAM_TEXT_INFO) {
+			slapi_log_error(SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+							"pam msg [%d] text info [%s]\n", ii, msg[ii]->msg);
+		} else {
+			slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+							"Error: unknown pam message type (%d: %s)\n",
+							msg[ii]->msg_style, msg[ii]->msg);
+			ret = PAM_CONV_ERR;
+		}
+	}
+
+	if (ret == PAM_CONV_ERR) {
+		free_pam_response(num_msg, reply);
+		reply = NULL;
+	}
+
+	*resp = reply;
+
+	return ret;
+}
+
+/*
+ * Do the actual work of authenticating with PAM. First, get the PAM identity
+ * based on the method used to convert the BIND identity to the PAM identity.
+ * Set up the structures that pam_start needs and call pam_start().  After
+ * that, call pam_authenticate and pam_acct_mgmt.  Check the various return
+ * values from these functions and map them to their corresponding LDAP BIND
+ * return values.  Return the appropriate LDAP error code.
+ * This function will also set the appropriate LDAP response controls in
+ * the given pblock.
+ * Since this function can be called multiple times, we only want to return
+ * the errors and controls to the user if this is the final call, so the
+ * final_method parameter tells us if this is the last one.  Coupled with
+ * the fallback argument, we can tell if we are able to send the response
+ * back to the client.
+ */
+static int
+do_one_pam_auth(
+	Slapi_PBlock *pb,
+	int method, /* get pam identity from ENTRY, RDN, or DN */
+	PRBool final_method, /* which method is the last one to try */
+	char *pam_service, /* name of service for pam_start() */
+	char *map_ident_attr, /* for ENTRY method, name of attribute holding pam identity */
+	PRBool fallback, /* if true, failure here should fallback to regular bind */
+	int pw_response_requested /* do we need to send pwd policy resp control */
+)
+{
+	MyStrBuf pam_id;
+	char *binddn = NULL;
+	int rc;
+	int retcode = LDAP_SUCCESS;
+	pam_handle_t *pam_handle;
+	struct my_pam_conv_str my_data;
+	struct pam_conv my_pam_conv = {pam_conv_func, NULL};
+	char buf[BUFSIZ]; /* for error messages */
+	char *errmsg = NULL; /* free with PR_smprintf_free */
+
+	slapi_pblock_get( pb, SLAPI_BIND_TARGET, &binddn );
+
+	if (method == PAMPT_MAP_METHOD_RDN) {
+		derive_from_bind_dn(pb, binddn, &pam_id);
+	} else if (method == PAMPT_MAP_METHOD_ENTRY) {
+		derive_from_bind_entry(pb, binddn, &pam_id, map_ident_attr);
+	} else {
+		init_my_str_buf(&pam_id, binddn);
+	}
+
+	/* do the pam stuff */
+	my_data.pb = pb;
+	my_data.pam_identity = pam_id.str;
+	my_pam_conv.appdata_ptr = &my_data;
+	rc = pam_start(pam_service, pam_id.str, &my_pam_conv, &pam_handle);
+	report_pam_error("during pam_start", rc, pam_handle);
+
+	if (rc == PAM_SUCCESS) {
+		/* use PAM_SILENT - there is no user interaction at this point */
+		rc = pam_authenticate(pam_handle, 0);
+		report_pam_error("during pam_authenticate", rc, pam_handle);
+		/* check different types of errors here */
+		if (rc == PAM_USER_UNKNOWN) {
+			errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM",
+								 pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */
+		} else if (rc == PAM_AUTH_ERR) {
+			errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]",
+								 pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */
+		} else if (rc == PAM_MAXTRIES) {
+			errmsg = PR_smprintf("Authentication retry limit exceeded in PAM for "
+								 "user id [%s], bind DN [%s]",
+								 pam_id.str, escape_string(binddn, buf));
+			if (pw_response_requested) {
+				slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED);
+			}
+			retcode = LDAP_CONSTRAINT_VIOLATION; /* max retries */
+		} else if (rc != PAM_SUCCESS) {
+			errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]",
+								 pam_strerror(pam_handle, rc), pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_OPERATIONS_ERROR; /* pam config or network problem */
+		}
+	}
+
+	/* if user authenticated successfully, see if there is anything we need
+	   to report back w.r.t. password or account lockout */
+	if (rc == PAM_SUCCESS) {
+		rc = pam_acct_mgmt(pam_handle, 0);
+		report_pam_error("during pam_acct_mgmt", rc, pam_handle);
+		/* check different types of errors here */
+		if (rc == PAM_USER_UNKNOWN) {
+			errmsg = PR_smprintf("User id [%s] for bind DN [%s] does not exist in PAM",
+								 pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_NO_SUCH_OBJECT; /* user unknown */
+		} else if (rc == PAM_AUTH_ERR) {
+			errmsg = PR_smprintf("Invalid PAM password for user id [%s], bind DN [%s]",
+								 pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_INVALID_CREDENTIALS; /* invalid creds */
+		} else if (rc == PAM_PERM_DENIED) {
+			errmsg = PR_smprintf("Access denied for PAM user id [%s], bind DN [%s]"
+								 " - see administrator",
+								 pam_id.str, escape_string(binddn, buf));
+			if (pw_response_requested) {
+				slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_ACCTLOCKED);
+			}
+			retcode = LDAP_UNWILLING_TO_PERFORM;
+		} else if (rc == PAM_ACCT_EXPIRED) {
+			errmsg = PR_smprintf("Expired PAM password for user id [%s], bind DN [%s]: "
+								 "reset required",
+								 pam_id.str, escape_string(binddn, buf));
+			slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
+			if (pw_response_requested) {
+				slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED);
+			}
+			retcode = LDAP_INVALID_CREDENTIALS;
+		} else if (rc == PAM_NEW_AUTHTOK_REQD) { /* handled same way as ACCT_EXPIRED */
+			errmsg = PR_smprintf("Expired PAM password for user id [%s], bind DN [%s]: "
+								 "reset required",
+								 pam_id.str, escape_string(binddn, buf));
+			slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
+			if (pw_response_requested) {
+				slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED);
+			}
+			retcode = LDAP_INVALID_CREDENTIALS;
+		} else if (rc != PAM_SUCCESS) {
+			errmsg = PR_smprintf("Unknown PAM error [%s] for user id [%s], bind DN [%s]",
+								 pam_strerror(pam_handle, rc), pam_id.str, escape_string(binddn, buf));
+			retcode = LDAP_OPERATIONS_ERROR; /* unknown */
+		}
+	}
+
+	rc = pam_end(pam_handle, rc);
+	report_pam_error("during pam_end", rc, pam_handle);
+
+	delete_my_str_buf(&pam_id);
+
+	if ((retcode == LDAP_SUCCESS) && (rc != PAM_SUCCESS)) {
+		errmsg = PR_smprintf("Unknown PAM error [%d] for user id [%d], bind DN [%s]",
+							 rc, pam_id.str, escape_string(binddn, buf));
+		retcode = LDAP_OPERATIONS_ERROR;
+	}
+
+	if (retcode != LDAP_SUCCESS) {
+		slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						"%s\n", errmsg);
+		if (final_method && !fallback) {
+			slapi_send_ldap_result(pb, retcode, NULL, errmsg, 0, NULL);
+		}
+	}
+
+	if (errmsg) {
+		PR_smprintf_free(errmsg);
+	}
+
+	return retcode;
+}
+
+/*
+ * Entry point into the PAM auth code.  Shields the rest of the app
+ * from PAM API code.  Get our config params, then call the actual
+ * code that does the PAM auth.  Can call that code up to 3 times,
+ * depending on what methods are set in the config.
+ */
+int
+pam_passthru_do_pam_auth(Slapi_PBlock *pb, Pam_PassthruConfig *cfg)
+{
+	int rc = LDAP_SUCCESS;
+	MyStrBuf pam_id_attr; /* avoid malloc if possible */
+	MyStrBuf pam_service; /* avoid malloc if possible */
+	int method1, method2, method3;
+	PRBool final_method;
+	PRBool fallback = PR_FALSE;
+	int pw_response_requested;
+	LDAPControl **reqctrls = NULL;
+
+	/* first lock and get the methods and other info */
+	/* we do this so we can acquire and release the lock quickly to
+	   avoid potential deadlocks */
+	slapi_lock_mutex(cfg->lock);
+	method1 = cfg->pamptconfig_map_method1;
+	method2 = cfg->pamptconfig_map_method2;
+	method3 = cfg->pamptconfig_map_method3;
+
+	init_my_str_buf(&pam_id_attr, cfg->pamptconfig_pam_ident_attr);
+	init_my_str_buf(&pam_service, cfg->pamptconfig_service);
+
+	fallback = cfg->pamptconfig_fallback;
+
+	slapi_unlock_mutex(cfg->lock);
+
+	slapi_pblock_get (pb, SLAPI_REQCONTROLS, &reqctrls);
+	slapi_pblock_get (pb, SLAPI_PWPOLICY, &pw_response_requested);
+
+	/* figure out which method is the last one - we only return error codes, controls
+	   to the client and send a response on the last method */
+
+	final_method = (method2 == PAMPT_MAP_METHOD_NONE);
+	rc = do_one_pam_auth(pb, method1, final_method, pam_service.str, pam_id_attr.str, fallback,
+						 pw_response_requested);
+	if ((rc != LDAP_SUCCESS) && !final_method) {
+		final_method = (method3 == PAMPT_MAP_METHOD_NONE);
+		rc = do_one_pam_auth(pb, method2, final_method, pam_service.str, pam_id_attr.str, fallback,
+							 pw_response_requested);
+		if ((rc != LDAP_SUCCESS) && !final_method) {
+			final_method = PR_TRUE;
+			rc = do_one_pam_auth(pb, method3, final_method, pam_service.str, pam_id_attr.str, fallback,
+								 pw_response_requested);
+		}
+	}
+
+	delete_my_str_buf(&pam_id_attr);
+	delete_my_str_buf(&pam_service);
+
+	return rc;
+}

+ 221 - 0
ldap/servers/plugins/pam_passthru/pam_ptpreop.c

@@ -0,0 +1,221 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2005 Red Hat
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/*
+ * pamptpreop.c - bind pre-operation plugin for Pass Through Authentication to PAM
+ *
+ */
+
+#include "pam_passthru.h"
+
+static Slapi_PluginDesc pdesc = { "pam_passthruauth",  PLUGIN_MAGIC_VENDOR_STR, PRODUCTTEXT,
+	"PAM pass through authentication plugin" };
+
+static void * pam_passthruauth_plugin_identity = NULL;
+
+/*
+ * function prototypes
+ */
+static int pam_passthru_bindpreop( Slapi_PBlock *pb );
+static int pam_passthru_bindpreop_start( Slapi_PBlock *pb );
+static int pam_passthru_bindpreop_close( Slapi_PBlock *pb );
+
+
+/*
+** Plugin identity mgmt
+*/
+
+void pam_passthruauth_set_plugin_identity(void * identity) 
+{
+	pam_passthruauth_plugin_identity=identity;
+}
+
+void * pam_passthruauth_get_plugin_identity()
+{
+	return pam_passthruauth_plugin_identity;
+}
+
+/*
+ * Plugin initialization function (which must be listed in the appropriate
+ * slapd config file).
+ */
+int
+pam_passthruauth_init( Slapi_PBlock *pb )
+{
+    PAM_PASSTHRU_ASSERT( pb != NULL );
+
+    slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+	    "=> pam_passthruauth_init\n" );
+
+    if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+		    (void *)SLAPI_PLUGIN_VERSION_01 ) != 0
+	    || slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+		    (void *)&pdesc ) != 0
+	    || slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN,
+		    (void *)pam_passthru_bindpreop_start ) != 0
+	    || slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN,
+		    (void *)pam_passthru_bindpreop ) != 0
+	    || slapi_pblock_set( pb, SLAPI_PLUGIN_CLOSE_FN,
+		    (void *)pam_passthru_bindpreop_close ) != 0  ) {
+	slapi_log_error( SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+		"pam_passthruauth_init failed\n" );
+	return( -1 );
+    }
+
+    slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+	"<= pam_passthruauth_init succeeded\n" );
+
+    return( 0 );
+}
+
+/*
+ * pam_passthru_bindpreop_start() is called before the directory server
+ * is fully up.  We parse our configuration and initialize any mutexes, etc.
+ */
+static int
+pam_passthru_bindpreop_start( Slapi_PBlock *pb )
+{
+	int rc;
+	Slapi_Entry *config_e = NULL; /* entry containing plugin config */
+
+    PAM_PASSTHRU_ASSERT( pb != NULL );
+
+    slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+	    "=> pam_passthru_bindpreop_start\n" );
+
+    if ( slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &config_e ) != 0 ) {
+		slapi_log_error( SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "missing config entry\n" );
+		return( -1 );
+    }
+
+    if (( rc = pam_passthru_config( config_e )) != LDAP_SUCCESS ) {
+		slapi_log_error( SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "configuration failed (%s)\n", ldap_err2string( rc ));
+		return( -1 );
+    }
+
+    return( 0 );
+}
+
+
+/*
+ * Called right before the Directory Server shuts down.
+ */
+static int
+pam_passthru_bindpreop_close( Slapi_PBlock *pb )
+{
+    PAM_PASSTHRU_ASSERT( pb != NULL );
+
+    slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+	    "=> pam_passthru_bindpreop_close\n" );
+
+    return( 0 );
+}
+
+
+static int
+pam_passthru_bindpreop( Slapi_PBlock *pb )
+{
+    int rc, method;
+    char *normbinddn, *errmsg = NULL;
+    Pam_PassthruConfig	*cfg;
+    struct berval	*creds;
+	int retcode = PAM_PASSTHRU_OP_NOT_HANDLED;
+
+    PAM_PASSTHRU_ASSERT( pb != NULL );
+
+    slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+	    "=> pam_passthru_bindpreop\n" );
+
+    /*
+     * retrieve parameters for bind operation
+     */
+    if ( slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 ||
+		 slapi_pblock_get( pb, SLAPI_BIND_TARGET, &normbinddn ) != 0 ||
+		 slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &creds ) != 0 ) {
+		slapi_log_error( SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "<= not handled (unable to retrieve bind parameters)\n" );
+		return retcode;
+    }
+
+    /*
+     * We only handle simple bind requests that include non-NULL binddn and
+     * credentials.  Let the Directory Server itself handle everything else.
+     */
+    if ( method != LDAP_AUTH_SIMPLE || *normbinddn == '\0' ||
+		 creds->bv_len == 0 ) {
+		slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "<= not handled (not simple bind or NULL dn/credentials)\n" );
+		return retcode;
+    }
+
+	/* get the config */
+	cfg = pam_passthru_get_config();
+
+	/* don't lock mutex here - simple integer access - assume atomic */
+	if (cfg->pamptconfig_secure) { /* is a secure connection required? */
+		int is_ssl = 0;
+		slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl);
+		if (!is_ssl) {
+			slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+							 "<= connection not secure (secure connection required; check config)");
+			return retcode;
+		}
+	}
+
+    /*
+     * Check to see if the target DN is one we should "pass through" to
+     * PAM
+     */
+    if ( pam_passthru_check_suffix( cfg, normbinddn ) != LDAP_SUCCESS ) {
+		slapi_log_error( SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+						 "<= not handled (not one of our suffixes)\n" );
+		return retcode;
+    }
+
+    /*
+     * We are now committed to handling this bind request.
+     * Chain it off to PAM
+     */
+	rc = pam_passthru_do_pam_auth(pb, cfg);
+
+    /*
+     * If bind succeeded, change authentication information associated
+     * with this connection.
+     */
+    if (rc == LDAP_SUCCESS) {
+        char *ndn = slapi_ch_strdup(normbinddn);
+        if ((slapi_pblock_set(pb, SLAPI_CONN_DN, ndn) != 0) ||
+			(slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD,
+							  SLAPD_AUTH_SIMPLE) != 0)) {
+            slapi_ch_free_string(&ndn);
+            rc = LDAP_OPERATIONS_ERROR;
+            errmsg = "unable to set connection DN or AUTHTYPE";
+            slapi_log_error(SLAPI_LOG_FATAL, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+							"%s\n", errmsg);
+        } else {
+			LDAPControl **reqctrls = NULL;
+			slapi_pblock_get(pb, SLAPI_REQCONTROLS, &reqctrls);
+			if (slapi_control_present(reqctrls, LDAP_CONTROL_AUTH_REQUEST, NULL, NULL)) {
+				slapi_add_auth_response_control(pb, ndn);
+			}
+		}
+    }
+
+	if (rc == LDAP_SUCCESS) {
+		/* we are handling the result */
+		slapi_send_ldap_result(pb, rc, NULL, errmsg, 0, NULL);
+		/* tell bind code we handled the result */
+		retcode = PAM_PASSTHRU_OP_HANDLED;
+	} else if (!cfg->pamptconfig_fallback) {
+		/* tell bind code we already sent back the error result in pam_ptimpl.c */
+		retcode = PAM_PASSTHRU_OP_HANDLED;
+	}
+
+    slapi_log_error(SLAPI_LOG_PLUGIN, PAM_PASSTHRU_PLUGIN_SUBSYSTEM,
+					"<= handled (error %d - %s)\n", rc, ldap_err2string(rc));
+
+    return retcode;
+}