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, *searchbase=NULL, *DN=NULL;
303 Connection *conn=NULL;
305 LDAPMessage *res=NULL, *msg;
309 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
310 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
312 Debug( LDAP_DEBUG_TRACE,
313 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
317 /* Convert the SASL name into an LDAP URI */
318 uri = slap_sasl_regexp( saslname );
322 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
326 /* Massive shortcut: search scope == base */
327 if( scope == LDAP_SCOPE_BASE ) {
328 DN = ch_strdup( searchbase );
332 /* Must do an internal search */
335 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
336 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
337 searchbase, scope ));
339 Debug( LDAP_DEBUG_TRACE,
340 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
341 searchbase, scope, 0 );
345 be = select_backend( searchbase, 0 );
346 if(( be == NULL ) || ( be->be_search == NULL))
348 searchbase = suffix_alias( be, searchbase );
350 rc = connection_internal_open( &conn, &client, saslname );
351 if( rc != LDAP_SUCCESS )
354 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
355 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
356 /*attrs=*/NULL, /*attrsonly=*/0 );
359 /* Read the client side of the internal search */
360 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
364 /* Make sure exactly one entry was returned */
365 rc = ldap_count_entries( client, res );
367 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
368 "slap_sasl2dn: search DN returned %d entries\n", rc ));
370 Debug( LDAP_DEBUG_TRACE,
371 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
377 msg = ldap_first_entry( client, res );
378 DN = ldap_get_dn( client, msg );
381 if( searchbase ) ch_free( searchbase );
382 if( filter ) filter_free( filter );
383 if( uri ) ch_free( uri );
384 if( conn ) connection_internal_close( conn );
385 if( res ) ldap_msgfree( res );
386 if( client ) ldap_unbind( client );
387 if( DN ) dn_normalize( DN );
389 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
390 "slap_sasl2dn: Converted SASL name to %s\n", DN ? DN : "<nothing>" ));
392 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
393 DN ? DN : "<nothing>", 0, 0 );
404 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
405 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
406 * the rule must be used as an internal search for entries. If that search
407 * returns the *assertDN entry, the match is successful.
409 * The assertDN should not have the dn: prefix
413 int slap_sasl_match( char *rule, char *assertDN, char *authc )
415 char *searchbase=NULL, *dn=NULL;
419 Connection *conn=NULL;
421 LDAPMessage *res=NULL, *msg;
426 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
427 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
429 Debug( LDAP_DEBUG_TRACE,
430 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
434 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
435 if( rc != LDAP_SUCCESS )
438 /* Massive shortcut: search scope == base */
439 if( scope == LDAP_SCOPE_BASE ) {
440 dn_normalize( searchbase );
441 rc = regcomp(®, searchbase, REG_EXTENDED|REG_ICASE|REG_NOSUB);
443 rc = regexec(®, assertDN, 0, NULL, 0);
449 rc = LDAP_INAPPROPRIATE_AUTH;
453 /* Must run an internal search. */
456 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
457 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
458 searchbase, scope ));
460 Debug( LDAP_DEBUG_TRACE,
461 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
462 searchbase, scope, 0 );
466 be = select_backend( searchbase, 0 );
467 if(( be == NULL ) || ( be->be_search == NULL)) {
468 rc = LDAP_INAPPROPRIATE_AUTH;
471 searchbase = suffix_alias( be, searchbase );
473 /* Make an internal connection on which to run the search */
474 rc = connection_internal_open( &conn, &client, authc );
475 if( rc != LDAP_SUCCESS )
478 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
479 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
480 /*attrs=*/NULL, /*attrsonly=*/0 );
483 /* On the client side of the internal search, read the results. Check
484 if the assertDN matches any of the DN's returned by the search */
485 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
489 for( msg=ldap_first_entry( client, res );
491 msg=ldap_next_entry( client, msg ) ) {
492 dn = ldap_get_dn( client, msg );
494 rc = strcmp( dn, assertDN );
501 rc = LDAP_INAPPROPRIATE_AUTH;
504 if( searchbase ) ch_free( searchbase );
505 if( filter ) filter_free( filter );
506 if( conn ) connection_internal_close( conn );
507 if( res ) ldap_msgfree( res );
508 if( client ) ldap_unbind( client );
510 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
511 "slap_sasl_match: comparison returned %d\n", rc ));
513 Debug( LDAP_DEBUG_TRACE,
514 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
525 * This function answers the question, "Can this ID authorize to that ID?",
526 * based on authorization rules. The rules are stored in the *searchDN, in the
527 * attribute named by *attr. If any of those rules map to the *assertDN, the
528 * authorization is approved.
530 * DN's passed in should have a dn: prefix
534 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
538 struct berval **vals=NULL;
539 AttributeDescription *ad=NULL;
543 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
544 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
545 assertDN, attr, searchDN ));
547 Debug( LDAP_DEBUG_TRACE,
548 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
549 assertDN, attr, searchDN);
552 rc = slap_str2ad( attr, &ad, &errmsg );
553 if( rc != LDAP_SUCCESS )
556 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN+3, ad, &vals );
557 if( rc != LDAP_SUCCESS )
560 /* Check if the *assertDN matches any **vals */
561 for( i=0; vals[i] != NULL; i++ ) {
562 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
563 if ( rc == LDAP_SUCCESS )
566 rc = LDAP_INAPPROPRIATE_AUTH;
569 if( vals ) ber_bvecfree( vals );
572 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
573 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
575 Debug( LDAP_DEBUG_TRACE,
576 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
584 #endif /* HAVE_CYRUS_SASL */
590 /* Check if a bind can SASL authorize to another identity.
591 Accepts authorization DN's with "dn:" prefix */
593 int slap_sasl_authorized( char *authcDN, char *authzDN )
597 #ifdef HAVE_CYRUS_SASL
598 /* User binding as anonymous */
599 if ( authzDN == NULL ) {
605 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
606 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
608 Debug( LDAP_DEBUG_TRACE,
609 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
613 /* If person is authorizing to self, succeed */
614 if ( !strcmp( authcDN, authzDN ) ) {
619 /* Check source rules */
620 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
622 if( rc == LDAP_SUCCESS )
625 /* Check destination rules */
626 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
628 if( rc == LDAP_SUCCESS )
632 rc = LDAP_INAPPROPRIATE_AUTH;
636 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
637 "slap_sasl_authorized: return %d\n", rc ));
639 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );