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.
15 #include <ac/stdlib.h>
18 #include <ac/string.h>
22 #ifdef HAVE_CYRUS_SASL
28 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
30 int slap_parseURI( char *uri, char **searchbase, int *scope, Filter **filter )
35 assert( uri != NULL );
41 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
42 "slap_parseURI: parsing %s\n", uri ));
44 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
48 /* If it does not look like a URI, assume it is a DN */
49 if( !strncasecmp( uri, "dn:", 3 ) ) {
51 uri += strspn( uri, " " );
52 *searchbase = ch_strdup( uri );
53 dn_normalize( *searchbase );
54 *scope = LDAP_SCOPE_BASE;
55 return( LDAP_SUCCESS );
57 if( strncasecmp( uri, "ldap://", 7 ) ) {
58 *searchbase = ch_strdup( uri );
59 dn_normalize( *searchbase );
60 *scope = LDAP_SCOPE_BASE;
61 return( LDAP_SUCCESS );
64 end = strchr( uri + 7, '/' );
66 return( LDAP_PROTOCOL_ERROR );
68 /* could check the hostname here */
70 /* Grab the searchbase */
72 end = strchr( start, '?' );
74 *searchbase = ch_strdup( start );
75 dn_normalize( *searchbase );
76 return( LDAP_SUCCESS );
79 *searchbase = ch_strdup( start );
81 dn_normalize( *searchbase );
85 end = strchr( start, '?' );
87 return( LDAP_SUCCESS );
92 if( !strncasecmp( start, "base?", 5 )) {
93 *scope = LDAP_SCOPE_BASE;
96 else if( !strncasecmp( start, "one?", 4 )) {
97 *scope = LDAP_SCOPE_ONELEVEL;
100 else if( !strncasecmp( start, "sub?", 3 )) {
101 *scope = LDAP_SCOPE_SUBTREE;
105 ch_free( *searchbase );
107 return( LDAP_PROTOCOL_ERROR );
110 /* Grab the filter */
111 *filter = str2filter( start );
113 return( LDAP_SUCCESS );
120 int slap_sasl_regexp_config( const char *match, const char *replace )
122 #ifdef HAVE_CYRUS_SASL
127 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
128 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
129 reg = &( SaslRegexp[nSaslRegexp] );
130 reg->match = ch_strdup( match );
131 reg->replace = ch_strdup( replace );
132 dn_normalize( reg->match );
133 dn_normalize( reg->replace );
135 /* Precompile matching pattern */
136 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
139 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
140 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
143 Debug( LDAP_DEBUG_ANY,
144 "SASL match pattern %s could not be compiled by regexp engine\n",
148 return( LDAP_OPERATIONS_ERROR );
151 /* Precompile replace pattern. Find the $<n> placeholders */
154 for ( c = reg->replace; *c; c++ ) {
160 if ( n == SASLREGEX_REPLACE ) {
162 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
163 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
164 reg->replace, SASLREGEX_REPLACE ));
166 Debug( LDAP_DEBUG_ANY,
167 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
168 reg->replace, SASLREGEX_REPLACE, 0 );
171 return( LDAP_OPERATIONS_ERROR );
173 reg->offset[n] = c - reg->replace;
178 /* Final placeholder, after the last $n */
179 reg->offset[n] = c - reg->replace;
185 return( LDAP_SUCCESS );
192 #ifdef HAVE_CYRUS_SASL
196 /* Take the passed in SASL name and attempt to convert it into an
197 LDAP URI to find the matching LDAP entry, using the pattern matching
198 strings given in the saslregexp config file directive(s) */
200 char *slap_sasl_regexp( char *saslname )
203 int i, n, len, insert;
208 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
209 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
211 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
215 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
218 /* Match the normalized SASL name to the saslregexp patterns */
219 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
220 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
221 reg->strings, 0) == 0 )
225 if( i >= nSaslRegexp )
229 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
230 * replace pattern of the form "x$1y$2z". The returned string needs
231 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
235 /* Get the total length of the final URI */
239 while( reg->offset[n] >= 0 ) {
240 /* Len of next section from replacement string (x,y,z above) */
241 len += reg->offset[n] - reg->offset[n-1] - 2;
242 if( reg->offset[n+1] < 0)
245 /* Len of string from saslname that matched next $i (b,d above) */
246 i = reg->replace[ reg->offset[n] + 1 ] - '0';
247 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
250 uri = ch_malloc( len + 1 );
252 /* Fill in URI with replace string, replacing $i as we go */
255 while( reg->offset[n] >= 0) {
256 /* Paste in next section from replacement string (x,y,z above) */
257 len = reg->offset[n] - reg->offset[n-1] - 2;
258 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
260 if( reg->offset[n+1] < 0)
263 /* Paste in string from saslname that matched next $i (b,d above) */
264 i = reg->replace[ reg->offset[n] + 1 ] - '0';
265 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
266 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
274 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
275 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
277 Debug( LDAP_DEBUG_TRACE,
278 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
289 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
290 * return the LDAP DN to which it matches. The SASL regexp rules in the config
291 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
292 * search with scope=base), just return the URI (or its searchbase). Otherwise
293 * an internal search must be done, and if that search returns exactly one
294 * entry, return the DN of that one entry.
297 char *slap_sasl2dn( char *saslname )
299 char *uri=NULL, *DN=NULL;
300 struct berval searchbase = {0, NULL};
304 Connection *conn=NULL;
306 LDAPMessage *res=NULL, *msg;
310 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
311 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
313 Debug( LDAP_DEBUG_TRACE,
314 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
318 /* Convert the SASL name into an LDAP URI */
319 uri = slap_sasl_regexp( saslname );
323 rc = slap_parseURI( uri, &searchbase.bv_val, &scope, &filter );
327 searchbase.bv_len = strlen( searchbase.bv_val );
328 /* Massive shortcut: search scope == base */
329 if( scope == LDAP_SCOPE_BASE ) {
330 DN = ch_strdup( searchbase.bv_val );
334 /* Must do an internal search */
337 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
338 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
339 searchbase.bv_val, scope ));
341 Debug( LDAP_DEBUG_TRACE,
342 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
343 searchbase.bv_val, scope, 0 );
347 be = select_backend( &searchbase, 0, 1 );
348 if(( be == NULL ) || ( be->be_search == NULL))
350 suffix_alias( be, &searchbase );
352 rc = connection_internal_open( &conn, &client, saslname );
353 if( rc != LDAP_SUCCESS )
356 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase.bv_val,
357 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
358 /*attrs=*/NULL, /*attrsonly=*/0 );
361 /* Read the client side of the internal search */
362 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
366 /* Make sure exactly one entry was returned */
367 rc = ldap_count_entries( client, res );
369 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
370 "slap_sasl2dn: search DN returned %d entries\n", rc ));
372 Debug( LDAP_DEBUG_TRACE,
373 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
379 msg = ldap_first_entry( client, res );
380 DN = ldap_get_dn( client, msg );
383 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
384 if( filter ) filter_free( filter );
385 if( uri ) ch_free( uri );
386 if( conn ) connection_internal_close( conn );
387 if( res ) ldap_msgfree( res );
388 if( client ) ldap_unbind( client );
389 if( DN ) dn_normalize( DN );
391 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
392 "slap_sasl2dn: Converted SASL name to %s\n", DN ? DN : "<nothing>" ));
394 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
395 DN ? DN : "<nothing>", 0, 0 );
406 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
407 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
408 * the rule must be used as an internal search for entries. If that search
409 * returns the *assertDN entry, the match is successful.
411 * The assertDN should not have the dn: prefix
415 int slap_sasl_match( char *rule, char *assertDN, char *authc )
418 struct berval searchbase = {0, NULL};
422 Connection *conn=NULL;
424 LDAPMessage *res=NULL, *msg;
429 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
430 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
432 Debug( LDAP_DEBUG_TRACE,
433 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
437 rc = slap_parseURI( rule, &searchbase.bv_val, &scope, &filter );
438 if( rc != LDAP_SUCCESS )
441 searchbase.bv_len = strlen( searchbase.bv_val );
442 /* Massive shortcut: search scope == base */
443 if( scope == LDAP_SCOPE_BASE ) {
444 dn_normalize( searchbase.bv_val );
445 rc = regcomp(®, searchbase.bv_val, REG_EXTENDED|REG_ICASE|REG_NOSUB);
447 rc = regexec(®, assertDN, 0, NULL, 0);
453 rc = LDAP_INAPPROPRIATE_AUTH;
457 /* Must run an internal search. */
460 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
461 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
462 searchbase.bv_val, scope ));
464 Debug( LDAP_DEBUG_TRACE,
465 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
466 searchbase.bv_val, scope, 0 );
470 be = select_backend( &searchbase, 0, 1 );
471 if(( be == NULL ) || ( be->be_search == NULL)) {
472 rc = LDAP_INAPPROPRIATE_AUTH;
475 suffix_alias( be, &searchbase );
477 /* Make an internal connection on which to run the search */
478 rc = connection_internal_open( &conn, &client, authc );
479 if( rc != LDAP_SUCCESS )
482 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase.bv_val,
483 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
484 /*attrs=*/NULL, /*attrsonly=*/0 );
487 /* On the client side of the internal search, read the results. Check
488 if the assertDN matches any of the DN's returned by the search */
489 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
493 for( msg=ldap_first_entry( client, res );
495 msg=ldap_next_entry( client, msg ) ) {
496 dn = ldap_get_dn( client, msg );
498 rc = strcmp( dn, assertDN );
505 rc = LDAP_INAPPROPRIATE_AUTH;
508 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
509 if( filter ) filter_free( filter );
510 if( conn ) connection_internal_close( conn );
511 if( res ) ldap_msgfree( res );
512 if( client ) ldap_unbind( client );
514 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
515 "slap_sasl_match: comparison returned %d\n", rc ));
517 Debug( LDAP_DEBUG_TRACE,
518 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
529 * This function answers the question, "Can this ID authorize to that ID?",
530 * based on authorization rules. The rules are stored in the *searchDN, in the
531 * attribute named by *attr. If any of those rules map to the *assertDN, the
532 * authorization is approved.
534 * DN's passed in should have a dn: prefix
538 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
542 struct berval **vals=NULL;
543 AttributeDescription *ad=NULL;
548 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
549 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
550 assertDN, attr, searchDN ));
552 Debug( LDAP_DEBUG_TRACE,
553 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
554 assertDN, attr, searchDN);
557 rc = slap_str2ad( attr, &ad, &errmsg );
558 if( rc != LDAP_SUCCESS )
561 bv.bv_val = searchDN+3;
562 bv.bv_len = strlen(bv.bv_val);
563 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
564 if( rc != LDAP_SUCCESS )
567 /* Check if the *assertDN matches any **vals */
568 for( i=0; vals[i] != NULL; i++ ) {
569 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
570 if ( rc == LDAP_SUCCESS )
573 rc = LDAP_INAPPROPRIATE_AUTH;
576 if( vals ) ber_bvecfree( vals );
579 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
580 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
582 Debug( LDAP_DEBUG_TRACE,
583 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
591 #endif /* HAVE_CYRUS_SASL */
597 /* Check if a bind can SASL authorize to another identity.
598 Accepts authorization DN's with "dn:" prefix */
600 int slap_sasl_authorized( char *authcDN, char *authzDN )
602 int rc = LDAP_INAPPROPRIATE_AUTH;
604 #ifdef HAVE_CYRUS_SASL
605 /* User binding as anonymous */
606 if ( authzDN == NULL ) {
612 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
613 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
615 Debug( LDAP_DEBUG_TRACE,
616 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
620 /* If person is authorizing to self, succeed */
621 if ( !strcmp( authcDN, authzDN ) ) {
626 /* Check source rules */
627 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
629 if( rc == LDAP_SUCCESS ) {
633 /* Check destination rules */
634 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
636 if( rc == LDAP_SUCCESS ) {
640 rc = LDAP_INAPPROPRIATE_AUTH;
646 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
647 "slap_sasl_authorized: return %d\n", rc ));
649 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );