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>
19 #include "proto-slap.h"
21 #include <ac/string.h>
23 #ifdef HAVE_CYRUS_SASL
33 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
35 int slap_parseURI( char *uri, char **searchbase, int *scope, Filter **filter )
40 assert( uri != NULL );
46 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
47 "slap_parseURI: parsing %s\n", uri ));
49 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
53 /* If it does not look like a URI, assume it is a DN */
54 if( !strncasecmp( uri, "dn:", 3 ) ) {
56 uri += strspn( uri, " " );
57 *searchbase = ch_strdup( uri );
58 dn_normalize( *searchbase );
59 *scope = LDAP_SCOPE_BASE;
60 return( LDAP_SUCCESS );
62 if( strncasecmp( uri, "ldap://", 7 ) ) {
63 *searchbase = ch_strdup( uri );
64 dn_normalize( *searchbase );
65 *scope = LDAP_SCOPE_BASE;
66 return( LDAP_SUCCESS );
69 end = strchr( uri + 7, '/' );
71 return( LDAP_PROTOCOL_ERROR );
73 /* could check the hostname here */
75 /* Grab the searchbase */
77 end = strchr( start, '?' );
79 *searchbase = ch_strdup( start );
80 dn_normalize( *searchbase );
81 return( LDAP_SUCCESS );
84 *searchbase = ch_strdup( start );
86 dn_normalize( *searchbase );
90 end = strchr( start, '?' );
92 return( LDAP_SUCCESS );
97 if( !strncasecmp( start, "base?", 5 )) {
98 *scope = LDAP_SCOPE_BASE;
101 else if( !strncasecmp( start, "one?", 4 )) {
102 *scope = LDAP_SCOPE_ONELEVEL;
105 else if( !strncasecmp( start, "sub?", 3 )) {
106 *scope = LDAP_SCOPE_SUBTREE;
110 ch_free( *searchbase );
112 return( LDAP_PROTOCOL_ERROR );
115 /* Grab the filter */
116 *filter = str2filter( start );
118 return( LDAP_SUCCESS );
125 int slap_sasl_regexp_config( const char *match, const char *replace )
127 #ifdef HAVE_CYRUS_SASL
132 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
133 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
134 reg = &( SaslRegexp[nSaslRegexp] );
135 reg->match = ch_strdup( match );
136 reg->replace = ch_strdup( replace );
137 dn_normalize( reg->match );
138 dn_normalize( reg->replace );
140 /* Precompile matching pattern */
141 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
144 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
145 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n"
148 Debug( LDAP_DEBUG_ANY,
149 "SASL match pattern %s could not be compiled by regexp engine\n",
153 return( LDAP_OPERATIONS_ERROR );
156 /* Precompile replace pattern. Find the $<n> placeholders */
159 for ( c = reg->replace; *c; c++ ) {
165 if ( n == SASLREGEX_REPLACE ) {
167 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
168 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
169 reg->replace, SASLREGEX_REPLACE ));
171 Debug( LDAP_DEBUG_ANY,
172 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
173 reg->replace, SASLREGEX_REPLACE, 0 );
176 return( LDAP_OPERATIONS_ERROR );
178 reg->offset[n] = c - reg->replace;
183 /* Final placeholder, after the last $n */
184 reg->offset[n] = c - reg->replace;
190 return( LDAP_SUCCESS );
197 #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) */
205 char *slap_sasl_regexp( char *saslname )
208 int i, n, len, insert;
213 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
214 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
216 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
220 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
223 /* Match the normalized SASL name to the saslregexp patterns */
224 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
225 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
226 reg->strings, 0) == 0 )
230 if( i >= nSaslRegexp )
234 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
235 * replace pattern of the form "x$1y$2z". The returned string needs
236 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
240 /* Get the total length of the final URI */
244 while( reg->offset[n] >= 0 ) {
245 /* Len of next section from replacement string (x,y,z above) */
246 len += reg->offset[n] - reg->offset[n-1] - 2;
247 if( reg->offset[n+1] < 0)
250 /* Len of string from saslname that matched next $i (b,d above) */
251 i = reg->replace[ reg->offset[n] + 1 ] - '0';
252 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
255 uri = ch_malloc( len + 1 );
257 /* Fill in URI with replace string, replacing $i as we go */
260 while( reg->offset[n] >= 0) {
261 /* Paste in next section from replacement string (x,y,z above) */
262 len = reg->offset[n] - reg->offset[n-1] - 2;
263 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
265 if( reg->offset[n+1] < 0)
268 /* Paste in string from saslname that matched next $i (b,d above) */
269 i = reg->replace[ reg->offset[n] + 1 ] - '0';
270 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
271 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
279 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
280 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
282 Debug( LDAP_DEBUG_TRACE,
283 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
294 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
295 * return the LDAP DN to which it matches. The SASL regexp rules in the config
296 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
297 * search with scope=base), just return the URI (or its searchbase). Otherwise
298 * an internal search must be done, and if that search returns exactly one
299 * entry, return the DN of that one entry.
302 char *slap_sasl2dn( char *saslname )
304 char *uri=NULL, *searchbase=NULL, *DN=NULL;
308 Connection *conn=NULL;
310 LDAPMessage *res=NULL, *msg;
314 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
315 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
317 Debug( LDAP_DEBUG_TRACE,
318 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
322 /* Convert the SASL name into an LDAP URI */
323 uri = slap_sasl_regexp( saslname );
327 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
331 /* Massive shortcut: search scope == base */
332 if( scope == LDAP_SCOPE_BASE ) {
333 DN = ch_strdup( searchbase );
337 /* Must do an internal search */
340 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
341 "slap_sasl2dn: performing internal search (base=%s, scope=%s)\n",
342 searchbase, scope ));
344 Debug( LDAP_DEBUG_TRACE,
345 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
346 searchbase, scope, 0 );
350 be = select_backend( searchbase, 0 );
351 if(( be == NULL ) || ( be->be_search == NULL))
353 searchbase = suffix_alias( be, searchbase );
355 rc = connection_internal_open( &conn, &client, saslname );
356 if( rc != LDAP_SUCCESS )
359 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
360 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
361 /*attrs=*/NULL, /*attrsonly=*/0 );
364 /* Read the client side of the internal search */
365 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
369 /* Make sure exactly one entry was returned */
370 rc = ldap_count_entries( client, res );
372 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
373 "slap_sasl2dn: search DN returned %d entries\n", rc ));
375 Debug( LDAP_DEBUG_TRACE,
376 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
382 msg = ldap_first_entry( client, res );
383 DN = ldap_get_dn( client, msg );
386 if( searchbase ) ch_free( searchbase );
387 if( filter ) filter_free( filter );
388 if( uri ) ch_free( uri );
389 if( conn ) connection_internal_close( conn );
390 if( res ) ldap_msgfree( res );
391 if( client ) ldap_unbind( client );
392 if( DN ) dn_normalize( DN );
394 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
395 "slap_sasl2dn: Converted SASL name to %s\n", DN ? DN : "<nothing>" ));
397 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
398 DN ? DN : "<nothing>", 0, 0 );
409 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
410 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
411 * the rule must be used as an internal search for entries. If that search
412 * returns the *assertDN entry, the match is successful.
414 * The assertDN should not have the dn: prefix
418 int slap_sasl_match( char *rule, char *assertDN, char *authc )
420 char *searchbase=NULL, *dn=NULL;
424 Connection *conn=NULL;
426 LDAPMessage *res=NULL, *msg;
431 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
432 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule );
434 Debug( LDAP_DEBUG_TRACE,
435 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
439 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
440 if( rc != LDAP_SUCCESS )
443 /* Massive shortcut: search scope == base */
444 if( scope == LDAP_SCOPE_BASE ) {
445 dn_normalize( searchbase );
446 rc = regcomp(®, searchbase, REG_EXTENDED|REG_ICASE|REG_NOSUB);
448 rc = regexec(®, assertDN, 0, NULL, 0);
454 rc = LDAP_INAPPROPRIATE_AUTH;
458 /* Must run an internal search. */
461 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
462 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
463 searchbase, scope ));
465 Debug( LDAP_DEBUG_TRACE,
466 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
467 searchbase, scope, 0 );
471 be = select_backend( searchbase, 0 );
472 if(( be == NULL ) || ( be->be_search == NULL)) {
473 rc = LDAP_INAPPROPRIATE_AUTH;
476 searchbase = suffix_alias( be, searchbase );
478 /* Make an internal connection on which to run the search */
479 rc = connection_internal_open( &conn, &client, authc );
480 if( rc != LDAP_SUCCESS )
483 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
484 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
485 /*attrs=*/NULL, /*attrsonly=*/0 );
488 /* On the client side of the internal search, read the results. Check
489 if the assertDN matches any of the DN's returned by the search */
490 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
494 for( msg=ldap_first_entry( client, res );
496 msg=ldap_next_entry( client, msg ) ) {
497 dn = ldap_get_dn( client, msg );
499 rc = strcmp( dn, assertDN );
506 rc = LDAP_INAPPROPRIATE_AUTH;
509 if( searchbase ) ch_free( searchbase );
510 if( filter ) filter_free( filter );
511 if( conn ) connection_internal_close( conn );
512 if( res ) ldap_msgfree( res );
513 if( client ) ldap_unbind( client );
515 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
516 "slap_sasl_match: comparison returned %d\n", rc ));
518 Debug( LDAP_DEBUG_TRACE,
519 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
530 * This function answers the question, "Can this ID authorize to that ID?",
531 * based on authorization rules. The rules are stored in the *searchDN, in the
532 * attribute named by *attr. If any of those rules map to the *assertDN, the
533 * authorization is approved.
535 * DN's passed in should have a dn: prefix
539 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
543 struct berval **vals=NULL;
544 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 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN+3, ad, &vals );
562 if( rc != LDAP_SUCCESS )
565 /* Check if the *assertDN matches any **vals */
566 for( i=0; vals[i] != NULL; i++ ) {
567 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
568 if ( rc == LDAP_SUCCESS )
571 rc = LDAP_INAPPROPRIATE_AUTH;
574 if( vals ) ber_bvecfree( vals );
575 if( ad ) ad_free( ad, 1 );
578 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
579 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
581 Debug( LDAP_DEBUG_TRACE,
582 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
590 #endif /* HAVE_CYRUS_SASL */
596 /* Check if a bind can SASL authorize to another identity.
597 Accepts authorization DN's with "dn:" prefix */
599 int slap_sasl_authorized( char *authcDN, char *authzDN )
603 #ifdef HAVE_CYRUS_SASL
604 /* User binding as anonymous */
605 if ( authzDN == NULL ) {
611 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
612 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
614 Debug( LDAP_DEBUG_TRACE,
615 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
619 /* If person is authorizing to self, succeed */
620 if ( !strcmp( authcDN, authzDN ) ) {
625 /* Check source rules */
626 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
628 if( rc == LDAP_SUCCESS )
631 /* Check destination rules */
632 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
634 if( rc == LDAP_SUCCESS )
638 rc = LDAP_INAPPROPRIATE_AUTH;
642 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
643 "slap_sasl_authorized: return %s\n", rc ));
645 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );