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
134 struct berval bv, nbv;
136 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
137 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
138 reg = &( SaslRegexp[nSaslRegexp] );
139 ber_str2bv( match, 0, 0, &bv );
140 rc = dnNormalize2( NULL, &bv, &nbv );
143 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
144 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
147 Debug( LDAP_DEBUG_ANY,
148 "SASL match pattern %s could not be normalized.\n",
153 reg->sr_match = nbv.bv_val;
155 ber_str2bv( replace, 0, 0, &bv );
156 rc = dnNormalize2( NULL, &bv, &nbv );
159 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
160 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
163 Debug( LDAP_DEBUG_ANY,
164 "SASL replace pattern %s could not be normalized.\n",
169 reg->sr_replace = nbv.bv_val;
171 /* Precompile matching pattern */
172 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
175 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
176 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
179 Debug( LDAP_DEBUG_ANY,
180 "SASL match pattern %s could not be compiled by regexp engine\n",
181 reg->sr_match, 0, 0 );
184 return( LDAP_OPERATIONS_ERROR );
187 /* Precompile replace pattern. Find the $<n> placeholders */
188 reg->sr_offset[0] = -2;
190 for ( c = reg->sr_replace; *c; c++ ) {
196 if ( n == SASLREGEX_REPLACE ) {
198 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
199 "slap_sasl_regexp_config: \"%s\" has too many $n "
200 "placeholders (max %d)\n",
201 reg->sr_replace, SASLREGEX_REPLACE ));
203 Debug( LDAP_DEBUG_ANY,
204 "SASL replace pattern %s has too many $n "
205 "placeholders (max %d)\n",
206 reg->sr_replace, SASLREGEX_REPLACE, 0 );
209 return( LDAP_OPERATIONS_ERROR );
211 reg->sr_offset[n] = c - reg->sr_replace;
216 /* Final placeholder, after the last $n */
217 reg->sr_offset[n] = c - reg->sr_replace;
219 reg->sr_offset[n] = -1;
223 return( LDAP_SUCCESS );
227 #ifdef HAVE_CYRUS_SASL
229 /* Take the passed in SASL name and attempt to convert it into an
230 LDAP URI to find the matching LDAP entry, using the pattern matching
231 strings given in the saslregexp config file directive(s) */
233 char *slap_sasl_regexp( char *saslname )
236 int i, n, len, insert;
240 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
241 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
243 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
247 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
250 /* Match the normalized SASL name to the saslregexp patterns */
251 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
252 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
253 reg->sr_strings, 0) == 0 )
257 if( i >= nSaslRegexp )
261 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
262 * replace pattern of the form "x$1y$2z". The returned string needs
263 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
267 /* Get the total length of the final URI */
271 while( reg->sr_offset[n] >= 0 ) {
272 /* Len of next section from replacement string (x,y,z above) */
273 len += reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
274 if( reg->sr_offset[n+1] < 0)
277 /* Len of string from saslname that matched next $i (b,d above) */
278 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
279 len += reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
282 uri = ch_malloc( len + 1 );
284 /* Fill in URI with replace string, replacing $i as we go */
287 while( reg->sr_offset[n] >= 0) {
288 /* Paste in next section from replacement string (x,y,z above) */
289 len = reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
290 strncpy( uri+insert, reg->sr_replace + reg->sr_offset[n-1] + 2, len);
292 if( reg->sr_offset[n+1] < 0)
295 /* Paste in string from saslname that matched next $i (b,d above) */
296 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
297 len = reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
298 strncpy( uri+insert, saslname + reg->sr_strings[i].rm_so, len );
306 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
307 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
309 Debug( LDAP_DEBUG_TRACE,
310 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
316 /* Two empty callback functions to avoid sending results */
317 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
318 ber_int_t msgid, ber_int_t err, const char *matched,
319 const char *text, BerVarray ref, const char *resoid,
320 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
324 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
325 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
330 /* This callback actually does some work...*/
331 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
332 Entry *e, AttributeName *an, int ao, LDAPControl **c)
334 struct berval *ndn = o->o_callback->sc_private;
336 /* We only want to be called once */
341 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
342 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
344 Debug( LDAP_DEBUG_TRACE,
345 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
349 ber_dupbv(ndn, &e->e_nname);
355 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
356 * return the LDAP DN to which it matches. The SASL regexp rules in the config
357 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
358 * search with scope=base), just return the URI (or its searchbase). Otherwise
359 * an internal search must be done, and if that search returns exactly one
360 * entry, return the DN of that one entry.
363 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
366 struct berval searchbase = {0, NULL};
370 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
374 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
375 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
377 Debug( LDAP_DEBUG_TRACE,
378 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
384 /* Convert the SASL name into an LDAP URI */
385 uri = slap_sasl_regexp( saslname->bv_val );
389 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
394 /* Massive shortcut: search scope == base */
395 if( scope == LDAP_SCOPE_BASE ) {
397 searchbase.bv_len = 0;
398 searchbase.bv_val = NULL;
402 /* Must do an internal search */
405 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
406 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
407 searchbase.bv_val, scope ));
409 Debug( LDAP_DEBUG_TRACE,
410 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
411 searchbase.bv_val, scope, 0 );
414 be = select_backend( &searchbase, 0, 1 );
415 if(( be == NULL ) || ( be->be_search == NULL))
417 suffix_alias( be, &searchbase );
419 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
420 op.o_tag = LDAP_REQ_SEARCH;
421 op.o_protocol = LDAP_VERSION3;
422 op.o_ndn = *saslname;
424 op.o_time = slap_get_time();
426 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
427 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
428 /*attrs=*/NULL, /*attrsonly=*/0 );
430 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
433 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
434 if( filter ) filter_free( filter );
435 if( uri ) ch_free( uri );
438 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
439 "slap_sasl2dn: Converted SASL name to %s\n",
440 dn->bv_len ? dn->bv_val : "<nothing>" ));
442 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
443 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
449 typedef struct smatch_info {
454 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
455 Entry *e, AttributeName *an, int ao, LDAPControl **c)
457 smatch_info *sm = o->o_callback->sc_private;
459 if (dn_match(sm->dn, &e->e_nname)) {
461 return -1; /* short-circuit the search */
468 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
469 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
470 * the rule must be used as an internal search for entries. If that search
471 * returns the *assertDN entry, the match is successful.
473 * The assertDN should not have the dn: prefix
477 int slap_sasl_match( char *rule, struct berval *assertDN, struct berval *authc )
479 struct berval searchbase = {0, NULL};
485 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_smatch, &sm};
489 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
490 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule ));
492 Debug( LDAP_DEBUG_TRACE,
493 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule, 0 );
496 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
497 if( rc != LDAP_SUCCESS )
500 /* Massive shortcut: search scope == base */
501 if( scope == LDAP_SCOPE_BASE ) {
502 rc = regcomp(®, searchbase.bv_val,
503 REG_EXTENDED|REG_ICASE|REG_NOSUB);
505 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
511 rc = LDAP_INAPPROPRIATE_AUTH;
515 /* Must run an internal search. */
518 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
519 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
520 searchbase.bv_val, scope ));
522 Debug( LDAP_DEBUG_TRACE,
523 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
524 searchbase.bv_val, scope, 0 );
527 be = select_backend( &searchbase, 0, 1 );
528 if(( be == NULL ) || ( be->be_search == NULL)) {
529 rc = LDAP_INAPPROPRIATE_AUTH;
532 suffix_alias( be, &searchbase );
537 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
538 op.o_tag = LDAP_REQ_SEARCH;
539 op.o_protocol = LDAP_VERSION3;
542 op.o_time = slap_get_time();
544 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
545 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
546 /*attrs=*/NULL, /*attrsonly=*/0 );
548 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
553 rc = LDAP_INAPPROPRIATE_AUTH;
556 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
557 if( filter ) filter_free( filter );
559 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
560 "slap_sasl_match: comparison returned %d\n", rc ));
562 Debug( LDAP_DEBUG_TRACE,
563 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
571 * This function answers the question, "Can this ID authorize to that ID?",
572 * based on authorization rules. The rules are stored in the *searchDN, in the
573 * attribute named by *attr. If any of those rules map to the *assertDN, the
574 * authorization is approved.
576 * DN's passed in should have a dn: prefix
579 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
584 AttributeDescription *ad=NULL;
588 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
589 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
590 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
592 Debug( LDAP_DEBUG_TRACE,
593 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
594 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
597 rc = slap_bv2ad( attr, &ad, &errmsg );
598 if( rc != LDAP_SUCCESS )
601 bv.bv_val = searchDN->bv_val + 3;
602 bv.bv_len = searchDN->bv_len - 3;
603 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
604 if( rc != LDAP_SUCCESS )
607 bv.bv_val = assertDN->bv_val + 3;
608 bv.bv_len = assertDN->bv_len - 3;
609 /* Check if the *assertDN matches any **vals */
610 for( i=0; vals[i].bv_val != NULL; i++ ) {
611 rc = slap_sasl_match( vals[i].bv_val, &bv, authc );
612 if ( rc == LDAP_SUCCESS )
615 rc = LDAP_INAPPROPRIATE_AUTH;
618 if( vals ) ber_bvarray_free( vals );
621 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
622 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
624 Debug( LDAP_DEBUG_TRACE,
625 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
630 #endif /* HAVE_CYRUS_SASL */
633 /* Check if a bind can SASL authorize to another identity.
634 Accepts authorization DN's with "dn:" prefix */
636 static struct berval sasl_authz_src = {
637 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
639 static struct berval sasl_authz_dst = {
640 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
642 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
644 int rc = LDAP_INAPPROPRIATE_AUTH;
646 #ifdef HAVE_CYRUS_SASL
647 /* User binding as anonymous */
648 if ( authzDN == NULL ) {
654 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
655 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
657 Debug( LDAP_DEBUG_TRACE,
658 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
661 /* If person is authorizing to self, succeed */
662 if ( dn_match( authcDN, authzDN ) ) {
667 /* Check source rules */
668 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
670 if( rc == LDAP_SUCCESS ) {
674 /* Check destination rules */
675 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
677 if( rc == LDAP_SUCCESS ) {
681 rc = LDAP_INAPPROPRIATE_AUTH;
687 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
688 "slap_sasl_authorized: return %d\n", rc ));
690 Debug( LDAP_DEBUG_TRACE,
691 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );