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
135 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
136 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
137 reg = &( SaslRegexp[nSaslRegexp] );
138 reg->match = ch_strdup( match );
139 reg->replace = ch_strdup( replace );
140 dn_normalize( reg->match );
141 dn_normalize( reg->replace );
143 /* Precompile matching pattern */
144 rc = regcomp( ®->workspace, reg->match, REG_EXTENDED|REG_ICASE );
147 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
148 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
151 Debug( LDAP_DEBUG_ANY,
152 "SASL match pattern %s could not be compiled by regexp engine\n",
156 return( LDAP_OPERATIONS_ERROR );
159 /* Precompile replace pattern. Find the $<n> placeholders */
162 for ( c = reg->replace; *c; c++ ) {
168 if ( n == SASLREGEX_REPLACE ) {
170 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
171 "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
172 reg->replace, SASLREGEX_REPLACE ));
174 Debug( LDAP_DEBUG_ANY,
175 "SASL replace pattern %s has too many $n placeholders (max %d)\n",
176 reg->replace, SASLREGEX_REPLACE, 0 );
179 return( LDAP_OPERATIONS_ERROR );
181 reg->offset[n] = c - reg->replace;
186 /* Final placeholder, after the last $n */
187 reg->offset[n] = c - reg->replace;
193 return( LDAP_SUCCESS );
197 #ifdef HAVE_CYRUS_SASL
199 /* Take the passed in SASL name and attempt to convert it into an
200 LDAP URI to find the matching LDAP entry, using the pattern matching
201 strings given in the saslregexp config file directive(s) */
203 char *slap_sasl_regexp( char *saslname )
206 int i, n, len, insert;
210 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
211 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
213 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
217 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
220 /* Match the normalized SASL name to the saslregexp patterns */
221 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
222 if ( regexec( ®->workspace, saslname, SASLREGEX_REPLACE,
223 reg->strings, 0) == 0 )
227 if( i >= nSaslRegexp )
231 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
232 * replace pattern of the form "x$1y$2z". The returned string needs
233 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
237 /* Get the total length of the final URI */
241 while( reg->offset[n] >= 0 ) {
242 /* Len of next section from replacement string (x,y,z above) */
243 len += reg->offset[n] - reg->offset[n-1] - 2;
244 if( reg->offset[n+1] < 0)
247 /* Len of string from saslname that matched next $i (b,d above) */
248 i = reg->replace[ reg->offset[n] + 1 ] - '0';
249 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
252 uri = ch_malloc( len + 1 );
254 /* Fill in URI with replace string, replacing $i as we go */
257 while( reg->offset[n] >= 0) {
258 /* Paste in next section from replacement string (x,y,z above) */
259 len = reg->offset[n] - reg->offset[n-1] - 2;
260 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
262 if( reg->offset[n+1] < 0)
265 /* Paste in string from saslname that matched next $i (b,d above) */
266 i = reg->replace[ reg->offset[n] + 1 ] - '0';
267 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
268 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
276 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
277 "slap_sasl_regexp: converted SASL name to %s\n", uri ));
279 Debug( LDAP_DEBUG_TRACE,
280 "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
286 /* Two empty callback functions to avoid sending results */
287 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
288 ber_int_t msgid, ber_int_t err, const char *matched,
289 const char *text, BerVarray ref, const char *resoid,
290 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
294 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
295 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
300 /* This callback actually does some work...*/
301 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
302 Entry *e, AttributeName *an, int ao, LDAPControl **c)
304 struct berval *ndn = o->o_callback->sc_private;
306 /* We only want to be called once */
311 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
312 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
314 Debug( LDAP_DEBUG_TRACE,
315 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
319 ber_dupbv(ndn, &e->e_nname);
325 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
326 * return the LDAP DN to which it matches. The SASL regexp rules in the config
327 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
328 * search with scope=base), just return the URI (or its searchbase). Otherwise
329 * an internal search must be done, and if that search returns exactly one
330 * entry, return the DN of that one entry.
333 void slap_sasl2dn( struct berval *saslname, struct berval *dn )
336 struct berval searchbase = {0, NULL};
340 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
344 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
345 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
347 Debug( LDAP_DEBUG_TRACE,
348 "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
354 /* Convert the SASL name into an LDAP URI */
355 uri = slap_sasl_regexp( saslname->bv_val );
359 rc = slap_parseURI( uri, &searchbase, &scope, &filter );
364 /* Massive shortcut: search scope == base */
365 if( scope == LDAP_SCOPE_BASE ) {
367 searchbase.bv_len = 0;
368 searchbase.bv_val = NULL;
372 /* Must do an internal search */
375 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
376 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
377 searchbase.bv_val, scope ));
379 Debug( LDAP_DEBUG_TRACE,
380 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
381 searchbase.bv_val, scope, 0 );
384 be = select_backend( &searchbase, 0, 1 );
385 if(( be == NULL ) || ( be->be_search == NULL))
387 suffix_alias( be, &searchbase );
389 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
390 op.o_tag = LDAP_REQ_SEARCH;
391 op.o_protocol = LDAP_VERSION3;
392 op.o_ndn = *saslname;
394 op.o_time = slap_get_time();
396 (*be->be_search)( be, /*conn*/NULL, &op, /*base*/NULL, &searchbase,
397 scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
398 /*attrs=*/NULL, /*attrsonly=*/0 );
400 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
403 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
404 if( filter ) filter_free( filter );
405 if( uri ) ch_free( uri );
408 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
409 "slap_sasl2dn: Converted SASL name to %s\n",
410 dn->bv_len ? dn->bv_val : "<nothing>" ));
412 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
413 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
419 typedef struct smatch_info {
424 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
425 Entry *e, AttributeName *an, int ao, LDAPControl **c)
427 smatch_info *sm = o->o_callback->sc_private;
429 if (dn_match(sm->dn, &e->e_nname)) {
431 return -1; /* short-circuit the search */
438 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
439 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
440 * the rule must be used as an internal search for entries. If that search
441 * returns the *assertDN entry, the match is successful.
443 * The assertDN should not have the dn: prefix
447 int slap_sasl_match( char *rule, struct berval *assertDN, struct berval *authc )
449 struct berval searchbase = {0, NULL};
455 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_smatch, &sm};
459 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
460 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule ));
462 Debug( LDAP_DEBUG_TRACE,
463 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule, 0 );
466 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
467 if( rc != LDAP_SUCCESS )
470 /* Massive shortcut: search scope == base */
471 if( scope == LDAP_SCOPE_BASE ) {
472 rc = regcomp(®, searchbase.bv_val,
473 REG_EXTENDED|REG_ICASE|REG_NOSUB);
475 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
481 rc = LDAP_INAPPROPRIATE_AUTH;
485 /* Must run an internal search. */
488 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
489 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
490 searchbase.bv_val, scope ));
492 Debug( LDAP_DEBUG_TRACE,
493 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
494 searchbase.bv_val, scope, 0 );
497 be = select_backend( &searchbase, 0, 1 );
498 if(( be == NULL ) || ( be->be_search == NULL)) {
499 rc = LDAP_INAPPROPRIATE_AUTH;
502 suffix_alias( be, &searchbase );
507 ldap_pvt_thread_mutex_init( &op.o_abandonmutex );
508 op.o_tag = LDAP_REQ_SEARCH;
509 op.o_protocol = LDAP_VERSION3;
512 op.o_time = slap_get_time();
514 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
515 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
516 /*attrs=*/NULL, /*attrsonly=*/0 );
518 ldap_pvt_thread_mutex_destroy( &op.o_abandonmutex );
523 rc = LDAP_INAPPROPRIATE_AUTH;
526 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
527 if( filter ) filter_free( filter );
529 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
530 "slap_sasl_match: comparison returned %d\n", rc ));
532 Debug( LDAP_DEBUG_TRACE,
533 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
541 * This function answers the question, "Can this ID authorize to that ID?",
542 * based on authorization rules. The rules are stored in the *searchDN, in the
543 * attribute named by *attr. If any of those rules map to the *assertDN, the
544 * authorization is approved.
546 * DN's passed in should have a dn: prefix
549 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, struct berval *attr, struct berval *authc)
554 AttributeDescription *ad=NULL;
558 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
559 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
560 assertDN->bv_val, attr->bv_val, searchDN->bv_val ));
562 Debug( LDAP_DEBUG_TRACE,
563 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
564 assertDN->bv_val, attr->bv_val, searchDN->bv_val);
567 rc = slap_bv2ad( attr, &ad, &errmsg );
568 if( rc != LDAP_SUCCESS )
571 bv.bv_val = searchDN->bv_val + 3;
572 bv.bv_len = searchDN->bv_len - 3;
573 rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
574 if( rc != LDAP_SUCCESS )
577 bv.bv_val = assertDN->bv_val + 3;
578 bv.bv_len = assertDN->bv_len - 3;
579 /* Check if the *assertDN matches any **vals */
580 for( i=0; vals[i].bv_val != NULL; i++ ) {
581 rc = slap_sasl_match( vals[i].bv_val, &bv, authc );
582 if ( rc == LDAP_SUCCESS )
585 rc = LDAP_INAPPROPRIATE_AUTH;
588 if( vals ) ber_bvarray_free( vals );
591 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
592 "slap_sasl_check_authz: %s check returning %s\n", attr->bv_val, rc ));
594 Debug( LDAP_DEBUG_TRACE,
595 "<==slap_sasl_check_authz: %s check returning %d\n", attr->bv_val, rc, 0);
600 #endif /* HAVE_CYRUS_SASL */
603 /* Check if a bind can SASL authorize to another identity.
604 Accepts authorization DN's with "dn:" prefix */
606 static struct berval sasl_authz_src = {
607 sizeof(SASL_AUTHZ_SOURCE_ATTR)-1, SASL_AUTHZ_SOURCE_ATTR };
609 static struct berval sasl_authz_dst = {
610 sizeof(SASL_AUTHZ_DEST_ATTR)-1, SASL_AUTHZ_DEST_ATTR };
612 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
614 int rc = LDAP_INAPPROPRIATE_AUTH;
616 #ifdef HAVE_CYRUS_SASL
617 /* User binding as anonymous */
618 if ( authzDN == NULL ) {
624 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
625 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
627 Debug( LDAP_DEBUG_TRACE,
628 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
631 /* If person is authorizing to self, succeed */
632 if ( dn_match( authcDN, authzDN ) ) {
637 /* Check source rules */
638 rc = slap_sasl_check_authz( authcDN, authzDN, &sasl_authz_src,
640 if( rc == LDAP_SUCCESS ) {
644 /* Check destination rules */
645 rc = slap_sasl_check_authz( authzDN, authcDN, &sasl_authz_dst,
647 if( rc == LDAP_SUCCESS ) {
651 rc = LDAP_INAPPROPRIATE_AUTH;
657 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
658 "slap_sasl_authorized: return %d\n", rc ));
660 Debug( LDAP_DEBUG_TRACE,
661 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );