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, "dn:", 3 ) ) {
50 uri += strspn( uri, " " );
51 *searchbase = ch_strdup( uri );
52 dn_normalize( *searchbase );
53 *scope = LDAP_SCOPE_BASE;
54 return( LDAP_SUCCESS );
56 if( strncasecmp( uri, "ldap://", 7 ) ) {
57 *searchbase = ch_strdup( uri );
58 dn_normalize( *searchbase );
59 *scope = LDAP_SCOPE_BASE;
60 return( LDAP_SUCCESS );
63 end = strchr( uri + 7, '/' );
65 return( LDAP_PROTOCOL_ERROR );
67 /* could check the hostname here */
69 /* Grab the searchbase */
71 end = strchr( start, '?' );
73 *searchbase = ch_strdup( start );
74 dn_normalize( *searchbase );
75 return( LDAP_SUCCESS );
78 *searchbase = ch_strdup( start );
80 dn_normalize( *searchbase );
84 end = strchr( start, '?' );
86 return( LDAP_SUCCESS );
91 if( !strncasecmp( start, "base?", 5 )) {
92 *scope = LDAP_SCOPE_BASE;
95 else if( !strncasecmp( start, "one?", 4 )) {
96 *scope = LDAP_SCOPE_ONELEVEL;
99 else if( !strncasecmp( start, "sub?", 3 )) {
100 *scope = LDAP_SCOPE_SUBTREE;
104 ch_free( *searchbase );
106 return( LDAP_PROTOCOL_ERROR );
109 /* Grab the filter */
110 *filter = str2filter( start );
112 return( LDAP_SUCCESS );
119 int slap_sasl_regexp_config( const char *match, const char *replace )
121 #ifdef HAVE_CYRUS_SASL
126 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
127 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
128 reg = &( SaslRegexp[nSaslRegexp] );
129 reg->match = ch_strdup( match );
130 reg->replace = ch_strdup( replace );
131 dn_normalize( reg->match );
132 dn_normalize( reg->replace );
134 /* Precompile matching pattern */
135 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
137 Debug( LDAP_DEBUG_ANY,
138 "SASL match pattern %s could not be compiled by regexp engine\n",
140 return( LDAP_OPERATIONS_ERROR );
143 /* Precompile replace pattern. Find the $<n> placeholders */
146 for ( c = reg->replace; *c; c++ ) {
152 if ( n == SASLREGEX_REPLACE ) {
153 Debug( LDAP_DEBUG_ANY,
154 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
155 reg->replace, SASLREGEX_REPLACE, 0 );
156 return( LDAP_OPERATIONS_ERROR );
158 reg->offset[n] = c - reg->replace;
163 /* Final placeholder, after the last $n */
164 reg->offset[n] = c - reg->replace;
170 return( LDAP_SUCCESS );
177 #ifdef HAVE_CYRUS_SASL
181 /* Take the passed in SASL name and attempt to convert it into an
182 LDAP URI to find the matching LDAP entry, using the pattern matching
183 strings given in the saslregexp config file directive(s) */
185 char *slap_sasl_regexp( char *saslname )
188 int i, n, len, insert;
192 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
194 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
197 /* Match the normalized SASL name to the saslregexp patterns */
198 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
199 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
200 reg->strings, 0) == 0 )
204 if( i >= nSaslRegexp )
208 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
209 * replace pattern of the form "x$1y$2z". The returned string needs
210 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
214 /* Get the total length of the final URI */
218 while( reg->offset[n] >= 0 ) {
219 /* Len of next section from replacement string (x,y,z above) */
220 len += reg->offset[n] - reg->offset[n-1] - 2;
221 if( reg->offset[n+1] < 0)
224 /* Len of string from saslname that matched next $i (b,d above) */
225 i = reg->replace[ reg->offset[n] + 1 ] - '0';
226 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
229 uri = ch_malloc( len + 1 );
231 /* Fill in URI with replace string, replacing $i as we go */
234 while( reg->offset[n] >= 0) {
235 /* Paste in next section from replacement string (x,y,z above) */
236 len = reg->offset[n] - reg->offset[n-1] - 2;
237 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
239 if( reg->offset[n+1] < 0)
242 /* Paste in string from saslname that matched next $i (b,d above) */
243 i = reg->replace[ reg->offset[n] + 1 ] - '0';
244 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
245 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
252 Debug( LDAP_DEBUG_TRACE,
253 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
262 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTHZID")
263 * return the LDAP DN to which it matches. The SASL regexp rules in the config
264 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
265 * search with scope=base), just return the URI (or its searchbase). Otherwise
266 * an internal search must be done, and if that search returns exactly one
267 * entry, return the DN of that one entry.
270 char *slap_sasl2dn( char *saslname )
272 char *uri=NULL, *searchbase=NULL, *DN=NULL;
276 Connection *conn=NULL;
278 LDAPMessage *res=NULL, *msg;
281 Debug( LDAP_DEBUG_TRACE,
282 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
284 /* Convert the SASL name into an LDAP URI */
285 uri = slap_sasl_regexp( saslname );
289 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
293 /* Massive shortcut: search scope == base */
294 if( scope == LDAP_SCOPE_BASE ) {
295 DN = ch_strdup( searchbase );
299 /* Must do an internal search */
301 Debug( LDAP_DEBUG_TRACE,
302 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
303 searchbase, scope, 0 );
305 be = select_backend( searchbase, 0 );
306 if(( be == NULL ) || ( be->be_search == NULL))
308 searchbase = suffix_alias( be, searchbase );
310 rc = connection_internal_open( &conn, &client, saslname );
311 if( rc != LDAP_SUCCESS )
314 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
315 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
316 /*attrs=*/NULL, /*attrsonly=*/0 );
319 /* Read the client side of the internal search */
320 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
324 /* Make sure exactly one entry was returned */
325 rc = ldap_count_entries( client, res );
326 Debug( LDAP_DEBUG_TRACE,
327 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
331 msg = ldap_first_entry( client, res );
332 DN = ldap_get_dn( client, msg );
335 if( searchbase ) ch_free( searchbase );
336 if( filter ) filter_free( filter );
337 if( uri ) ch_free( uri );
338 if( conn ) connection_internal_close( conn );
339 if( res ) ldap_msgfree( res );
340 if( client ) ldap_unbind( client );
341 if( DN ) dn_normalize( DN );
342 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
343 DN ? DN : "<nothing>", 0, 0 );
352 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
353 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
354 * the rule must be used as an internal search for entries. If that search
355 * returns the *assertDN entry, the match is successful.
357 * The assertDN should not have the dn: prefix
361 int slap_sasl_match( char *rule, char *assertDN, char *authc )
363 char *searchbase=NULL, *dn=NULL;
367 Connection *conn=NULL;
369 LDAPMessage *res=NULL, *msg;
373 Debug( LDAP_DEBUG_TRACE,
374 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
376 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
377 if( rc != LDAP_SUCCESS )
380 /* Massive shortcut: search scope == base */
381 if( scope == LDAP_SCOPE_BASE ) {
382 dn_normalize( searchbase );
383 rc = regcomp(®, searchbase, REG_EXTENDED|REG_ICASE|REG_NOSUB);
385 rc = regexec(®, assertDN, 0, NULL, 0);
391 rc = LDAP_INAPPROPRIATE_AUTH;
395 /* Must run an internal search. */
397 Debug( LDAP_DEBUG_TRACE,
398 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
399 searchbase, scope, 0 );
401 be = select_backend( searchbase, 0 );
402 if(( be == NULL ) || ( be->be_search == NULL)) {
403 rc = LDAP_INAPPROPRIATE_AUTH;
406 searchbase = suffix_alias( be, searchbase );
408 /* Make an internal connection on which to run the search */
409 rc = connection_internal_open( &conn, &client, authc );
410 if( rc != LDAP_SUCCESS )
413 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
414 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
415 /*attrs=*/NULL, /*attrsonly=*/0 );
418 /* On the client side of the internal search, read the results. Check
419 if the assertDN matches any of the DN's returned by the search */
420 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
424 for( msg=ldap_first_entry( client, res );
426 msg=ldap_next_entry( client, msg ) ) {
427 dn = ldap_get_dn( client, msg );
429 rc = strcmp( dn, assertDN );
436 rc = LDAP_INAPPROPRIATE_AUTH;
439 if( searchbase ) ch_free( searchbase );
440 if( filter ) filter_free( filter );
441 if( conn ) connection_internal_close( conn );
442 if( res ) ldap_msgfree( res );
443 if( client ) ldap_unbind( client );
444 Debug( LDAP_DEBUG_TRACE,
445 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
454 * This function answers the question, "Can this ID authorize to that ID?",
455 * based on authorization rules. The rules are stored in the *searchDN, in the
456 * attribute named by *attr. If any of those rules map to the *assertDN, the
457 * authorization is approved.
459 * DN's passed in should have a dn: prefix
463 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
467 struct berval **vals=NULL;
468 AttributeDescription *ad=NULL;
471 Debug( LDAP_DEBUG_TRACE,
472 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
473 assertDN, attr, searchDN);
474 rc = slap_str2ad( attr, &ad, &errmsg );
475 if( rc != LDAP_SUCCESS )
478 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN+3, ad, &vals );
479 if( rc != LDAP_SUCCESS )
482 /* Check if the *assertDN matches any **vals */
483 for( i=0; vals[i] != NULL; i++ ) {
484 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
485 if ( rc == LDAP_SUCCESS )
488 rc = LDAP_INAPPROPRIATE_AUTH;
491 if( vals ) ber_bvecfree( vals );
492 if( ad ) ad_free( ad, 1 );
494 Debug( LDAP_DEBUG_TRACE,
495 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
501 #endif /* HAVE_CYRUS_SASL */
507 /* Check if a bind can SASL authorize to another identity.
508 Accepts authorization DN's with "dn:" prefix */
510 int slap_sasl_authorized( char *authcDN, char *authzDN )
514 #ifdef HAVE_CYRUS_SASL
515 /* User binding as anonymous */
516 if ( authzDN == NULL ) {
521 Debug( LDAP_DEBUG_TRACE,
522 "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
524 /* If person is authorizing to self, succeed */
525 if ( !strcmp( authcDN, authzDN ) ) {
530 /* Check source rules */
531 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
533 if( rc == LDAP_SUCCESS )
536 /* Check destination rules */
537 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
539 if( rc == LDAP_SUCCESS )
543 rc = LDAP_INAPPROPRIATE_AUTH;
546 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );