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++ ) {
167 if ( *c == '\\' && c[1] ) {
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 /* FIXME: move this check to after select_backend, and set
373 * the selected backend in conn->c_authz_backend, to allow
374 * passwd_extop to be used on in-directory SASL secrets.
375 * ... when all of that gets implemented...
377 /* Massive shortcut: search scope == base */
378 if( scope == LDAP_SCOPE_BASE ) {
380 searchbase.bv_len = 0;
381 searchbase.bv_val = NULL;
385 /* Must do an internal search */
388 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
389 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
390 searchbase.bv_val, scope ));
392 Debug( LDAP_DEBUG_TRACE,
393 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
394 searchbase.bv_val, scope, 0 );
397 be = select_backend( &searchbase, 0, 1 );
398 if(( be == NULL ) || ( be->be_search == NULL))
400 suffix_alias( be, &searchbase );
402 op.o_tag = LDAP_REQ_SEARCH;
403 op.o_protocol = LDAP_VERSION3;
404 op.o_ndn = *saslname;
406 op.o_time = slap_get_time();
408 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
409 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
410 /*attrs=*/NULL, /*attrsonly=*/0 );
413 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
414 if( filter ) filter_free( filter );
415 if( uri.bv_val ) ch_free( uri.bv_val );
418 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
419 "slap_sasl2dn: Converted SASL name to %s\n",
420 dn->bv_len ? dn->bv_val : "<nothing>" ));
422 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
423 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
429 typedef struct smatch_info {
434 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
435 Entry *e, AttributeName *an, int ao, LDAPControl **c)
437 smatch_info *sm = o->o_callback->sc_private;
439 if (dn_match(sm->dn, &e->e_nname)) {
441 return -1; /* short-circuit the search */
448 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
449 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
450 * the rule must be used as an internal search for entries. If that search
451 * returns the *assertDN entry, the match is successful.
453 * The assertDN should not have the dn: prefix
457 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
459 struct berval searchbase = {0, NULL};
465 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
469 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
470 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
472 Debug( LDAP_DEBUG_TRACE,
473 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
476 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
477 if( rc != LDAP_SUCCESS )
480 /* Massive shortcut: search scope == base */
481 if( scope == LDAP_SCOPE_BASE ) {
482 rc = regcomp(®, searchbase.bv_val,
483 REG_EXTENDED|REG_ICASE|REG_NOSUB);
485 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
491 rc = LDAP_INAPPROPRIATE_AUTH;
495 /* Must run an internal search. */
498 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
499 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
500 searchbase.bv_val, scope ));
502 Debug( LDAP_DEBUG_TRACE,
503 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
504 searchbase.bv_val, scope, 0 );
507 be = select_backend( &searchbase, 0, 1 );
508 if(( be == NULL ) || ( be->be_search == NULL)) {
509 rc = LDAP_INAPPROPRIATE_AUTH;
512 suffix_alias( be, &searchbase );
518 op.o_tag = LDAP_REQ_SEARCH;
519 op.o_protocol = LDAP_VERSION3;
522 op.o_time = slap_get_time();
524 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
525 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
526 /*attrs=*/NULL, /*attrsonly=*/0 );
531 rc = LDAP_INAPPROPRIATE_AUTH;
534 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
535 if( filter ) filter_free( filter );
537 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
538 "slap_sasl_match: comparison returned %d\n", rc ));
540 Debug( LDAP_DEBUG_TRACE,
541 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
549 * This function answers the question, "Can this ID authorize to that ID?",
550 * based on authorization rules. The rules are stored in the *searchDN, in the
551 * attribute named by *attr. If any of those rules map to the *assertDN, the
552 * authorization is approved.
554 * The DNs should not have the dn: prefix
557 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
562 AttributeDescription *ad=NULL;
565 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
566 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
567 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
569 Debug( LDAP_DEBUG_TRACE,
570 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
571 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
574 rc = slap_bv2ad( attr, &ad, &errmsg );
575 if( rc != LDAP_SUCCESS )
578 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
579 if( rc != LDAP_SUCCESS )
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], assertDN, 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 * The DNs should not have the dn: prefix
610 static struct berval sasl_authz_src = {
611 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
613 static struct berval sasl_authz_dst = {
614 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
616 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
618 int rc = LDAP_INAPPROPRIATE_AUTH;
620 #ifdef HAVE_CYRUS_SASL
621 /* User binding as anonymous */
622 if ( authzDN == NULL ) {
628 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
629 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
631 Debug( LDAP_DEBUG_TRACE,
632 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
635 /* If person is authorizing to self, succeed */
636 if ( dn_match( authcDN, authzDN ) ) {
641 /* Check source rules */
642 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
644 if( rc == LDAP_SUCCESS ) {
648 /* Check destination rules */
649 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
651 if( rc == LDAP_SUCCESS ) {
655 rc = LDAP_INAPPROPRIATE_AUTH;
661 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
662 "slap_sasl_authorized: return %d\n", rc ));
664 Debug( LDAP_DEBUG_TRACE,
665 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );