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>
34 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
36 static int slap_parseURI( struct berval *uri,
37 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;
50 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
51 "slap_parseURI: parsing %s\n", uri->bv_val ));
53 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
56 /* If it does not look like a URI, assume it is a DN */
57 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
58 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
59 bv.bv_val += strspn( bv.bv_val, " " );
61 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
62 rc = dnNormalize2( NULL, &bv, searchbase );
63 if (rc == LDAP_SUCCESS) {
64 *scope = LDAP_SCOPE_BASE;
69 rc = ldap_url_parse( uri->bv_val, &ludp );
70 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
71 bv.bv_val = uri->bv_val;
75 if ( rc != LDAP_URL_SUCCESS ) {
76 return( LDAP_PROTOCOL_ERROR );
79 /* could check the hostname here */
82 *scope = ludp->lud_scope;
85 if ( ludp->lud_filter ) {
86 *filter = str2filter( ludp->lud_filter );
89 /* Grab the searchbase */
90 bv.bv_val = ludp->lud_dn;
91 bv.bv_len = strlen( bv.bv_val );
92 rc = dnNormalize2( NULL, &bv, searchbase );
94 ldap_free_urldesc( ludp );
100 int slap_sasl_regexp_config( const char *match, const char *replace )
102 #ifdef HAVE_CYRUS_SASL
106 struct berval bv, nbv;
108 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
109 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
110 reg = &( SaslRegexp[nSaslRegexp] );
111 ber_str2bv( match, 0, 0, &bv );
112 rc = dnNormalize2( NULL, &bv, &nbv );
115 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
116 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
119 Debug( LDAP_DEBUG_ANY,
120 "SASL match pattern %s could not be normalized.\n",
125 reg->sr_match = nbv.bv_val;
127 ber_str2bv( replace, 0, 0, &bv );
128 rc = dnNormalize2( NULL, &bv, &nbv );
131 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
132 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
135 Debug( LDAP_DEBUG_ANY,
136 "SASL replace pattern %s could not be normalized.\n",
141 reg->sr_replace = nbv.bv_val;
143 /* Precompile matching pattern */
144 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
147 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
148 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
151 Debug( LDAP_DEBUG_ANY,
152 "SASL match pattern %s could not be compiled by regexp engine\n",
153 reg->sr_match, 0, 0 );
156 return( LDAP_OPERATIONS_ERROR );
159 /* Precompile replace pattern. Find the $<n> placeholders */
160 reg->sr_offset[0] = -2;
162 for ( c = reg->sr_replace; *c; c++ ) {
168 if ( n == SASLREGEX_REPLACE ) {
170 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
171 "slap_sasl_regexp_config: \"%s\" has too many $n "
172 "placeholders (max %d)\n",
173 reg->sr_replace, SASLREGEX_REPLACE ));
175 Debug( LDAP_DEBUG_ANY,
176 "SASL replace pattern %s has too many $n "
177 "placeholders (max %d)\n",
178 reg->sr_replace, SASLREGEX_REPLACE, 0 );
181 return( LDAP_OPERATIONS_ERROR );
183 reg->sr_offset[n] = c - reg->sr_replace;
188 /* Final placeholder, after the last $n */
189 reg->sr_offset[n] = c - reg->sr_replace;
191 reg->sr_offset[n] = -1;
195 return( LDAP_SUCCESS );
199 #ifdef HAVE_CYRUS_SASL
201 /* Take the passed in SASL name and attempt to convert it into an
202 LDAP URI to find the matching LDAP entry, using the pattern matching
203 strings given in the saslregexp config file directive(s) */
204 static int slap_sasl_regexp( struct berval *in, struct berval *out )
206 char *saslname = in->bv_val;
207 int i, n, len, insert;
214 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
215 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
217 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
221 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
224 /* Match the normalized SASL name to the saslregexp patterns */
225 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
226 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
227 reg->sr_strings, 0) == 0 )
231 if( i >= nSaslRegexp )
235 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
236 * replace pattern of the form "x$1y$2z". The returned string needs
237 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
241 /* Get the total length of the final URI */
245 while( reg->sr_offset[n] >= 0 ) {
246 /* Len of next section from replacement string (x,y,z above) */
247 len += reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
248 if( reg->sr_offset[n+1] < 0)
251 /* Len of string from saslname that matched next $i (b,d above) */
252 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
253 len += reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
256 out->bv_val = ch_malloc( len + 1 );
259 /* Fill in URI with replace string, replacing $i as we go */
262 while( reg->sr_offset[n] >= 0) {
263 /* Paste in next section from replacement string (x,y,z above) */
264 len = reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
265 strncpy( out->bv_val+insert, reg->sr_replace + reg->sr_offset[n-1] + 2, len);
267 if( reg->sr_offset[n+1] < 0)
270 /* Paste in string from saslname that matched next $i (b,d above) */
271 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
272 len = reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
273 strncpy( out->bv_val+insert, saslname + reg->sr_strings[i].rm_so, len );
279 out->bv_val[insert] = '\0';
281 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
282 "slap_sasl_regexp: converted SASL name to %s\n", out->bv_val ));
284 Debug( LDAP_DEBUG_TRACE,
285 "slap_sasl_regexp: converted SASL name to %s\n", out->bv_val, 0, 0 );
291 /* Two empty callback functions to avoid sending results */
292 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
293 ber_int_t msgid, ber_int_t err, const char *matched,
294 const char *text, BerVarray ref, const char *resoid,
295 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
299 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
300 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
305 /* This callback actually does some work...*/
306 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
307 Entry *e, AttributeName *an, int ao, LDAPControl **c)
309 struct berval *ndn = o->o_callback->sc_private;
311 /* We only want to be called once */
316 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
317 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
319 Debug( LDAP_DEBUG_TRACE,
320 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
324 ber_dupbv(ndn, &e->e_nname);
330 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
331 * return the LDAP DN to which it matches. The SASL regexp rules in the config
332 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
333 * search with scope=base), just return the URI (or its searchbase). Otherwise
334 * an internal search must be done, and if that search returns exactly one
335 * entry, return the DN of that one entry.
338 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
340 struct berval uri = {0, NULL};
341 struct berval searchbase = {0, NULL};
345 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
349 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
350 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
352 Debug( LDAP_DEBUG_TRACE,
353 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
359 /* Convert the SASL name into an LDAP URI */
360 if( !slap_sasl_regexp( saslname, &uri ) )
363 rc = slap_parseURI( &uri, &searchbase, &scope, &filter );
368 /* Massive shortcut: search scope == base */
369 if( scope == LDAP_SCOPE_BASE ) {
371 searchbase.bv_len = 0;
372 searchbase.bv_val = NULL;
376 /* Must do an internal search */
379 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
380 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
381 searchbase.bv_val, scope ));
383 Debug( LDAP_DEBUG_TRACE,
384 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
385 searchbase.bv_val, scope, 0 );
388 be = select_backend( &searchbase, 0, 1 );
389 if(( be == NULL ) || ( be->be_search == NULL))
391 suffix_alias( be, &searchbase );
393 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
394 op.o_tag = LDAP_REQ_SEARCH;
395 op.o_protocol = LDAP_VERSION3;
396 op.o_ndn = *saslname;
398 op.o_time = slap_get_time();
400 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
401 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
402 /*attrs=*/NULL, /*attrsonly=*/0 );
404 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
407 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
408 if( filter ) filter_free( filter );
409 if( uri.bv_val ) ch_free( uri.bv_val );
412 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
413 "slap_sasl2dn: Converted SASL name to %s\n",
414 dn->bv_len ? dn->bv_val : "<nothing>" ));
416 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
417 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
423 typedef struct smatch_info {
428 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
429 Entry *e, AttributeName *an, int ao, LDAPControl **c)
431 smatch_info *sm = o->o_callback->sc_private;
433 if (dn_match(sm->dn, &e->e_nname)) {
435 return -1; /* short-circuit the search */
442 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
443 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
444 * the rule must be used as an internal search for entries. If that search
445 * returns the *assertDN entry, the match is successful.
447 * The assertDN should not have the dn: prefix
451 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
453 struct berval searchbase = {0, NULL};
459 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
463 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
464 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
466 Debug( LDAP_DEBUG_TRACE,
467 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
470 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
471 if( rc != LDAP_SUCCESS )
474 /* Massive shortcut: search scope == base */
475 if( scope == LDAP_SCOPE_BASE ) {
476 rc = regcomp(®, searchbase.bv_val,
477 REG_EXTENDED|REG_ICASE|REG_NOSUB);
479 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
485 rc = LDAP_INAPPROPRIATE_AUTH;
489 /* Must run an internal search. */
492 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
493 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
494 searchbase.bv_val, scope ));
496 Debug( LDAP_DEBUG_TRACE,
497 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
498 searchbase.bv_val, scope, 0 );
501 be = select_backend( &searchbase, 0, 1 );
502 if(( be == NULL ) || ( be->be_search == NULL)) {
503 rc = LDAP_INAPPROPRIATE_AUTH;
506 suffix_alias( be, &searchbase );
512 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
513 op.o_tag = LDAP_REQ_SEARCH;
514 op.o_protocol = LDAP_VERSION3;
517 op.o_time = slap_get_time();
519 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
520 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
521 /*attrs=*/NULL, /*attrsonly=*/0 );
523 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
528 rc = LDAP_INAPPROPRIATE_AUTH;
531 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
532 if( filter ) filter_free( filter );
534 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
535 "slap_sasl_match: comparison returned %d\n", rc ));
537 Debug( LDAP_DEBUG_TRACE,
538 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
546 * This function answers the question, "Can this ID authorize to that ID?",
547 * based on authorization rules. The rules are stored in the *searchDN, in the
548 * attribute named by *attr. If any of those rules map to the *assertDN, the
549 * authorization is approved.
551 * DN's passed in should have a dn: prefix
554 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
559 AttributeDescription *ad=NULL;
563 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
564 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
565 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
567 Debug( LDAP_DEBUG_TRACE,
568 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
569 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
572 rc = slap_bv2ad( attr, &ad, &errmsg );
573 if( rc != LDAP_SUCCESS )
576 bv.bv_val = searchDN->bv_val + 3;
577 bv.bv_len = searchDN->bv_len - 3;
578 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
579 if( rc != LDAP_SUCCESS )
582 bv.bv_val = assertDN->bv_val + 3;
583 bv.bv_len = assertDN->bv_len - 3;
584 /* Check if the *assertDN matches any **vals */
585 for( i=0; vals[i].bv_val != NULL; i++ ) {
586 rc = slap_sasl_match( &vals[i], &bv, authc );
587 if ( rc == LDAP_SUCCESS )
590 rc = LDAP_INAPPROPRIATE_AUTH;
593 if( vals ) ber_bvarray_free( vals );
596 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
597 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
599 Debug( LDAP_DEBUG_TRACE,
600 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
605 #endif /* HAVE_CYRUS_SASL */
608 /* Check if a bind can SASL authorize to another identity.
609 Accepts authorization DN's with "dn:" prefix */
611 static struct berval sasl_authz_src = {
612 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
614 static struct berval sasl_authz_dst = {
615 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
617 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
619 int rc = LDAP_INAPPROPRIATE_AUTH;
621 #ifdef HAVE_CYRUS_SASL
622 /* User binding as anonymous */
623 if ( authzDN == NULL ) {
629 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
630 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
632 Debug( LDAP_DEBUG_TRACE,
633 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
636 /* If person is authorizing to self, succeed */
637 if ( dn_match( authcDN, authzDN ) ) {
642 /* Check source rules */
643 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
645 if( rc == LDAP_SUCCESS ) {
649 /* Check destination rules */
650 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
652 if( rc == LDAP_SUCCESS ) {
656 rc = LDAP_INAPPROPRIATE_AUTH;
662 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
663 "slap_sasl_authorized: return %d\n", rc ));
665 Debug( LDAP_DEBUG_TRACE,
666 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );