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
135 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
136 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
137 reg = &( SaslRegexp[nSaslRegexp] );
138 reg->match = ch_strdup( match );
139 reg->replace = ch_strdup( replace );
140 dn_normalize( reg->match );
141 dn_normalize( reg->replace );
143 /* Precompile matching pattern */
144 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
147 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
148 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
151 Debug( LDAP_DEBUG_ANY,
152 "SASL match pattern %s could not be compiled by regexp engine\n",
156 return( LDAP_OPERATIONS_ERROR );
159 /* Precompile replace pattern. Find the $<n> placeholders */
162 for ( c = reg->replace; *c; c++ ) {
168 if ( n == SASLREGEX_REPLACE ) {
170 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
171 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
172 reg->replace, SASLREGEX_REPLACE ));
174 Debug( LDAP_DEBUG_ANY,
175 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
176 reg->replace, SASLREGEX_REPLACE, 0 );
179 return( LDAP_OPERATIONS_ERROR );
181 reg->offset[n] = c - reg->replace;
186 /* Final placeholder, after the last $n */
187 reg->offset[n] = c - reg->replace;
193 return( LDAP_SUCCESS );
197 #ifdef HAVE_CYRUS_SASL
199 /* Take the passed in SASL name and attempt to convert it into an
200 LDAP URI to find the matching LDAP entry, using the pattern matching
201 strings given in the saslregexp config file directive(s) */
203 char *slap_sasl_regexp( char *saslname )
206 int i, n, len, insert;
210 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
211 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
213 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
217 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
220 /* Match the normalized SASL name to the saslregexp patterns */
221 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
222 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
223 reg->strings, 0) == 0 )
227 if( i >= nSaslRegexp )
231 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
232 * replace pattern of the form "x$1y$2z". The returned string needs
233 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
237 /* Get the total length of the final URI */
241 while( reg->offset[n] >= 0 ) {
242 /* Len of next section from replacement string (x,y,z above) */
243 len += reg->offset[n] - reg->offset[n-1] - 2;
244 if( reg->offset[n+1] < 0)
247 /* Len of string from saslname that matched next $i (b,d above) */
248 i = reg->replace[ reg->offset[n] + 1 ] - '0';
249 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
252 uri = ch_malloc( len + 1 );
254 /* Fill in URI with replace string, replacing $i as we go */
257 while( reg->offset[n] >= 0) {
258 /* Paste in next section from replacement string (x,y,z above) */
259 len = reg->offset[n] - reg->offset[n-1] - 2;
260 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
262 if( reg->offset[n+1] < 0)
265 /* Paste in string from saslname that matched next $i (b,d above) */
266 i = reg->replace[ reg->offset[n] + 1 ] - '0';
267 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
268 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
276 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
277 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
279 Debug( LDAP_DEBUG_TRACE,
280 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
288 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
289 * return the LDAP DN to which it matches. The SASL regexp rules in the config
290 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
291 * search with scope=base), just return the URI (or its searchbase). Otherwise
292 * an internal search must be done, and if that search returns exactly one
293 * entry, return the DN of that one entry.
296 char *slap_sasl2dn( char *saslname )
299 struct berval searchbase = {0, NULL};
300 struct berval dn = {0, NULL};
305 Connection *conn=NULL;
307 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 );
317 /* Convert the SASL name into an LDAP URI */
318 uri = slap_sasl_regexp( saslname );
322 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
327 /* Massive shortcut: search scope == base */
328 if( scope == LDAP_SCOPE_BASE ) {
330 searchbase.bv_len = 0;
331 searchbase.bv_val = NULL;
335 /* Must do an internal search */
338 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
339 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
340 searchbase.bv_val, scope ));
342 Debug( LDAP_DEBUG_TRACE,
343 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
344 searchbase.bv_val, scope, 0 );
348 be = select_backend( &searchbase, 0, 1 );
349 if(( be == NULL ) || ( be->be_search == NULL))
351 suffix_alias( be, &searchbase );
353 rc = connection_internal_open( &conn, &client, saslname );
354 if( rc != LDAP_SUCCESS )
357 (*be->be_search)( be, conn, LDAP_STAILQ_FIRST(&conn->c_ops), /*base*/NULL, &searchbase,
358 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
359 /*attrs=*/NULL, /*attrsonly=*/0 );
362 /* Read the client side of the internal search */
363 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
367 /* Make sure exactly one entry was returned */
368 rc = ldap_count_entries( client, res );
370 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
371 "slap_sasl2dn: search DN returned %d entries\n", rc ));
373 Debug( LDAP_DEBUG_TRACE,
374 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
380 msg = ldap_first_entry( client, res );
381 dn.bv_val = ldap_get_dn( client, msg );
382 dn.bv_len = dn.bv_val ? strlen( dn.bv_val ) : 0;
384 rc = dnNormalize2( NULL, &dn, &ndn );
385 ldap_memfree( dn.bv_val );
386 if( rc != LDAP_SUCCESS ) {
395 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
396 if( filter ) filter_free( filter );
397 if( uri ) ch_free( uri );
398 if( conn ) connection_internal_close( conn );
399 if( res ) ldap_msgfree( res );
400 if( client ) ldap_unbind( client );
403 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
404 "slap_sasl2dn: Converted SASL name to %s\n",
405 dn.bv_len ? dn.bv_val : "<nothing>" ));
407 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
408 dn.bv_len ? dn.bv_val : "<nothing>", 0, 0 );
416 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
417 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
418 * the rule must be used as an internal search for entries. If that search
419 * returns the *assertDN entry, the match is successful.
421 * The assertDN should not have the dn: prefix
425 int slap_sasl_match( char *rule, char *assertDN, char *authc )
427 struct berval searchbase = {0, NULL};
431 Connection *conn=NULL;
433 LDAPMessage *res=NULL, *msg;
437 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
438 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
440 Debug( LDAP_DEBUG_TRACE,
441 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
444 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
445 if( rc != LDAP_SUCCESS )
448 /* Massive shortcut: search scope == base */
449 if( scope == LDAP_SCOPE_BASE ) {
450 rc = regcomp(®, searchbase.bv_val,
451 REG_EXTENDED|REG_ICASE|REG_NOSUB);
453 rc = regexec(®, assertDN, 0, NULL, 0);
459 rc = LDAP_INAPPROPRIATE_AUTH;
463 /* Must run an internal search. */
466 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
467 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
468 searchbase.bv_val, scope ));
470 Debug( LDAP_DEBUG_TRACE,
471 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
472 searchbase.bv_val, scope, 0 );
475 be = select_backend( &searchbase, 0, 1 );
476 if(( be == NULL ) || ( be->be_search == NULL)) {
477 rc = LDAP_INAPPROPRIATE_AUTH;
480 suffix_alias( be, &searchbase );
482 /* Make an internal connection on which to run the search */
483 rc = connection_internal_open( &conn, &client, authc );
484 if( rc != LDAP_SUCCESS )
487 (*be->be_search)( be, conn, LDAP_STAILQ_FIRST(&conn->c_ops), /*base=*/NULL, &searchbase,
488 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
489 /*attrs=*/NULL, /*attrsonly=*/0 );
491 /* On the client side of the internal search, read the results. Check
492 if the assertDN matches any of the DN's returned by the search */
493 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
497 for( msg=ldap_first_entry( client, res );
499 msg=ldap_next_entry( client, msg ) )
502 dn.bv_val = ldap_get_dn( client, msg );
506 dn.bv_len = strlen( dn.bv_val );
507 rc = dnNormalize2( NULL, &dn, &ndn );
508 ldap_memfree( dn.bv_val );
509 if( rc != LDAP_SUCCESS ) {
512 rc = strcmp( ndn.bv_val, assertDN );
520 rc = LDAP_INAPPROPRIATE_AUTH;
523 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
524 if( filter ) filter_free( filter );
525 if( conn ) connection_internal_close( conn );
526 if( res ) ldap_msgfree( res );
527 if( client ) ldap_unbind( client );
529 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
530 "slap_sasl_match: comparison returned %d\n", rc ));
532 Debug( LDAP_DEBUG_TRACE,
533 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
541 * This function answers the question, "Can this ID authorize to that ID?",
542 * based on authorization rules. The rules are stored in the *searchDN, in the
543 * attribute named by *attr. If any of those rules map to the *assertDN, the
544 * authorization is approved.
546 * DN's passed in should have a dn: prefix
549 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
554 AttributeDescription *ad=NULL;
558 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
559 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
560 assertDN, attr, searchDN ));
562 Debug( LDAP_DEBUG_TRACE,
563 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
564 assertDN, attr, searchDN);
567 rc = slap_str2ad( attr, &ad, &errmsg );
568 if( rc != LDAP_SUCCESS )
571 bv.bv_val = searchDN+3;
572 bv.bv_len = strlen(bv.bv_val);
573 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
574 if( rc != LDAP_SUCCESS )
577 /* Check if the *assertDN matches any **vals */
578 for( i=0; vals[i].bv_val != NULL; i++ ) {
579 rc = slap_sasl_match( vals[i].bv_val, assertDN+3, authc );
580 if ( rc == LDAP_SUCCESS )
583 rc = LDAP_INAPPROPRIATE_AUTH;
586 if( vals ) bvarray_free( vals );
589 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
590 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
592 Debug( LDAP_DEBUG_TRACE,
593 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
598 #endif /* HAVE_CYRUS_SASL */
601 /* Check if a bind can SASL authorize to another identity.
602 Accepts authorization DN's with "dn:" prefix */
604 int slap_sasl_authorized( char *authcDN, char *authzDN )
606 int rc = LDAP_INAPPROPRIATE_AUTH;
608 #ifdef HAVE_CYRUS_SASL
609 /* User binding as anonymous */
610 if ( authzDN == NULL ) {
616 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
617 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
619 Debug( LDAP_DEBUG_TRACE,
620 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
623 /* If person is authorizing to self, succeed */
624 if ( !strcmp( authcDN, authzDN ) ) {
629 /* Check source rules */
630 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
632 if( rc == LDAP_SUCCESS ) {
636 /* Check destination rules */
637 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
639 if( rc == LDAP_SUCCESS ) {
643 rc = LDAP_INAPPROPRIATE_AUTH;
649 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
650 "slap_sasl_authorized: return %d\n", rc ));
652 Debug( LDAP_DEBUG_TRACE,
653 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );