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 );
45 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
47 /* If it does not look like a URI, assume it is a DN */
48 if( strncasecmp( uri, "ldap://", 7 ) ) {
49 *searchbase = ch_strdup( uri );
50 dn_normalize( *searchbase );
51 *scope = LDAP_SCOPE_BASE;
52 return( LDAP_SUCCESS );
55 end = strchr( uri + 7, '/' );
57 return( LDAP_PROTOCOL_ERROR );
59 /* could check the hostname here */
61 /* Grab the searchbase */
63 end = strchr( start, '?' );
65 *searchbase = ch_strdup( start );
66 dn_normalize( *searchbase );
67 return( LDAP_SUCCESS );
70 *searchbase = ch_strdup( start );
72 dn_normalize( *searchbase );
76 end = strchr( start, '?' );
78 return( LDAP_SUCCESS );
83 if( !strncasecmp( start, "base?", 5 )) {
84 *scope = LDAP_SCOPE_BASE;
87 else if( !strncasecmp( start, "one?", 4 )) {
88 *scope = LDAP_SCOPE_ONELEVEL;
91 else if( !strncasecmp( start, "sub?", 3 )) {
92 *scope = LDAP_SCOPE_SUBTREE;
96 ch_free( *searchbase );
98 return( LDAP_PROTOCOL_ERROR );
101 /* Grab the filter */
102 *filter = str2filter( start );
104 return( LDAP_SUCCESS );
111 int slap_sasl_regexp_config( const char *match, const char *replace )
113 #ifdef HAVE_CYRUS_SASL
118 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
119 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
120 reg = &( SaslRegexp[nSaslRegexp] );
121 reg->match = ch_strdup( match );
122 reg->replace = ch_strdup( replace );
123 dn_normalize( reg->match );
124 dn_normalize( reg->replace );
126 /* Precompile matching pattern */
127 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
129 Debug( LDAP_DEBUG_ANY,
130 "SASL match pattern %s could not be compiled by regexp engine\n",
132 return( LDAP_OPERATIONS_ERROR );
135 /* Precompile replace pattern. Find the $<n> placeholders */
138 for ( c = reg->replace; *c; c++ ) {
144 if ( n == SASLREGEX_REPLACE ) {
145 Debug( LDAP_DEBUG_ANY,
146 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
147 reg->replace, SASLREGEX_REPLACE, 0 );
148 return( LDAP_OPERATIONS_ERROR );
150 reg->offset[n] = c - reg->replace;
155 /* Final placeholder, after the last $n */
156 reg->offset[n] = c - reg->replace;
162 return( LDAP_SUCCESS );
169 #ifdef HAVE_CYRUS_SASL
173 /* Take the passed in SASL name and attempt to convert it into an
174 LDAP URI to find the matching LDAP entry, using the pattern matching
175 strings given in the saslregexp config file directive(s) */
177 char *slap_sasl_regexp( char *saslname )
180 int i, n, len, insert;
184 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
186 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
189 /* Match the normalized SASL name to the saslregexp patterns */
190 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
191 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
192 reg->strings, 0) == 0 )
196 if( i >= nSaslRegexp )
200 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
201 * replace pattern of the form "x$1y$2z". The returned string needs
202 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
206 /* Get the total length of the final URI */
210 while( reg->offset[n] >= 0 ) {
211 /* Len of next section from replacement string (x,y,z above) */
212 len += reg->offset[n] - reg->offset[n-1] - 2;
213 if( reg->offset[n+1] < 0)
216 /* Len of string from saslname that matched next $i (b,d above) */
217 i = reg->replace[ reg->offset[n] + 1 ] - '0';
218 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
221 uri = ch_malloc( len + 1 );
223 /* Fill in URI with replace string, replacing $i as we go */
226 while( reg->offset[n] >= 0) {
227 /* Paste in next section from replacement string (x,y,z above) */
228 len = reg->offset[n] - reg->offset[n-1] - 2;
229 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
231 if( reg->offset[n+1] < 0)
234 /* Paste in string from saslname that matched next $i (b,d above) */
235 i = reg->replace[ reg->offset[n] + 1 ] - '0';
236 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
237 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
244 Debug( LDAP_DEBUG_TRACE,
245 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
254 * Given a SASL name (e.g. "UID=name+cn=REALM,cn=MECH,cn=AUTHZ")
255 * return the LDAP DN to which it matches. The SASL regexp rules in the config
256 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
257 * search with scope=base), just return the URI (or its searchbase). Otherwise
258 * an internal search must be done, and if that search returns exactly one
259 * entry, return the DN of that one entry.
263 char *slap_sasl2dn( char *saslname )
265 char *uri=NULL, *searchbase=NULL, *DN=NULL;
269 Connection *conn=NULL;
271 LDAPMessage *res=NULL, *msg;
274 Debug( LDAP_DEBUG_TRACE,
275 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
277 /* Convert the SASL name into an LDAP URI */
278 uri = slap_sasl_regexp( saslname );
282 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
286 /* Massive shortcut: search scope == base */
287 if( scope == LDAP_SCOPE_BASE ) {
288 DN = ch_strdup( searchbase );
292 /* Must do an internal search */
294 Debug( LDAP_DEBUG_TRACE,
295 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
296 searchbase, scope, 0 );
298 be = select_backend( searchbase, 0 );
299 if(( be == NULL ) || ( be->be_search == NULL))
301 searchbase = suffix_alias( be, searchbase );
303 rc = connection_internal_open( &conn, &client, saslname );
304 if( rc != LDAP_SUCCESS )
307 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
308 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
309 /*attrs=*/NULL, /*attrsonly=*/0 );
312 /* Read the client side of the internal search */
313 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
317 /* Make sure exactly one entry was returned */
318 rc = ldap_count_entries( client, res );
319 Debug( LDAP_DEBUG_TRACE,
320 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
324 msg = ldap_first_entry( client, res );
325 DN = ldap_get_dn( client, msg );
328 if( searchbase ) ch_free( searchbase );
329 if( filter ) filter_free( filter );
330 if( uri ) ch_free( uri );
331 if( conn ) connection_internal_close( conn );
332 if( res ) ldap_msgfree( res );
333 if( client ) ldap_unbind( client );
334 if( DN ) dn_normalize( DN );
335 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
336 DN ? DN : "<nothing>", 0, 0 );
345 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
346 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
347 * the rule must be used as an internal search for entries. If that search
348 * returns the *assertDN entry, the match is successful.
352 int slap_sasl_match( char *rule, char *assertDN, char *authc )
354 char *searchbase=NULL, *dn=NULL;
358 Connection *conn=NULL;
360 LDAPMessage *res=NULL, *msg;
363 Debug( LDAP_DEBUG_TRACE,
364 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
366 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
367 if( rc != LDAP_SUCCESS )
370 /* Massive shortcut: search scope == base */
371 if( scope == LDAP_SCOPE_BASE ) {
372 dn_normalize( searchbase );
373 if( strcmp( searchbase, assertDN ) == 0 )
376 rc = LDAP_INAPPROPRIATE_AUTH;
380 /* Must run an internal search. */
382 Debug( LDAP_DEBUG_TRACE,
383 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
384 searchbase, scope, 0 );
386 be = select_backend( searchbase, 0 );
387 if(( be == NULL ) || ( be->be_search == NULL)) {
388 rc = LDAP_INAPPROPRIATE_AUTH;
391 searchbase = suffix_alias( be, searchbase );
393 /* Make an internal connection on which to run the search */
394 rc = connection_internal_open( &conn, &client, authc );
395 if( rc != LDAP_SUCCESS )
398 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
399 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
400 /*attrs=*/NULL, /*attrsonly=*/0 );
403 /* On the client side of the internal search, read the results. Check
404 if the assertDN matches any of the DN's returned by the search */
405 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
409 for( msg=ldap_first_entry( client, res );
411 msg=ldap_next_entry( client, msg ) ) {
412 dn = ldap_get_dn( client, msg );
414 rc = strcmp( dn, assertDN );
421 rc = LDAP_INAPPROPRIATE_AUTH;
424 if( searchbase ) ch_free( searchbase );
425 if( filter ) filter_free( filter );
426 if( conn ) connection_internal_close( conn );
427 if( res ) ldap_msgfree( res );
428 if( client ) ldap_unbind( client );
429 Debug( LDAP_DEBUG_TRACE,
430 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
439 * This function answers the question, "Can this ID authorize to that ID?",
440 * based on authorization rules. The rules are stored in the *searchDN, in the
441 * attribute named by *attr. If any of those rules map to the *assertDN, the
442 * authorization is approved.
446 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
450 struct berval **vals=NULL;
451 AttributeDescription *ad=NULL;
454 Debug( LDAP_DEBUG_TRACE,
455 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
456 assertDN, attr, searchDN);
457 rc = slap_str2ad( attr, &ad, &errmsg );
458 if( rc != LDAP_SUCCESS )
461 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
462 if( rc != LDAP_SUCCESS )
465 /* Check if the *assertDN matches any **vals */
466 for( i=0; vals[i] != NULL; i++ ) {
467 rc = slap_sasl_match( vals[i]->bv_val, assertDN, authc );
468 if ( rc == LDAP_SUCCESS )
471 rc = LDAP_INAPPROPRIATE_AUTH;
474 if( vals ) ber_bvecfree( vals );
475 if( ad ) ad_free( ad, 1 );
477 Debug( LDAP_DEBUG_TRACE,
478 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
484 #endif /* HAVE_CYRUS_SASL */
490 /* Check if a bind can SASL authorize to another identity. */
492 int slap_sasl_authorized( Connection *conn,
493 const char *authcid, const char *authzid )
496 char *saslname=NULL,*authcDN=NULL,*realm=NULL, *authzDN=NULL;
498 #ifdef HAVE_CYRUS_SASL
499 Debug( LDAP_DEBUG_TRACE,
500 "==>slap_sasl_authorized: can %s become %s?\n", authcid, authzid, 0 );
502 /* Create a complete SASL name for the SASL regexp patterns */
504 sasl_getprop( conn->c_sasl_context, SASL_REALM, (void **)&realm );
507 rc = strlen("uid=,cn=,cn=,cn=AUTHZ ");
508 if ( realm ) rc += strlen( realm );
509 if ( authcid ) rc += strlen( authcid );
510 rc += strlen( conn->c_sasl_bind_mech );
511 saslname = ch_malloc( rc );
513 /* Build the SASL name with whatever we have, and normalize it */
517 rc += sprintf( saslname+rc, "%sUID=%s", rc?",":"", authcid);
519 rc += sprintf( saslname+rc, "%sCN=%s", rc?",":"", realm);
520 if ( conn->c_sasl_bind_mech )
521 rc += sprintf( saslname+rc, "%sCN=%s", rc?",":"",
522 conn->c_sasl_bind_mech);
523 sprintf( saslname+rc, "%sCN=AUTHZ", rc?",":"");
524 dn_normalize( saslname );
526 authcDN = slap_sasl2dn( saslname );
527 if( authcDN == NULL )
530 /* Normalize the name given by the clientside of the connection */
531 authzDN = ch_strdup( authzid );
532 dn_normalize( authzDN );
535 /* Check source rules */
536 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
538 if( rc == LDAP_SUCCESS )
541 /* Check destination rules */
542 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
544 if( rc == LDAP_SUCCESS )
548 rc = LDAP_INAPPROPRIATE_AUTH;
551 if( saslname ) ch_free( saslname );
552 if( authcDN ) ch_free( authcDN );
553 if( authzDN ) ch_free( authzDN );
554 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );