2 * Copyright (c) 2000, Mark Adamson, Carnegie Mellon. All rights reserved.
3 * This software is not subject to any license of Carnegie Mellon University.
5 * Redistribution and use in source and binary forms are permitted without
6 * restriction or fee of any kind as long as this notice is preserved.
8 * The name "Carnegie Mellon" must not be used to endorse or promote
9 * products derived from this software without prior written permission.
17 #include <ac/stdlib.h>
18 #include <ac/string.h>
22 #ifdef HAVE_CYRUS_SASL
25 #ifdef HAVE_SASL_SASL_H
26 #include <sasl/sasl.h>
33 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
35 static int slap_parseURI( struct berval *uri,
36 struct berval *searchbase, int *scope, Filter **filter,
43 assert( uri != NULL && uri->bv_val != NULL );
44 searchbase->bv_val = NULL;
45 searchbase->bv_len = 0;
55 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
56 "slap_parseURI: parsing %s\n", uri->bv_val ));
58 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
61 /* If it does not look like a URI, assume it is a DN */
62 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
63 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
64 bv.bv_val += strspn( bv.bv_val, " " );
66 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
67 rc = dnNormalize2( NULL, &bv, searchbase );
68 if (rc == LDAP_SUCCESS) {
69 *scope = LDAP_SCOPE_BASE;
74 rc = ldap_url_parse( uri->bv_val, &ludp );
75 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
76 bv.bv_val = uri->bv_val;
80 if ( rc != LDAP_URL_SUCCESS ) {
81 return( LDAP_PROTOCOL_ERROR );
84 /* could check the hostname here */
87 *scope = ludp->lud_scope;
90 if ( ludp->lud_filter ) {
91 *filter = str2filter( ludp->lud_filter );
92 if ( *filter == NULL )
93 rc = LDAP_PROTOCOL_ERROR;
95 ber_str2bv( ludp->lud_filter, 0, 1, fstr );
98 /* Grab the searchbase */
99 if ( rc == LDAP_URL_SUCCESS ) {
100 bv.bv_val = ludp->lud_dn;
101 bv.bv_len = strlen( bv.bv_val );
102 rc = dnNormalize2( NULL, &bv, searchbase );
105 ldap_free_urldesc( ludp );
110 static int slap_sasl_rx_off(char *rep, int *off)
115 /* Precompile replace pattern. Find the $<n> placeholders */
118 for ( c = rep; *c; c++ ) {
119 if ( *c == '\\' && c[1] ) {
124 if ( n == SASLREGEX_REPLACE ) {
126 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
127 "slap_sasl_regexp_config: \"%s\" has too many $n "
128 "placeholders (max %d)\n",
129 rep, SASLREGEX_REPLACE ));
131 Debug( LDAP_DEBUG_ANY,
132 "SASL replace pattern %s has too many $n "
133 "placeholders (max %d)\n",
134 rep, SASLREGEX_REPLACE, 0 );
137 return( LDAP_OPERATIONS_ERROR );
144 /* Final placeholder, after the last $n */
148 return( LDAP_SUCCESS );
150 #endif /* HAVE_CYRUS_SASL */
152 int slap_sasl_regexp_config( const char *match, const char *replace )
154 #ifdef HAVE_CYRUS_SASL
158 struct berval bv, nbv;
161 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
162 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
163 reg = &( SaslRegexp[nSaslRegexp] );
164 ber_str2bv( match, 0, 0, &bv );
165 rc = dnNormalize2( NULL, &bv, &nbv );
168 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
169 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
172 Debug( LDAP_DEBUG_ANY,
173 "SASL match pattern %s could not be normalized.\n",
178 reg->sr_match = nbv.bv_val;
180 ber_str2bv( replace, 0, 0, &bv );
181 rc = slap_parseURI( &bv, ®->sr_replace.dn, ®->sr_replace.scope,
182 &filter, ®->sr_replace.filter );
183 if ( filter ) filter_free( filter );
186 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
187 "slap_sasl_regexp_config: \"%s\" could not be parsed.\n",
190 Debug( LDAP_DEBUG_ANY,
191 "SASL replace pattern %s could not be parsed.\n",
197 /* Precompile matching pattern */
198 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
201 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
202 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
205 Debug( LDAP_DEBUG_ANY,
206 "SASL match pattern %s could not be compiled by regexp engine\n",
207 reg->sr_match, 0, 0 );
210 return( LDAP_OPERATIONS_ERROR );
213 rc = slap_sasl_rx_off( reg->sr_replace.dn.bv_val, reg->sr_dn_offset );
214 if ( rc != LDAP_SUCCESS ) return rc;
216 if (reg->sr_replace.filter.bv_val ) {
217 rc = slap_sasl_rx_off( reg->sr_replace.filter.bv_val, reg->sr_fi_offset );
218 if ( rc != LDAP_SUCCESS ) return rc;
223 return( LDAP_SUCCESS );
227 #ifdef HAVE_CYRUS_SASL
229 /* Perform replacement on regexp matches */
230 static void slap_sasl_rx_exp( char *rep, int *off, regmatch_t *str,
231 char *saslname, struct berval *out )
233 int i, n, len, insert;
235 /* Get the total length of the final URI */
239 while( off[n] >= 0 ) {
240 /* Len of next section from replacement string (x,y,z above) */
241 len += off[n] - off[n-1] - 2;
245 /* Len of string from saslname that matched next $i (b,d above) */
246 i = rep[ off[n] + 1 ] - '0';
247 len += str[i].rm_eo - str[i].rm_so;
250 out->bv_val = ch_malloc( len + 1 );
253 /* Fill in URI with replace string, replacing $i as we go */
256 while( off[n] >= 0) {
257 /* Paste in next section from replacement string (x,y,z above) */
258 len = off[n] - off[n-1] - 2;
259 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
264 /* Paste in string from saslname that matched next $i (b,d above) */
265 i = rep[ off[n] + 1 ] - '0';
266 len = str[i].rm_eo - str[i].rm_so;
267 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
273 out->bv_val[insert] = '\0';
276 /* Take the passed in SASL name and attempt to convert it into an
277 LDAP URI to find the matching LDAP entry, using the pattern matching
278 strings given in the saslregexp config file directive(s) */
280 static int slap_sasl_regexp( struct berval *in, SaslUri_t *out )
282 char *saslname = in->bv_val;
283 char *scope[] = { "base", "one", "sub" };
287 memset( out, 0, sizeof( *out ) );
290 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
291 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
293 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
297 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
300 /* Match the normalized SASL name to the saslregexp patterns */
301 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
302 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
303 reg->sr_strings, 0) == 0 )
307 if( i >= nSaslRegexp )
311 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
312 * replace pattern of the form "x$1y$2z". The returned string needs
313 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
315 slap_sasl_rx_exp( reg->sr_replace.dn.bv_val, reg->sr_dn_offset,
316 reg->sr_strings, saslname, &out->dn );
318 if ( reg->sr_replace.filter.bv_val )
319 slap_sasl_rx_exp( reg->sr_replace.filter.bv_val,
320 reg->sr_fi_offset, reg->sr_strings, saslname, &out->filter );
322 out->scope = reg->sr_replace.scope;
325 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
326 "slap_sasl_regexp: converted SASL name to ldap:///%s??%s?%s\n",
327 out->dn.bv_val, scope[out->scope], out->filter.bv_val ?
328 out->filter.bv_val : "" ));
330 Debug( LDAP_DEBUG_TRACE,
331 "slap_sasl_regexp: converted SASL name to ldap:///%s??%s?%s\n",
332 out->dn.bv_val, scope[out->scope], out->filter.bv_val ?
333 out->filter.bv_val : "" );
339 /* Two empty callback functions to avoid sending results */
340 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
341 ber_int_t msgid, ber_int_t err, const char *matched,
342 const char *text, BerVarray ref, const char *resoid,
343 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
347 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
348 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
353 /* This callback actually does some work...*/
354 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
355 Entry *e, AttributeName *an, int ao, LDAPControl **c)
357 struct berval *ndn = o->o_callback->sc_private;
359 /* We only want to be called once */
364 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
365 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
367 Debug( LDAP_DEBUG_TRACE,
368 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
372 ber_dupbv(ndn, &e->e_nname);
378 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
379 * return the LDAP DN to which it matches. The SASL regexp rules in the config
380 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
381 * search with scope=base), just return the URI (or its searchbase). Otherwise
382 * an internal search must be done, and if that search returns exactly one
383 * entry, return the DN of that one entry.
386 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
391 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
396 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
397 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
399 Debug( LDAP_DEBUG_TRACE,
400 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
406 /* Convert the SASL name into a minimal URI */
407 if( !slap_sasl_regexp( saslname, &uri ) )
410 if ( uri.filter.bv_val )
411 filter = str2filter( uri.filter.bv_val );
413 /* FIXME: move this check to after select_backend, and set
414 * the selected backend in conn->c_authz_backend, to allow
415 * passwd_extop to be used on in-directory SASL secrets.
416 * ... when all of that gets implemented...
418 /* Massive shortcut: search scope == base */
419 if( uri.scope == LDAP_SCOPE_BASE ) {
422 uri.dn.bv_val = NULL;
426 /* Must do an internal search */
429 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
430 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
431 uri.dn.bv_val, uri.scope ));
433 Debug( LDAP_DEBUG_TRACE,
434 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
435 uri.dn.bv_val, uri.scope, 0 );
438 be = select_backend( &uri.dn, 0, 1 );
439 if(( be == NULL ) || ( be->be_search == NULL))
441 suffix_alias( be, &uri.dn );
443 op.o_tag = LDAP_REQ_SEARCH;
444 op.o_protocol = LDAP_VERSION3;
445 op.o_ndn = *saslname;
447 op.o_time = slap_get_time();
449 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &uri.dn,
450 uri.scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
451 /*attrs=*/NULL, /*attrsonly=*/0 );
454 if( uri.dn.bv_len ) ch_free( uri.dn.bv_val );
455 if( uri.filter.bv_len ) ch_free( uri.filter.bv_val );
456 if( filter ) filter_free( filter );
459 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
460 "slap_sasl2dn: Converted SASL name to %s\n",
461 dn->bv_len ? dn->bv_val : "<nothing>" ));
463 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
464 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
470 typedef struct smatch_info {
475 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
476 Entry *e, AttributeName *an, int ao, LDAPControl **c)
478 smatch_info *sm = o->o_callback->sc_private;
480 if (dn_match(sm->dn, &e->e_nname)) {
482 return -1; /* short-circuit the search */
489 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
490 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
491 * the rule must be used as an internal search for entries. If that search
492 * returns the *assertDN entry, the match is successful.
494 * The assertDN should not have the dn: prefix
498 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
500 struct berval searchbase = {0, NULL};
506 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
510 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
511 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
513 Debug( LDAP_DEBUG_TRACE,
514 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
517 rc = slap_parseURI( rule, &searchbase, &scope, &filter, NULL );
518 if( rc != LDAP_SUCCESS )
521 /* Massive shortcut: search scope == base */
522 if( scope == LDAP_SCOPE_BASE ) {
523 rc = regcomp(®, searchbase.bv_val,
524 REG_EXTENDED|REG_ICASE|REG_NOSUB);
526 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
532 rc = LDAP_INAPPROPRIATE_AUTH;
536 /* Must run an internal search. */
539 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
540 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
541 searchbase.bv_val, scope ));
543 Debug( LDAP_DEBUG_TRACE,
544 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
545 searchbase.bv_val, scope, 0 );
548 be = select_backend( &searchbase, 0, 1 );
549 if(( be == NULL ) || ( be->be_search == NULL)) {
550 rc = LDAP_INAPPROPRIATE_AUTH;
553 suffix_alias( be, &searchbase );
559 op.o_tag = LDAP_REQ_SEARCH;
560 op.o_protocol = LDAP_VERSION3;
563 op.o_time = slap_get_time();
565 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
566 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
567 /*attrs=*/NULL, /*attrsonly=*/0 );
572 rc = LDAP_INAPPROPRIATE_AUTH;
575 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
576 if( filter ) filter_free( filter );
578 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
579 "slap_sasl_match: comparison returned %d\n", rc ));
581 Debug( LDAP_DEBUG_TRACE,
582 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
590 * This function answers the question, "Can this ID authorize to that ID?",
591 * based on authorization rules. The rules are stored in the *searchDN, in the
592 * attribute named by *attr. If any of those rules map to the *assertDN, the
593 * authorization is approved.
595 * The DNs should not have the dn: prefix
598 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
603 AttributeDescription *ad=NULL;
606 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
607 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
608 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
610 Debug( LDAP_DEBUG_TRACE,
611 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
612 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
615 rc = slap_bv2ad( attr, &ad, &errmsg );
616 if( rc != LDAP_SUCCESS )
619 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
620 if( rc != LDAP_SUCCESS )
623 /* Check if the *assertDN matches any **vals */
624 for( i=0; vals[i].bv_val != NULL; i++ ) {
625 rc = slap_sasl_match( &vals[i], assertDN, authc );
626 if ( rc == LDAP_SUCCESS )
629 rc = LDAP_INAPPROPRIATE_AUTH;
632 if( vals ) ber_bvarray_free( vals );
635 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
636 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
638 Debug( LDAP_DEBUG_TRACE,
639 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
644 #endif /* HAVE_CYRUS_SASL */
647 /* Check if a bind can SASL authorize to another identity.
648 * The DNs should not have the dn: prefix
651 static struct berval sasl_authz_src = {
652 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
654 static struct berval sasl_authz_dst = {
655 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
657 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
659 int rc = LDAP_INAPPROPRIATE_AUTH;
661 #ifdef HAVE_CYRUS_SASL
662 /* User binding as anonymous */
663 if ( authzDN == NULL ) {
669 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
670 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
672 Debug( LDAP_DEBUG_TRACE,
673 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
676 /* If person is authorizing to self, succeed */
677 if ( dn_match( authcDN, authzDN ) ) {
682 /* Check source rules */
683 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
685 if( rc == LDAP_SUCCESS ) {
689 /* Check destination rules */
690 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
692 if( rc == LDAP_SUCCESS ) {
696 rc = LDAP_INAPPROPRIATE_AUTH;
702 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
703 "slap_sasl_authorized: return %d\n", rc ));
705 Debug( LDAP_DEBUG_TRACE,
706 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );