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 op.o_tag = LDAP_REQ_SEARCH;
398 op.o_protocol = LDAP_VERSION3;
399 op.o_ndn = *saslname;
401 op.o_time = slap_get_time();
403 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
404 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
405 /*attrs=*/NULL, /*attrsonly=*/0 );
408 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
409 if( filter ) filter_free( filter );
410 if( uri.bv_val ) ch_free( uri.bv_val );
413 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
414 "slap_sasl2dn: Converted SASL name to %s\n",
415 dn->bv_len ? dn->bv_val : "<nothing>" ));
417 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
418 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
424 typedef struct smatch_info {
429 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
430 Entry *e, AttributeName *an, int ao, LDAPControl **c)
432 smatch_info *sm = o->o_callback->sc_private;
434 if (dn_match(sm->dn, &e->e_nname)) {
436 return -1; /* short-circuit the search */
443 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
444 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
445 * the rule must be used as an internal search for entries. If that search
446 * returns the *assertDN entry, the match is successful.
448 * The assertDN should not have the dn: prefix
452 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
454 struct berval searchbase = {0, NULL};
460 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
464 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
465 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
467 Debug( LDAP_DEBUG_TRACE,
468 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
471 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
472 if( rc != LDAP_SUCCESS )
475 /* Massive shortcut: search scope == base */
476 if( scope == LDAP_SCOPE_BASE ) {
477 rc = regcomp(®, searchbase.bv_val,
478 REG_EXTENDED|REG_ICASE|REG_NOSUB);
480 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
486 rc = LDAP_INAPPROPRIATE_AUTH;
490 /* Must run an internal search. */
493 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
494 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
495 searchbase.bv_val, scope ));
497 Debug( LDAP_DEBUG_TRACE,
498 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
499 searchbase.bv_val, scope, 0 );
502 be = select_backend( &searchbase, 0, 1 );
503 if(( be == NULL ) || ( be->be_search == NULL)) {
504 rc = LDAP_INAPPROPRIATE_AUTH;
507 suffix_alias( be, &searchbase );
513 op.o_tag = LDAP_REQ_SEARCH;
514 op.o_protocol = LDAP_VERSION3;
517 op.o_time = slap_get_time();
519 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
520 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
521 /*attrs=*/NULL, /*attrsonly=*/0 );
526 rc = LDAP_INAPPROPRIATE_AUTH;
529 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
530 if( filter ) filter_free( filter );
532 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
533 "slap_sasl_match: comparison returned %d\n", rc ));
535 Debug( LDAP_DEBUG_TRACE,
536 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
544 * This function answers the question, "Can this ID authorize to that ID?",
545 * based on authorization rules. The rules are stored in the *searchDN, in the
546 * attribute named by *attr. If any of those rules map to the *assertDN, the
547 * authorization is approved.
549 * DN's passed in should have a dn: prefix
552 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
557 AttributeDescription *ad=NULL;
561 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
562 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
563 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
565 Debug( LDAP_DEBUG_TRACE,
566 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
567 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
570 rc = slap_bv2ad( attr, &ad, &errmsg );
571 if( rc != LDAP_SUCCESS )
574 bv.bv_val = searchDN->bv_val + 3;
575 bv.bv_len = searchDN->bv_len - 3;
576 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
577 if( rc != LDAP_SUCCESS )
580 bv.bv_val = assertDN->bv_val + 3;
581 bv.bv_len = assertDN->bv_len - 3;
582 /* Check if the *assertDN matches any **vals */
583 for( i=0; vals[i].bv_val != NULL; i++ ) {
584 rc = slap_sasl_match( &vals[i], &bv, authc );
585 if ( rc == LDAP_SUCCESS )
588 rc = LDAP_INAPPROPRIATE_AUTH;
591 if( vals ) ber_bvarray_free( vals );
594 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
595 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
597 Debug( LDAP_DEBUG_TRACE,
598 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
603 #endif /* HAVE_CYRUS_SASL */
606 /* Check if a bind can SASL authorize to another identity.
607 Accepts authorization DN's with "dn:" prefix */
609 static struct berval sasl_authz_src = {
610 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
612 static struct berval sasl_authz_dst = {
613 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
615 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
617 int rc = LDAP_INAPPROPRIATE_AUTH;
619 #ifdef HAVE_CYRUS_SASL
620 /* User binding as anonymous */
621 if ( authzDN == NULL ) {
627 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
628 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
630 Debug( LDAP_DEBUG_TRACE,
631 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
634 /* If person is authorizing to self, succeed */
635 if ( dn_match( authcDN, authzDN ) ) {
640 /* Check source rules */
641 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
643 if( rc == LDAP_SUCCESS ) {
647 /* Check destination rules */
648 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
650 if( rc == LDAP_SUCCESS ) {
654 rc = LDAP_INAPPROPRIATE_AUTH;
660 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
661 "slap_sasl_authorized: return %d\n", rc ));
663 Debug( LDAP_DEBUG_TRACE,
664 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );