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
25 #ifdef HAVE_SASL_SASL_H
26 #include <sasl/sasl.h>
34 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
36 static int slap_parseURI( char *uri,
37 struct berval *searchbase, int *scope, Filter **filter )
44 assert( uri != NULL );
45 searchbase->bv_val = NULL;
46 searchbase->bv_len = 0;
51 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
52 "slap_parseURI: parsing %s\n", uri ));
54 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
57 /* If it does not look like a URI, assume it is a DN */
58 if( !strncasecmp( uri, "dn:", sizeof("dn:")-1 ) ) {
59 uri += sizeof("dn:")-1;
60 uri += strspn( uri, " " );
62 /* FIXME: if dnNormalize actually uses input bv_len we
63 * will have to make this right.
66 rc = dnNormalize2( NULL, &bv, searchbase );
67 if (rc == LDAP_SUCCESS) {
68 *scope = LDAP_SCOPE_BASE;
73 /* FIXME: should use ldap_url_parse() */
74 if( strncasecmp( uri, "ldap://", sizeof("ldap://")-1 ) ) {
79 end = strchr( uri + (sizeof("ldap://")-1), '/' );
81 return( LDAP_PROTOCOL_ERROR );
83 /* could check the hostname here */
85 /* Grab the searchbase */
87 end = strchr( start, '?' );
91 return dnNormalize2( NULL, &bv, searchbase );
94 bv.bv_len = end - start;
95 rc = dnNormalize2( NULL, &bv, searchbase );
97 if (rc != LDAP_SUCCESS)
102 end = strchr( start, '?' );
104 return( LDAP_SUCCESS );
109 if( !strncasecmp( start, "base?", sizeof("base?")-1 )) {
110 *scope = LDAP_SCOPE_BASE;
111 start += sizeof("base?")-1;
113 else if( !strncasecmp( start, "one?", sizeof("one?")-1 )) {
114 *scope = LDAP_SCOPE_ONELEVEL;
115 start += sizeof("one?")-1;
117 else if( !strncasecmp( start, "sub?", sizeof("sub?")-1 )) {
118 *scope = LDAP_SCOPE_SUBTREE;
119 start += sizeof("sub?")-1;
122 free( searchbase->bv_val );
123 searchbase->bv_val = NULL;
124 return( LDAP_PROTOCOL_ERROR );
127 /* Grab the filter */
128 *filter = str2filter( start );
130 return( LDAP_SUCCESS );
134 int slap_sasl_regexp_config( const char *match, const char *replace )
136 #ifdef HAVE_CYRUS_SASL
140 struct berval bv, nbv;
142 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
143 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
144 reg = &( SaslRegexp[nSaslRegexp] );
145 ber_str2bv( match, 0, 0, &bv );
146 rc = dnNormalize2( NULL, &bv, &nbv );
149 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
150 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
153 Debug( LDAP_DEBUG_ANY,
154 "SASL match pattern %s could not be normalized.\n",
159 reg->sr_match = nbv.bv_val;
161 ber_str2bv( replace, 0, 0, &bv );
162 rc = dnNormalize2( NULL, &bv, &nbv );
165 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
166 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
169 Debug( LDAP_DEBUG_ANY,
170 "SASL replace pattern %s could not be normalized.\n",
175 reg->sr_replace = nbv.bv_val;
177 /* Precompile matching pattern */
178 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
181 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
182 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
185 Debug( LDAP_DEBUG_ANY,
186 "SASL match pattern %s could not be compiled by regexp engine\n",
187 reg->sr_match, 0, 0 );
190 return( LDAP_OPERATIONS_ERROR );
193 /* Precompile replace pattern. Find the $<n> placeholders */
194 reg->sr_offset[0] = -2;
196 for ( c = reg->sr_replace; *c; c++ ) {
202 if ( n == SASLREGEX_REPLACE ) {
204 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
205 "slap_sasl_regexp_config: \"%s\" has too many $n "
206 "placeholders (max %d)\n",
207 reg->sr_replace, SASLREGEX_REPLACE ));
209 Debug( LDAP_DEBUG_ANY,
210 "SASL replace pattern %s has too many $n "
211 "placeholders (max %d)\n",
212 reg->sr_replace, SASLREGEX_REPLACE, 0 );
215 return( LDAP_OPERATIONS_ERROR );
217 reg->sr_offset[n] = c - reg->sr_replace;
222 /* Final placeholder, after the last $n */
223 reg->sr_offset[n] = c - reg->sr_replace;
225 reg->sr_offset[n] = -1;
229 return( LDAP_SUCCESS );
233 #ifdef HAVE_CYRUS_SASL
235 /* Take the passed in SASL name and attempt to convert it into an
236 LDAP URI to find the matching LDAP entry, using the pattern matching
237 strings given in the saslregexp config file directive(s) */
239 char *slap_sasl_regexp( char *saslname )
242 int i, n, len, insert;
246 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
247 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
249 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
253 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
256 /* Match the normalized SASL name to the saslregexp patterns */
257 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
258 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
259 reg->sr_strings, 0) == 0 )
263 if( i >= nSaslRegexp )
267 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
268 * replace pattern of the form "x$1y$2z". The returned string needs
269 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
273 /* Get the total length of the final URI */
277 while( reg->sr_offset[n] >= 0 ) {
278 /* Len of next section from replacement string (x,y,z above) */
279 len += reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
280 if( reg->sr_offset[n+1] < 0)
283 /* Len of string from saslname that matched next $i (b,d above) */
284 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
285 len += reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
288 uri = ch_malloc( len + 1 );
290 /* Fill in URI with replace string, replacing $i as we go */
293 while( reg->sr_offset[n] >= 0) {
294 /* Paste in next section from replacement string (x,y,z above) */
295 len = reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
296 strncpy( uri+insert, reg->sr_replace + reg->sr_offset[n-1] + 2, len);
298 if( reg->sr_offset[n+1] < 0)
301 /* Paste in string from saslname that matched next $i (b,d above) */
302 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
303 len = reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
304 strncpy( uri+insert, saslname + reg->sr_strings[i].rm_so, len );
312 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
313 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
315 Debug( LDAP_DEBUG_TRACE,
316 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
322 /* Two empty callback functions to avoid sending results */
323 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
324 ber_int_t msgid, ber_int_t err, const char *matched,
325 const char *text, BerVarray ref, const char *resoid,
326 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
330 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
331 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
336 /* This callback actually does some work...*/
337 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
338 Entry *e, AttributeName *an, int ao, LDAPControl **c)
340 struct berval *ndn = o->o_callback->sc_private;
342 /* We only want to be called once */
347 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
348 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
350 Debug( LDAP_DEBUG_TRACE,
351 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
355 ber_dupbv(ndn, &e->e_nname);
361 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
362 * return the LDAP DN to which it matches. The SASL regexp rules in the config
363 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
364 * search with scope=base), just return the URI (or its searchbase). Otherwise
365 * an internal search must be done, and if that search returns exactly one
366 * entry, return the DN of that one entry.
369 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
372 struct berval searchbase = {0, NULL};
376 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
380 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
381 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
383 Debug( LDAP_DEBUG_TRACE,
384 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
390 /* Convert the SASL name into an LDAP URI */
391 uri = slap_sasl_regexp( saslname->bv_val );
395 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
400 /* Massive shortcut: search scope == base */
401 if( scope == LDAP_SCOPE_BASE ) {
403 searchbase.bv_len = 0;
404 searchbase.bv_val = NULL;
408 /* Must do an internal search */
411 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
412 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
413 searchbase.bv_val, scope ));
415 Debug( LDAP_DEBUG_TRACE,
416 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
417 searchbase.bv_val, scope, 0 );
420 be = select_backend( &searchbase, 0, 1 );
421 if(( be == NULL ) || ( be->be_search == NULL))
423 suffix_alias( be, &searchbase );
425 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
426 op.o_tag = LDAP_REQ_SEARCH;
427 op.o_protocol = LDAP_VERSION3;
428 op.o_ndn = *saslname;
430 op.o_time = slap_get_time();
432 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
433 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
434 /*attrs=*/NULL, /*attrsonly=*/0 );
436 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
439 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
440 if( filter ) filter_free( filter );
441 if( uri ) ch_free( uri );
444 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
445 "slap_sasl2dn: Converted SASL name to %s\n",
446 dn->bv_len ? dn->bv_val : "<nothing>" ));
448 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
449 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
455 typedef struct smatch_info {
460 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
461 Entry *e, AttributeName *an, int ao, LDAPControl **c)
463 smatch_info *sm = o->o_callback->sc_private;
465 if (dn_match(sm->dn, &e->e_nname)) {
467 return -1; /* short-circuit the search */
474 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
475 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
476 * the rule must be used as an internal search for entries. If that search
477 * returns the *assertDN entry, the match is successful.
479 * The assertDN should not have the dn: prefix
483 int slap_sasl_match( char *rule, struct berval *assertDN, struct berval *authc )
485 struct berval searchbase = {0, NULL};
491 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_smatch, &sm};
495 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
496 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule ));
498 Debug( LDAP_DEBUG_TRACE,
499 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule, 0 );
502 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
503 if( rc != LDAP_SUCCESS )
506 /* Massive shortcut: search scope == base */
507 if( scope == LDAP_SCOPE_BASE ) {
508 rc = regcomp(®, searchbase.bv_val,
509 REG_EXTENDED|REG_ICASE|REG_NOSUB);
511 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
517 rc = LDAP_INAPPROPRIATE_AUTH;
521 /* Must run an internal search. */
524 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
525 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
526 searchbase.bv_val, scope ));
528 Debug( LDAP_DEBUG_TRACE,
529 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
530 searchbase.bv_val, scope, 0 );
533 be = select_backend( &searchbase, 0, 1 );
534 if(( be == NULL ) || ( be->be_search == NULL)) {
535 rc = LDAP_INAPPROPRIATE_AUTH;
538 suffix_alias( be, &searchbase );
543 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
544 op.o_tag = LDAP_REQ_SEARCH;
545 op.o_protocol = LDAP_VERSION3;
548 op.o_time = slap_get_time();
550 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
551 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
552 /*attrs=*/NULL, /*attrsonly=*/0 );
554 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
559 rc = LDAP_INAPPROPRIATE_AUTH;
562 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
563 if( filter ) filter_free( filter );
565 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
566 "slap_sasl_match: comparison returned %d\n", rc ));
568 Debug( LDAP_DEBUG_TRACE,
569 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
577 * This function answers the question, "Can this ID authorize to that ID?",
578 * based on authorization rules. The rules are stored in the *searchDN, in the
579 * attribute named by *attr. If any of those rules map to the *assertDN, the
580 * authorization is approved.
582 * DN's passed in should have a dn: prefix
585 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
590 AttributeDescription *ad=NULL;
594 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
595 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
596 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
598 Debug( LDAP_DEBUG_TRACE,
599 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
600 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
603 rc = slap_bv2ad( attr, &ad, &errmsg );
604 if( rc != LDAP_SUCCESS )
607 bv.bv_val = searchDN->bv_val + 3;
608 bv.bv_len = searchDN->bv_len - 3;
609 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
610 if( rc != LDAP_SUCCESS )
613 bv.bv_val = assertDN->bv_val + 3;
614 bv.bv_len = assertDN->bv_len - 3;
615 /* Check if the *assertDN matches any **vals */
616 for( i=0; vals[i].bv_val != NULL; i++ ) {
617 rc = slap_sasl_match( vals[i].bv_val, &bv, authc );
618 if ( rc == LDAP_SUCCESS )
621 rc = LDAP_INAPPROPRIATE_AUTH;
624 if( vals ) ber_bvarray_free( vals );
627 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
628 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
630 Debug( LDAP_DEBUG_TRACE,
631 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
636 #endif /* HAVE_CYRUS_SASL */
639 /* Check if a bind can SASL authorize to another identity.
640 Accepts authorization DN's with "dn:" prefix */
642 static struct berval sasl_authz_src = {
643 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
645 static struct berval sasl_authz_dst = {
646 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
648 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
650 int rc = LDAP_INAPPROPRIATE_AUTH;
652 #ifdef HAVE_CYRUS_SASL
653 /* User binding as anonymous */
654 if ( authzDN == NULL ) {
660 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
661 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
663 Debug( LDAP_DEBUG_TRACE,
664 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
667 /* If person is authorizing to self, succeed */
668 if ( dn_match( authcDN, authzDN ) ) {
673 /* Check source rules */
674 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
676 if( rc == LDAP_SUCCESS ) {
680 /* Check destination rules */
681 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
683 if( rc == LDAP_SUCCESS ) {
687 rc = LDAP_INAPPROPRIATE_AUTH;
693 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
694 "slap_sasl_authorized: return %d\n", rc ));
696 Debug( LDAP_DEBUG_TRACE,
697 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );