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 )
33 struct berval bv, *nbase = NULL;
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 = dnNormalize( NULL, &bv, &nbase );
61 if (rc == LDAP_SUCCESS) {
62 *scope = LDAP_SCOPE_BASE;
68 if( strncasecmp( uri, "ldap://", 7 ) ) {
73 end = strchr( uri + 7, '/' );
75 return( LDAP_PROTOCOL_ERROR );
77 /* could check the hostname here */
79 /* Grab the searchbase */
81 end = strchr( start, '?' );
85 rc = dnNormalize( NULL, &bv, &nbase );
86 if (rc == LDAP_SUCCESS) {
93 bv.bv_len = end - start;
94 rc = dnNormalize( NULL, &bv, &nbase );
96 if (rc != LDAP_SUCCESS)
103 end = strchr( start, '?' );
105 return( LDAP_SUCCESS );
110 if( !strncasecmp( start, "base?", 5 )) {
111 *scope = LDAP_SCOPE_BASE;
114 else if( !strncasecmp( start, "one?", 4 )) {
115 *scope = LDAP_SCOPE_ONELEVEL;
118 else if( !strncasecmp( start, "sub?", 3 )) {
119 *scope = LDAP_SCOPE_SUBTREE;
123 free( searchbase->bv_val );
124 searchbase->bv_val = NULL;
125 return( LDAP_PROTOCOL_ERROR );
128 /* Grab the filter */
129 *filter = str2filter( start );
131 return( LDAP_SUCCESS );
138 int slap_sasl_regexp_config( const char *match, const char *replace )
140 #ifdef HAVE_CYRUS_SASL
145 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
146 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
147 reg = &( SaslRegexp[nSaslRegexp] );
148 reg->match = ch_strdup( match );
149 reg->replace = ch_strdup( replace );
150 dn_normalize( reg->match );
151 dn_normalize( reg->replace );
153 /* Precompile matching pattern */
154 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
157 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
158 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
161 Debug( LDAP_DEBUG_ANY,
162 "SASL match pattern %s could not be compiled by regexp engine\n",
166 return( LDAP_OPERATIONS_ERROR );
169 /* Precompile replace pattern. Find the $<n> placeholders */
172 for ( c = reg->replace; *c; c++ ) {
178 if ( n == SASLREGEX_REPLACE ) {
180 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
181 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
182 reg->replace, SASLREGEX_REPLACE ));
184 Debug( LDAP_DEBUG_ANY,
185 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
186 reg->replace, SASLREGEX_REPLACE, 0 );
189 return( LDAP_OPERATIONS_ERROR );
191 reg->offset[n] = c - reg->replace;
196 /* Final placeholder, after the last $n */
197 reg->offset[n] = c - reg->replace;
203 return( LDAP_SUCCESS );
210 #ifdef HAVE_CYRUS_SASL
214 /* Take the passed in SASL name and attempt to convert it into an
215 LDAP URI to find the matching LDAP entry, using the pattern matching
216 strings given in the saslregexp config file directive(s) */
218 char *slap_sasl_regexp( char *saslname )
221 int i, n, len, insert;
226 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
227 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
229 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
233 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
236 /* Match the normalized SASL name to the saslregexp patterns */
237 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
238 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
239 reg->strings, 0) == 0 )
243 if( i >= nSaslRegexp )
247 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
248 * replace pattern of the form "x$1y$2z". The returned string needs
249 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
253 /* Get the total length of the final URI */
257 while( reg->offset[n] >= 0 ) {
258 /* Len of next section from replacement string (x,y,z above) */
259 len += reg->offset[n] - reg->offset[n-1] - 2;
260 if( reg->offset[n+1] < 0)
263 /* Len of 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;
268 uri = ch_malloc( len + 1 );
270 /* Fill in URI with replace string, replacing $i as we go */
273 while( reg->offset[n] >= 0) {
274 /* Paste in next section from replacement string (x,y,z above) */
275 len = reg->offset[n] - reg->offset[n-1] - 2;
276 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
278 if( reg->offset[n+1] < 0)
281 /* Paste in string from saslname that matched next $i (b,d above) */
282 i = reg->replace[ reg->offset[n] + 1 ] - '0';
283 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
284 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
292 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
293 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
295 Debug( LDAP_DEBUG_TRACE,
296 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
304 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
305 * return the LDAP DN to which it matches. The SASL regexp rules in the config
306 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
307 * search with scope=base), just return the URI (or its searchbase). Otherwise
308 * an internal search must be done, and if that search returns exactly one
309 * entry, return the DN of that one entry.
312 char *slap_sasl2dn( char *saslname )
315 struct berval searchbase = {0, NULL};
316 struct berval dn = {0, NULL};
317 struct berval *ndn = NULL;
321 Connection *conn=NULL;
323 LDAPMessage *res=NULL, *msg;
326 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
327 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
329 Debug( LDAP_DEBUG_TRACE,
330 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
333 /* Convert the SASL name into an LDAP URI */
334 uri = slap_sasl_regexp( saslname );
338 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
343 /* Massive shortcut: search scope == base */
344 if( scope == LDAP_SCOPE_BASE ) {
346 searchbase.bv_len = 0;
347 searchbase.bv_val = NULL;
351 /* Must do an internal search */
354 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
355 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
356 searchbase.bv_val, scope ));
358 Debug( LDAP_DEBUG_TRACE,
359 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
360 searchbase.bv_val, scope, 0 );
364 be = select_backend( &searchbase, 0, 1 );
365 if(( be == NULL ) || ( be->be_search == NULL))
367 suffix_alias( be, &searchbase );
369 rc = connection_internal_open( &conn, &client, saslname );
370 if( rc != LDAP_SUCCESS )
373 (*be->be_search)( be, conn, conn->c_ops, /*base*/NULL, &searchbase,
374 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
375 /*attrs=*/NULL, /*attrsonly=*/0 );
378 /* Read the client side of the internal search */
379 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
383 /* Make sure exactly one entry was returned */
384 rc = ldap_count_entries( client, res );
386 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
387 "slap_sasl2dn: search DN returned %d entries\n", rc ));
389 Debug( LDAP_DEBUG_TRACE,
390 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
396 msg = ldap_first_entry( client, res );
397 dn.bv_val = ldap_get_dn( client, msg );
398 dn.bv_len = dn.bv_val ? strlen( dn.bv_val ) : 0;
400 rc = dnNormalize( NULL, &dn, &ndn );
401 ldap_memfree( dn.bv_val );
402 if( rc != LDAP_SUCCESS ) {
412 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
413 if( filter ) filter_free( filter );
414 if( uri ) ch_free( uri );
415 if( conn ) connection_internal_close( conn );
416 if( res ) ldap_msgfree( res );
417 if( client ) ldap_unbind( client );
420 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
421 "slap_sasl2dn: Converted SASL name to %s\n",
422 dn.bv_len ? dn.bv_val : "<nothing>" ));
424 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
425 dn.bv_len ? dn.bv_val : "<nothing>", 0, 0 );
433 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
434 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
435 * the rule must be used as an internal search for entries. If that search
436 * returns the *assertDN entry, the match is successful.
438 * The assertDN should not have the dn: prefix
442 int slap_sasl_match( char *rule, char *assertDN, char *authc )
444 struct berval searchbase = {0, NULL};
448 Connection *conn=NULL;
450 LDAPMessage *res=NULL, *msg;
454 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
455 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
457 Debug( LDAP_DEBUG_TRACE,
458 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
461 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
462 if( rc != LDAP_SUCCESS )
465 /* Massive shortcut: search scope == base */
466 if( scope == LDAP_SCOPE_BASE ) {
467 rc = regcomp(®, searchbase.bv_val,
468 REG_EXTENDED|REG_ICASE|REG_NOSUB);
470 rc = regexec(®, assertDN, 0, NULL, 0);
476 rc = LDAP_INAPPROPRIATE_AUTH;
480 /* Must run an internal search. */
483 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
484 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
485 searchbase.bv_val, scope ));
487 Debug( LDAP_DEBUG_TRACE,
488 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
489 searchbase.bv_val, scope, 0 );
492 be = select_backend( &searchbase, 0, 1 );
493 if(( be == NULL ) || ( be->be_search == NULL)) {
494 rc = LDAP_INAPPROPRIATE_AUTH;
497 suffix_alias( be, &searchbase );
499 /* Make an internal connection on which to run the search */
500 rc = connection_internal_open( &conn, &client, authc );
501 if( rc != LDAP_SUCCESS )
504 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, &searchbase,
505 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
506 /*attrs=*/NULL, /*attrsonly=*/0 );
508 /* On the client side of the internal search, read the results. Check
509 if the assertDN matches any of the DN's returned by the search */
510 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
514 for( msg=ldap_first_entry( client, res );
516 msg=ldap_next_entry( client, msg ) )
519 dn.bv_val = ldap_get_dn( client, msg );
522 struct berval *ndn = NULL;
523 dn.bv_len = strlen( dn.bv_val );
524 rc = dnNormalize( NULL, &dn, &ndn );
525 ldap_memfree( dn.bv_val );
526 if( rc != LDAP_SUCCESS ) {
529 rc = strcmp( ndn->bv_val, assertDN );
537 rc = LDAP_INAPPROPRIATE_AUTH;
540 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
541 if( filter ) filter_free( filter );
542 if( conn ) connection_internal_close( conn );
543 if( res ) ldap_msgfree( res );
544 if( client ) ldap_unbind( client );
546 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
547 "slap_sasl_match: comparison returned %d\n", rc ));
549 Debug( LDAP_DEBUG_TRACE,
550 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
561 * This function answers the question, "Can this ID authorize to that ID?",
562 * based on authorization rules. The rules are stored in the *searchDN, in the
563 * attribute named by *attr. If any of those rules map to the *assertDN, the
564 * authorization is approved.
566 * DN's passed in should have a dn: prefix
570 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
574 struct berval **vals=NULL;
575 AttributeDescription *ad=NULL;
580 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
581 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
582 assertDN, attr, searchDN ));
584 Debug( LDAP_DEBUG_TRACE,
585 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
586 assertDN, attr, searchDN);
589 rc = slap_str2ad( attr, &ad, &errmsg );
590 if( rc != LDAP_SUCCESS )
593 bv.bv_val = searchDN+3;
594 bv.bv_len = strlen(bv.bv_val);
595 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
596 if( rc != LDAP_SUCCESS )
599 /* Check if the *assertDN matches any **vals */
600 for( i=0; vals[i] != NULL; i++ ) {
601 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
602 if ( rc == LDAP_SUCCESS )
605 rc = LDAP_INAPPROPRIATE_AUTH;
608 if( vals ) ber_bvecfree( vals );
611 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
612 "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
614 Debug( LDAP_DEBUG_TRACE,
615 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
623 #endif /* HAVE_CYRUS_SASL */
629 /* Check if a bind can SASL authorize to another identity.
630 Accepts authorization DN's with "dn:" prefix */
632 int slap_sasl_authorized( char *authcDN, char *authzDN )
634 int rc = LDAP_INAPPROPRIATE_AUTH;
636 #ifdef HAVE_CYRUS_SASL
637 /* User binding as anonymous */
638 if ( authzDN == NULL ) {
644 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
645 "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
647 Debug( LDAP_DEBUG_TRACE,
648 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
652 /* If person is authorizing to self, succeed */
653 if ( !strcmp( authcDN, authzDN ) ) {
658 /* Check source rules */
659 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
661 if( rc == LDAP_SUCCESS ) {
665 /* Check destination rules */
666 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
668 if( rc == LDAP_SUCCESS ) {
672 rc = LDAP_INAPPROPRIATE_AUTH;
678 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
679 "slap_sasl_authorized: return %d\n", rc ));
681 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );