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( Connection *conn, 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 /* Must do an internal search */
415 be = select_backend( &uri.dn, 0, 1 );
417 conn->c_authz_backend = be;
419 /* Massive shortcut: search scope == base */
420 if( uri.scope == LDAP_SCOPE_BASE ) {
423 uri.dn.bv_val = NULL;
428 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
429 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
430 uri.dn.bv_val, uri.scope ));
432 Debug( LDAP_DEBUG_TRACE,
433 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
434 uri.dn.bv_val, uri.scope, 0 );
437 if(( be == NULL ) || ( be->be_search == NULL))
439 suffix_alias( be, &uri.dn );
441 op.o_tag = LDAP_REQ_SEARCH;
442 op.o_protocol = LDAP_VERSION3;
443 op.o_ndn = *saslname;
445 op.o_time = slap_get_time();
447 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &uri.dn,
448 uri.scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
449 /*attrs=*/NULL, /*attrsonly=*/0 );
452 if( uri.dn.bv_len ) ch_free( uri.dn.bv_val );
453 if( uri.filter.bv_len ) ch_free( uri.filter.bv_val );
454 if( filter ) filter_free( filter );
457 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
458 "slap_sasl2dn: Converted SASL name to %s\n",
459 dn->bv_len ? dn->bv_val : "<nothing>" ));
461 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
462 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
468 typedef struct smatch_info {
473 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
474 Entry *e, AttributeName *an, int ao, LDAPControl **c)
476 smatch_info *sm = o->o_callback->sc_private;
478 if (dn_match(sm->dn, &e->e_nname)) {
480 return -1; /* short-circuit the search */
487 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
488 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
489 * the rule must be used as an internal search for entries. If that search
490 * returns the *assertDN entry, the match is successful.
492 * The assertDN should not have the dn: prefix
496 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
498 struct berval searchbase = {0, NULL};
504 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
508 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
509 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
511 Debug( LDAP_DEBUG_TRACE,
512 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
515 rc = slap_parseURI( rule, &searchbase, &scope, &filter, NULL );
516 if( rc != LDAP_SUCCESS )
519 /* Massive shortcut: search scope == base */
520 if( scope == LDAP_SCOPE_BASE ) {
521 rc = regcomp(®, searchbase.bv_val,
522 REG_EXTENDED|REG_ICASE|REG_NOSUB);
524 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
530 rc = LDAP_INAPPROPRIATE_AUTH;
534 /* Must run an internal search. */
537 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
538 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
539 searchbase.bv_val, scope ));
541 Debug( LDAP_DEBUG_TRACE,
542 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
543 searchbase.bv_val, scope, 0 );
546 be = select_backend( &searchbase, 0, 1 );
547 if(( be == NULL ) || ( be->be_search == NULL)) {
548 rc = LDAP_INAPPROPRIATE_AUTH;
551 suffix_alias( be, &searchbase );
557 op.o_tag = LDAP_REQ_SEARCH;
558 op.o_protocol = LDAP_VERSION3;
561 op.o_time = slap_get_time();
563 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
564 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
565 /*attrs=*/NULL, /*attrsonly=*/0 );
570 rc = LDAP_INAPPROPRIATE_AUTH;
573 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
574 if( filter ) filter_free( filter );
576 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
577 "slap_sasl_match: comparison returned %d\n", rc ));
579 Debug( LDAP_DEBUG_TRACE,
580 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
588 * This function answers the question, "Can this ID authorize to that ID?",
589 * based on authorization rules. The rules are stored in the *searchDN, in the
590 * attribute named by *attr. If any of those rules map to the *assertDN, the
591 * authorization is approved.
593 * The DNs should not have the dn: prefix
596 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
601 AttributeDescription *ad=NULL;
604 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
605 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
606 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
608 Debug( LDAP_DEBUG_TRACE,
609 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
610 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
613 rc = slap_bv2ad( attr, &ad, &errmsg );
614 if( rc != LDAP_SUCCESS )
617 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
618 if( rc != LDAP_SUCCESS )
621 /* Check if the *assertDN matches any **vals */
622 for( i=0; vals[i].bv_val != NULL; i++ ) {
623 rc = slap_sasl_match( &vals[i], assertDN, authc );
624 if ( rc == LDAP_SUCCESS )
627 rc = LDAP_INAPPROPRIATE_AUTH;
630 if( vals ) ber_bvarray_free( vals );
633 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
634 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
636 Debug( LDAP_DEBUG_TRACE,
637 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
642 #endif /* HAVE_CYRUS_SASL */
645 /* Check if a bind can SASL authorize to another identity.
646 * The DNs should not have the dn: prefix
649 static struct berval sasl_authz_src = {
650 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
652 static struct berval sasl_authz_dst = {
653 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
655 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
657 int rc = LDAP_INAPPROPRIATE_AUTH;
659 #ifdef HAVE_CYRUS_SASL
660 /* User binding as anonymous */
661 if ( authzDN == NULL ) {
667 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
668 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
670 Debug( LDAP_DEBUG_TRACE,
671 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
674 /* If person is authorizing to self, succeed */
675 if ( dn_match( authcDN, authzDN ) ) {
680 /* Check source rules */
681 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
683 if( rc == LDAP_SUCCESS ) {
687 /* Check destination rules */
688 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
690 if( rc == LDAP_SUCCESS ) {
694 rc = LDAP_INAPPROPRIATE_AUTH;
700 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
701 "slap_sasl_authorized: return %d\n", rc ));
703 Debug( LDAP_DEBUG_TRACE,
704 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );