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( struct berval *uri,
37 struct berval *searchbase, int *scope, Filter **filter )
43 assert( uri != NULL && uri->bv_val != NULL );
44 searchbase->bv_val = NULL;
45 searchbase->bv_len = 0;
50 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
51 "slap_parseURI: parsing %s\n", uri->bv_val ));
53 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
56 /* If it does not look like a URI, assume it is a DN */
57 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
58 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
59 bv.bv_val += strspn( bv.bv_val, " " );
61 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
62 rc = dnNormalize2( NULL, &bv, searchbase );
63 if (rc == LDAP_SUCCESS) {
64 *scope = LDAP_SCOPE_BASE;
69 rc = ldap_url_parse( uri->bv_val, &ludp );
70 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
71 bv.bv_val = uri->bv_val;
75 if ( rc != LDAP_URL_SUCCESS ) {
76 return( LDAP_PROTOCOL_ERROR );
79 /* could check the hostname here */
82 *scope = ludp->lud_scope;
85 if ( ludp->lud_filter ) {
86 *filter = str2filter( ludp->lud_filter );
87 if ( *filter == NULL )
88 rc = LDAP_PROTOCOL_ERROR;
91 /* Grab the searchbase */
92 if ( rc == LDAP_URL_SUCCESS ) {
93 bv.bv_val = ludp->lud_dn;
94 bv.bv_len = strlen( bv.bv_val );
95 rc = dnNormalize2( NULL, &bv, searchbase );
98 ldap_free_urldesc( ludp );
104 int slap_sasl_regexp_config( const char *match, const char *replace )
106 #ifdef HAVE_CYRUS_SASL
110 struct berval bv, nbv;
112 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
113 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
114 reg = &( SaslRegexp[nSaslRegexp] );
115 ber_str2bv( match, 0, 0, &bv );
116 rc = dnNormalize2( NULL, &bv, &nbv );
119 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
120 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
123 Debug( LDAP_DEBUG_ANY,
124 "SASL match pattern %s could not be normalized.\n",
129 reg->sr_match = nbv.bv_val;
131 ber_str2bv( replace, 0, 0, &bv );
132 rc = dnNormalize2( NULL, &bv, &nbv );
135 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
136 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
139 Debug( LDAP_DEBUG_ANY,
140 "SASL replace pattern %s could not be normalized.\n",
145 reg->sr_replace = nbv.bv_val;
147 /* Precompile matching pattern */
148 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
151 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
152 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
155 Debug( LDAP_DEBUG_ANY,
156 "SASL match pattern %s could not be compiled by regexp engine\n",
157 reg->sr_match, 0, 0 );
160 return( LDAP_OPERATIONS_ERROR );
163 /* Precompile replace pattern. Find the $<n> placeholders */
164 reg->sr_offset[0] = -2;
166 for ( c = reg->sr_replace; *c; c++ ) {
172 if ( n == SASLREGEX_REPLACE ) {
174 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
175 "slap_sasl_regexp_config: \"%s\" has too many $n "
176 "placeholders (max %d)\n",
177 reg->sr_replace, SASLREGEX_REPLACE ));
179 Debug( LDAP_DEBUG_ANY,
180 "SASL replace pattern %s has too many $n "
181 "placeholders (max %d)\n",
182 reg->sr_replace, SASLREGEX_REPLACE, 0 );
185 return( LDAP_OPERATIONS_ERROR );
187 reg->sr_offset[n] = c - reg->sr_replace;
192 /* Final placeholder, after the last $n */
193 reg->sr_offset[n] = c - reg->sr_replace;
195 reg->sr_offset[n] = -1;
199 return( LDAP_SUCCESS );
203 #ifdef HAVE_CYRUS_SASL
205 /* Take the passed in SASL name and attempt to convert it into an
206 LDAP URI to find the matching LDAP entry, using the pattern matching
207 strings given in the saslregexp config file directive(s) */
208 static int slap_sasl_regexp( struct berval *in, struct berval *out )
210 char *saslname = in->bv_val;
211 int i, n, len, insert;
218 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
219 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
221 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
225 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
228 /* Match the normalized SASL name to the saslregexp patterns */
229 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
230 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
231 reg->sr_strings, 0) == 0 )
235 if( i >= nSaslRegexp )
239 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
240 * replace pattern of the form "x$1y$2z". The returned string needs
241 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
245 /* Get the total length of the final URI */
249 while( reg->sr_offset[n] >= 0 ) {
250 /* Len of next section from replacement string (x,y,z above) */
251 len += reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
252 if( reg->sr_offset[n+1] < 0)
255 /* Len of string from saslname that matched next $i (b,d above) */
256 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
257 len += reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
260 out->bv_val = ch_malloc( len + 1 );
263 /* Fill in URI with replace string, replacing $i as we go */
266 while( reg->sr_offset[n] >= 0) {
267 /* Paste in next section from replacement string (x,y,z above) */
268 len = reg->sr_offset[n] - reg->sr_offset[n-1] - 2;
269 strncpy( out->bv_val+insert, reg->sr_replace + reg->sr_offset[n-1] + 2, len);
271 if( reg->sr_offset[n+1] < 0)
274 /* Paste in string from saslname that matched next $i (b,d above) */
275 i = reg->sr_replace[ reg->sr_offset[n] + 1 ] - '0';
276 len = reg->sr_strings[i].rm_eo - reg->sr_strings[i].rm_so;
277 strncpy( out->bv_val+insert, saslname + reg->sr_strings[i].rm_so, len );
283 out->bv_val[insert] = '\0';
285 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
286 "slap_sasl_regexp: converted SASL name to %s\n", out->bv_val ));
288 Debug( LDAP_DEBUG_TRACE,
289 "slap_sasl_regexp: converted SASL name to %s\n", out->bv_val, 0, 0 );
295 /* Two empty callback functions to avoid sending results */
296 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
297 ber_int_t msgid, ber_int_t err, const char *matched,
298 const char *text, BerVarray ref, const char *resoid,
299 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
303 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
304 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
309 /* This callback actually does some work...*/
310 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
311 Entry *e, AttributeName *an, int ao, LDAPControl **c)
313 struct berval *ndn = o->o_callback->sc_private;
315 /* We only want to be called once */
320 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
321 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
323 Debug( LDAP_DEBUG_TRACE,
324 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
328 ber_dupbv(ndn, &e->e_nname);
334 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
335 * return the LDAP DN to which it matches. The SASL regexp rules in the config
336 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
337 * search with scope=base), just return the URI (or its searchbase). Otherwise
338 * an internal search must be done, and if that search returns exactly one
339 * entry, return the DN of that one entry.
342 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
344 struct berval uri = {0, NULL};
345 struct berval searchbase = {0, NULL};
349 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
353 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
354 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
356 Debug( LDAP_DEBUG_TRACE,
357 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
363 /* Convert the SASL name into an LDAP URI */
364 if( !slap_sasl_regexp( saslname, &uri ) )
367 rc = slap_parseURI( &uri, &searchbase, &scope, &filter );
372 /* Massive shortcut: search scope == base */
373 if( scope == LDAP_SCOPE_BASE ) {
375 searchbase.bv_len = 0;
376 searchbase.bv_val = NULL;
380 /* Must do an internal search */
383 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
384 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
385 searchbase.bv_val, scope ));
387 Debug( LDAP_DEBUG_TRACE,
388 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
389 searchbase.bv_val, scope, 0 );
392 be = select_backend( &searchbase, 0, 1 );
393 if(( be == NULL ) || ( be->be_search == NULL))
395 suffix_alias( be, &searchbase );
397 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
398 op.o_tag = LDAP_REQ_SEARCH;
399 op.o_protocol = LDAP_VERSION3;
400 op.o_ndn = *saslname;
402 op.o_time = slap_get_time();
404 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
405 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
406 /*attrs=*/NULL, /*attrsonly=*/0 );
408 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
411 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
412 if( filter ) filter_free( filter );
413 if( uri.bv_val ) ch_free( uri.bv_val );
416 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
417 "slap_sasl2dn: Converted SASL name to %s\n",
418 dn->bv_len ? dn->bv_val : "<nothing>" ));
420 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
421 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
427 typedef struct smatch_info {
432 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
433 Entry *e, AttributeName *an, int ao, LDAPControl **c)
435 smatch_info *sm = o->o_callback->sc_private;
437 if (dn_match(sm->dn, &e->e_nname)) {
439 return -1; /* short-circuit the search */
446 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
447 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
448 * the rule must be used as an internal search for entries. If that search
449 * returns the *assertDN entry, the match is successful.
451 * The assertDN should not have the dn: prefix
455 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
457 struct berval searchbase = {0, NULL};
463 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
467 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
468 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
470 Debug( LDAP_DEBUG_TRACE,
471 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
474 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
475 if( rc != LDAP_SUCCESS )
478 /* Massive shortcut: search scope == base */
479 if( scope == LDAP_SCOPE_BASE ) {
480 rc = regcomp(®, searchbase.bv_val,
481 REG_EXTENDED|REG_ICASE|REG_NOSUB);
483 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
489 rc = LDAP_INAPPROPRIATE_AUTH;
493 /* Must run an internal search. */
496 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
497 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
498 searchbase.bv_val, scope ));
500 Debug( LDAP_DEBUG_TRACE,
501 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
502 searchbase.bv_val, scope, 0 );
505 be = select_backend( &searchbase, 0, 1 );
506 if(( be == NULL ) || ( be->be_search == NULL)) {
507 rc = LDAP_INAPPROPRIATE_AUTH;
510 suffix_alias( be, &searchbase );
516 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
517 op.o_tag = LDAP_REQ_SEARCH;
518 op.o_protocol = LDAP_VERSION3;
521 op.o_time = slap_get_time();
523 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
524 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
525 /*attrs=*/NULL, /*attrsonly=*/0 );
527 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
532 rc = LDAP_INAPPROPRIATE_AUTH;
535 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
536 if( filter ) filter_free( filter );
538 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
539 "slap_sasl_match: comparison returned %d\n", rc ));
541 Debug( LDAP_DEBUG_TRACE,
542 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
550 * This function answers the question, "Can this ID authorize to that ID?",
551 * based on authorization rules. The rules are stored in the *searchDN, in the
552 * attribute named by *attr. If any of those rules map to the *assertDN, the
553 * authorization is approved.
555 * DN's passed in should have a dn: prefix
558 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
563 AttributeDescription *ad=NULL;
567 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
568 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
569 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
571 Debug( LDAP_DEBUG_TRACE,
572 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
573 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
576 rc = slap_bv2ad( attr, &ad, &errmsg );
577 if( rc != LDAP_SUCCESS )
580 bv.bv_val = searchDN->bv_val + 3;
581 bv.bv_len = searchDN->bv_len - 3;
582 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
583 if( rc != LDAP_SUCCESS )
586 bv.bv_val = assertDN->bv_val + 3;
587 bv.bv_len = assertDN->bv_len - 3;
588 /* Check if the *assertDN matches any **vals */
589 for( i=0; vals[i].bv_val != NULL; i++ ) {
590 rc = slap_sasl_match( &vals[i], &bv, authc );
591 if ( rc == LDAP_SUCCESS )
594 rc = LDAP_INAPPROPRIATE_AUTH;
597 if( vals ) ber_bvarray_free( vals );
600 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
601 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
603 Debug( LDAP_DEBUG_TRACE,
604 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
609 #endif /* HAVE_CYRUS_SASL */
612 /* Check if a bind can SASL authorize to another identity.
613 Accepts authorization DN's with "dn:" prefix */
615 static struct berval sasl_authz_src = {
616 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
618 static struct berval sasl_authz_dst = {
619 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
621 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
623 int rc = LDAP_INAPPROPRIATE_AUTH;
625 #ifdef HAVE_CYRUS_SASL
626 /* User binding as anonymous */
627 if ( authzDN == NULL ) {
633 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
634 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
636 Debug( LDAP_DEBUG_TRACE,
637 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
640 /* If person is authorizing to self, succeed */
641 if ( dn_match( authcDN, authzDN ) ) {
646 /* Check source rules */
647 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
649 if( rc == LDAP_SUCCESS ) {
653 /* Check destination rules */
654 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
656 if( rc == LDAP_SUCCESS ) {
660 rc = LDAP_INAPPROPRIATE_AUTH;
666 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
667 "slap_sasl_authorized: return %d\n", rc ));
669 Debug( LDAP_DEBUG_TRACE,
670 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );