diff options
author | Rich Megginson <rmeggins@redhat.com> | 2005-02-05 01:30:00 +0000 |
---|---|---|
committer | Rich Megginson <rmeggins@redhat.com> | 2005-02-05 01:30:00 +0000 |
commit | 9eb2b56ec631aa3fffc9a80afcbe40f6cc645d8a (patch) | |
tree | fb1d804bace42936ec86cab5f1cc55338a94b8e7 | |
parent | ca998e8f9dfa4e306433eb58b139825684c7fc0a (diff) | |
download | ds-9eb2b56ec631aa3fffc9a80afcbe40f6cc645d8a.tar.gz ds-9eb2b56ec631aa3fffc9a80afcbe40f6cc645d8a.tar.xz ds-9eb2b56ec631aa3fffc9a80afcbe40f6cc645d8a.zip |
checkin first version of pam plugin
-rw-r--r-- | ldap/servers/plugins/pam_passthru/Makefile | 93 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/README | 184 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/config.ldif | 20 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/libpam_passthru.def | 13 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_passthru.h | 104 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_ptconfig.c | 539 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_ptdebug.c | 22 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_ptdllmain.c | 130 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_ptimpl.c | 397 | ||||
-rw-r--r-- | ldap/servers/plugins/pam_passthru/pam_ptpreop.c | 221 |
10 files changed, 1723 insertions, 0 deletions
diff --git a/ldap/servers/plugins/pam_passthru/Makefile b/ldap/servers/plugins/pam_passthru/Makefile new file mode 100644 index 00000000..c71fdea7 --- /dev/null +++ b/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 diff --git a/ldap/servers/plugins/pam_passthru/README b/ldap/servers/plugins/pam_passthru/README new file mode 100644 index 00000000..efbc48a3 --- /dev/null +++ b/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 diff --git a/ldap/servers/plugins/pam_passthru/config.ldif b/ldap/servers/plugins/pam_passthru/config.ldif new file mode 100644 index 00000000..2dd9065c --- /dev/null +++ b/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 + diff --git a/ldap/servers/plugins/pam_passthru/libpam_passthru.def b/ldap/servers/plugins/pam_passthru/libpam_passthru.def new file mode 100644 index 00000000..02c2f0f7 --- /dev/null +++ b/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 diff --git a/ldap/servers/plugins/pam_passthru/pam_passthru.h b/ldap/servers/plugins/pam_passthru/pam_passthru.h new file mode 100644 index 00000000..5b437880 --- /dev/null +++ b/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_ */ diff --git a/ldap/servers/plugins/pam_passthru/pam_ptconfig.c b/ldap/servers/plugins/pam_passthru/pam_ptconfig.c new file mode 100644 index 00000000..4ba28f9c --- /dev/null +++ b/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 ); +} diff --git a/ldap/servers/plugins/pam_passthru/pam_ptdebug.c b/ldap/servers/plugins/pam_passthru/pam_ptdebug.c new file mode 100644 index 00000000..dec8632d --- /dev/null +++ b/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 diff --git a/ldap/servers/plugins/pam_passthru/pam_ptdllmain.c b/ldap/servers/plugins/pam_passthru/pam_ptdllmain.c new file mode 100644 index 00000000..28d8d805 --- /dev/null +++ b/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 diff --git a/ldap/servers/plugins/pam_passthru/pam_ptimpl.c b/ldap/servers/plugins/pam_passthru/pam_ptimpl.c new file mode 100644 index 00000000..d7558af3 --- /dev/null +++ b/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; +} diff --git a/ldap/servers/plugins/pam_passthru/pam_ptpreop.c b/ldap/servers/plugins/pam_passthru/pam_ptpreop.c new file mode 100644 index 00000000..71ee4973 --- /dev/null +++ b/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; +} |