From 34b95c520ea6122f7116302726d380d8a4d577b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 28 Jan 2021 12:30:47 +0000 Subject: [PATCH] ITS#9438 Add remoteauth overlay --- configure.ac | 17 + doc/man/man5/slapo-remoteauth.5 | 160 +++++ servers/slapd/bconfig.c | 2 + servers/slapd/overlays/Makefile.in | 4 + servers/slapd/overlays/remoteauth.c | 996 ++++++++++++++++++++++++++ tests/data/remoteauth/config.ldif | 21 + tests/data/remoteauth/default_domain | 3 + tests/data/remoteauth/remoteauth.conf | 21 + tests/run.in | 6 +- tests/scripts/conf.sh | 1 + tests/scripts/defines.sh | 1 + tests/scripts/test082-remoteauth | 417 +++++++++++ 12 files changed, 1647 insertions(+), 2 deletions(-) create mode 100644 doc/man/man5/slapo-remoteauth.5 create mode 100644 servers/slapd/overlays/remoteauth.c create mode 100644 tests/data/remoteauth/config.ldif create mode 100644 tests/data/remoteauth/default_domain create mode 100644 tests/data/remoteauth/remoteauth.conf create mode 100755 tests/scripts/test082-remoteauth diff --git a/configure.ac b/configure.ac index a2b129755b..f1349670c1 100644 --- a/configure.ac +++ b/configure.ac @@ -353,6 +353,7 @@ Overlays="accesslog \ ppolicy \ proxycache \ refint \ + remoteauth \ retcode \ rwm \ seqmod \ @@ -393,6 +394,8 @@ OL_ARG_ENABLE(proxycache, [AS_HELP_STRING([--enable-proxycache], [Proxy Cache ov no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(refint, [AS_HELP_STRING([--enable-refint], [Referential Integrity overlay])], no, [no yes mod], ol_enable_overlays) +OL_ARG_ENABLE(remoteauth, [AS_HELP_STRING([--enable-remoteauth], [Deferred Authentication overlay])], + no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(retcode, [AS_HELP_STRING([--enable-retcode], [Return Code testing overlay])], no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(rwm, [AS_HELP_STRING([--enable-rwm], [Rewrite/Remap overlay])], @@ -569,6 +572,7 @@ BUILD_MEMBEROF=no BUILD_PPOLICY=no BUILD_PROXYCACHE=no BUILD_REFINT=no +BUILD_REMOTEAUTH=no BUILD_RETCODE=no BUILD_RWM=no BUILD_SEQMOD=no @@ -2861,6 +2865,18 @@ if test "$ol_enable_refint" != no ; then AC_DEFINE_UNQUOTED(SLAPD_OVER_REFINT,$MFLAG,[define for Referential Integrity overlay]) fi +if test "$ol_enable_remoteauth" != no ; then + BUILD_REMOTEAUTH=$ol_enable_remoteauth + if test "$ol_enable_remoteauth" = mod ; then + MFLAG=SLAPD_MOD_DYNAMIC + SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS remoteauth.la" + else + MFLAG=SLAPD_MOD_STATIC + SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS remoteauth.o" + fi + AC_DEFINE_UNQUOTED(SLAPD_OVER_REMOTEAUTH,$MFLAG,[define for Deferred Authentication overlay]) +fi + if test "$ol_enable_retcode" != no ; then BUILD_RETCODE=$ol_enable_retcode if test "$ol_enable_retcode" = mod ; then @@ -3033,6 +3049,7 @@ dnl overlays AC_SUBST(BUILD_PPOLICY) AC_SUBST(BUILD_PROXYCACHE) AC_SUBST(BUILD_REFINT) + AC_SUBST(BUILD_REMOTEAUTH) AC_SUBST(BUILD_RETCODE) AC_SUBST(BUILD_RWM) AC_SUBST(BUILD_SEQMOD) diff --git a/doc/man/man5/slapo-remoteauth.5 b/doc/man/man5/slapo-remoteauth.5 new file mode 100644 index 0000000000..29b2f3863f --- /dev/null +++ b/doc/man/man5/slapo-remoteauth.5 @@ -0,0 +1,160 @@ +.TH SLAPO-REMOTEAUTH 5 "RELEASEDATE" "OpenLDAP LDVERSION" +.\" Copyright 1998-2021 The OpenLDAP Foundation, All Rights Reserved. +.\" Copying restrictions apply. See the COPYRIGHT file. +.\" $OpenLDAP$ +.SH NAME +slapo-remoteauth \- Delegate authentication requests to remote directories, e.g. Active Directory +.SH SYNOPSIS +ETCDIR/slapd.conf +.SH DESCRIPTION +The +.B remoteauth +overlay to +.BR slapd (8) +provides passthrough authentication to remote directory servers, e.g. +Active Directory, for LDAP simple bind operations. The local LDAP entry +referenced in the bind operation is mapped to its counterpart in the remote +directory. An LDAP bind operation is performed against the remote directory +and results are returned based on those of the remote operation. +.LP +A slapd server configured with the +.B remoteauth +overlay handles an authentication request based on the presence of +.B userPassword +in the local entry. If the +.B userPassword +is present, authentication is performed locally, otherwise the +.B remoteauth +overlay performs the authentication request to the configured remote directory +server. +.LP + +.SH CONFIGURATION + +The following options can be applied to the +.B remoteauth +overlay within the slapd.conf file. All options should follow the +.B overlay remoteauth +directive. + +.TP +.B overlay remoteauth +This directive adds the +.B remoteauth +overlay to the current database, see +.BR slapd.conf (5) +for details. + +.TP +.B remoteauth_dn_attribute +Attribute in the local entry that is used to store the bind DN to a remote +directory server. + +.TP +.B remoteauth_mapping +For a non-Windows deployment, a domain can be considered as a collection of +one or more hosts to which slapd server authentcates against on behalf of +authenticating users. +For a given domain name, the mapping specifies the target server(s), +e.g., Active Directory domain controller(s), to connect to via LDAP. +The second argument can be given either as a hostname, an LDAP URI, or a file +containing a list of hostnames/URIs, one per line. The hostnames are tried in +sequence until the connection succeeds. + +This option can be provided more than once to provide mapping information for +different domains. For example: + +.nf + remoteauth_mapping americas file:///path/to/americas.domain.hosts + remoteauth_mapping asiapacific file:///path/to/asiapacific.domain.hosts + remoteauth_mapping emea emeadc1.emea.example.com +.fi + +.TP +.B remoteauth_domain_attribute +Attribute in the local entry that specifies the domain name, any text after +"\\" or ":" is ignored. + +.TP +.B remoteauth_default_domain +Default domain. + + +.TP +.B remoteauth_default_realm +Fallback server to connect to for domains not specified in +.BR remoteauth_mapping . + +.TP +.B remoteauth_retry_count +Number of connection retries attempted. Default is 3. + +.TP +.B remoteauth_store +Whether to store the password in the local entry on successful bind. Default is +off. + +.HP +.hy 0 +.B remoteauth_tls +.B [starttls=yes] +.B [tls_cert=] +.B [tls_key=] +.B [tls_cacert=] +.B [tls_cacertdir=] +.B [tls_reqcert=never|allow|try|demand] +.B [tls_reqsan=never|allow|try|demand] +.B [tls_cipher_suite=] +.B [tls_ecname=] +.B [tls_crlcheck=none|peer|all] +.RS +Remoteauth specific TLS configuration, see +.BR slapd.conf (5) +for more details on each of the parameters and defaults. +.RE + +.TP +.B remoteauth_tls_peerkey_hash : +Mapping between remote server hostnames and their public key hashes. Only one +mapping per hostname is supported and if any pins are specified, all hosts +need to be pinned. If set, pinning is in effect regardless of whether or not +certificate name validation is enabled by +.BR tls_reqcert . + +.SH EXAMPLE +A typical example configuration of +.B remoteauth +overlay for AD is shown below (as a +.BR slapd.conf (5) +snippet): + +.LP +.nf + database + #... + + overlay remoteauth + remoteauth_dn_attribute seeAlso + remoteauth_domain_attribute associatedDomain + remoteauth_default_realm americas.example.com + + remoteauth_mapping americas file:///home/ldap/etc/remoteauth.americas + remoteauth_mapping emea emeadc1.emea.example.com + + remoteauth_tls starttls=yes tls_reqcert=demand tls_cacert=/home/ldap/etc/example-ca.pem + remoteauth_tls_peerkey_hash ldap.americas.tld sha256:Bxv3MkLoDm6gt/iDfeGNdNNqa5TTpPDdIwvZM/cIgeo= +.fi + +Where seeAlso contains the AD bind DN for the user, associatedDomain contains the +Windows Domain Id in the form of : in which +anything following, including ":", is ignored. + +.SH SEE ALSO +.BR slapd.conf (5), +.BR slapd (8). + +.SH Copyrights +Copyright 2004-2021 The OpenLDAP Foundation. +Portions Copyright 2004-2017 Howard Chu, Symas Corporation. +Portions Copyright 2017-2021 Ondřej Kuzník, Symas Corporation. +Portions Copyright 2004 Hewlett-Packard Company diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index d1974cce84..97656dd475 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -283,6 +283,8 @@ static OidRec OidMacros[] = { * OLcfgOv{Oc|At}:19 -> collect * OLcfgOv{Oc|At}:20 -> retcode * OLcfgOv{Oc|At}:21 -> sssvlv + * OLcfgOv{Oc|At}:22 -> autoca + * OLcfgOv{Oc|At}:24 -> remoteauth */ /* alphabetical ordering */ diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in index 509f3d4767..6613e7a9f8 100644 --- a/servers/slapd/overlays/Makefile.in +++ b/servers/slapd/overlays/Makefile.in @@ -27,6 +27,7 @@ SRCS = overlays.c \ collect.c \ ppolicy.c \ refint.c \ + remoteauth.c \ retcode.c \ rwm.c rwmconf.c rwmdn.c rwmmap.c \ seqmod.c \ @@ -102,6 +103,9 @@ ppolicy.la : ppolicy.lo refint.la : refint.lo $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS) +remoteauth.la : remoteauth.lo + $(LTLINK_MOD) -module -o $@ remoteauth.lo version.lo $(LINK_LIBS) + retcode.la : retcode.lo $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS) diff --git a/servers/slapd/overlays/remoteauth.c b/servers/slapd/overlays/remoteauth.c new file mode 100644 index 0000000000..f0b251acc5 --- /dev/null +++ b/servers/slapd/overlays/remoteauth.c @@ -0,0 +1,996 @@ +/* $OpenLDAP$ */ +/* remoteauth.c - Overlay to delegate bind processing to a remote server */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2004-2021 The OpenLDAP Foundation. + * Portions Copyright 2017-2021 Ondřej Kuzník, Symas Corporation. + * Portions Copyright 2004-2017 Howard Chu, Symas Corporation. + * Portions Copyright 2004 Hewlett-Packard Company. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "portable.h" + +#include +#if SLAPD_MODULES +#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */ +#include +#endif +#include +#include +#include +#include +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +#ifndef UP_STR +#define UP_STR "userPassword" +#endif /* UP_STR */ + +#ifndef LDAP_PREFIX +#define LDAP_PREFIX "ldap://" +#endif /* LDAP_PREFIX */ + +#ifndef FILE_PREFIX +#define FILE_PREFIX "file://" +#endif /* LDAP_PREFIX */ + +typedef struct _ad_info { + struct _ad_info *next; + char *domain; + char *realm; +} ad_info; + +typedef struct _ad_pin { + struct _ad_pin *next; + char *hostname; + char *pin; +} ad_pin; + +typedef struct _ad_private { + char *dn; + AttributeDescription *dn_ad; + char *domain_attr; + AttributeDescription *domain_ad; + + AttributeDescription *up_ad; + ad_info *mappings; + + char *default_realm; + char *default_domain; + + int up_set; + int retry_count; + int store_on_success; + + ad_pin *pins; + slap_bindconf ad_tls; +} ad_private; + +enum { + REMOTE_AUTH_MAPPING = 1, + REMOTE_AUTH_DN_ATTRIBUTE, + REMOTE_AUTH_DOMAIN_ATTRIBUTE, + REMOTE_AUTH_DEFAULT_DOMAIN, + REMOTE_AUTH_DEFAULT_REALM, + REMOTE_AUTH_CACERT_DIR, + REMOTE_AUTH_CACERT_FILE, + REMOTE_AUTH_VALIDATE_CERTS, + REMOTE_AUTH_RETRY_COUNT, + REMOTE_AUTH_TLS, + REMOTE_AUTH_TLS_PIN, + REMOTE_AUTH_STORE_ON_SUCCESS, +}; + +static ConfigDriver remoteauth_cf_gen; + +static ConfigTable remoteauthcfg[] = { + { "remoteauth_mapping", "mapping between domain and realm", 2, 3, 0, + ARG_MAGIC|REMOTE_AUTH_MAPPING, + remoteauth_cf_gen, + "( OLcfgOvAt:24.1 NAME 'olcRemoteAuthMapping' " + "DESC 'Mapping from domain name to server' " + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, + { "remoteauth_dn_attribute", "Attribute to use as AD bind DN", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DN_ATTRIBUTE, + remoteauth_cf_gen, + "( OLcfgOvAt:24.2 NAME 'olcRemoteAuthDNAttribute' " + "DESC 'Attribute in entry to use as bind DN for AD' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_domain_attribute", "Attribute to use as domain determinant", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DOMAIN_ATTRIBUTE, + remoteauth_cf_gen, + "( OLcfgOvAt:24.3 NAME 'olcRemoteAuthDomainAttribute' " + "DESC 'Attribute in entry to determine windows domain' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_default_domain", "Default Windows domain", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DEFAULT_DOMAIN, + remoteauth_cf_gen, + "( OLcfgOvAt:24.4 NAME 'olcRemoteAuthDefaultDomain' " + "DESC 'Default Windows domain to use' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_default_realm", "Default AD realm", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DEFAULT_REALM, + remoteauth_cf_gen, + "( OLcfgOvAt:24.5 NAME 'olcRemoteAuthDefaultRealm' " + "DESC 'Default AD realm to use' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_store", "on|off", 1, 2, 0, + ARG_OFFSET|ARG_ON_OFF|REMOTE_AUTH_STORE_ON_SUCCESS, + (void *)offsetof(ad_private, store_on_success), + "( OLcfgOvAt:24.6 NAME 'olcRemoteAuthStore' " + "DESC 'Store password locally on success' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_retry_count", "integer", 2, 2, 0, + ARG_OFFSET|ARG_UINT|REMOTE_AUTH_RETRY_COUNT, + (void *)offsetof(ad_private, retry_count), + "( OLcfgOvAt:24.7 NAME 'olcRemoteAuthRetryCount' " + "DESC 'Number of retries attempted' " + "SYNTAX OMsInteger SINGLE-VALUE )", + NULL, { .v_uint = 3 } + }, + { "remoteauth_tls", "tls settings", 2, 0, 0, + ARG_MAGIC|REMOTE_AUTH_TLS, + remoteauth_cf_gen, + "( OLcfgOvAt:24.8 NAME 'olcRemoteAuthTLS' " + "DESC 'StartTLS settings' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_tls_peerkey_hash", "mapping between hostnames and their public key hash", 3, 3, 0, + ARG_MAGIC|REMOTE_AUTH_TLS_PIN, + remoteauth_cf_gen, + "( OLcfgOvAt:24.9 NAME 'olcRemoteAuthTLSPeerkeyHash' " + "DESC 'StartTLS hostname to public key pin mapping file' " + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL } +}; + +static ConfigOCs remoteauthocs[] = { + { "( OLcfgOvOc:24.1 " + "NAME 'olcRemoteAuthCfg' " + "DESC 'Remote Directory passthough authentication configuration' " + "SUP olcOverlayConfig " + "MUST olcRemoteAuthTLS " + "MAY ( olcRemoteAuthMapping $ olcRemoteAuthDNAttribute $ " + " olcRemoteAuthDomainAttribute $ olcRemoteAuthDefaultDomain $ " + " olcRemoteAuthDefaultRealm $ olcRemoteAuthStore $ " + " olcRemoteAuthRetryCount $ olcRemoteAuthTLSPeerkeyHash ) )", + Cft_Overlay, remoteauthcfg }, + { NULL, 0, NULL } +}; + +static int +remoteauth_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ad_private *ad = (ad_private *)on->on_bi.bi_private; + struct berval bv; + int i, rc = 0; + ad_info *map; + const char *text = NULL; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + for ( map = ad->mappings; map; map = map->next ) { + char *str; + + str = ch_malloc( strlen( map->domain ) + + strlen( map->realm ) + 2 ); + sprintf( str, "%s %s", map->domain, map->realm ); + ber_str2bv( str, strlen( str ), 1, &bv ); + ch_free( str ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, &bv ); + if ( rc ) return rc; + } + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( ad->dn ) + value_add_one( &c->rvalue_vals, &ad->dn_ad->ad_cname ); + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( ad->domain_attr ) + value_add_one( + &c->rvalue_vals, &ad->domain_ad->ad_cname ); + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ber_str2bv( ad->default_domain, 0, 1, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ber_str2bv( ad->default_realm, 0, 1, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + case REMOTE_AUTH_TLS: + bindconf_tls_unparse( &ad->ad_tls, &bv ); + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + value_add_one( &c->rvalue_vals, &bv ); + break; + case REMOTE_AUTH_TLS_PIN: { + ad_pin *pin = ad->pins; + for ( pin = ad->pins; pin; pin = pin->next ) { + bv.bv_val = ch_malloc( strlen( pin->hostname ) + + strlen( pin->pin ) + 2 ); + bv.bv_len = sprintf( + bv.bv_val, "%s %s", pin->hostname, pin->pin ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, &bv ); + if ( rc ) return rc; + } + } break; + + default: + abort(); + } + break; + case LDAP_MOD_DELETE: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + if ( c->valx < 0 ) { + /* delete all mappings */ + while ( ad->mappings ) { + map = ad->mappings; + ad->mappings = ad->mappings->next; + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } + } else { + /* delete a specific mapping indicated by 'valx'*/ + ad_info *pmap = NULL; + + for ( map = ad->mappings, i = 0; + ( map ) && ( i < c->valx ); + pmap = map, map = map->next, i++ ) + ; + + if ( pmap ) { + pmap->next = map->next; + map->next = NULL; + + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } else if ( ad->mappings ) { + /* delete the first item in the list */ + map = ad->mappings; + ad->mappings = map->next; + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } + } + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( ad->dn ) { + ch_free( ad->dn ); + ad->dn = NULL; /* Don't free AttributeDescription */ + } + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( ad->domain_attr ) { + ch_free( ad->domain_attr ); + /* Don't free AttributeDescription */ + ad->domain_attr = NULL; + } + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ch_free( ad->default_domain ); + ad->default_domain = NULL; + } + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ch_free( ad->default_realm ); + ad->default_realm = NULL; + } + break; + case REMOTE_AUTH_TLS: + /* MUST + SINGLE-VALUE -> this is a replace */ + bindconf_free( &ad->ad_tls ); + break; + case REMOTE_AUTH_TLS_PIN: + while ( ad->pins ) { + ad_pin *pin = ad->pins; + ad->pins = ad->pins->next; + ch_free( pin->hostname ); + ch_free( pin->pin ); + ch_free( pin ); + } + break; + /* ARG_OFFSET */ + case REMOTE_AUTH_STORE_ON_SUCCESS: + case REMOTE_AUTH_RETRY_COUNT: + abort(); + break; + default: + abort(); + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + /* add mapping to head of list */ + map = ch_malloc( sizeof(ad_info) ); + map->domain = ber_strdup( c->argv[1] ); + map->realm = ber_strdup( c->argv[2] ); + map->next = ad->mappings; + ad->mappings = map; + + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( slap_str2ad( c->argv[1], &ad->dn_ad, &text ) == + LDAP_SUCCESS ) { + ad->dn = ber_strdup( ad->dn_ad->ad_cname.bv_val ); + } else { + strncpy( c->cr_msg, text, sizeof(c->cr_msg) ); + c->cr_msg[sizeof(c->cr_msg) - 1] = '\0'; + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( slap_str2ad( c->argv[1], &ad->domain_ad, &text ) == + LDAP_SUCCESS ) { + ad->domain_attr = + ber_strdup( ad->domain_ad->ad_cname.bv_val ); + } else { + strncpy( c->cr_msg, text, sizeof(c->cr_msg) ); + c->cr_msg[sizeof(c->cr_msg) - 1] = '\0'; + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ch_free( ad->default_domain ); + ad->default_domain = NULL; + } + ad->default_domain = ber_strdup( c->argv[1] ); + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ch_free( ad->default_realm ); + ad->default_realm = NULL; + } + ad->default_realm = ber_strdup( c->argv[1] ); + break; + case REMOTE_AUTH_TLS: + for ( i=1; i < c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &ad->ad_tls ) ) { + rc = 1; + break; + } + } + bindconf_tls_defaults( &ad->ad_tls ); + break; + case REMOTE_AUTH_TLS_PIN: { + ad_pin *pin = ch_calloc( 1, sizeof(ad_pin) ); + + pin->hostname = ber_strdup( c->argv[1] ); + pin->pin = ber_strdup( c->argv[2] ); + pin->next = ad->pins; + ad->pins = pin; + } break; + /* ARG_OFFSET */ + case REMOTE_AUTH_STORE_ON_SUCCESS: + case REMOTE_AUTH_RETRY_COUNT: + abort(); + break; + default: + abort(); + } + break; + default: + abort(); + } + + return rc; +} + +static char * +get_realm( + const char *domain, + ad_info *mappings, + const char *default_realm, + int *isfile ) +{ + ad_info *ai; + char *dom = NULL, *ch, *ret = NULL; + + if ( isfile ) *isfile = 0; + + if ( !domain ) { + ret = default_realm ? ch_strdup( default_realm ) : NULL; + goto exit; + } + + /* munge any DOMAIN\user or DOMAIN:user values into just DOMAIN */ + + ch = strchr( domain, '\\' ); + if ( !ch ) ch = strchr( domain, ':' ); + + if ( ch ) { + dom = ch_malloc( ch - domain + 1 ); + strncpy( dom, domain, ch - domain ); + dom[ch - domain] = '\0'; + } else { + dom = ch_strdup( domain ); + } + + for ( ai = mappings; ai; ai = ai->next ) + if ( strcasecmp( ai->domain, dom ) == 0 ) { + ret = ch_strdup( ai->realm ); + break; + } + + if ( !ai ) + ret = default_realm ? ch_strdup( default_realm ) : + NULL; /* no mapping found */ +exit: + if ( dom ) ch_free( dom ); + if ( ret && + ( strncasecmp( ret, FILE_PREFIX, strlen( FILE_PREFIX ) ) == 0 ) ) { + char *p; + + p = ret; + ret = ch_strdup( p + strlen( FILE_PREFIX ) ); + ch_free( p ); + if ( isfile ) *isfile = 1; + } + + return ret; +} + +static char * +get_ldap_url( const char *realm, int isfile ) +{ + char *ldap_url = NULL; + FILE *fp; + + if ( !realm ) return NULL; + + if ( !isfile ) { + if ( strstr( realm, "://" ) ) { + return ch_strdup( realm ); + } + + ldap_url = ch_malloc( 1 + strlen( LDAP_PREFIX ) + strlen( realm ) ); + sprintf( ldap_url, "%s%s", LDAP_PREFIX, realm ); + return ldap_url; + } + + fp = fopen( realm, "r" ); + if ( !fp ) { + char ebuf[128]; + int saved_errno = errno; + Debug( LDAP_DEBUG_TRACE, "remoteauth: " + "Unable to open realm file (%s)\n", + sock_errstr( saved_errno, ebuf, sizeof(ebuf) ) ); + return NULL; + } + /* + * Read each line in the file and return a URL of the form + * "ldap:// ldap:// ... ldap://" + * which can be passed to ldap_initialize. + */ + while ( !feof( fp ) ) { + char line[512], *p; + + p = fgets( line, sizeof(line), fp ); + if ( !p ) continue; + + /* terminate line at first whitespace */ + for ( p = line; *p; p++ ) + if ( isspace( *p ) ) { + *p = '\0'; + break; + } + + if ( ldap_url ) { + char *nu; + + nu = ch_malloc( strlen( ldap_url ) + 2 + strlen( LDAP_PREFIX ) + + strlen( line ) ); + + if ( strstr( line, "://" ) ) { + sprintf( nu, "%s %s", ldap_url, line ); + } else { + sprintf( nu, "%s %s%s", ldap_url, LDAP_PREFIX, line ); + } + ch_free( ldap_url ); + ldap_url = nu; + } else { + ldap_url = ch_malloc( 1 + strlen( line ) + strlen( LDAP_PREFIX ) ); + if ( strstr( line, "://" ) ) { + strcpy( ldap_url, line ); + } else { + sprintf( ldap_url, "%s%s", LDAP_PREFIX, line ); + } + } + } + + fclose( fp ); + + return ldap_url; +} + +static void +trace_remoteauth_parameters( ad_private *ap ) +{ + ad_info *pad_info; + struct berval bv; + + if ( !ap ) return; + + Debug( LDAP_DEBUG_TRACE, "remoteauth_dn_attribute: %s\n", + ap->dn ? ap->dn : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_domain_attribute: %s\n", + ap->domain_attr ? ap->domain_attr : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_default_realm: %s\n", + ap->default_realm ? ap->default_realm : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_default_domain: %s\n", + ap->default_domain ? ap->default_domain : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_retry_count: %d\n", ap->retry_count ); + + bindconf_tls_unparse( &ap->ad_tls, &bv ); + Debug( LDAP_DEBUG_TRACE, "remoteauth_tls:%s\n", bv.bv_val ); + ch_free( bv.bv_val ); + + pad_info = ap->mappings; + while ( pad_info ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_mappings(%s,%s)\n", + pad_info->domain ? pad_info->domain : "NULL", + pad_info->realm ? pad_info->realm : "NULL" ); + pad_info = pad_info->next; + } + + return; +} + +static int +remoteauth_conn_cb( + LDAP *ld, + Sockbuf *sb, + LDAPURLDesc *srv, + struct sockaddr *addr, + struct ldap_conncb *ctx ) +{ + ad_private *ap = ctx->lc_arg; + ad_pin *pin = NULL; + char *host; + + host = srv->lud_host; + if ( !host || !*host ) { + host = "localhost"; + } + + for ( pin = ap->pins; pin; pin = pin->next ) { + if ( !strcasecmp( host, pin->hostname ) ) break; + } + + if ( pin ) { + int rc = ldap_set_option( ld, LDAP_OPT_X_TLS_PEERKEY_HASH, pin->pin ); + if ( rc == LDAP_SUCCESS ) { + return 0; + } + + Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: " + "TLS Peerkey hash could not be set to '%s': %d\n", + pin->pin, rc ); + } else { + Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: " + "No TLS Peerkey hash found for host '%s'\n", + host ); + } + + return -1; +} + +static void +remoteauth_conn_delcb( LDAP *ld, Sockbuf *sb, struct ldap_conncb *ctx ) +{ + return; +} + +static int +remoteauth_bind( Operation *op, SlapReply *rs ) +{ + Entry *e; + int rc; + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + ad_private *ap = (ad_private *)on->on_bi.bi_private; + Attribute *a_dom, *a_dn; + struct ldap_conncb ad_conncb = { .lc_add = remoteauth_conn_cb, + .lc_del = remoteauth_conn_delcb, + .lc_arg = ap }; + struct berval dn = { 0 }; + char *dom_val, *realm = NULL; + char *ldap_url = NULL; + LDAP *ld = NULL; + int protocol = LDAP_VERSION3, isfile = 0; + int tries = 0; + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + trace_remoteauth_parameters( ap ); + } + + if ( op->orb_method != LDAP_AUTH_SIMPLE ) + return SLAP_CB_CONTINUE; /* only do password auth */ + + /* Can't handle root via this mechanism */ + if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) return SLAP_CB_CONTINUE; + + if ( !ap->up_set ) { + const char *txt = NULL; + + if ( slap_str2ad( UP_STR, &ap->up_ad, &txt ) ) + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "userPassword attr undefined: %s\n", + txt ? txt : "" ); + ap->up_set = 1; + } + + if ( !ap->up_ad ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "password attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* userPassword not defined */ + } + + if ( !ap->dn ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "remote DN attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* no mapped DN attribute */ + } + + if ( !ap->domain_attr ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "domain attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* no way to know domain */ + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + rc = SLAP_CB_CONTINUE; + /* if userPassword is defined in entry, skip to the end */ + if ( attr_find( e->e_attrs, ap->up_ad ) ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "user has a password, skipping\n", + op->o_log_prefix ); + goto exit; + } + + a_dom = attr_find( e->e_attrs, ap->domain_ad ); + if ( !a_dom ) + dom_val = ap->default_domain; + else { + dom_val = a_dom->a_vals[0].bv_val; + } + + if ( !dom_val ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "user has no domain nor do we have a default, skipping\n", + op->o_log_prefix ); + goto exit; /* user has no domain */ + } + + realm = get_realm( dom_val, ap->mappings, ap->default_realm, &isfile ); + if ( !realm ) goto exit; + + a_dn = attr_find( e->e_attrs, ap->dn_ad ); + if ( !a_dn ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "no remote DN found on user\n", + op->o_log_prefix ); + goto exit; /* user has no DN for the other directory */ + } + + ber_dupbv_x( &dn, a_dn->a_vals, op->o_tmpmemctx ); + be_entry_release_r( op, e ); + e = NULL; + + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "(realm, dn) = (%s, %s)\n", + op->o_log_prefix, realm, dn.bv_val ); + + ldap_url = get_ldap_url( realm, isfile ); + if ( !ldap_url ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "No LDAP URL obtained\n", + op->o_log_prefix ); + goto exit; + } + +retry: + rc = ldap_initialize( &ld, ldap_url ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "Cannot initialize %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; /* user has no DN for the other directory */ + } + + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ); + +#ifdef HAVE_TLS + rc = bindconf_tls_set( &ap->ad_tls, ld ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "bindconf_tls_set failed\n", + op->o_log_prefix ); + goto exit; + } + + if ( ap->pins ) { + if ( (rc = ldap_set_option( ld, LDAP_OPT_CONNECT_CB, &ad_conncb )) != + LDAP_SUCCESS ) { + goto exit; + } + } + + if ( (rc = ldap_connect( ld )) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "Cannot connect to %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; + } + + if ( ap->ad_tls.sb_tls && !ldap_tls_inplace( ld ) ) { + if ( (rc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "LDAP TLS failed %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; + } + } + +#endif /* HAVE_TLS */ + + rc = ldap_sasl_bind_s( ld, dn.bv_val, LDAP_SASL_SIMPLE, + &op->oq_bind.rb_cred, NULL, NULL, NULL ); + if ( rc == LDAP_SUCCESS ) { + if ( ap->store_on_success ) { + const char *txt; + + Operation op2 = *op; + SlapReply r2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Modifications m = {}; + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_callback = &cb; + op2.orm_modlist = &m; + op2.orm_no_opattrs = 0; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + m.sml_op = LDAP_MOD_ADD; + m.sml_flags = 0; + m.sml_next = NULL; + m.sml_type = ap->up_ad->ad_cname; + m.sml_desc = ap->up_ad; + m.sml_numvals = 1; + m.sml_values = op->o_tmpcalloc( + sizeof(struct berval), 2, op->o_tmpmemctx ); + + slap_passwd_hash( &op->oq_bind.rb_cred, &m.sml_values[0], &txt ); + if ( m.sml_values[0].bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: " + "password hashing for '%s' failed, storing password in " + "plain text\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + ber_dupbv( &m.sml_values[0], &op->oq_bind.rb_cred ); + } + + /* + * If this server is a shadow use the frontend to perform this + * modify. That will trigger the update referral, which can then be + * forwarded by the chain overlay. Obviously the updateref and + * chain overlay must be configured appropriately for this to be + * useful. + */ + if ( SLAP_SHADOW(op->o_bd) ) { + op2.o_bd = frontendDB; + } else { + op2.o_bd->bd_info = (BackendInfo *)on->on_info; + } + + if ( op2.o_bd->be_modify( &op2, &r2 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: " + "attempt to store password in entry '%s' failed, " + "ignoring\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + } + ch_free( m.sml_values[0].bv_val ); + } + goto exit; + } + + if ( rc == LDAP_INVALID_CREDENTIALS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "ldap_sasl_bind_s (%s) failed: invalid credentials\n", + op->o_log_prefix, ldap_url ); + goto exit; + } + + if ( tries < ap->retry_count ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "ldap_sasl_bind_s failed %s: %s (try #%d)\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ), tries ); + if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL ); + tries++; + goto retry; + } else + goto exit; + +exit: + if ( dn.bv_val ) { + op->o_tmpfree( dn.bv_val, op->o_tmpmemctx ); + } + if ( e ) { + be_entry_release_r( op, e ); + } + if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL ); + if ( ldap_url ) ch_free( ldap_url ); + if ( realm ) ch_free( realm ); + if ( rc == SLAP_CB_CONTINUE ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "continue\n", op->o_log_prefix ); + return rc; + } else { + /* for rc == 0, frontend sends result */ + if ( rc ) { + if ( rc > 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "failed\n", op->o_log_prefix ); + send_ldap_error( op, rs, rc, "remoteauth_bind failed" ); + } else { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "operations error\n", op->o_log_prefix ); + send_ldap_error( op, rs, LDAP_OPERATIONS_ERROR, + "remoteauth_bind operations error" ); + } + } + + return rs->sr_err; + } +} + +static int +remoteauth_db_init( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ad_private *ap; + + if ( SLAP_ISGLOBALOVERLAY(be) ) { + Debug( LDAP_DEBUG_ANY, "remoteauth_db_init: " + "remoteauth overlay must be instantiated within a " + "database.\n" ); + return 1; + } + + ap = ch_calloc( 1, sizeof(ad_private) ); + + ap->dn = NULL; + ap->dn_ad = NULL; + ap->domain_attr = NULL; + ap->domain_ad = NULL; + + ap->up_ad = NULL; + ap->mappings = NULL; + + ap->default_realm = NULL; + ap->default_domain = NULL; + + ap->pins = NULL; + + ap->up_set = 0; + ap->retry_count = 3; + + on->on_bi.bi_private = ap; + + return LDAP_SUCCESS; +} + +static int +remoteauth_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ad_private *ap = (ad_private *)on->on_bi.bi_private; + ad_info *ai = ap->mappings; + + while ( ai ) { + if ( ai->domain ) ch_free( ai->domain ); + if ( ai->realm ) ch_free( ai->realm ); + ai = ai->next; + } + + if ( ap->dn ) ch_free( ap->dn ); + if ( ap->default_domain ) ch_free( ap->default_domain ); + if ( ap->default_realm ) ch_free( ap->default_realm ); + + bindconf_free( &ap->ad_tls ); + + ch_free( ap ); + + return 0; +} + +static slap_overinst remoteauth; + +int +remoteauth_initialize( void ) +{ + int rc; + + remoteauth.on_bi.bi_type = "remoteauth"; + remoteauth.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + + remoteauth.on_bi.bi_cf_ocs = remoteauthocs; + rc = config_register_schema( remoteauthcfg, remoteauthocs ); + if ( rc ) return rc; + + remoteauth.on_bi.bi_db_init = remoteauth_db_init; + remoteauth.on_bi.bi_db_destroy = remoteauth_db_destroy; + remoteauth.on_bi.bi_op_bind = remoteauth_bind; + + return overlay_register( &remoteauth ); +} + +#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return remoteauth_initialize(); +} +#endif diff --git a/tests/data/remoteauth/config.ldif b/tests/data/remoteauth/config.ldif new file mode 100644 index 0000000000..f59351a23b --- /dev/null +++ b/tests/data/remoteauth/config.ldif @@ -0,0 +1,21 @@ +dn: olcOverlay={0}remoteauth,olcDatabase={1}@BACKEND@,cn=config +objectClass: olcOverlayConfig +objectclass: olcRemoteAuthCfg +olcOverlay: {0}remoteauth +olcRemoteAuthRetryCount: 3 +olcRemoteAuthTLS: starttls=critical + tls_cert="@TESTDIR@/tls/certs/localhost.crt" + tls_key="@TESTDIR@/tls/private/localhost.key" + tls_cacert="@TESTDIR@/tls/ca/certs/testsuiteCA.crt" + tls_reqcert=demand tls_reqsan=allow +#openssl# tls_crlcheck=none +olcRemoteAuthDNAttribute: seeAlso +olcRemoteAuthDomainAttribute: o +olcRemoteAuthDefaultDomain: default +olcRemoteAuthDefaultRealm: @SURIP3@ +olcRemoteAuthStore: FALSE +olcRemoteAuthMapping: default file://@TESTDIR@/default_domain +olcRemoteAuthMapping: working_ldaps @SURIP3@ +olcRemoteAuthMapping: failing_ldaps @SURIP2@ +olcRemoteAuthMapping: self @URIP1@ + diff --git a/tests/data/remoteauth/default_domain b/tests/data/remoteauth/default_domain new file mode 100644 index 0000000000..6a8846372f --- /dev/null +++ b/tests/data/remoteauth/default_domain @@ -0,0 +1,3 @@ +ldap://we/should/not/be/able/to/connect/to +@SURIP2@ +@SURIP3@ diff --git a/tests/data/remoteauth/remoteauth.conf b/tests/data/remoteauth/remoteauth.conf new file mode 100644 index 0000000000..9f30e179ff --- /dev/null +++ b/tests/data/remoteauth/remoteauth.conf @@ -0,0 +1,21 @@ +overlay remoteauth + +# defaults +#remoteauth_retry_count 3 +#remoteauth_store off + +remoteauth_tls starttls=critical + tls_cert=@TESTDIR@/tls/certs/localhost.crt + tls_key=@TESTDIR@/tls/private/localhost.key + tls_cacert=@TESTDIR@/tls/ca/certs/testsuiteCA.crt + +remoteauth_dn_attribute seeAlso +remoteauth_domain_attribute o +remoteauth_default_domain default +remoteauth_default_realm @SURIP3@ + +# It's a trap! (ehm... stack) cn=config entries will be emitted in reverse order +remoteauth_mapping self @URIP1@ +remoteauth_mapping failing_ldaps @SURIP2@ +remoteauth_mapping working_ldaps @SURIP3@ +remoteauth_mapping default file://@TESTDIR@/default_domain diff --git a/tests/run.in b/tests/run.in index c63f772cc4..e45b537e61 100644 --- a/tests/run.in +++ b/tests/run.in @@ -49,6 +49,7 @@ AC_memberof=memberof@BUILD_MEMBEROF@ AC_pcache=pcache@BUILD_PROXYCACHE@ AC_ppolicy=ppolicy@BUILD_PPOLICY@ AC_refint=refint@BUILD_REFINT@ +AC_remoteauth=remoteauth@BUILD_REMOTEAUTH@ AC_retcode=retcode@BUILD_RETCODE@ AC_translucent=translucent@BUILD_TRANSLUCENT@ AC_unique=unique@BUILD_UNIQUE@ @@ -75,8 +76,9 @@ if test "${AC_asyncmeta}" = "asyncmetamod" && test "${AC_LIBS_DYNAMIC}" = "stati AC_meta="asyncmetano" fi export AC_ldap AC_mdb AC_meta AC_asyncmeta AC_monitor AC_null AC_perl AC_relay AC_sql \ - AC_accesslog AC_autoca AC_constraint AC_dds AC_dynlist AC_memberof AC_pcache AC_ppolicy \ - AC_refint AC_retcode AC_rwm AC_unique AC_syncprov AC_translucent \ + AC_accesslog AC_autoca AC_constraint AC_dds AC_dynlist AC_memberof \ + AC_pcache AC_ppolicy AC_refint AC_remoteauth \ + AC_retcode AC_rwm AC_unique AC_syncprov AC_translucent \ AC_valsort \ AC_lloadd \ AC_WITH_SASL AC_WITH_TLS AC_WITH_MODULES_ENABLED AC_ACI_ENABLED \ diff --git a/tests/scripts/conf.sh b/tests/scripts/conf.sh index d6eae4366c..60e0d2786f 100755 --- a/tests/scripts/conf.sh +++ b/tests/scripts/conf.sh @@ -43,6 +43,7 @@ sed -e "s/@BACKEND@/${BACKEND}/" \ -e "s/^#${AC_ppolicy}#//" \ -e "s/^#${AC_refint}#//" \ -e "s/^#${AC_retcode}#//" \ + -e "s/^#${AC_remoteauth}#//" \ -e "s/^#${AC_rwm}#//" \ -e "s/^#${AC_syncprov}#//" \ -e "s/^#${AC_translucent}#//" \ diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh index b369f52791..12102393bb 100755 --- a/tests/scripts/defines.sh +++ b/tests/scripts/defines.sh @@ -37,6 +37,7 @@ MEMBEROF=${AC_memberof-memberofno} PROXYCACHE=${AC_pcache-pcacheno} PPOLICY=${AC_ppolicy-ppolicyno} REFINT=${AC_refint-refintno} +REMOTEAUTH=${AC_remoteauth-remoteauthno} RETCODE=${AC_retcode-retcodeno} RWM=${AC_rwm-rwmno} SYNCPROV=${AC_syncprov-syncprovno} diff --git a/tests/scripts/test082-remoteauth b/tests/scripts/test082-remoteauth new file mode 100755 index 0000000000..01f142714f --- /dev/null +++ b/tests/scripts/test082-remoteauth @@ -0,0 +1,417 @@ +#! /bin/sh +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 2016-2021 Ondřej Kuzník, Symas Corp. +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . + +echo "running defines.sh" +. $SRCDIR/scripts/defines.sh + +if test $WITH_TLS = no ; then + echo "TLS support not available, test skipped" + exit 0 +fi + +if test $REMOTEAUTH = remoteauthno; then + echo "RemoteAuth overlay not available, test skipped" + exit 0 +fi + +mkdir -p $TESTDIR $DBDIR1 $DBDIR2 $TESTDIR/confdir +cp -r $DATADIR/tls $TESTDIR + +. $CONFFILTER < $DATADIR/remoteauth/default_domain > $TESTDIR/default_domain + +. $CONFFILTER $BACKEND < $TLSCONF > $CONF1 + +$SLAPPASSWD -g -n >$CONFIGPWF +echo "database config" >>$CONF1 +echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF1 +echo "TLSCACertificateFile $TESTDIR/tls/ca/certs/testsuiteCA.crt" >>$CONF1 + +$SLAPD -Tt -n 0 -f $CONF1 -F $TESTDIR/confdir -d $LVL > $LOG1 2>&1 +RC=$? +if test $RC != 0 ; then + echo "slaptest failed ($RC)!" + exit $RC +fi + +echo -n "Running slapadd to build slapd database... " +$SLAPADD -F $TESTDIR/confdir -l $LDIFORDERED +RC=$? +if test $RC != 0 ; then + echo "slapadd failed ($RC)!" + exit $RC +fi + +echo "DB tweaks..." +$SLAPMODIFY -f $CONF1 >>$LOG1 2>&1 < $LOG1 2>&1 & +REMOTEAUTH_PID=$! +if test $WAIT != 0 ; then + echo REMOTEAUTH_PID $REMOTEAUTH_PID + read foo +fi +KILLPIDS="$REMOTEAUTH_PID" + +sleep $SLEEP0 + +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \ + 'objectclass=*' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting ${SLEEP1} seconds for slapd to start..." + sleep ${SLEEP1} +done + +if [ "$REMOTEAUTH" = remoteauthmod ]; then +$LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF \ + >> $TESTOUT 2>&1 <> $TESTOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo -n "Preparing second server on $URI2 and $SURIP3... " +. $CONFFILTER $BACKEND < $TLSCONF | sed -e "s,$DBDIR1,$DBDIR2," > $CONF2 + +echo -n "loading data... " +$SLAPADD -f $CONF2 -l $LDIFORDERED +RC=$? +if test $RC != 0 ; then + echo "slapadd failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo -n "tweaking DB contents... " +$SLAPMODIFY -f $CONF2 >>$LOG2 2>&1 < $LOG2 2>&1 & +BACKEND_PID=$! +if test $WAIT != 0 ; then + echo BACKEND_PID $BACKEND_PID + read foo +fi +KILLPIDS="$KILLPIDS $BACKEND_PID" + +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -H $URI2 \ + 'objectclass=*' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting ${SLEEP1} seconds for slapd to start..." + sleep ${SLEEP1} +done + +if test $RC != 0 ; then + echo "failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +. $CONFFILTER $BACKEND < $TLSCONF > $CONF1 + +echo "TLSCACertificateFile $TESTDIR/tls/ca/certs/testsuiteCA.crt" >>$CONF1 +echo "database config" >>$CONF1 +echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF1 + +# We check basic remoteauth operation and generated configuration in these +# circumstances: +# 1. configured online through cn=config (what we set up above) +# 2. the server from 1. restarted (loading from cn=config on startup) +# 3. configured and started through a slapd.conf +# +# All of the above should present the same behaviour and cn=config output + +echo "Saving generated config before server restart..." +echo "# search output from dynamically configured server..." >> $SERVER1OUT +$LDAPSEARCH -D cn=config -H $URI1 -y $CONFIGPWF \ + -b "olcOverlay={0}remoteauth,olcDatabase={1}$BACKEND,cn=config" \ + >> $SERVER1OUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo -n "Checking bind handling... " + +$LDAPWHOAMI -H $URI1 -x -D "$BJORNSDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "1 " + +$LDAPWHOAMI -H $URI1 -x -D "$JOHNDDN" -w bjorn2 >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "2 " + +$LDAPWHOAMI -H $URI1 -x -D "$MELLIOTDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "3 " + +echo "ok" + +echo "Stopping slapd on TCP/IP port $PORT1..." +kill -HUP $REMOTEAUTH_PID +KILLPIDS="$BACKEND_PID" +sleep $SLEEP0 +echo "Starting slapd on TCP/IP port $PORT1..." +$SLAPD -F $TESTDIR/confdir -h $URI1 -d $LVL >> $LOG1 2>&1 & +REMOTEAUTH_PID=$! +if test $WAIT != 0 ; then + echo REMOTEAUTH_PID $REMOTEAUTH_PID + read foo +fi +KILLPIDS="$KILLPIDS $REMOTEAUTH_PID" + +sleep $SLEEP0 + +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \ + 'objectclass=*' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting ${SLEEP1} seconds for slapd to start..." + sleep ${SLEEP1} +done + +echo "Saving generated config after server restart..." +echo "# search output from dynamically configured server after restart..." >> $SERVER2OUT +$LDAPSEARCH -D cn=config -H $URI1 -y $CONFIGPWF \ + -b "olcOverlay={0}remoteauth,olcDatabase={1}$BACKEND,cn=config" \ + >> $SERVER2OUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo -n "Checking bind handling... " + +$LDAPWHOAMI -H $URI1 -x -D "$BJORNSDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "1 " + +$LDAPWHOAMI -H $URI1 -x -D "$JOHNDDN" -w bjorn2 >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "2 " + +$LDAPWHOAMI -H $URI1 -x -D "$MELLIOTDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "3 " + +echo "ok" + +echo "Stopping slapd on TCP/IP port $PORT1..." +kill -HUP $REMOTEAUTH_PID +KILLPIDS="$BACKEND_PID" +sleep $SLEEP0 + +echo "Testing slapd.conf suppport..." +sed -e "s,database\\s*monitor,\\ +TLSCACertificateFile $TESTDIR/tls/ca/certs/testsuiteCA.crt\\ +\\ +#remoteauthmod#moduleload ../servers/slapd/overlays/remoteauth.la\\ +include $TESTDIR/remoteauth.conf\\ +\\ +database monitor," $TLSCONF | . $CONFFILTER $BACKEND >$CONF1 +echo "database config" >>$CONF1 +echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF1 + +. $CONFFILTER $BACKEND < $DATADIR/remoteauth/remoteauth.conf >$TESTDIR/remoteauth.conf + +echo "Starting slapd on TCP/IP port $PORT1..." +$SLAPD -f $CONF1 -h $URI1 -d $LVL >> $LOG1 2>&1 & +REMOTEAUTH_PID=$! +if test $WAIT != 0 ; then + echo REMOTEAUTH_PID $REMOTEAUTH_PID + read foo +fi +KILLPIDS="$KILLPIDS $REMOTEAUTH_PID" + +sleep $SLEEP0 + +for i in 0 1 2 3 4 5; do + $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \ + 'objectclass=*' > /dev/null 2>&1 + RC=$? + if test $RC = 0 ; then + break + fi + echo "Waiting ${SLEEP1} seconds for slapd to start..." + sleep ${SLEEP1} +done + +echo "Saving generated config from a slapd.conf sourced server..." +echo "# search output from server running from slapd.conf..." >> $SERVER3OUT +$LDAPSEARCH -D cn=config -H $URI1 -y $CONFIGPWF \ + -b "olcOverlay={0}remoteauth,olcDatabase={1}$BACKEND,cn=config" \ + >> $SERVER3OUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +echo -n "Checking bind handling... " + +$LDAPWHOAMI -H $URI1 -x -D "$BJORNSDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "1 " + +$LDAPWHOAMI -H $URI1 -x -D "$JOHNDDN" -w bjorn2 >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "2 " + +$LDAPWHOAMI -H $URI1 -x -D "$MELLIOTDN" -w bjorn >/dev/null +RC=$? +if test $RC != 0 ; then + echo "ldapwhoami failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi +echo -n "3 " + +echo "ok" + +test $KILLSERVERS != no && kill -HUP $KILLPIDS + +# LDIFFILTER doesn't (un)wrap long lines yet, so the result would differ +#. $CONFFILTER $BACKEND < $DATADIR/remoteauth/config.ldif \ +# | $LDIFFILTER -s a > $SERVER6FLT + +# We've already filtered out the ordering markers, now sort the entries +echo "Filtering ldapsearch results..." +$LDIFFILTER -s a < $SERVER1OUT > $SERVER1FLT +$LDIFFILTER -s a < $SERVER2OUT > $SERVER2FLT +$LDIFFILTER -s a < $SERVER3OUT > $SERVER3FLT +echo "Filtering expected entries..." + +echo "Comparing filter output..." +#$CMP $SERVER6FLT $SERVER1FLT > $CMPOUT && \ +$CMP $SERVER1FLT $SERVER2FLT > $CMPOUT && \ +$CMP $SERVER2FLT $SERVER3FLT > $CMPOUT + +if test $? != 0 ; then + echo "Comparison failed" + exit 1 +fi + +echo ">>>>> Test succeeded" + +test $KILLSERVERS != no && wait + +exit 0