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
28 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
30 static int slap_parseURI( char *uri,
31 struct berval *searchbase, int *scope, Filter **filter )
38 assert( uri != NULL );
39 searchbase->bv_val = NULL;
40 searchbase->bv_len = 0;
45 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
46 "slap_parseURI: parsing %s\n", uri ));
48 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
51 /* If it does not look like a URI, assume it is a DN */
52 if( !strncasecmp( uri, "dn:", sizeof("dn:")-1 ) ) {
53 uri += sizeof("dn:")-1;
54 uri += strspn( uri, " " );
56 /* FIXME: if dnNormalize actually uses input bv_len we
57 * will have to make this right.
60 rc = dnNormalize2( NULL, &bv, searchbase );
61 if (rc == LDAP_SUCCESS) {
62 *scope = LDAP_SCOPE_BASE;
67 /* FIXME: should use ldap_url_parse() */
68 if( strncasecmp( uri, "ldap://", sizeof("ldap://")-1 ) ) {
73 end = strchr( uri + (sizeof("ldap://")-1), '/' );
75 return( LDAP_PROTOCOL_ERROR );
77 /* could check the hostname here */
79 /* Grab the searchbase */
81 end = strchr( start, '?' );
85 return dnNormalize2( NULL, &bv, searchbase );
88 bv.bv_len = end - start;
89 rc = dnNormalize2( NULL, &bv, searchbase );
91 if (rc != LDAP_SUCCESS)
96 end = strchr( start, '?' );
98 return( LDAP_SUCCESS );
103 if( !strncasecmp( start, "base?", sizeof("base?")-1 )) {
104 *scope = LDAP_SCOPE_BASE;
105 start += sizeof("base?")-1;
107 else if( !strncasecmp( start, "one?", sizeof("one?")-1 )) {
108 *scope = LDAP_SCOPE_ONELEVEL;
109 start += sizeof("one?")-1;
111 else if( !strncasecmp( start, "sub?", sizeof("sub?")-1 )) {
112 *scope = LDAP_SCOPE_SUBTREE;
113 start += sizeof("sub?")-1;
116 free( searchbase->bv_val );
117 searchbase->bv_val = NULL;
118 return( LDAP_PROTOCOL_ERROR );
121 /* Grab the filter */
122 *filter = str2filter( start );
124 return( LDAP_SUCCESS );
128 int slap_sasl_regexp_config( const char *match, const char *replace )
130 #ifdef HAVE_CYRUS_SASL
134 struct berval bv, nbv;
136 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
137 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
138 reg = &( SaslRegexp[nSaslRegexp] );
139 ber_str2bv( match, 0, 0, &bv );
140 rc = dnNormalize2( NULL, &bv, &nbv );
143 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
144 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
147 Debug( LDAP_DEBUG_ANY,
148 "SASL match pattern %s could not be normalized.\n",
153 reg->match = nbv.bv_val;
155 ber_str2bv( replace, 0, 0, &bv );
156 rc = dnNormalize2( NULL, &bv, &nbv );
159 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
160 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
163 Debug( LDAP_DEBUG_ANY,
164 "SASL replace pattern %s could not be normalized.\n",
169 reg->replace = nbv.bv_val;
171 /* Precompile matching pattern */
172 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
175 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
176 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
179 Debug( LDAP_DEBUG_ANY,
180 "SASL match pattern %s could not be compiled by regexp engine\n",
184 return( LDAP_OPERATIONS_ERROR );
187 /* Precompile replace pattern. Find the $<n> placeholders */
190 for ( c = reg->replace; *c; c++ ) {
196 if ( n == SASLREGEX_REPLACE ) {
198 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
199 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
200 reg->replace, SASLREGEX_REPLACE ));
202 Debug( LDAP_DEBUG_ANY,
203 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
204 reg->replace, SASLREGEX_REPLACE, 0 );
207 return( LDAP_OPERATIONS_ERROR );
209 reg->offset[n] = c - reg->replace;
214 /* Final placeholder, after the last $n */
215 reg->offset[n] = c - reg->replace;
221 return( LDAP_SUCCESS );
225 #ifdef HAVE_CYRUS_SASL
227 /* Take the passed in SASL name and attempt to convert it into an
228 LDAP URI to find the matching LDAP entry, using the pattern matching
229 strings given in the saslregexp config file directive(s) */
231 char *slap_sasl_regexp( char *saslname )
234 int i, n, len, insert;
238 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
239 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
241 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
245 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
248 /* Match the normalized SASL name to the saslregexp patterns */
249 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
250 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
251 reg->strings, 0) == 0 )
255 if( i >= nSaslRegexp )
259 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
260 * replace pattern of the form "x$1y$2z". The returned string needs
261 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
265 /* Get the total length of the final URI */
269 while( reg->offset[n] >= 0 ) {
270 /* Len of next section from replacement string (x,y,z above) */
271 len += reg->offset[n] - reg->offset[n-1] - 2;
272 if( reg->offset[n+1] < 0)
275 /* Len of string from saslname that matched next $i (b,d above) */
276 i = reg->replace[ reg->offset[n] + 1 ] - '0';
277 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
280 uri = ch_malloc( len + 1 );
282 /* Fill in URI with replace string, replacing $i as we go */
285 while( reg->offset[n] >= 0) {
286 /* Paste in next section from replacement string (x,y,z above) */
287 len = reg->offset[n] - reg->offset[n-1] - 2;
288 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
290 if( reg->offset[n+1] < 0)
293 /* Paste in string from saslname that matched next $i (b,d above) */
294 i = reg->replace[ reg->offset[n] + 1 ] - '0';
295 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
296 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
304 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
305 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
307 Debug( LDAP_DEBUG_TRACE,
308 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
314 /* Two empty callback functions to avoid sending results */
315 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
316 ber_int_t msgid, ber_int_t err, const char *matched,
317 const char *text, BerVarray ref, const char *resoid,
318 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
322 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
323 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
328 /* This callback actually does some work...*/
329 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
330 Entry *e, AttributeName *an, int ao, LDAPControl **c)
332 struct berval *ndn = o->o_callback->sc_private;
334 /* We only want to be called once */
339 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
340 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
342 Debug( LDAP_DEBUG_TRACE,
343 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
347 ber_dupbv(ndn, &e->e_nname);
353 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
354 * return the LDAP DN to which it matches. The SASL regexp rules in the config
355 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
356 * search with scope=base), just return the URI (or its searchbase). Otherwise
357 * an internal search must be done, and if that search returns exactly one
358 * entry, return the DN of that one entry.
361 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
364 struct berval searchbase = {0, NULL};
368 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
372 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
373 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
375 Debug( LDAP_DEBUG_TRACE,
376 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
382 /* Convert the SASL name into an LDAP URI */
383 uri = slap_sasl_regexp( saslname->bv_val );
387 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
392 /* Massive shortcut: search scope == base */
393 if( scope == LDAP_SCOPE_BASE ) {
395 searchbase.bv_len = 0;
396 searchbase.bv_val = NULL;
400 /* Must do an internal search */
403 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
404 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
405 searchbase.bv_val, scope ));
407 Debug( LDAP_DEBUG_TRACE,
408 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
409 searchbase.bv_val, scope, 0 );
412 be = select_backend( &searchbase, 0, 1 );
413 if(( be == NULL ) || ( be->be_search == NULL))
415 suffix_alias( be, &searchbase );
417 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
418 op.o_tag = LDAP_REQ_SEARCH;
419 op.o_protocol = LDAP_VERSION3;
420 op.o_ndn = *saslname;
422 op.o_time = slap_get_time();
424 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
425 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
426 /*attrs=*/NULL, /*attrsonly=*/0 );
428 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
431 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
432 if( filter ) filter_free( filter );
433 if( uri ) ch_free( uri );
436 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
437 "slap_sasl2dn: Converted SASL name to %s\n",
438 dn->bv_len ? dn->bv_val : "<nothing>" ));
440 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
441 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
447 typedef struct smatch_info {
452 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
453 Entry *e, AttributeName *an, int ao, LDAPControl **c)
455 smatch_info *sm = o->o_callback->sc_private;
457 if (dn_match(sm->dn, &e->e_nname)) {
459 return -1; /* short-circuit the search */
466 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
467 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
468 * the rule must be used as an internal search for entries. If that search
469 * returns the *assertDN entry, the match is successful.
471 * The assertDN should not have the dn: prefix
475 int slap_sasl_match( char *rule, struct berval *assertDN, struct berval *authc )
477 struct berval searchbase = {0, NULL};
483 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_smatch, &sm};
487 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
488 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule ));
490 Debug( LDAP_DEBUG_TRACE,
491 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule, 0 );
494 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
495 if( rc != LDAP_SUCCESS )
498 /* Massive shortcut: search scope == base */
499 if( scope == LDAP_SCOPE_BASE ) {
500 rc = regcomp(®, searchbase.bv_val,
501 REG_EXTENDED|REG_ICASE|REG_NOSUB);
503 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
509 rc = LDAP_INAPPROPRIATE_AUTH;
513 /* Must run an internal search. */
516 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
517 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
518 searchbase.bv_val, scope ));
520 Debug( LDAP_DEBUG_TRACE,
521 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
522 searchbase.bv_val, scope, 0 );
525 be = select_backend( &searchbase, 0, 1 );
526 if(( be == NULL ) || ( be->be_search == NULL)) {
527 rc = LDAP_INAPPROPRIATE_AUTH;
530 suffix_alias( be, &searchbase );
535 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
536 op.o_tag = LDAP_REQ_SEARCH;
537 op.o_protocol = LDAP_VERSION3;
540 op.o_time = slap_get_time();
542 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
543 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
544 /*attrs=*/NULL, /*attrsonly=*/0 );
546 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
551 rc = LDAP_INAPPROPRIATE_AUTH;
554 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
555 if( filter ) filter_free( filter );
557 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
558 "slap_sasl_match: comparison returned %d\n", rc ));
560 Debug( LDAP_DEBUG_TRACE,
561 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
569 * This function answers the question, "Can this ID authorize to that ID?",
570 * based on authorization rules. The rules are stored in the *searchDN, in the
571 * attribute named by *attr. If any of those rules map to the *assertDN, the
572 * authorization is approved.
574 * DN's passed in should have a dn: prefix
577 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
582 AttributeDescription *ad=NULL;
586 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
587 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
588 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
590 Debug( LDAP_DEBUG_TRACE,
591 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
592 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
595 rc = slap_bv2ad( attr, &ad, &errmsg );
596 if( rc != LDAP_SUCCESS )
599 bv.bv_val = searchDN->bv_val + 3;
600 bv.bv_len = searchDN->bv_len - 3;
601 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
602 if( rc != LDAP_SUCCESS )
605 bv.bv_val = assertDN->bv_val + 3;
606 bv.bv_len = assertDN->bv_len - 3;
607 /* Check if the *assertDN matches any **vals */
608 for( i=0; vals[i].bv_val != NULL; i++ ) {
609 rc = slap_sasl_match( vals[i].bv_val, &bv, authc );
610 if ( rc == LDAP_SUCCESS )
613 rc = LDAP_INAPPROPRIATE_AUTH;
616 if( vals ) ber_bvarray_free( vals );
619 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
620 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
622 Debug( LDAP_DEBUG_TRACE,
623 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
628 #endif /* HAVE_CYRUS_SASL */
631 /* Check if a bind can SASL authorize to another identity.
632 Accepts authorization DN's with "dn:" prefix */
634 static struct berval sasl_authz_src = {
635 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
637 static struct berval sasl_authz_dst = {
638 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
640 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
642 int rc = LDAP_INAPPROPRIATE_AUTH;
644 #ifdef HAVE_CYRUS_SASL
645 /* User binding as anonymous */
646 if ( authzDN == NULL ) {
652 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
653 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
655 Debug( LDAP_DEBUG_TRACE,
656 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
659 /* If person is authorizing to self, succeed */
660 if ( dn_match( authcDN, authzDN ) ) {
665 /* Check source rules */
666 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
668 if( rc == LDAP_SUCCESS ) {
672 /* Check destination rules */
673 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
675 if( rc == LDAP_SUCCESS ) {
679 rc = LDAP_INAPPROPRIATE_AUTH;
685 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
686 "slap_sasl_authorized: return %d\n", rc ));
688 Debug( LDAP_DEBUG_TRACE,
689 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );