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, struct berval *searchbase, int *scope, Filter **filter )
37 assert( uri != NULL );
38 searchbase->bv_val = NULL;
39 searchbase->bv_len = 0;
44 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
45 "slap_parseURI: parsing %s\n", uri ));
47 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:", 3 ) ) {
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;
66 if( strncasecmp( uri, "ldap://", 7 ) ) {
71 end = strchr( uri + 7, '/' );
73 return( LDAP_PROTOCOL_ERROR );
75 /* could check the hostname here */
77 /* Grab the searchbase */
79 end = strchr( start, '?' );
83 return dnNormalize2( NULL, &bv, searchbase );
86 bv.bv_len = end - start;
87 rc = dnNormalize2( NULL, &bv, searchbase );
89 if (rc != LDAP_SUCCESS)
94 end = strchr( start, '?' );
96 return( LDAP_SUCCESS );
101 if( !strncasecmp( start, "base?", 5 )) {
102 *scope = LDAP_SCOPE_BASE;
105 else if( !strncasecmp( start, "one?", 4 )) {
106 *scope = LDAP_SCOPE_ONELEVEL;
109 else if( !strncasecmp( start, "sub?", 3 )) {
110 *scope = LDAP_SCOPE_SUBTREE;
114 free( searchbase->bv_val );
115 searchbase->bv_val = NULL;
116 return( LDAP_PROTOCOL_ERROR );
119 /* Grab the filter */
120 *filter = str2filter( start );
122 return( LDAP_SUCCESS );
129 int slap_sasl_regexp_config( const char *match, const char *replace )
131 #ifdef HAVE_CYRUS_SASL
136 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
137 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
138 reg = &( SaslRegexp[nSaslRegexp] );
139 reg->match = ch_strdup( match );
140 reg->replace = ch_strdup( replace );
141 dn_normalize( reg->match );
142 dn_normalize( reg->replace );
144 /* Precompile matching pattern */
145 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
148 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
149 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
152 Debug( LDAP_DEBUG_ANY,
153 "SASL match pattern %s could not be compiled by regexp engine\n",
157 return( LDAP_OPERATIONS_ERROR );
160 /* Precompile replace pattern. Find the $<n> placeholders */
163 for ( c = reg->replace; *c; c++ ) {
169 if ( n == SASLREGEX_REPLACE ) {
171 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
172 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
173 reg->replace, SASLREGEX_REPLACE ));
175 Debug( LDAP_DEBUG_ANY,
176 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
177 reg->replace, SASLREGEX_REPLACE, 0 );
180 return( LDAP_OPERATIONS_ERROR );
182 reg->offset[n] = c - reg->replace;
187 /* Final placeholder, after the last $n */
188 reg->offset[n] = c - reg->replace;
194 return( LDAP_SUCCESS );
201 #ifdef HAVE_CYRUS_SASL
205 /* Take the passed in SASL name and attempt to convert it into an
206 LDAP URI to find the matching LDAP entry, using the pattern matching
207 strings given in the saslregexp config file directive(s) */
209 char *slap_sasl_regexp( char *saslname )
212 int i, n, len, insert;
217 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
218 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
220 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
224 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
227 /* Match the normalized SASL name to the saslregexp patterns */
228 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
229 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
230 reg->strings, 0) == 0 )
234 if( i >= nSaslRegexp )
238 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
239 * replace pattern of the form "x$1y$2z". The returned string needs
240 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
244 /* Get the total length of the final URI */
248 while( reg->offset[n] >= 0 ) {
249 /* Len of next section from replacement string (x,y,z above) */
250 len += reg->offset[n] - reg->offset[n-1] - 2;
251 if( reg->offset[n+1] < 0)
254 /* Len of string from saslname that matched next $i (b,d above) */
255 i = reg->replace[ reg->offset[n] + 1 ] - '0';
256 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
259 uri = ch_malloc( len + 1 );
261 /* Fill in URI with replace string, replacing $i as we go */
264 while( reg->offset[n] >= 0) {
265 /* Paste in next section from replacement string (x,y,z above) */
266 len = reg->offset[n] - reg->offset[n-1] - 2;
267 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
269 if( reg->offset[n+1] < 0)
272 /* Paste in string from saslname that matched next $i (b,d above) */
273 i = reg->replace[ reg->offset[n] + 1 ] - '0';
274 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
275 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
283 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
284 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
286 Debug( LDAP_DEBUG_TRACE,
287 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
295 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
296 * return the LDAP DN to which it matches. The SASL regexp rules in the config
297 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
298 * search with scope=base), just return the URI (or its searchbase). Otherwise
299 * an internal search must be done, and if that search returns exactly one
300 * entry, return the DN of that one entry.
303 char *slap_sasl2dn( char *saslname )
306 struct berval searchbase = {0, NULL};
307 struct berval dn = {0, NULL};
312 Connection *conn=NULL;
314 LDAPMessage *res=NULL, *msg;
317 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
318 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
320 Debug( LDAP_DEBUG_TRACE,
321 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
324 /* Convert the SASL name into an LDAP URI */
325 uri = slap_sasl_regexp( saslname );
329 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
334 /* Massive shortcut: search scope == base */
335 if( scope == LDAP_SCOPE_BASE ) {
337 searchbase.bv_len = 0;
338 searchbase.bv_val = NULL;
342 /* Must do an internal search */
345 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
346 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
347 searchbase.bv_val, scope ));
349 Debug( LDAP_DEBUG_TRACE,
350 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
351 searchbase.bv_val, scope, 0 );
355 be = select_backend( &searchbase, 0, 1 );
356 if(( be == NULL ) || ( be->be_search == NULL))
358 suffix_alias( be, &searchbase );
360 rc = connection_internal_open( &conn, &client, saslname );
361 if( rc != LDAP_SUCCESS )
364 (*be->be_search)( be, conn, STAILQ_FIRST(&conn->c_ops), /*base*/NULL, &searchbase,
365 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
366 /*attrs=*/NULL, /*attrsonly=*/0 );
369 /* Read the client side of the internal search */
370 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
374 /* Make sure exactly one entry was returned */
375 rc = ldap_count_entries( client, res );
377 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
378 "slap_sasl2dn: search DN returned %d entries\n", rc ));
380 Debug( LDAP_DEBUG_TRACE,
381 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
387 msg = ldap_first_entry( client, res );
388 dn.bv_val = ldap_get_dn( client, msg );
389 dn.bv_len = dn.bv_val ? strlen( dn.bv_val ) : 0;
391 rc = dnNormalize2( NULL, &dn, &ndn );
392 ldap_memfree( dn.bv_val );
393 if( rc != LDAP_SUCCESS ) {
402 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
403 if( filter ) filter_free( filter );
404 if( uri ) ch_free( uri );
405 if( conn ) connection_internal_close( conn );
406 if( res ) ldap_msgfree( res );
407 if( client ) ldap_unbind( client );
410 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
411 "slap_sasl2dn: Converted SASL name to %s\n",
412 dn.bv_len ? dn.bv_val : "<nothing>" ));
414 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
415 dn.bv_len ? dn.bv_val : "<nothing>", 0, 0 );
423 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
424 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
425 * the rule must be used as an internal search for entries. If that search
426 * returns the *assertDN entry, the match is successful.
428 * The assertDN should not have the dn: prefix
432 int slap_sasl_match( char *rule, char *assertDN, char *authc )
434 struct berval searchbase = {0, NULL};
438 Connection *conn=NULL;
440 LDAPMessage *res=NULL, *msg;
444 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
445 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
447 Debug( LDAP_DEBUG_TRACE,
448 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
451 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
452 if( rc != LDAP_SUCCESS )
455 /* Massive shortcut: search scope == base */
456 if( scope == LDAP_SCOPE_BASE ) {
457 rc = regcomp(®, searchbase.bv_val,
458 REG_EXTENDED|REG_ICASE|REG_NOSUB);
460 rc = regexec(®, assertDN, 0, NULL, 0);
466 rc = LDAP_INAPPROPRIATE_AUTH;
470 /* Must run an internal search. */
473 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
474 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
475 searchbase.bv_val, scope ));
477 Debug( LDAP_DEBUG_TRACE,
478 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
479 searchbase.bv_val, scope, 0 );
482 be = select_backend( &searchbase, 0, 1 );
483 if(( be == NULL ) || ( be->be_search == NULL)) {
484 rc = LDAP_INAPPROPRIATE_AUTH;
487 suffix_alias( be, &searchbase );
489 /* Make an internal connection on which to run the search */
490 rc = connection_internal_open( &conn, &client, authc );
491 if( rc != LDAP_SUCCESS )
494 (*be->be_search)( be, conn, STAILQ_FIRST(&conn->c_ops), /*base=*/NULL, &searchbase,
495 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
496 /*attrs=*/NULL, /*attrsonly=*/0 );
498 /* On the client side of the internal search, read the results. Check
499 if the assertDN matches any of the DN's returned by the search */
500 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
504 for( msg=ldap_first_entry( client, res );
506 msg=ldap_next_entry( client, msg ) )
509 dn.bv_val = ldap_get_dn( client, msg );
513 dn.bv_len = strlen( dn.bv_val );
514 rc = dnNormalize2( NULL, &dn, &ndn );
515 ldap_memfree( dn.bv_val );
516 if( rc != LDAP_SUCCESS ) {
519 rc = strcmp( ndn.bv_val, assertDN );
527 rc = LDAP_INAPPROPRIATE_AUTH;
530 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
531 if( filter ) filter_free( filter );
532 if( conn ) connection_internal_close( conn );
533 if( res ) ldap_msgfree( res );
534 if( client ) ldap_unbind( client );
536 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
537 "slap_sasl_match: comparison returned %d\n", rc ));
539 Debug( LDAP_DEBUG_TRACE,
540 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
551 * This function answers the question, "Can this ID authorize to that ID?",
552 * based on authorization rules. The rules are stored in the *searchDN, in the
553 * attribute named by *attr. If any of those rules map to the *assertDN, the
554 * authorization is approved.
556 * DN's passed in should have a dn: prefix
560 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
565 AttributeDescription *ad=NULL;
570 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
571 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
572 assertDN, attr, searchDN ));
574 Debug( LDAP_DEBUG_TRACE,
575 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
576 assertDN, attr, searchDN);
579 rc = slap_str2ad( attr, &ad, &errmsg );
580 if( rc != LDAP_SUCCESS )
583 bv.bv_val = searchDN+3;
584 bv.bv_len = strlen(bv.bv_val);
585 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
586 if( rc != LDAP_SUCCESS )
589 /* Check if the *assertDN matches any **vals */
590 for( i=0; vals[i].bv_val != NULL; i++ ) {
591 rc = slap_sasl_match( vals[i].bv_val, assertDN+3, authc );
592 if ( rc == LDAP_SUCCESS )
595 rc = LDAP_INAPPROPRIATE_AUTH;
598 if( vals ) bvarray_free( vals );
601 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
602 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
604 Debug( LDAP_DEBUG_TRACE,
605 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
613 #endif /* HAVE_CYRUS_SASL */
619 /* Check if a bind can SASL authorize to another identity.
620 Accepts authorization DN's with "dn:" prefix */
622 int slap_sasl_authorized( char *authcDN, char *authzDN )
624 int rc = LDAP_INAPPROPRIATE_AUTH;
626 #ifdef HAVE_CYRUS_SASL
627 /* User binding as anonymous */
628 if ( authzDN == NULL ) {
634 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
635 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
637 Debug( LDAP_DEBUG_TRACE,
638 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
642 /* If person is authorizing to self, succeed */
643 if ( !strcmp( authcDN, authzDN ) ) {
648 /* Check source rules */
649 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
651 if( rc == LDAP_SUCCESS ) {
655 /* Check destination rules */
656 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
658 if( rc == LDAP_SUCCESS ) {
662 rc = LDAP_INAPPROPRIATE_AUTH;
668 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
669 "slap_sasl_authorized: return %d\n", rc ));
671 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );