From bf1ee530ea0a931f886cd7d7b3bfafa75cc8ae0d Mon Sep 17 00:00:00 2001 From: Mark Adamson Date: Thu, 21 Sep 2000 17:32:54 +0000 Subject: [PATCH] Implementation of SASL authorization. --- doc/man/man5/slapd.conf.5 | 57 ++++ libraries/libldap/open.c | 49 ++++ servers/slapd/Makefile.in | 4 +- servers/slapd/bind.c | 60 ++-- servers/slapd/config.c | 14 + servers/slapd/connection.c | 60 +++- servers/slapd/daemon.c | 5 + servers/slapd/proto-slap.h | 2 +- servers/slapd/sasl.c | 35 +-- servers/slapd/saslauthz.c | 564 ++++++++++++++++++++++++++++++++++++ servers/slapd/slap.h | 14 + servers/slapd/tools/mimic.c | 8 + 12 files changed, 810 insertions(+), 62 deletions(-) create mode 100644 servers/slapd/saslauthz.c diff --git a/doc/man/man5/slapd.conf.5 b/doc/man/man5/slapd.conf.5 index 8d92c31452..bd36aca34d 100644 --- a/doc/man/man5/slapd.conf.5 +++ b/doc/man/man5/slapd.conf.5 @@ -335,6 +335,63 @@ The property specifies the maximum security layer receive buffer size allowed. 0 disables security layers. The default is 65536. .TP +.B saslregexp +Used by the SASL authorization mechanism to convert a SASL authenticated +username to an LDAP DN. When an authorization request is received, the SASL +.B USERNAME, REALM, +and +.B MECHANISM +are taken, when available, and combined into a SASL name of the +form +.RS +.RS +.TP +.B uid=[+realm=][,cn=],cn=AUTHZ + +.RE +This SASL name is then compared against the +.B match +regular expression, and if the match is successful, the SASL name is +replaced with the +.B replace +string. If there are wildcard strings in the +.B match +regular expression that are enclosed in parenthesis, e.g. +.RS +.RS +.TP +.B uid=(.*)+realm=.* + +.RE +.RE +then the portion of the SASL name that matched the wildcard will be stored +in the numbered placeholder variable $1. If there are other wildcard strings +in parenthesis, the matching strings will be in $2, $3, etc. up to $9. The +placeholders can then be used in the +.B replace +string, e.g. +.RS +.RS +.TP +.B cn=$1,ou=Accounts,dc=$2,dc=$4. + +.RE +.RE +The replaced SASL name can be either a DN or an LDAP URI. If the latter, the slapd +server will use the URI to search its own database, and if the search returns +exactly one entry, the SASL name is replaced by the DN of that entry. +Multiple +.B saslregexp +options can be given in the configuration file to allow for multiple matching +and replacement patterns. The matching patterns are checked in the order they +appear in the file, stopping at the first successful match. +.LP +.B Caution: +Because the plus sign + is a character recognized by the regular expression engine, +and it will appear in SASL names that include a REALM, be careful to escape the +plus sign with a double backslash \\\\+ to remove the character's special meaning. +.RE +.TP .B schemacheck { on | off } Turn schema checking on or off. The default is on. .TP diff --git a/libraries/libldap/open.c b/libraries/libldap/open.c index b62a2ffa9f..1cdba63ecd 100644 --- a/libraries/libldap/open.c +++ b/libraries/libldap/open.c @@ -336,3 +336,52 @@ ldap_int_open_connection( return( 0 ); } + + +int ldap_open_internal_connection( LDAP **ldp, ber_socket_t *fdp ) +{ + int rc; + LDAPConn *c; + LDAPRequest *lr; + + rc = ldap_create( ldp ); + if( rc != LDAP_SUCCESS ) { + *ldp = NULL; + return( rc ); + } + + /* Make it appear that a search request, msgid 0, was sent */ + lr = (LDAPRequest *)LDAP_CALLOC( 1, sizeof( LDAPRequest )); + if( lr == NULL ) { + ldap_unbind( *ldp ); + *ldp = NULL; + return( LDAP_NO_MEMORY ); + } + memset(lr, 0, sizeof( LDAPRequest )); + lr->lr_msgid = 0; + lr->lr_status = LDAP_REQST_INPROGRESS; + lr->lr_res_errno = LDAP_SUCCESS; + (*ldp)->ld_requests = lr; + + /* Attach the passed socket as the *LDAP's connection */ + c = ldap_new_connection( *ldp, NULL, 1, 0, NULL); + if( c == NULL ) { + ldap_unbind( *ldp ); + *ldp = NULL; + return( LDAP_NO_MEMORY ); + } + ber_sockbuf_ctrl( c->lconn_sb, LBER_SB_OPT_SET_FD, fdp ); + ber_sockbuf_add_io( c->lconn_sb, &ber_sockbuf_io_tcp, + LBER_SBIOD_LEVEL_PROVIDER, NULL ); + (*ldp)->ld_defconn = c; + + /* Add the connection to the *LDAP's select pool */ + ldap_mark_select_read( *ldp, c->lconn_sb ); + ldap_mark_select_write( *ldp, c->lconn_sb ); + + /* Make this connection an LDAP V3 protocol connection */ + rc = LDAP_VERSION3; + ldap_set_option( *ldp, LDAP_OPT_PROTOCOL_VERSION, &rc ); + + return( LDAP_SUCCESS ); +} diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in index eb54e1542e..c2765718ac 100644 --- a/servers/slapd/Makefile.in +++ b/servers/slapd/Makefile.in @@ -16,7 +16,7 @@ SRCS = main.c daemon.c connection.c search.c filter.c add.c charray.c \ phonetic.c acl.c str2filter.c aclparse.c init.c user.c \ repl.c lock.c controls.c extended.c kerberos.c passwd.c \ schema.c schema_check.c schema_init.c schema_prep.c \ - schemaparse.c ad.c at.c mr.c syntax.c oc.c \ + schemaparse.c ad.c at.c mr.c syntax.c oc.c saslauthz.c \ monitor.c configinfo.c starttls.c index.c sets.c\ root_dse.c sasl.c module.c suffixalias.c $(@PLAT@_SRCS) @@ -27,7 +27,7 @@ OBJS = main.o daemon.o connection.o search.o filter.o add.o charray.o \ phonetic.o acl.o str2filter.o aclparse.o init.o user.o \ repl.o lock.o controls.o extended.o kerberos.o passwd.o \ schema.o schema_check.o schema_init.o schema_prep.o \ - schemaparse.o ad.o at.o mr.o syntax.o oc.o \ + schemaparse.o ad.o at.o mr.o syntax.o oc.o saslauthz.o \ monitor.o configinfo.o starttls.o index.o sets.o\ root_dse.o sasl.o module.o suffixalias.o $(@PLAT@_OBJS) diff --git a/servers/slapd/bind.c b/servers/slapd/bind.c index 4d96e0431e..e88001e4d2 100644 --- a/servers/slapd/bind.c +++ b/servers/slapd/bind.c @@ -37,7 +37,6 @@ do_bind( ber_int_t version; ber_tag_t method; char *mech; - char *saslmech; char *dn; char *ndn; ber_tag_t tag; @@ -204,49 +203,42 @@ do_bind( } ldap_pvt_thread_mutex_lock( &conn->c_mutex ); - - if ( conn->c_sasl_bind_mech != NULL ) { - /* SASL bind is in progress */ - saslmech = NULL; - + if ( conn->c_sasl_bind_in_progress ) { if((strcmp(conn->c_sasl_bind_mech, mech) != 0)) { - /* mechanism changed */ + /* mechanism changed between bind steps */ slap_sasl_reset(conn); } - - free( conn->c_sasl_bind_mech ); - conn->c_sasl_bind_mech = NULL; - -#ifdef LDAP_DEBUG } else { - /* SASL bind is NOT in progress */ - saslmech = mech; - assert( conn->c_sasl_bind_mech == NULL ); -#endif + conn->c_sasl_bind_mech = mech; + mech = NULL; } - ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); edn = NULL; - rc = slap_sasl_bind( conn, op, dn, ndn, saslmech, &cred, - &edn, &ssf ); + rc = slap_sasl_bind( conn, op, dn, ndn, &cred, &edn, &ssf ); + ldap_pvt_thread_mutex_lock( &conn->c_mutex ); if( rc == LDAP_SUCCESS ) { - ldap_pvt_thread_mutex_lock( &conn->c_mutex ); conn->c_dn = edn; - conn->c_authmech = mech; + conn->c_authmech = conn->c_sasl_bind_mech; + conn->c_sasl_bind_mech = NULL; + conn->c_sasl_bind_in_progress = 0; if( ssf ) conn->c_sasl_layers++; conn->c_sasl_ssf = ssf; if( ssf > conn->c_ssf ) { conn->c_ssf = ssf; } - ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); - } else if ( rc == LDAP_SASL_BIND_IN_PROGRESS ) { - conn->c_sasl_bind_mech = mech; - } + conn->c_sasl_bind_in_progress = 1; - mech = NULL; + } else { + if ( conn->c_sasl_bind_mech ) { + free( conn->c_sasl_bind_mech ); + conn->c_sasl_bind_mech = NULL; + } + conn->c_sasl_bind_in_progress = 0; + } + ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); goto cleanup; @@ -255,14 +247,10 @@ do_bind( ldap_pvt_thread_mutex_lock( &conn->c_mutex ); if ( conn->c_sasl_bind_mech != NULL ) { - assert( conn->c_sasl_bind_in_progress ); - free(conn->c_sasl_bind_mech); conn->c_sasl_bind_mech = NULL; - - } else { - assert( !conn->c_sasl_bind_in_progress ); } + conn->c_sasl_bind_in_progress = 0; slap_sasl_reset( conn ); ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); @@ -416,16 +404,6 @@ do_bind( } cleanup: - if( rc != LDAP_SASL_BIND_IN_PROGRESS ) { - ldap_pvt_thread_mutex_lock( &conn->c_mutex ); - - /* dispose of mech */ - free( conn->c_sasl_bind_mech ); - conn->c_sasl_bind_mech = NULL; - - ldap_pvt_thread_mutex_unlock( &conn->c_mutex ); - } - if( dn != NULL ) { free( dn ); } diff --git a/servers/slapd/config.c b/servers/slapd/config.c index 6a2a998421..ff6e7162c1 100644 --- a/servers/slapd/config.c +++ b/servers/slapd/config.c @@ -44,6 +44,9 @@ char *default_search_nbase = NULL; char *slapd_pid_file = NULL; char *slapd_args_file = NULL; +int nSaslRegexp = 0; +SaslRegexp_t *SaslRegexp = NULL; + static char *fp_getline(FILE *fp, int *lineno); static void fp_getline_init(int *lineno); static int fp_parse_line(char *line, int *argcp, char **argv); @@ -1108,6 +1111,17 @@ read_config( const char *fname ) #endif + } else if ( !strcasecmp( cargv[0], "saslregexp" ) ) { + if ( cargc != 3 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: need 2 args in \"saslregexp \"\n", + fname, lineno, 0 ); + return( 1 ); + } + rc = slap_sasl_regexp_config( cargv[1], cargv[2] ); + if ( rc ) + return rc; + /* pass anything else to the current backend info/db config routine */ } else { if ( bi != NULL ) { diff --git a/servers/slapd/connection.c b/servers/slapd/connection.c index d03a7c9a79..4a77ce00e4 100644 --- a/servers/slapd/connection.c +++ b/servers/slapd/connection.c @@ -16,7 +16,6 @@ #include #include "ldap_pvt.h" - #include "slap.h" /* protected by connections_mutex */ @@ -1258,3 +1257,62 @@ int connection_write(ber_socket_t s) ldap_pvt_thread_mutex_unlock( &connections_mutex ); return 0; } + + +/* + * Create client side and server side connection structures, connected to + * one another, for the front end to use for searches on arbitrary back ends. + */ + +int connection_internal_open( Connection **conn, LDAP **ldp, char *id ) +{ + int rc; + ber_socket_t fd[2] = {-1,-1}; + Operation *op; + + + *conn=NULL; + *ldp=NULL; + + rc = pipe( fd ); + if( rc == -1 ) + return( LDAP_OPERATIONS_ERROR ); + + rc = connection_init( fd[1], "INT", "localhost", + "localhost:0", "localhost:00", 0, 256, id ); + if( rc < 0 ) { + close( fd[0] ); + close( fd[1] ); + return( LDAP_OPERATIONS_ERROR ); + } + slapd_add_internal( fd[1] ); + + /* A search operation, number 0 */ + op = slap_op_alloc( NULL, 0, LDAP_REQ_SEARCH, 0); + op->o_ndn = ch_strdup( id ); + op->o_protocol = LDAP_VERSION3; + + (*conn) = connection_get( fd[1] ); + (*conn)->c_ops = op; + (*conn)->c_conn_state = SLAP_C_ACTIVE; + + + /* Create the client side of the connection */ + rc = ldap_open_internal_connection( ldp, &(fd[0]) ); + if( rc != LDAP_SUCCESS ) { + close( fd[0] ); + return( LDAP_OPERATIONS_ERROR ); + } + + /* The connection_get() will have locked the connection's mutex */ + pthread_mutex_unlock( &((*conn)->c_mutex) ); + + return( LDAP_SUCCESS ); +} + + +void connection_internal_close( Connection *conn ) +{ + connection_closing( conn ); + connection_close( conn ); +} diff --git a/servers/slapd/daemon.c b/servers/slapd/daemon.c index bca5aed17b..14f3f168d9 100644 --- a/servers/slapd/daemon.c +++ b/servers/slapd/daemon.c @@ -1304,3 +1304,8 @@ slap_sig_wake( int sig ) /* reinstall self */ (void) SIGNAL_REINSTALL( sig, slap_sig_wake ); } + + +void slapd_add_internal(ber_socket_t s) { + slapd_add(s); +} diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h index 955a05bb75..ca1a3e49b4 100644 --- a/servers/slapd/proto-slap.h +++ b/servers/slapd/proto-slap.h @@ -542,7 +542,7 @@ LDAP_SLAPD_F (int) slap_sasl_close( Connection *c ); LDAP_SLAPD_F (int) slap_sasl_bind LDAP_P(( Connection *conn, Operation *op, const char *dn, const char *ndn, - const char *mech, struct berval *cred, + struct berval *cred, char **edn, slap_ssf_t *ssf )); /* oc.c */ diff --git a/servers/slapd/sasl.c b/servers/slapd/sasl.c index 10dc15c097..af9687f753 100644 --- a/servers/slapd/sasl.c +++ b/servers/slapd/sasl.c @@ -74,6 +74,8 @@ slap_sasl_authorize( const char **user, const char **errstr) { + char *cuser; + int rc; Connection *conn = context; *user = NULL; @@ -97,7 +99,6 @@ slap_sasl_authorize( if ( authzid == NULL || *authzid == '\0' || strcmp( authcid, authzid ) == 0 ) { - char* cuser; size_t len = sizeof("u:") + strlen( authcid ); cuser = ch_malloc( len ); @@ -114,13 +115,18 @@ slap_sasl_authorize( return SASL_OK; } - Debug( LDAP_DEBUG_TRACE, "SASL Authorize [conn=%ld]: " - "\"%s\" as \"%s\" disallowed. No policy.\n", - (long) (conn ? conn->c_connid : -1), - authcid, authzid ); + rc = slap_sasl_authorized( conn, authcid, authzid ); + Debug( LDAP_DEBUG_TRACE, "SASL Authorization returned %d\n", rc,0,0); + if( rc ) { + *errstr = "not authorized"; + return SASL_NOAUTHZ; + } - *errstr = "no proxy policy"; - return SASL_NOAUTHZ; + cuser = ch_strdup( authzid ); + dn_normalize( cuser ); + *errstr = NULL; + *user = cuser; + return SASL_OK; } @@ -373,7 +379,6 @@ int slap_sasl_bind( Operation *op, const char *dn, const char *ndn, - const char *mech, struct berval *cred, char **edn, slap_ssf_t *ssfp ) @@ -388,8 +393,9 @@ int slap_sasl_bind( int sc; Debug(LDAP_DEBUG_ARGS, - "==> sasl_bind: dn=\"%s\" mech=%s datalen=%d\n", - dn, mech ? mech : "", cred ? cred->bv_len : 0 ); + "==> sasl_bind: dn=\"%s\" mech=%s datalen=%d\n", dn, + conn->c_sasl_bind_in_progress ? "":conn->c_sasl_bind_mech, + cred ? cred->bv_len : 0 ); if( ctx == NULL ) { send_ldap_result( conn, op, LDAP_UNAVAILABLE, @@ -397,9 +403,9 @@ int slap_sasl_bind( return rc; } - if ( mech != NULL ) { + if ( !conn->c_sasl_bind_in_progress ) { sc = sasl_server_start( ctx, - mech, + conn->c_sasl_bind_mech, cred->bv_val, cred->bv_len, (char **)&response.bv_val, &reslen, &errstr ); @@ -480,11 +486,6 @@ int slap_sasl_bind( Debug(LDAP_DEBUG_TRACE, "<== slap_sasl_bind: authzdn: \"%s\"\n", *edn, 0, 0); - } else { - rc = LDAP_INAPPROPRIATE_AUTH; - errstr = "authorization disallowed"; - Debug(LDAP_DEBUG_TRACE, "<== slap_sasl_bind: %s\n", - errstr, 0, 0); } if( rc == LDAP_SUCCESS ) { diff --git a/servers/slapd/saslauthz.c b/servers/slapd/saslauthz.c new file mode 100644 index 0000000000..f762f20289 --- /dev/null +++ b/servers/slapd/saslauthz.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2000, Mark Adamson, Carnegie Mellon. All rights reserved. + * This software is not subject to any license of Carnegie Mellon University. + * + * Redistribution and use in source and binary forms are permitted without + * restriction or fee of any kind as long as this notice is preserved. + * + * The name "Carnegie Mellon" must not be used to endorse or promote + * products derived from this software without prior written permission. + * + */ + +#include "portable.h" + +#include +#include + +#define SLAPD_TOOLS +#include "slap.h" +#undef SLAPD_TOOLS +#include "proto-slap.h" + +#ifdef HAVE_STRINGS_H +#include +#elif defined (HAVE_STRING_H) +#include +#endif + +#ifdef HAVE_CYRUS_SASL +#include +#include +#include + +extern int nSaslRegexp; +extern SaslRegexp_t *SaslRegexp; +#endif + + + + + +/* URI format: ldap:///[?[][?[][?[]]]] */ + +int slap_parseURI( char *uri, char **searchbase, int *scope, Filter **filter ) +{ + char *start, *end; + + + assert( uri != NULL ); + *searchbase = NULL; + *scope = -1; + *filter = NULL; + + Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 ); + + /* If it does not look like a URI, assume it is a DN */ + if( strncasecmp( uri, "ldap://", 7 ) ) { + *searchbase = ch_strdup( uri ); + dn_normalize( *searchbase ); + *scope = LDAP_SCOPE_BASE; + return( LDAP_SUCCESS ); + } + + end = index( uri + 7, '/' ); + if ( end == NULL ) + return( LDAP_PROTOCOL_ERROR ); + + /* could check the hostname here */ + + /* Grab the searchbase */ + start = end+1; + end = index( start, '?' ); + if( end == NULL ) { + *searchbase = ch_strdup( start ); + dn_normalize( *searchbase ); + return( LDAP_SUCCESS ); + } + *end = '\0'; + *searchbase = ch_strdup( start ); + *end = '?'; + dn_normalize( *searchbase ); + + /* Skip the attrs */ + start = end+1; + end = index( start, '?' ); + if( end == NULL ) { + return( LDAP_SUCCESS ); + } + + /* Grab the scope */ + start = end+1; + if( !strncasecmp( start, "base?", 5 )) { + *scope = LDAP_SCOPE_BASE; + start += 5; + } + else if( !strncasecmp( start, "one?", 4 )) { + *scope = LDAP_SCOPE_ONELEVEL; + start += 4; + } + else if( !strncasecmp( start, "sub?", 3 )) { + *scope = LDAP_SCOPE_SUBTREE; + start += 4; + } + else { + ch_free( *searchbase ); + *searchbase = NULL; + return( LDAP_PROTOCOL_ERROR ); + } + + /* Grab the filter */ + *filter = str2filter( start ); + + return( LDAP_SUCCESS ); +} + + + + + +int slap_sasl_regexp_config( const char *match, const char *replace ) +{ +#ifdef HAVE_CYRUS_SASL + const char *c; + int rc, n; + SaslRegexp_t *reg; + + SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp, + (nSaslRegexp + 1) * sizeof(SaslRegexp_t) ); + reg = &( SaslRegexp[nSaslRegexp] ); + reg->match = ch_strdup( match ); + reg->replace = ch_strdup( replace ); + dn_normalize( reg->match ); + dn_normalize( reg->replace ); + + /* Precompile matching pattern */ + rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "SASL match pattern %s could not be compiled by regexp engine\n", + reg->match, 0, 0 ); + return( LDAP_OPERATIONS_ERROR ); + } + + /* Precompile replace pattern. Find the $ placeholders */ + reg->offset[0] = -2; + n = 1; + for ( c = reg->replace; *c; c++ ) { + if ( *c == '\\' ) { + c++; + continue; + } + if ( *c == '$' ) { + if ( n == SASLREGEX_REPLACE ) { + Debug( LDAP_DEBUG_ANY, + "SASL replace pattern %s has too many $n placeholders (max %d)\n", + reg->replace, SASLREGEX_REPLACE, 0 ); + return( LDAP_OPERATIONS_ERROR ); + } + reg->offset[n] = c - reg->replace; + n++; + } + } + + /* Final placeholder, after the last $n */ + reg->offset[n] = c - reg->replace; + n++; + reg->offset[n] = -1; + + nSaslRegexp++; +#endif + return( LDAP_SUCCESS ); +} + + + + + +#ifdef HAVE_CYRUS_SASL + + + +/* Take the passed in SASL name and attempt to convert it into an + LDAP URI to find the matching LDAP entry, using the pattern matching + strings given in the saslregexp config file directive(s) */ +static +char *slap_sasl_regexp( char *saslname ) +{ + char *uri=NULL; + int i, n, len, insert; + SaslRegexp_t *reg; + + + Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n", + saslname, 0, 0 ); + if (( saslname == NULL ) || ( nSaslRegexp == 0 )) + return( NULL ); + + /* Match the normalized SASL name to the saslregexp patterns */ + for( reg = SaslRegexp,i=0; iworkspace, saslname, SASLREGEX_REPLACE, + reg->strings, 0) == REG_OK ) + break; + } + + if( i >= nSaslRegexp ) + return( NULL ); + + /* + * The match pattern may have been of the form "a(b.*)c(d.*)e" and the + * replace pattern of the form "x$1y$2z". The returned string needs + * to replace the $1,$2 with the strings that matched (b.*) and (d.*) + */ + + + /* Get the total length of the final URI */ + + n=1; + len = 0; + while( reg->offset[n] >= 0 ) { + /* Len of next section from replacement string (x,y,z above) */ + len += reg->offset[n] - reg->offset[n-1] - 2; + if( reg->offset[n+1] < 0) + break; + + /* Len of string from saslname that matched next $i (b,d above) */ + i = reg->replace[ reg->offset[n] + 1 ] - '0'; + len += reg->strings[i].rm_eo - reg->strings[i].rm_so; + n++; + } + uri = ch_malloc( len + 1 ); + + /* Fill in URI with replace string, replacing $i as we go */ + n=1; + insert = 0; + while( reg->offset[n] >= 0) { + /* Paste in next section from replacement string (x,y,z above) */ + len = reg->offset[n] - reg->offset[n-1] - 2; + strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len); + insert += len; + if( reg->offset[n+1] < 0) + break; + + /* Paste in string from saslname that matched next $i (b,d above) */ + i = reg->replace[ reg->offset[n] + 1 ] - '0'; + len = reg->strings[i].rm_eo - reg->strings[i].rm_so; + strncpy( uri+insert, saslname + reg->strings[i].rm_so, len ); + insert += len; + + n++; + } + + uri[insert] = '\0'; + Debug( LDAP_DEBUG_TRACE, + "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 ); + return( uri ); +} + + + + + +/* + * Given a SASL name (e.g. "UID=name+REALM=company,cn=GSSAPI,cn=AUTHZ") + * return the LDAP DN to which it matches. The SASL regexp rules in the config + * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a + * search with scope=base), just return the URI (or its searchbase). Otherwise + * an internal search must be done, and if that search returns exactly one + * entry, return the DN of that one entry. + */ + +static +char *slap_sasl2dn( char *saslname ) +{ + char *uri=NULL, *searchbase=NULL, *DN=NULL; + int rc, scope; + Backend *be; + Filter *filter=NULL; + Connection *conn=NULL; + LDAP *client=NULL; + LDAPMessage *res=NULL, *msg; + + + Debug( LDAP_DEBUG_TRACE, + "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 ); + + /* Convert the SASL name into an LDAP URI */ + uri = slap_sasl_regexp( saslname ); + if( uri == NULL ) + goto FINISHED; + + rc = slap_parseURI( uri, &searchbase, &scope, &filter ); + if( rc ) + goto FINISHED; + + /* Massive shortcut: search scope == base */ + if( scope == LDAP_SCOPE_BASE ) { + DN = ch_strdup( searchbase ); + goto FINISHED; + } + + /* Must do an internal search */ + + Debug( LDAP_DEBUG_TRACE, + "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n", + searchbase, scope, 0 ); + + be = select_backend( searchbase ); + if(( be == NULL ) || ( be->be_search == NULL)) + goto FINISHED; + searchbase = suffix_alias( be, searchbase ); + + rc = connection_internal_open( &conn, &client, saslname ); + if( rc != LDAP_SUCCESS ) + goto FINISHED; + + (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase, + scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL, + /*attrs=*/NULL, /*attrsonly=*/0 ); + + + /* Read the client side of the internal search */ + rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res ); + if( rc == -1 ) + goto FINISHED; + + /* Make sure exactly one entry was returned */ + rc = ldap_count_entries( client, res ); + Debug( LDAP_DEBUG_TRACE, + "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 ); + if( rc != 1 ) + goto FINISHED; + + msg = ldap_first_entry( client, res ); + DN = ldap_get_dn( client, msg ); + +FINISHED: + if( searchbase ) ch_free( searchbase ); + if( filter ) filter_free( filter ); + if( uri ) ch_free( uri ); + if( conn ) connection_internal_close( conn ); + if( res ) ldap_msgfree( res ); + if( client ) ldap_unbind( client ); + if( DN ) dn_normalize( DN ); + Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n", + DN ? DN : "", 0, 0 ); + return( DN ); +} + + + + + +/* + * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base + * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise, + * the rule must be used as an internal search for entries. If that search + * returns the *assertDN entry, the match is successful. + */ + +static +int slap_sasl_match( char *rule, char *assertDN, char *authc ) +{ + char *searchbase=NULL, *dn=NULL; + int rc, scope; + Backend *be; + Filter *filter=NULL; + Connection *conn=NULL; + LDAP *client=NULL; + LDAPMessage *res=NULL, *msg; + + + Debug( LDAP_DEBUG_TRACE, + "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 ); + + rc = slap_parseURI( rule, &searchbase, &scope, &filter ); + if( rc != LDAP_SUCCESS ) + goto CONCLUDED; + + /* Massive shortcut: search scope == base */ + if( scope == LDAP_SCOPE_BASE ) { + dn_normalize( searchbase ); + if( strcmp( searchbase, assertDN ) == 0 ) + rc = LDAP_SUCCESS; + else + rc = LDAP_INAPPROPRIATE_AUTH; + goto CONCLUDED; + } + + /* Must run an internal search. */ + + Debug( LDAP_DEBUG_TRACE, + "slap_sasl_match: performing internal search (base=%s, scope=%d)\n", + searchbase, scope, 0 ); + + be = select_backend( searchbase ); + if(( be == NULL ) || ( be->be_search == NULL)) { + rc = LDAP_INAPPROPRIATE_AUTH; + goto CONCLUDED; + } + searchbase = suffix_alias( be, searchbase ); + + /* Make an internal connection on which to run the search */ + rc = connection_internal_open( &conn, &client, authc ); + if( rc != LDAP_SUCCESS ) + goto CONCLUDED; + + (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase, + scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL, + /*attrs=*/NULL, /*attrsonly=*/0 ); + + + /* On the client side of the internal search, read the results. Check + if the assertDN matches any of the DN's returned by the search */ + rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res ); + if( rc == -1 ) + goto CONCLUDED; + + for( msg=ldap_first_entry( client, res ); + msg; + msg=ldap_next_entry( client, msg ) ) { + dn = ldap_get_dn( client, msg ); + dn_normalize( dn ); + rc = strcmp( dn, assertDN ); + ch_free( dn ); + if( rc == 0 ) { + rc = LDAP_SUCCESS; + goto CONCLUDED; + } + } + rc = LDAP_INAPPROPRIATE_AUTH; + +CONCLUDED: + if( searchbase ) ch_free( searchbase ); + if( filter ) filter_free( filter ); + if( conn ) connection_internal_close( conn ); + if( res ) ldap_msgfree( res ); + if( client ) ldap_unbind( client ); + Debug( LDAP_DEBUG_TRACE, + "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0); + return( rc ); +} + + + + + +/* + * This function answers the question, "Can this ID authorize to that ID?", + * based on authorization rules. The rules are stored in the *searchDN, in the + * attribute named by *attr. If any of those rules map to the *assertDN, the + * authorization is approved. + */ + +static int +slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc) +{ + const char *errmsg; + int i, rc; + struct berval **vals=NULL; + AttributeDescription *ad=NULL; + + + Debug( LDAP_DEBUG_TRACE, + "==>slap_sasl_check_authz: does %s match %s rule in %s?\n", + assertDN, attr, searchDN); + rc = slap_str2ad( attr, &ad, &errmsg ); + if( rc != LDAP_SUCCESS ) + goto COMPLETE; + + rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals ); + if( rc != LDAP_SUCCESS ) + goto COMPLETE; + + /* Check if the *assertDN matches any **vals */ + for( i=0; vals[i] != NULL; i++ ) { + rc = slap_sasl_match( vals[i]->bv_val, assertDN, authc ); + if ( rc == LDAP_SUCCESS ) + goto COMPLETE; + } + rc = LDAP_INAPPROPRIATE_AUTH; + +COMPLETE: + if( vals ) ber_bvecfree( vals ); + if( ad ) ad_free( ad, 1 ); + + Debug( LDAP_DEBUG_TRACE, + "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0); + return( rc ); +} + + + +#endif /* HAVE_CYRUS_SASL */ + + + + + +/* Check if a bind can SASL authorize to another identity. */ + +int slap_sasl_authorized( Connection *conn, char *authcid, char *authzid ) +{ + int rc; + char *saslname=NULL,*authcDN=NULL,*realm=NULL, *authzDN=NULL; + +#ifdef HAVE_CYRUS_SASL + Debug( LDAP_DEBUG_TRACE, + "==>slap_sasl_authorized: can %s become %s?\n", authcid, authzid, 0 ); + + /* Create a complete SASL name for the SASL regexp patterns */ + + sasl_getprop( conn->c_sasl_context, SASL_REALM, (void **)&realm ); + + /* Allocate space */ + rc = strlen("uid=+realm=,cn=,cn=AUTHZ "); + if ( realm ) rc += strlen( realm ); + if ( authcid ) rc += strlen( authcid ); + rc += strlen( conn->c_sasl_bind_mech ); + saslname = ch_malloc( rc ); + + /* Build the SASL name with whatever we have, and normalize it */ + saslname[0] = '\0'; + rc = 0; + if ( authcid ) + rc += sprintf( saslname+rc, "%sUID=%s", rc?",":"", authcid); + if ( realm ) + rc += sprintf( saslname+rc, "%sREALM=%s", rc?"+":"", realm); + if ( conn->c_sasl_bind_mech ) + rc += sprintf( saslname+rc, "%sCN=%s", rc?",":"", + conn->c_sasl_bind_mech); + sprintf( saslname+rc, "%sCN=AUTHZ", rc?",":""); + dn_normalize( saslname ); + + authcDN = slap_sasl2dn( saslname ); + if( authcDN == NULL ) + goto DONE; + + /* Normalize the name given by the clientside of the connection */ + authzDN = ch_strdup( authzid ); + dn_normalize( authzDN ); + + + /* Check source rules */ + rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR, + authcDN ); + if( rc == LDAP_SUCCESS ) + goto DONE; + + /* Check destination rules */ + rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR, + authcDN ); + if( rc == LDAP_SUCCESS ) + goto DONE; + +#endif + rc = LDAP_INAPPROPRIATE_AUTH; + +DONE: + if( saslname ) ch_free( saslname ); + if( authcDN ) ch_free( authcDN ); + if( authzDN ) ch_free( authzDN ); + Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 ); + return( rc ); +} diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 69007e65e9..cb1e733c17 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -1176,6 +1176,20 @@ typedef struct slap_conn { #define Statslog( level, fmt, connid, opid, arg1, arg2, arg3 ) #endif + +#define SASLREGEX_REPLACE 10 +#define SASL_AUTHZ_SOURCE_ATTR "saslAuthzTo" +#define SASL_AUTHZ_DEST_ATTR "saslAuthzFrom" + +typedef struct sasl_regexp { + char *match; /* regexp match pattern */ + char *replace; /* regexp replace pattern */ + regex_t workspace; /* workspace for regexp engine */ + regmatch_t strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */ + int offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */ +} SaslRegexp_t; + + LDAP_END_DECL #include "proto-slap.h" diff --git a/servers/slapd/tools/mimic.c b/servers/slapd/tools/mimic.c index 555a1b31cc..08b73f459d 100644 --- a/servers/slapd/tools/mimic.c +++ b/servers/slapd/tools/mimic.c @@ -152,7 +152,15 @@ char * slap_sasl_secprops( const char *in ) return NULL; } + +int slap_sasl_regexp_config( const char *match, const char *replace ) +{ + return(0); +} + + void connection2anonymous( Connection *c ) { assert(0); } + -- 2.39.5