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>
21 #include "proto-slap.h"
25 #elif defined (HAVE_STRING_H)
29 #ifdef HAVE_CYRUS_SASL
34 extern int nSaslRegexp;
35 extern SaslRegexp_t *SaslRegexp;
42 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
44 int slap_parseURI( char *uri, char **searchbase, int *scope, Filter **filter )
49 assert( uri != NULL );
54 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
56 /* If it does not look like a URI, assume it is a DN */
57 if( strncasecmp( uri, "ldap://", 7 ) ) {
58 *searchbase = ch_strdup( uri );
59 dn_normalize( *searchbase );
60 *scope = LDAP_SCOPE_BASE;
61 return( LDAP_SUCCESS );
64 end = index( uri + 7, '/' );
66 return( LDAP_PROTOCOL_ERROR );
68 /* could check the hostname here */
70 /* Grab the searchbase */
72 end = index( start, '?' );
74 *searchbase = ch_strdup( start );
75 dn_normalize( *searchbase );
76 return( LDAP_SUCCESS );
79 *searchbase = ch_strdup( start );
81 dn_normalize( *searchbase );
85 end = index( start, '?' );
87 return( LDAP_SUCCESS );
92 if( !strncasecmp( start, "base?", 5 )) {
93 *scope = LDAP_SCOPE_BASE;
96 else if( !strncasecmp( start, "one?", 4 )) {
97 *scope = LDAP_SCOPE_ONELEVEL;
100 else if( !strncasecmp( start, "sub?", 3 )) {
101 *scope = LDAP_SCOPE_SUBTREE;
105 ch_free( *searchbase );
107 return( LDAP_PROTOCOL_ERROR );
110 /* Grab the filter */
111 *filter = str2filter( start );
113 return( LDAP_SUCCESS );
120 int slap_sasl_regexp_config( const char *match, const char *replace )
122 #ifdef HAVE_CYRUS_SASL
127 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
128 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
129 reg = &( SaslRegexp[nSaslRegexp] );
130 reg->match = ch_strdup( match );
131 reg->replace = ch_strdup( replace );
132 dn_normalize( reg->match );
133 dn_normalize( reg->replace );
135 /* Precompile matching pattern */
136 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
138 Debug( LDAP_DEBUG_ANY,
139 "SASL match pattern %s could not be compiled by regexp engine\n",
141 return( LDAP_OPERATIONS_ERROR );
144 /* Precompile replace pattern. Find the $<n> placeholders */
147 for ( c = reg->replace; *c; c++ ) {
153 if ( n == SASLREGEX_REPLACE ) {
154 Debug( LDAP_DEBUG_ANY,
155 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
156 reg->replace, SASLREGEX_REPLACE, 0 );
157 return( LDAP_OPERATIONS_ERROR );
159 reg->offset[n] = c - reg->replace;
164 /* Final placeholder, after the last $n */
165 reg->offset[n] = c - reg->replace;
171 return( LDAP_SUCCESS );
178 #ifdef HAVE_CYRUS_SASL
182 /* Take the passed in SASL name and attempt to convert it into an
183 LDAP URI to find the matching LDAP entry, using the pattern matching
184 strings given in the saslregexp config file directive(s) */
186 char *slap_sasl_regexp( char *saslname )
189 int i, n, len, insert;
193 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
195 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
198 /* Match the normalized SASL name to the saslregexp patterns */
199 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
200 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
201 reg->strings, 0) == REG_OK )
205 if( i >= nSaslRegexp )
209 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
210 * replace pattern of the form "x$1y$2z". The returned string needs
211 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
215 /* Get the total length of the final URI */
219 while( reg->offset[n] >= 0 ) {
220 /* Len of next section from replacement string (x,y,z above) */
221 len += reg->offset[n] - reg->offset[n-1] - 2;
222 if( reg->offset[n+1] < 0)
225 /* Len of string from saslname that matched next $i (b,d above) */
226 i = reg->replace[ reg->offset[n] + 1 ] - '0';
227 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
230 uri = ch_malloc( len + 1 );
232 /* Fill in URI with replace string, replacing $i as we go */
235 while( reg->offset[n] >= 0) {
236 /* Paste in next section from replacement string (x,y,z above) */
237 len = reg->offset[n] - reg->offset[n-1] - 2;
238 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
240 if( reg->offset[n+1] < 0)
243 /* Paste in string from saslname that matched next $i (b,d above) */
244 i = reg->replace[ reg->offset[n] + 1 ] - '0';
245 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
246 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
253 Debug( LDAP_DEBUG_TRACE,
254 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
263 * Given a SASL name (e.g. "UID=name+REALM=company,cn=GSSAPI,cn=AUTHZ")
264 * return the LDAP DN to which it matches. The SASL regexp rules in the config
265 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
266 * search with scope=base), just return the URI (or its searchbase). Otherwise
267 * an internal search must be done, and if that search returns exactly one
268 * entry, return the DN of that one entry.
272 char *slap_sasl2dn( char *saslname )
274 char *uri=NULL, *searchbase=NULL, *DN=NULL;
278 Connection *conn=NULL;
280 LDAPMessage *res=NULL, *msg;
283 Debug( LDAP_DEBUG_TRACE,
284 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
286 /* Convert the SASL name into an LDAP URI */
287 uri = slap_sasl_regexp( saslname );
291 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
295 /* Massive shortcut: search scope == base */
296 if( scope == LDAP_SCOPE_BASE ) {
297 DN = ch_strdup( searchbase );
301 /* Must do an internal search */
303 Debug( LDAP_DEBUG_TRACE,
304 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
305 searchbase, scope, 0 );
307 be = select_backend( searchbase );
308 if(( be == NULL ) || ( be->be_search == NULL))
310 searchbase = suffix_alias( be, searchbase );
312 rc = connection_internal_open( &conn, &client, saslname );
313 if( rc != LDAP_SUCCESS )
316 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
317 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
318 /*attrs=*/NULL, /*attrsonly=*/0 );
321 /* Read the client side of the internal search */
322 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
326 /* Make sure exactly one entry was returned */
327 rc = ldap_count_entries( client, res );
328 Debug( LDAP_DEBUG_TRACE,
329 "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
333 msg = ldap_first_entry( client, res );
334 DN = ldap_get_dn( client, msg );
337 if( searchbase ) ch_free( searchbase );
338 if( filter ) filter_free( filter );
339 if( uri ) ch_free( uri );
340 if( conn ) connection_internal_close( conn );
341 if( res ) ldap_msgfree( res );
342 if( client ) ldap_unbind( client );
343 if( DN ) dn_normalize( DN );
344 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
345 DN ? DN : "<nothing>", 0, 0 );
354 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
355 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
356 * the rule must be used as an internal search for entries. If that search
357 * returns the *assertDN entry, the match is successful.
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;
372 Debug( LDAP_DEBUG_TRACE,
373 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
375 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
376 if( rc != LDAP_SUCCESS )
379 /* Massive shortcut: search scope == base */
380 if( scope == LDAP_SCOPE_BASE ) {
381 dn_normalize( searchbase );
382 if( strcmp( searchbase, assertDN ) == 0 )
385 rc = LDAP_INAPPROPRIATE_AUTH;
389 /* Must run an internal search. */
391 Debug( LDAP_DEBUG_TRACE,
392 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
393 searchbase, scope, 0 );
395 be = select_backend( searchbase );
396 if(( be == NULL ) || ( be->be_search == NULL)) {
397 rc = LDAP_INAPPROPRIATE_AUTH;
400 searchbase = suffix_alias( be, searchbase );
402 /* Make an internal connection on which to run the search */
403 rc = connection_internal_open( &conn, &client, authc );
404 if( rc != LDAP_SUCCESS )
407 (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
408 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
409 /*attrs=*/NULL, /*attrsonly=*/0 );
412 /* On the client side of the internal search, read the results. Check
413 if the assertDN matches any of the DN's returned by the search */
414 rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
418 for( msg=ldap_first_entry( client, res );
420 msg=ldap_next_entry( client, msg ) ) {
421 dn = ldap_get_dn( client, msg );
423 rc = strcmp( dn, assertDN );
430 rc = LDAP_INAPPROPRIATE_AUTH;
433 if( searchbase ) ch_free( searchbase );
434 if( filter ) filter_free( filter );
435 if( conn ) connection_internal_close( conn );
436 if( res ) ldap_msgfree( res );
437 if( client ) ldap_unbind( client );
438 Debug( LDAP_DEBUG_TRACE,
439 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
448 * This function answers the question, "Can this ID authorize to that ID?",
449 * based on authorization rules. The rules are stored in the *searchDN, in the
450 * attribute named by *attr. If any of those rules map to the *assertDN, the
451 * authorization is approved.
455 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
459 struct berval **vals=NULL;
460 AttributeDescription *ad=NULL;
463 Debug( LDAP_DEBUG_TRACE,
464 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
465 assertDN, attr, searchDN);
466 rc = slap_str2ad( attr, &ad, &errmsg );
467 if( rc != LDAP_SUCCESS )
470 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
471 if( rc != LDAP_SUCCESS )
474 /* Check if the *assertDN matches any **vals */
475 for( i=0; vals[i] != NULL; i++ ) {
476 rc = slap_sasl_match( vals[i]->bv_val, assertDN, authc );
477 if ( rc == LDAP_SUCCESS )
480 rc = LDAP_INAPPROPRIATE_AUTH;
483 if( vals ) ber_bvecfree( vals );
484 if( ad ) ad_free( ad, 1 );
486 Debug( LDAP_DEBUG_TRACE,
487 "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
493 #endif /* HAVE_CYRUS_SASL */
499 /* Check if a bind can SASL authorize to another identity. */
501 int slap_sasl_authorized( Connection *conn, char *authcid, char *authzid )
504 char *saslname=NULL,*authcDN=NULL,*realm=NULL, *authzDN=NULL;
506 #ifdef HAVE_CYRUS_SASL
507 Debug( LDAP_DEBUG_TRACE,
508 "==>slap_sasl_authorized: can %s become %s?\n", authcid, authzid, 0 );
510 /* Create a complete SASL name for the SASL regexp patterns */
512 sasl_getprop( conn->c_sasl_context, SASL_REALM, (void **)&realm );
515 rc = strlen("uid=+realm=,cn=,cn=AUTHZ ");
516 if ( realm ) rc += strlen( realm );
517 if ( authcid ) rc += strlen( authcid );
518 rc += strlen( conn->c_sasl_bind_mech );
519 saslname = ch_malloc( rc );
521 /* Build the SASL name with whatever we have, and normalize it */
525 rc += sprintf( saslname+rc, "%sUID=%s", rc?",":"", authcid);
527 rc += sprintf( saslname+rc, "%sREALM=%s", rc?"+":"", realm);
528 if ( conn->c_sasl_bind_mech )
529 rc += sprintf( saslname+rc, "%sCN=%s", rc?",":"",
530 conn->c_sasl_bind_mech);
531 sprintf( saslname+rc, "%sCN=AUTHZ", rc?",":"");
532 dn_normalize( saslname );
534 authcDN = slap_sasl2dn( saslname );
535 if( authcDN == NULL )
538 /* Normalize the name given by the clientside of the connection */
539 authzDN = ch_strdup( authzid );
540 dn_normalize( authzDN );
543 /* Check source rules */
544 rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
546 if( rc == LDAP_SUCCESS )
549 /* Check destination rules */
550 rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
552 if( rc == LDAP_SUCCESS )
556 rc = LDAP_INAPPROPRIATE_AUTH;
559 if( saslname ) ch_free( saslname );
560 if( authcDN ) ch_free( authcDN );
561 if( authzDN ) ch_free( authzDN );
562 Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );