diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9d9a188a4d..e6f3fceb7b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,13 +23,13 @@ build-openssl-heimdal-lloadd: stage: build script: - apt update - - DEBIAN_FRONTEND=noninteractive apt install -y build-essential pkg-config automake libsasl2-dev heimdal-multidev libssl-dev libltdl-dev groff-base unixodbc-dev libwiredtiger-dev libperl-dev heimdal-kdc libsasl2-modules-gssapi-heimdal sasl2-bin libevent-dev + - DEBIAN_FRONTEND=noninteractive apt install -y build-essential python3 gdb procps pkg-config automake libsasl2-dev heimdal-multidev libssl-dev libltdl-dev groff-base unixodbc-dev libwiredtiger-dev libperl-dev heimdal-kdc libsasl2-modules-gssapi-heimdal sasl2-bin libevent-dev - autoreconf - ./configure --enable-backends=mod --enable-overlays=mod --enable-modules --enable-dynamic --disable-ndb --enable-balancer=mod --disable-asyncmeta - make depend - make - ulimit -n 4096 # back-monitor takes a while scanning a long connections array - - make test + - SLAPD_COMMON_WRAPPER=gdb make test artifacts: name: testdir when: on_failure @@ -41,13 +41,13 @@ build-gnutls-mit-standalone-lloadd: stage: build script: - apt update - - DEBIAN_FRONTEND=noninteractive apt install -y build-essential pkg-config automake libsasl2-dev libltdl-dev groff-base unixodbc-dev libwiredtiger-dev libperl-dev krb5-user krb5-kdc krb5-admin-server libsasl2-modules-gssapi-mit sasl2-bin libgnutls28-dev libevent-dev + - DEBIAN_FRONTEND=noninteractive apt install -y build-essential python3 gdb procps pkg-config automake libsasl2-dev libltdl-dev groff-base unixodbc-dev libwiredtiger-dev libperl-dev krb5-user krb5-kdc krb5-admin-server libsasl2-modules-gssapi-mit sasl2-bin libgnutls28-dev libevent-dev - autoreconf - ./configure --enable-backends=mod --enable-overlays=mod --disable-autoca --enable-modules --enable-dynamic --disable-ndb --enable-balancer=yes --disable-asyncmeta - make depend - make - ulimit -n 4096 # back-monitor takes a while scanning a long connections array - - make test + - SLAPD_COMMON_WRAPPER=gdb make test artifacts: name: testdir when: on_failure diff --git a/configure.ac b/configure.ac index 9f9491331c..cd2a151bf6 100644 --- a/configure.ac +++ b/configure.ac @@ -350,6 +350,7 @@ Overlays="accesslog \ ppolicy \ proxycache \ refint \ + remoteauth \ retcode \ rwm \ seqmod \ @@ -390,6 +391,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])], @@ -566,6 +569,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 @@ -2842,6 +2846,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 @@ -3014,6 +3030,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/guide/admin/limits.sdf b/doc/guide/admin/limits.sdf index c9bc933267..aab18fbf30 100644 --- a/doc/guide/admin/limits.sdf +++ b/doc/guide/admin/limits.sdf @@ -253,6 +253,12 @@ E: limits group/groupOfNames/member="cn=dirsync,dc=example,dc=org" size.prtota E: limits users size.soft=5 size.hard=100 size.prtotal=disabled E: limits anonymous size.soft=2 size.hard=5 size.prtotal=disabled +H2: Glued/Subordinate database configurations +When using subordinate databases, it is necessary for any limits that +are to be applied across the parent and its subordinates to be defined in both +the parent and its subordinates. Otherwise the settings on the subordinate databases +are not honored. + H2: Further Information For further information please see {{slapd.conf}}(5), {{ldapsearch}}(1) and {{slapd.access}}(5) diff --git a/doc/guide/admin/monitoringslapd.sdf b/doc/guide/admin/monitoringslapd.sdf index 166c64bd70..dbc5829c5a 100644 --- a/doc/guide/admin/monitoringslapd.sdf +++ b/doc/guide/admin/monitoringslapd.sdf @@ -43,8 +43,12 @@ slapd Configuration File}} chapters. H2: Monitor configuration via cn=config(5) -{{This section has yet to be written.}} +The {{monitor backend}} is statically built into slapd and can be +instantiated via ldapadd. +> dn: olcDatabase=monitor,cn=config +> objectClass: olcDatabaseConfig +> olcDatabase: monitor H2: Monitor configuration via slapd.conf(5) diff --git a/doc/man/man5/slapd-config.5 b/doc/man/man5/slapd-config.5 index f2c98115cd..00b2260d07 100644 --- a/doc/man/man5/slapd-config.5 +++ b/doc/man/man5/slapd-config.5 @@ -1637,6 +1637,17 @@ is requested cannot exceed the size limit of regular searches unless extended by the .B prtotal switch. + +The \fBolcLimits\fP statement is typically used to let an unlimited +number of entries be returned by searches performed +with the identity used by the consumer for synchronization purposes +by means of the RFC 4533 LDAP Content Synchronization protocol +(see \fBolcSyncrepl\fP for details). + +When using subordinate databases, it is necessary for any limits that +are to be applied across the parent and its subordinates to be defined in +both the parent and its subordinates. Otherwise the settings on the +subordinate databases are not honored. .RE .TP .B olcMaxDerefDepth: diff --git a/doc/man/man5/slapd.conf.5 b/doc/man/man5/slapd.conf.5 index 76b329712f..05d4b7d55d 100644 --- a/doc/man/man5/slapd.conf.5 +++ b/doc/man/man5/slapd.conf.5 @@ -1583,7 +1583,7 @@ the use of the pagedResults control as a means to circumvent size limitations on regular searches; the keyword .I disabled disables the control, i.e. no paged results can be returned. -Note that the total number of entries returned when the pagedResults control +Note that the total number of entries returned when the pagedResults control is requested cannot exceed the .B hard size limit of regular searches unless extended by the @@ -1595,6 +1595,11 @@ number of entries be returned by searches performed with the identity used by the consumer for synchronization purposes by means of the RFC 4533 LDAP Content Synchronization protocol (see \fBsyncrepl\fP for details). + +When using subordinate databases, it is necessary for any limits that +are to be applied across the parent and its subordinates to be defined in +both the parent and its subordinates. Otherwise the settings on the +subordinate databases are not honored. .RE .TP .B maxderefdepth diff --git a/doc/man/man5/slapo-accesslog.5 b/doc/man/man5/slapo-accesslog.5 index dfb49add3f..2707b7cbbe 100644 --- a/doc/man/man5/slapo-accesslog.5 +++ b/doc/man/man5/slapo-accesslog.5 @@ -137,6 +137,20 @@ The schema is loaded automatically by the overlay. The schema includes a number of object classes and associated attribute types as described below. +The root entry of the underlying accesslog database makes use +of the +.B auditContainer +class which is as follows: +.LP +.RS 4 +( 1.3.6.1.4.1.4203.666.11.5.2.0 + NAME 'auditContainer' + DESC 'AuditLog container' + SUP top STRUCTURAL + MAY ( cn $ reqStart $ reqEnd ) ) +.RE +.P + There is a basic .B auditObject @@ -378,7 +392,7 @@ filter. DESC 'ModRDN operation' SUP auditWriteObject STRUCTURAL MUST ( reqNewRDN $ reqDeleteOldRDN ) - MAY ( reqNewSuperior $ reqOld ) ) + MAY ( reqNewSuperior $ reqMod $ reqOld ) ) .RE .P The diff --git a/doc/man/man5/slapo-auditlog.5 b/doc/man/man5/slapo-auditlog.5 index e98de8786e..7f4be053f2 100644 --- a/doc/man/man5/slapo-auditlog.5 +++ b/doc/man/man5/slapo-auditlog.5 @@ -11,8 +11,9 @@ ETCDIR/slapd.d .SH DESCRIPTION The Audit Logging overlay can be used to record all changes on a given backend database to a specified log file. Changes are logged as standard -LDIF, with an additional comment header giving the timestamp of the change -and the identity of the user making the change. +LDIF, with an additional comment header providing six fields of +information about the change. A second comment header is added at the end +of the operation to note the termination of the change. .LP For Add and Modify operations the identity comes from the modifiersName associated with the operation. This is usually the same as the requestor's @@ -31,6 +32,19 @@ Specify the fully qualified path for the log file. .B olcAuditlogFile For use with .B cn=config +.SH COMMENT FIELD INFORMATION +The first field is the operation type. +.br +The second field is the timestamp of the operation in seconds since epoch. +.br +The third field is the suffix of the database. +.br +The fourth field is the recorded modifiersName. +.br +The fifth field is the originating IP address and port. +.br +The sixth field is the connection number. A connection number of -1 +indicates an internal slapd operation. .SH EXAMPLE The following LDIF could be used to add this overlay to .B cn=config @@ -48,6 +62,30 @@ olcAuditlogFile: /tmp/auditlog.ldif .RE .LP .LP +.SH EXAMPLE CHANGELOG +.LP +.RS +.nf +# modify 1614223245 dc=example,dc=com cn=admin,dc=example,dc=com IP=[::1]:47270 conn=1002 +dn: uid=joepublic,ou=people,dc=example,dc=com +changetype: modify +replace: displayName +displayName: Joe Public +- +replace: entryCSN +entryCSN: 20210225032045.045229Z#000000#001#000000 +- +replace: modifiersName +modifiersName: cn=admin,dc=example,dc=com +- +replace: modifyTimestamp +modifyTimestamp: 20210225032045Z +- +# end modify 1614223245 + +.fi +.RE +.LP .SH FILES .TP ETCDIR/slapd.conf 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/include/ldap.h b/include/ldap.h index ee223eff47..8f45144e11 100644 --- a/include/ldap.h +++ b/include/ldap.h @@ -179,6 +179,7 @@ LDAP_BEGIN_DECL #define LDAP_OPT_X_TLS_PROTOCOL_TLS1_0 ((3 << 8) + 1) #define LDAP_OPT_X_TLS_PROTOCOL_TLS1_1 ((3 << 8) + 2) #define LDAP_OPT_X_TLS_PROTOCOL_TLS1_2 ((3 << 8) + 3) +#define LDAP_OPT_X_TLS_PROTOCOL_TLS1_3 ((3 << 8) + 4) #define LDAP_OPT_X_SASL_CBINDING_NONE 0 #define LDAP_OPT_X_SASL_CBINDING_TLS_UNIQUE 1 diff --git a/libraries/libldap/tls_o.c b/libraries/libldap/tls_o.c index 8eee6abcbd..8aea072f3f 100644 --- a/libraries/libldap/tls_o.c +++ b/libraries/libldap/tls_o.c @@ -292,6 +292,13 @@ tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) #ifdef SSL_OP_NO_TLSv1 #ifdef SSL_OP_NO_TLSv1_1 #ifdef SSL_OP_NO_TLSv1_2 +#ifdef SSL_OP_NO_TLSv1_3 + if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_TLS1_3) + SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | + SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3 ); + else +#endif if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_TLS1_2) SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | @@ -310,8 +317,10 @@ tlso_ctx_init( struct ldapoptions *lo, struct ldaptls *lt, int is_server ) #endif if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL3 ) SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 ); - else if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) + else if ( lo->ldo_tls_protocol_min > LDAP_OPT_X_TLS_PROTOCOL_SSL2 ) { SSL_CTX_set_options( ctx, SSL_OP_NO_SSLv2 ); + SSL_CTX_clear_options( ctx, SSL_OP_NO_SSLv3 ); + } if ( lo->ldo_tls_ciphersuite && !SSL_CTX_set_cipher_list( ctx, lt->lt_ciphersuite ) ) diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index a8e60ca60f..686e37be08 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 7b1ddac68c..60e0d2786f 100755 --- a/tests/scripts/conf.sh +++ b/tests/scripts/conf.sh @@ -29,6 +29,7 @@ sed -e "s/@BACKEND@/${BACKEND}/" \ -e "s/@RELAY@/${RELAY}/" \ -e "s/^#relay-${RELAY}#//" \ -e "s/^#${BACKENDTYPE}#//" \ + -e "s/^#${AC_TLS_TYPE}#//" \ -e "s/^#${AC_ldap}#//" \ -e "s/^#${AC_meta}#//" \ -e "s/^#${AC_asyncmeta}#//" \ @@ -42,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 0afa1f8c35..40b0d467c1 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} @@ -54,7 +55,7 @@ ACI=${AC_ACI_ENABLED-acino} SLEEP0=${SLEEP0-1} SLEEP1=${SLEEP1-7} SLEEP2=${SLEEP2-15} -TIMEOUT=${TIMEOUT-4} +TIMEOUT=${TIMEOUT-8} # dirs PROGDIR=./progs @@ -65,6 +66,10 @@ case "$SCHEMADIR" in .*) ABS_SCHEMADIR="$TESTWD/$SCHEMADIR" ;; *) ABS_SCHEMADIR="$SCHEMADIR" ;; esac +case "$SRCDIR" in +.*) ABS_SRCDIR="$TESTWD/$SRCDIR" ;; +*) ABS_SRCDIR="$SRCDIR" ;; +esac DBDIR1A=$TESTDIR/db.1.a DBDIR1B=$TESTDIR/db.1.b @@ -181,6 +186,23 @@ SLURPLOG=$TESTDIR/slurp.log CONFIGPWF=$TESTDIR/configpw +# wrappers (valgrind, gdb, environment variables, etc.) +if [ -n "$WRAPPER" ]; then + : # skip +elif [ "$SLAPD_COMMON_WRAPPER" = gdb ]; then + WRAPPER="$ABS_SRCDIR/scripts/grandchild_wrapper.py gdb -nx -x $ABS_SRCDIR/scripts/gdb.py -batch-silent -return-child-result --args" +elif [ "$SLAPD_COMMON_WRAPPER" = valgrind ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --fullpath-after=`dirname $ABS_SRCDIR` --keep-debuginfo=yes --leak-check=full" +elif [ "$SLAPD_COMMON_WRAPPER" = "valgrind-errstop" ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --vgdb=yes --vgdb-error=1" +elif [ "$SLAPD_COMMON_WRAPPER" = vgdb ]; then + WRAPPER="valgrind --log-file=$TESTDIR/valgrind.%p.log --vgdb=yes --vgdb-error=0" +fi + +if [ -n "$WRAPPER" ]; then + SLAPD_WRAPPER="$TESTWD/../libtool --mode=execute env $WRAPPER" +fi + # args SASLARGS="-Q" TOOLARGS="-x $LDAP_TOOLARGS" @@ -192,11 +214,11 @@ CONFDIRSYNC=$SRCDIR/scripts/confdirsync.sh MONITORDATA=$SRCDIR/scripts/monitor_data.sh -SLAPADD="$TESTWD/../servers/slapd/slapd -Ta -d 0 $LDAP_VERBOSE" -SLAPCAT="$TESTWD/../servers/slapd/slapd -Tc -d 0 $LDAP_VERBOSE" -SLAPINDEX="$TESTWD/../servers/slapd/slapd -Ti -d 0 $LDAP_VERBOSE" -SLAPMODIFY="$TESTWD/../servers/slapd/slapd -Tm -d 0 $LDAP_VERBOSE" -SLAPPASSWD="$TESTWD/../servers/slapd/slapd -Tpasswd" +SLAPADD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Ta -d 0 $LDAP_VERBOSE" +SLAPCAT="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tc -d 0 $LDAP_VERBOSE" +SLAPINDEX="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Ti -d 0 $LDAP_VERBOSE" +SLAPMODIFY="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tm -d 0 $LDAP_VERBOSE" +SLAPPASSWD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -Tpasswd" unset DIFF_OPTIONS # NOTE: -u/-c is not that portable... @@ -204,8 +226,8 @@ DIFF="diff -i" CMP="diff -i" BCMP="diff -iB" CMPOUT=/dev/null -SLAPD="$TESTWD/../servers/slapd/slapd -s0" -LLOADD="$TESTWD/../servers/lloadd/lloadd -s0" +SLAPD="$SLAPD_WRAPPER $TESTWD/../servers/slapd/slapd -s0" +LLOADD="$SLAPD_WRAPPER $TESTWD/../servers/lloadd/lloadd -s0" LDAPPASSWD="$CLIENTDIR/ldappasswd $TOOLARGS" LDAPSASLSEARCH="$CLIENTDIR/ldapsearch $SASLARGS $TOOLPROTO $LDAP_TOOLARGS -LLL" LDAPSASLWHOAMI="$CLIENTDIR/ldapwhoami $SASLARGS $LDAP_TOOLARGS" diff --git a/tests/scripts/gdb.py b/tests/scripts/gdb.py new file mode 100644 index 0000000000..6c39022a46 --- /dev/null +++ b/tests/scripts/gdb.py @@ -0,0 +1,85 @@ +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 2020-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 +## . +""" +This GDB script sets up the debugger to run the program and see if it finishes +of its own accord or is terminated by a signal (like SIGABRT/SIGSEGV). In the +latter case, it saves a full backtrace and core file. + +These signals are considered part of normal operation and will not trigger the +above handling: +- SIGPIPE: normal in a networked environment +- SIGHUP: normally used to tell a process to shut down +""" + +import os +import os.path + +import gdb + + +def format_program(inferior=None, thread=None): + "Format program name and p(t)id" + + if thread: + inferior = thread.inferior + elif inferior is None: + inferior = gdb.selected_inferior() + + try: + name = os.path.basename(inferior.progspace.filename) + except AttributeError: # inferior has died already + name = "unknown" + + if thread: + pid = ".".join(tid for tid in thread.ptid if tid) + else: + pid = inferior.pid + + return "{}.{}".format(name, pid) + + +def stop_handler(event): + "Inferior stopped on a signal, record core, backtrace and exit" + + if not isinstance(event, gdb.SignalEvent): + # Ignore breakpoints + return + + thread = event.inferior_thread + + identifier = format_program(thread=thread) + prefix = os.path.expandvars("${TESTDIR}/") + identifier + + if event.stop_signal == "SIGHUP": + # TODO: start a timer to catch shutdown issues/deadlocks + gdb.execute("continue") + return + + gdb.execute('generate-core-file {}.core'.format(prefix)) + + with open(prefix + ".backtrace", "w") as bt_file: + backtrace = gdb.execute("thread apply all backtrace full", + to_string=True) + bt_file.write(backtrace) + + gdb.execute("continue") + + +# We or we could allow the runner to disable randomisation +gdb.execute("set disable-randomization off") + +gdb.execute("handle SIGPIPE noprint") +gdb.execute("handle SIGINT pass") +gdb.events.stop.connect(stop_handler) +gdb.execute("run") diff --git a/tests/scripts/grandchild_wrapper.py b/tests/scripts/grandchild_wrapper.py new file mode 100755 index 0000000000..700222d9e8 --- /dev/null +++ b/tests/scripts/grandchild_wrapper.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 2020-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 +## . +""" +Running slapd under GDB in our testsuite, KILLPIDS would record gdb's PID +rather than slapd's. When we want the server to shut down, SIGHUP is sent to +KILLPIDS but GDB cannot handle being signalled directly and the entire thing is +terminated immediately. There might be tests that rely on slapd being given the +chance to shut down gracefully, to do this, we need to make sure the signal is +actually sent to slapd. + +This script attempts to address this shortcoming in our test suite, serving as +the front for gdb/other wrappers, catching SIGHUPs and redirecting them to the +oldest living grandchild. The way we start up gdb, that process should be +slapd, our intended target. + +This requires the pgrep utility provided by the procps package on Debian +systems. +""" + +import asyncio +import os +import signal +import sys + + +async def signal_to_grandchild(child): + # Get the first child, that should be the one we're after + pgrep = await asyncio.create_subprocess_exec( + "pgrep", "-o", "--parent", str(child.pid), + stdout=asyncio.subprocess.PIPE) + + stdout, _ = await pgrep.communicate() + if not stdout: + return + + grandchild = [int(pid) for pid in stdout.split()][0] + + os.kill(grandchild, signal.SIGHUP) + + +def sighup_handler(child): + asyncio.create_task(signal_to_grandchild(child)) + + +async def main(args=None): + if args is None: + args = sys.argv[1:] + + child = await asyncio.create_subprocess_exec(*args) + + # If we got a SIGHUP before we got the child fully started, there's no + # point signalling anyway + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGHUP, sighup_handler, child) + + raise SystemExit(await child.wait()) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/tests/scripts/test079-proxy-timeout b/tests/scripts/test079-proxy-timeout index e097c0739f..075d646599 100755 --- a/tests/scripts/test079-proxy-timeout +++ b/tests/scripts/test079-proxy-timeout @@ -48,6 +48,24 @@ if test $WAIT != 0 ; then read foo fi +echo "Using ldapsearch to check that slapd is running..." +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 test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + # # Start ldapd that will proxy for the remote server # @@ -66,15 +84,32 @@ fi KILLPIDS="$SERVERPID $PROXYPID" -sleep $SLEEP0 +echo "Using ldapsearch to check that slapd is running..." +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 "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi ############################################################################## # # Test 1: Test that shared connections are timed out # -NOW=`date +%s` -echo "Create shared connection towards remote LDAP (time_t now=$NOW timeout=`expr $NOW + $TIMEOUT`)" +CONN_BEGINS=`date +%s` +CONN_EXPIRES=`expr $CONN_BEGINS + $TIMEOUT` +echo "Create shared connection towards remote LDAP (time_t now=$CONN_BEGINS timeout=$CONN_EXPIRES)" $LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \ -D "cn=Manager,dc=local,dc=com" \ @@ -102,7 +137,7 @@ fi # Check that connections are established by searching for olmDbConnURI from Monitor -echo "Checking that proxy has created connections towards backend" +echo "Checking that proxy has created connections towards backend (time_t now=`date +%s`)" $LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \ -D "cn=Manager,dc=local,dc=com" \ @@ -129,9 +164,10 @@ fi # Wait for connections to be closed, either due to # - idle-timeout and # - conn-ttl - +# sleep 2 second overtime for robustness of the test case echo "Sleeping until idle-timeout and conn-ttl have passed" -sleep `expr $TIMEOUT + 1` +NOW=`date +%s` +sleep `expr $CONN_EXPIRES - $NOW + 2` echo "Checking that proxy has closed expired connections towards the remote LDAP server (time_t now=`date +%s`)" @@ -163,8 +199,9 @@ fi # Test 2: Test that private connections are timed out # -NOW=`date +%s` -echo "Create private connection towards remote LDAP (time_t now=$NOW timeout=`expr $NOW + $TIMEOUT`)" +CONN_BEGINS=`date +%s` +CONN_EXPIRES=`expr $CONN_BEGINS + $TIMEOUT` +echo "Create private connection towards remote LDAP (time_t now=$CONN_BEGINS timeout=$CONN_EXPIRES)" # Create fifos that are used to pass searches from the test case to ldapsearch rm -f $TESTDIR/ldapsearch1.fifo $TESTDIR/ldapsearch2.fifo @@ -192,9 +229,11 @@ exec 4>$TESTDIR/ldapsearch2.fifo # Trigger LDAP connections towards the proxy by executing a search echo 'objectclass=*' >&3 echo 'objectclass=*' >&4 -sleep 1 -echo "Checking that proxy has created connections towards backend" +# wait for ldapsearches (running as background processes) to execute search operations +sleep 2 + +echo "Checking that proxy has created connections towards backend (time_t now=`date +%s`)" $LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \ -D "cn=Manager,dc=local,dc=com" \ @@ -218,9 +257,13 @@ if test $RC != 0 ; then exit $RC fi - +# Wait for connections to be closed, either due to +# - idle-timeout and +# - conn-ttl +# sleep 2 second overtime for robustness of the test case echo "Sleeping until idle-timeout and conn-ttl have passed" -sleep `expr $TIMEOUT + 1` +NOW=`date +%s` +sleep `expr $CONN_EXPIRES - $NOW + 2` echo "Checking that proxy has closed expired connections towards the remote LDAP server (time_t now=`date +%s`)" @@ -258,8 +301,9 @@ exec 4>&- # echo "Checking that idle-timeout is reset on activity" -NOW=`date +%s` -echo "Create cached connection: idle-timeout timeout starts (time_t now=$NOW, original_timeout=`expr $NOW + $TIMEOUT`)" +CONN_BEGINS=`date +%s` +CONN_EXPIRES=`expr $CONN_BEGINS + $TIMEOUT` +echo "Create cached connection: idle-timeout timeout starts (time_t now=$CONN_BEGINS, original_timeout=$CONN_EXPIRES)" $LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \ -D "cn=Manager,dc=local,dc=com" \ -H $URI2 \ @@ -272,10 +316,13 @@ if test $RC != 0 ; then exit $RC fi -# sleep second less than idle-timeout to extend the timeout -sleep `expr $TIMEOUT - 1` +# sleep until 2 seconds before idle-timeout, then extend the timeout by executing another search operation NOW=`date +%s` -echo "Do another search to reset the timeout (time_t now=$NOW, new_timeout=`expr $NOW + $TIMEOUT`)" +sleep `expr $CONN_EXPIRES - $NOW - 2` + +CONN_BEGINS=`date +%s` +CONN_EXPIRES=`expr $CONN_BEGINS + $TIMEOUT` +echo "Do another search to reset the timeout (time_t now=$CONN_BEGINS, new_timeout=$CONN_EXPIRES)" $LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \ -D "cn=Manager,dc=local,dc=com" \ -H $URI2 \ @@ -288,7 +335,9 @@ if test $RC != 0 ; then exit $RC fi -sleep `expr $TIMEOUT - 1` +# sleep until 2 seconds before new extended idle-timeout, check that connection still exist +NOW=`date +%s` +sleep `expr $CONN_EXPIRES - $NOW - 2` echo "Check that connection is still alive due to idle-timeout reset (time_t now=`date +%s`)" $LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \ -D "cn=Manager,dc=local,dc=com" \ @@ -301,7 +350,9 @@ if test $RC != 0 ; then exit $RC fi -sleep 2 +# sleep until 2 seconds after timeout, check that connection does not exist +NOW=`date +%s` +sleep `expr $CONN_EXPIRES - $NOW + 2` echo "Check that connection is closed after extended idle-timeout has passed (time_t now=`date +%s`)" $LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \ -D "cn=Manager,dc=local,dc=com" \ @@ -314,7 +365,6 @@ if test $RC != 1 ; then exit $RC fi - test $KILLSERVERS != no && kill -HUP $KILLPIDS echo ">>>>> Test succeeded" diff --git a/tests/scripts/test082-remoteauth b/tests/scripts/test082-remoteauth new file mode 100755 index 0000000000..4ad7b325cb --- /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 support..." +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