2 * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
3 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
6 * Copyright (c) 2000, Mark Adamson, Carnegie Mellon. All rights reserved.
7 * This software is not subject to any license of Carnegie Mellon University.
9 * Redistribution and use in source and binary forms are permitted without
10 * restriction or fee of any kind as long as this notice is preserved.
12 * The name "Carnegie Mellon" must not be used to endorse or promote
13 * products derived from this software without prior written permission.
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
26 #ifdef HAVE_CYRUS_SASL
29 #ifdef HAVE_SASL_SASL_H
30 #include <sasl/sasl.h>
37 #define SASLREGEX_REPLACE 10
39 typedef struct sasl_regexp {
40 char *sr_match; /* regexp match pattern */
41 char *sr_replace; /* regexp replace pattern */
42 regex_t sr_workspace; /* workspace for regexp engine */
43 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
44 int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
47 static int nSaslRegexp = 0;
48 static SaslRegexp_t *SaslRegexp = NULL;
50 /* What SASL proxy authorization policies are allowed? */
51 #define SASL_AUTHZ_NONE 0
52 #define SASL_AUTHZ_FROM 1
53 #define SASL_AUTHZ_TO 2
55 static int authz_policy = SASL_AUTHZ_NONE;
57 int slap_sasl_setpolicy( const char *arg )
59 int rc = LDAP_SUCCESS;
61 if ( strcasecmp( arg, "none" ) == 0 )
62 authz_policy = SASL_AUTHZ_NONE;
63 else if ( strcasecmp( arg, "from" ) == 0 )
64 authz_policy = SASL_AUTHZ_FROM;
65 else if ( strcasecmp( arg, "to" ) == 0 )
66 authz_policy = SASL_AUTHZ_TO;
67 else if ( strcasecmp( arg, "both" ) == 0 )
68 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
74 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
76 static int slap_parseURI( struct berval *uri,
77 struct berval *searchbase, int *scope, Filter **filter )
83 assert( uri != NULL && uri->bv_val != NULL );
84 searchbase->bv_val = NULL;
85 searchbase->bv_len = 0;
90 LDAP_LOG( TRANSPORT, ENTRY,
91 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
93 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
96 /* If it does not look like a URI, assume it is a DN */
97 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
98 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
99 bv.bv_val += strspn( bv.bv_val, " " );
101 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
103 rc = dnNormalize2( NULL, &bv, searchbase );
104 if( rc == LDAP_SUCCESS ) {
105 *scope = LDAP_SCOPE_BASE;
110 rc = ldap_url_parse( uri->bv_val, &ludp );
111 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
112 bv.bv_val = uri->bv_val;
116 if ( rc != LDAP_URL_SUCCESS ) {
117 return LDAP_PROTOCOL_ERROR;
120 if (( ludp->lud_host && *ludp->lud_host )
121 || ludp->lud_attrs || ludp->lud_exts )
123 /* host part should be empty */
124 /* attrs and extensions parts should be empty */
125 return LDAP_PROTOCOL_ERROR;
129 *scope = ludp->lud_scope;
131 /* Grab the filter */
132 if ( ludp->lud_filter ) {
133 *filter = str2filter( ludp->lud_filter );
134 if ( *filter == NULL ) {
135 rc = LDAP_PROTOCOL_ERROR;
140 /* Grab the searchbase */
141 bv.bv_val = ludp->lud_dn;
142 bv.bv_len = strlen( bv.bv_val );
143 rc = dnNormalize2( NULL, &bv, searchbase );
146 if( rc != LDAP_SUCCESS ) {
147 if( *filter ) filter_free( *filter );
150 ldap_free_urldesc( ludp );
154 static int slap_sasl_rx_off(char *rep, int *off)
159 /* Precompile replace pattern. Find the $<n> placeholders */
162 for ( c = rep; *c; c++ ) {
163 if ( *c == '\\' && c[1] ) {
168 if ( n == SASLREGEX_REPLACE ) {
170 LDAP_LOG( TRANSPORT, ERR,
171 "slap_sasl_rx_off: \"%s\" has too many $n "
172 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
174 Debug( LDAP_DEBUG_ANY,
175 "SASL replace pattern %s has too many $n "
176 "placeholders (max %d)\n",
177 rep, SASLREGEX_REPLACE, 0 );
180 return( LDAP_OTHER );
187 /* Final placeholder, after the last $n */
191 return( LDAP_SUCCESS );
194 int slap_sasl_regexp_config( const char *match, const char *replace )
199 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
200 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
202 reg->sr_match = ch_strdup( match );
203 reg->sr_replace = ch_strdup( replace );
205 /* Precompile matching pattern */
206 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
209 LDAP_LOG( TRANSPORT, ERR,
210 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
211 reg->sr_match, 0, 0 );
213 Debug( LDAP_DEBUG_ANY,
214 "SASL match pattern %s could not be compiled by regexp engine\n",
215 reg->sr_match, 0, 0 );
218 return( LDAP_OTHER );
221 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
222 if ( rc != LDAP_SUCCESS ) return rc;
225 return( LDAP_SUCCESS );
229 /* Perform replacement on regexp matches */
230 static void slap_sasl_rx_exp(
234 const char *saslname,
237 int i, n, len, insert;
239 /* Get the total length of the final URI */
243 while( off[n] >= 0 ) {
244 /* Len of next section from replacement string (x,y,z above) */
245 len += off[n] - off[n-1] - 2;
249 /* Len of string from saslname that matched next $i (b,d above) */
250 i = rep[ off[n] + 1 ] - '0';
251 len += str[i].rm_eo - str[i].rm_so;
254 out->bv_val = ch_malloc( len + 1 );
257 /* Fill in URI with replace string, replacing $i as we go */
260 while( off[n] >= 0) {
261 /* Paste in next section from replacement string (x,y,z above) */
262 len = off[n] - off[n-1] - 2;
263 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
268 /* Paste in string from saslname that matched next $i (b,d above) */
269 i = rep[ off[n] + 1 ] - '0';
270 len = str[i].rm_eo - str[i].rm_so;
271 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
277 out->bv_val[insert] = '\0';
280 /* Take the passed in SASL name and attempt to convert it into an
281 LDAP URI to find the matching LDAP entry, using the pattern matching
282 strings given in the saslregexp config file directive(s) */
284 static int slap_sasl_regexp( struct berval *in, struct berval *out )
286 char *saslname = in->bv_val;
287 char *scope[] = { "base", "one", "sub" };
291 memset( out, 0, sizeof( *out ) );
294 LDAP_LOG( TRANSPORT, ENTRY,
295 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
297 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
301 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
304 /* Match the normalized SASL name to the saslregexp patterns */
305 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
306 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
307 reg->sr_strings, 0) == 0 )
311 if( i >= nSaslRegexp )
315 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
316 * replace pattern of the form "x$1y$2z". The returned string needs
317 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
319 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
320 reg->sr_strings, saslname, out );
323 LDAP_LOG( TRANSPORT, ENTRY,
324 "slap_sasl_regexp: converted SASL name to %s\n",
325 out->bv_len ? out->bv_val : "", 0, 0 );
327 Debug( LDAP_DEBUG_TRACE,
328 "slap_sasl_regexp: converted SASL name to ldap:///%s??%s?%s\n",
329 out->bv_len ? out->bv_val : "", 0, 0 );
335 /* Two empty callback functions to avoid sending results */
336 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
337 ber_int_t msgid, ber_int_t err, const char *matched,
338 const char *text, BerVarray ref, const char *resoid,
339 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
343 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
344 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
349 /* This callback actually does some work...*/
350 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
351 Entry *e, AttributeName *an, int ao, LDAPControl **c)
353 struct berval *ndn = o->o_callback->sc_private;
355 /* We only want to be called once */
361 LDAP_LOG( TRANSPORT, DETAIL1,
362 "slap_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
364 Debug( LDAP_DEBUG_TRACE,
365 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
370 ber_dupbv(ndn, &e->e_nname);
375 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
376 * return the LDAP DN to which it matches. The SASL regexp rules in the config
377 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
378 * search with scope=base), just return the URI (or its searchbase). Otherwise
379 * an internal search must be done, and if that search returns exactly one
380 * entry, return the DN of that one entry.
383 void slap_sasl2dn( Connection *conn,
384 struct berval *saslname, struct berval *sasldn )
388 struct berval dn = { 0, NULL };
389 int scope = LDAP_SCOPE_BASE;
390 Filter *filter = NULL;
391 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
393 struct berval regout = { 0, NULL };
396 LDAP_LOG( TRANSPORT, ENTRY,
397 "slap_sasl2dn: converting SASL name %s to DN.\n",
398 saslname->bv_val, 0, 0 );
400 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
401 "converting SASL name %s to a DN\n",
402 saslname->bv_val, 0,0 );
405 sasldn->bv_val = NULL;
407 cb.sc_private = sasldn;
409 /* Convert the SASL name into a minimal URI */
410 if( !slap_sasl_regexp( saslname, ®out ) ) {
414 rc = slap_parseURI( ®out, &dn, &scope, &filter );
415 if( rc != LDAP_SUCCESS ) {
419 /* Must do an internal search */
420 be = select_backend( &dn, 0, 1 );
422 /* Massive shortcut: search scope == base */
423 if( scope == LDAP_SCOPE_BASE ) {
431 LDAP_LOG( TRANSPORT, DETAIL1,
432 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
433 dn.bv_val, scope, 0 );
435 Debug( LDAP_DEBUG_TRACE,
436 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
437 dn.bv_val, scope, 0 );
440 if(( be == NULL ) || ( be->be_search == NULL)) {
443 suffix_alias( be, &dn );
445 op.o_tag = LDAP_REQ_SEARCH;
446 op.o_protocol = LDAP_VERSION3;
447 op.o_ndn = *saslname;
449 op.o_time = slap_get_time();
450 op.o_do_not_cache = 1;
452 (*be->be_search)( be, conn, &op, NULL, &dn,
453 scope, LDAP_DEREF_NEVER, 1, 0,
454 filter, NULL, NULL, 1 );
457 if( sasldn->bv_len ) {
458 conn->c_authz_backend = be;
460 if( dn.bv_len ) ch_free( dn.bv_val );
461 if( filter ) filter_free( filter );
464 LDAP_LOG( TRANSPORT, ENTRY,
465 "slap_sasl2dn: Converted SASL name to %s\n",
466 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
468 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
469 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
475 typedef struct smatch_info {
480 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
481 Entry *e, AttributeName *an, int ao, LDAPControl **c)
483 smatch_info *sm = o->o_callback->sc_private;
485 if (dn_match(sm->dn, &e->e_nname)) {
487 return -1; /* short-circuit the search */
494 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
495 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
496 * the rule must be used as an internal search for entries. If that search
497 * returns the *assertDN entry, the match is successful.
499 * The assertDN should not have the dn: prefix
503 int slap_sasl_match(Connection *conn, struct berval *rule, struct berval *assertDN, struct berval *authc )
505 struct berval searchbase = {0, NULL};
511 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
515 LDAP_LOG( TRANSPORT, ENTRY,
516 "slap_sasl_match: comparing DN %s to rule %s\n",
517 assertDN->bv_val, rule->bv_val,0 );
519 Debug( LDAP_DEBUG_TRACE,
520 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
523 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
524 if( rc != LDAP_SUCCESS )
527 /* Massive shortcut: search scope == base */
528 if( scope == LDAP_SCOPE_BASE ) {
529 rc = regcomp(®, searchbase.bv_val,
530 REG_EXTENDED|REG_ICASE|REG_NOSUB);
532 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
538 rc = LDAP_INAPPROPRIATE_AUTH;
542 /* Must run an internal search. */
545 LDAP_LOG( TRANSPORT, DETAIL1,
546 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
547 searchbase.bv_val, scope,0 );
549 Debug( LDAP_DEBUG_TRACE,
550 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
551 searchbase.bv_val, scope, 0 );
554 be = select_backend( &searchbase, 0, 1 );
555 if(( be == NULL ) || ( be->be_search == NULL)) {
556 rc = LDAP_INAPPROPRIATE_AUTH;
559 suffix_alias( be, &searchbase );
565 op.o_tag = LDAP_REQ_SEARCH;
566 op.o_protocol = LDAP_VERSION3;
569 op.o_time = slap_get_time();
570 op.o_do_not_cache = 1;
572 (*be->be_search)( be, conn, &op, /*base=*/NULL, &searchbase,
573 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
574 /*attrs=*/NULL, /*attrsonly=*/0 );
579 rc = LDAP_INAPPROPRIATE_AUTH;
582 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
583 if( filter ) filter_free( filter );
585 LDAP_LOG( TRANSPORT, ENTRY,
586 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
588 Debug( LDAP_DEBUG_TRACE,
589 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
597 * This function answers the question, "Can this ID authorize to that ID?",
598 * based on authorization rules. The rules are stored in the *searchDN, in the
599 * attribute named by *attr. If any of those rules map to the *assertDN, the
600 * authorization is approved.
602 * The DNs should not have the dn: prefix
605 slap_sasl_check_authz( Connection *conn,
606 struct berval *searchDN,
607 struct berval *assertDN,
608 AttributeDescription *ad,
609 struct berval *authc )
615 LDAP_LOG( TRANSPORT, ENTRY,
616 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
617 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
619 Debug( LDAP_DEBUG_TRACE,
620 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
621 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
624 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
625 if( rc != LDAP_SUCCESS )
628 /* Check if the *assertDN matches any **vals */
629 for( i=0; vals[i].bv_val != NULL; i++ ) {
630 rc = slap_sasl_match( conn, &vals[i], assertDN, authc );
631 if ( rc == LDAP_SUCCESS )
634 rc = LDAP_INAPPROPRIATE_AUTH;
637 if( vals ) ber_bvarray_free( vals );
640 LDAP_LOG( TRANSPORT, RESULTS,
641 "slap_sasl_check_authz: %s check returning %s\n",
642 ad->ad_cname.bv_val, rc, 0 );
644 Debug( LDAP_DEBUG_TRACE,
645 "<==slap_sasl_check_authz: %s check returning %d\n", ad->ad_cname.bv_val, rc, 0);
650 #endif /* HAVE_CYRUS_SASL */
653 /* Check if a bind can SASL authorize to another identity.
654 * The DNs should not have the dn: prefix
657 int slap_sasl_authorized( Connection *conn,
658 struct berval *authcDN, struct berval *authzDN )
660 int rc = LDAP_INAPPROPRIATE_AUTH;
662 #ifdef HAVE_CYRUS_SASL
663 /* User binding as anonymous */
664 if ( authzDN == NULL ) {
670 LDAP_LOG( TRANSPORT, ENTRY,
671 "slap_sasl_authorized: can %s become %s?\n",
672 authcDN->bv_val, authzDN->bv_val, 0 );
674 Debug( LDAP_DEBUG_TRACE,
675 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
678 /* If person is authorizing to self, succeed */
679 if ( dn_match( authcDN, authzDN ) ) {
684 /* Check source rules */
685 if( authz_policy & SASL_AUTHZ_TO ) {
686 rc = slap_sasl_check_authz( conn, authcDN, authzDN,
687 slap_schema.si_ad_saslAuthzTo, authcDN );
688 if( rc == LDAP_SUCCESS ) {
693 /* Check destination rules */
694 if( authz_policy & SASL_AUTHZ_FROM ) {
695 rc = slap_sasl_check_authz( conn, authzDN, authcDN,
696 slap_schema.si_ad_saslAuthzFrom, authcDN );
697 if( rc == LDAP_SUCCESS ) {
702 rc = LDAP_INAPPROPRIATE_AUTH;
708 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
710 Debug( LDAP_DEBUG_TRACE,
711 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );