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>
33 #define SASLREGEX_REPLACE 10
35 typedef struct sasl_uri {
41 typedef struct sasl_regexp {
42 char *sr_match; /* regexp match pattern */
43 SaslUri_t sr_replace; /* regexp replace pattern */
44 regex_t sr_workspace; /* workspace for regexp engine */
45 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
46 int sr_dn_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
47 int sr_fi_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
50 static int nSaslRegexp = 0;
51 static SaslRegexp_t *SaslRegexp = NULL;
53 /* What SASL proxy authorization policies are allowed? */
54 #define SASL_AUTHZ_NONE 0
55 #define SASL_AUTHZ_FROM 1
56 #define SASL_AUTHZ_TO 2
58 static int authz_policy = SASL_AUTHZ_NONE;
60 int slap_sasl_setpolicy( const char *arg )
62 int rc = LDAP_SUCCESS;
64 if ( strcasecmp( arg, "none" ) == 0 )
65 authz_policy = SASL_AUTHZ_NONE;
66 else if ( strcasecmp( arg, "from" ) == 0 )
67 authz_policy = SASL_AUTHZ_FROM;
68 else if ( strcasecmp( arg, "to" ) == 0 )
69 authz_policy = SASL_AUTHZ_TO;
70 else if ( strcasecmp( arg, "both" ) == 0 )
71 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
77 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
79 static int slap_parseURI( struct berval *uri,
80 struct berval *searchbase, int *scope, Filter **filter,
87 assert( uri != NULL && uri->bv_val != NULL );
88 searchbase->bv_val = NULL;
89 searchbase->bv_len = 0;
99 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
100 "slap_parseURI: parsing %s\n", uri->bv_val ));
102 Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
105 /* If it does not look like a URI, assume it is a DN */
106 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
107 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
108 bv.bv_val += strspn( bv.bv_val, " " );
110 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
111 rc = dnNormalize2( NULL, &bv, searchbase );
112 if (rc == LDAP_SUCCESS) {
113 *scope = LDAP_SCOPE_BASE;
118 rc = ldap_url_parse( uri->bv_val, &ludp );
119 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
120 bv.bv_val = uri->bv_val;
124 if ( rc != LDAP_URL_SUCCESS ) {
125 return( LDAP_PROTOCOL_ERROR );
128 if ( ludp->lud_host && *ludp->lud_host ) {
129 /* host part should be empty */
130 return( LDAP_PROTOCOL_ERROR );
134 *scope = ludp->lud_scope;
136 /* Grab the filter */
137 if ( ludp->lud_filter ) {
138 *filter = str2filter( ludp->lud_filter );
139 if ( *filter == NULL )
140 rc = LDAP_PROTOCOL_ERROR;
142 ber_str2bv( ludp->lud_filter, 0, 1, fstr );
145 /* Grab the searchbase */
146 if ( rc == LDAP_URL_SUCCESS ) {
147 bv.bv_val = ludp->lud_dn;
148 bv.bv_len = strlen( bv.bv_val );
149 rc = dnNormalize2( NULL, &bv, searchbase );
152 ldap_free_urldesc( ludp );
157 static int slap_sasl_rx_off(char *rep, int *off)
162 /* Precompile replace pattern. Find the $<n> placeholders */
165 for ( c = rep; *c; c++ ) {
166 if ( *c == '\\' && c[1] ) {
171 if ( n == SASLREGEX_REPLACE ) {
173 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
174 "slap_sasl_regexp_config: \"%s\" has too many $n "
175 "placeholders (max %d)\n",
176 rep, SASLREGEX_REPLACE ));
178 Debug( LDAP_DEBUG_ANY,
179 "SASL replace pattern %s has too many $n "
180 "placeholders (max %d)\n",
181 rep, SASLREGEX_REPLACE, 0 );
184 return( LDAP_OPERATIONS_ERROR );
191 /* Final placeholder, after the last $n */
195 return( LDAP_SUCCESS );
197 #endif /* HAVE_CYRUS_SASL */
199 int slap_sasl_regexp_config( const char *match, const char *replace )
201 #ifdef HAVE_CYRUS_SASL
205 struct berval bv, nbv;
208 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
209 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
210 reg = &( SaslRegexp[nSaslRegexp] );
211 ber_str2bv( match, 0, 0, &bv );
212 rc = dnNormalize2( NULL, &bv, &nbv );
215 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
216 "slap_sasl_regexp_config: \"%s\" could not be normalized.\n",
219 Debug( LDAP_DEBUG_ANY,
220 "SASL match pattern %s could not be normalized.\n",
225 reg->sr_match = nbv.bv_val;
227 ber_str2bv( replace, 0, 0, &bv );
228 rc = slap_parseURI( &bv, ®->sr_replace.dn, ®->sr_replace.scope,
229 &filter, ®->sr_replace.filter );
230 if ( filter ) filter_free( filter );
233 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
234 "slap_sasl_regexp_config: \"%s\" could not be parsed.\n",
237 Debug( LDAP_DEBUG_ANY,
238 "SASL replace pattern %s could not be parsed.\n",
244 /* Precompile matching pattern */
245 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
248 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
249 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
252 Debug( LDAP_DEBUG_ANY,
253 "SASL match pattern %s could not be compiled by regexp engine\n",
254 reg->sr_match, 0, 0 );
257 return( LDAP_OPERATIONS_ERROR );
260 rc = slap_sasl_rx_off( reg->sr_replace.dn.bv_val, reg->sr_dn_offset );
261 if ( rc != LDAP_SUCCESS ) return rc;
263 if (reg->sr_replace.filter.bv_val ) {
264 rc = slap_sasl_rx_off( reg->sr_replace.filter.bv_val, reg->sr_fi_offset );
265 if ( rc != LDAP_SUCCESS ) return rc;
270 return( LDAP_SUCCESS );
274 #ifdef HAVE_CYRUS_SASL
276 /* Perform replacement on regexp matches */
277 static void slap_sasl_rx_exp( char *rep, int *off, regmatch_t *str,
278 char *saslname, struct berval *out )
280 int i, n, len, insert;
282 /* Get the total length of the final URI */
286 while( off[n] >= 0 ) {
287 /* Len of next section from replacement string (x,y,z above) */
288 len += off[n] - off[n-1] - 2;
292 /* Len of string from saslname that matched next $i (b,d above) */
293 i = rep[ off[n] + 1 ] - '0';
294 len += str[i].rm_eo - str[i].rm_so;
297 out->bv_val = ch_malloc( len + 1 );
300 /* Fill in URI with replace string, replacing $i as we go */
303 while( off[n] >= 0) {
304 /* Paste in next section from replacement string (x,y,z above) */
305 len = off[n] - off[n-1] - 2;
306 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
311 /* Paste in string from saslname that matched next $i (b,d above) */
312 i = rep[ off[n] + 1 ] - '0';
313 len = str[i].rm_eo - str[i].rm_so;
314 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
320 out->bv_val[insert] = '\0';
323 /* Take the passed in SASL name and attempt to convert it into an
324 LDAP URI to find the matching LDAP entry, using the pattern matching
325 strings given in the saslregexp config file directive(s) */
327 static int slap_sasl_regexp( struct berval *in, SaslUri_t *out )
329 char *saslname = in->bv_val;
330 char *scope[] = { "base", "one", "sub" };
334 memset( out, 0, sizeof( *out ) );
337 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
338 "slap_sasl_regexp: converting SASL name %s\n", saslname ));
340 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
344 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
347 /* Match the normalized SASL name to the saslregexp patterns */
348 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
349 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
350 reg->sr_strings, 0) == 0 )
354 if( i >= nSaslRegexp )
358 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
359 * replace pattern of the form "x$1y$2z". The returned string needs
360 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
362 slap_sasl_rx_exp( reg->sr_replace.dn.bv_val, reg->sr_dn_offset,
363 reg->sr_strings, saslname, &out->dn );
365 if ( reg->sr_replace.filter.bv_val )
366 slap_sasl_rx_exp( reg->sr_replace.filter.bv_val,
367 reg->sr_fi_offset, reg->sr_strings, saslname, &out->filter );
369 out->scope = reg->sr_replace.scope;
372 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
373 "slap_sasl_regexp: converted SASL name to ldap:///%s??%s?%s\n",
374 out->dn.bv_val, scope[out->scope], out->filter.bv_val ?
375 out->filter.bv_val : "" ));
377 Debug( LDAP_DEBUG_TRACE,
378 "slap_sasl_regexp: converted SASL name to ldap:///%s??%s?%s\n",
379 out->dn.bv_val, scope[out->scope], out->filter.bv_val ?
380 out->filter.bv_val : "" );
386 /* Two empty callback functions to avoid sending results */
387 static void sasl_sc_r( Connection *conn, Operation *o, ber_tag_t tag,
388 ber_int_t msgid, ber_int_t err, const char *matched,
389 const char *text, BerVarray ref, const char *resoid,
390 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
394 static void sasl_sc_s( Connection *conn, Operation *o, ber_int_t err,
395 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
400 /* This callback actually does some work...*/
401 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
402 Entry *e, AttributeName *an, int ao, LDAPControl **c)
404 struct berval *ndn = o->o_callback->sc_private;
406 /* We only want to be called once */
412 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
413 "slap_sasl2dn: search DN returned more than 1 entry\n" ));
415 Debug( LDAP_DEBUG_TRACE,
416 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
421 ber_dupbv(ndn, &e->e_nname);
426 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
427 * return the LDAP DN to which it matches. The SASL regexp rules in the config
428 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
429 * search with scope=base), just return the URI (or its searchbase). Otherwise
430 * an internal search must be done, and if that search returns exactly one
431 * entry, return the DN of that one entry.
434 void slap_sasl2dn( Connection *conn, struct berval *saslname, struct berval *dn )
439 slap_callback cb = {sasl_sc_r, sasl_sc_s, sasl_sc_sasl2dn, NULL};
444 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
445 "slap_sasl2dn: converting SASL name %s to DN.\n", saslname->bv_val ));
447 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
448 "converting SASL name %s to a DN\n", saslname->bv_val, 0,0 );
455 /* Convert the SASL name into a minimal URI */
456 if( !slap_sasl_regexp( saslname, &uri ) )
459 if ( uri.filter.bv_val )
460 filter = str2filter( uri.filter.bv_val );
462 /* Must do an internal search */
464 be = select_backend( &uri.dn, 0, 1 );
466 /* Massive shortcut: search scope == base */
467 if( uri.scope == LDAP_SCOPE_BASE ) {
470 uri.dn.bv_val = NULL;
475 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
476 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
477 uri.dn.bv_val, uri.scope ));
479 Debug( LDAP_DEBUG_TRACE,
480 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
481 uri.dn.bv_val, uri.scope, 0 );
484 if(( be == NULL ) || ( be->be_search == NULL)) {
487 suffix_alias( be, &uri.dn );
489 op.o_tag = LDAP_REQ_SEARCH;
490 op.o_protocol = LDAP_VERSION3;
491 op.o_ndn = *saslname;
493 op.o_time = slap_get_time();
495 (*be->be_search)( be, NULL, &op, NULL, &uri.dn,
496 uri.scope, LDAP_DEREF_NEVER, 1, 0,
497 filter, NULL, NULL, 1 );
500 conn->c_authz_backend = be;
504 if( uri.dn.bv_len ) ch_free( uri.dn.bv_val );
505 if( uri.filter.bv_len ) ch_free( uri.filter.bv_val );
506 if( filter ) filter_free( filter );
509 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
510 "slap_sasl2dn: Converted SASL name to %s\n",
511 dn->bv_len ? dn->bv_val : "<nothing>" ));
513 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
514 dn->bv_len ? dn->bv_val : "<nothing>", 0, 0 );
520 typedef struct smatch_info {
525 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
526 Entry *e, AttributeName *an, int ao, LDAPControl **c)
528 smatch_info *sm = o->o_callback->sc_private;
530 if (dn_match(sm->dn, &e->e_nname)) {
532 return -1; /* short-circuit the search */
539 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
540 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
541 * the rule must be used as an internal search for entries. If that search
542 * returns the *assertDN entry, the match is successful.
544 * The assertDN should not have the dn: prefix
548 int slap_sasl_match( struct berval *rule, struct berval *assertDN, struct berval *authc )
550 struct berval searchbase = {0, NULL};
556 slap_callback cb = { sasl_sc_r, sasl_sc_s, sasl_sc_smatch, NULL };
560 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
561 "slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val ));
563 Debug( LDAP_DEBUG_TRACE,
564 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
567 rc = slap_parseURI( rule, &searchbase, &scope, &filter, NULL );
568 if( rc != LDAP_SUCCESS )
571 /* Massive shortcut: search scope == base */
572 if( scope == LDAP_SCOPE_BASE ) {
573 rc = regcomp(®, searchbase.bv_val,
574 REG_EXTENDED|REG_ICASE|REG_NOSUB);
576 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
582 rc = LDAP_INAPPROPRIATE_AUTH;
586 /* Must run an internal search. */
589 LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
590 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
591 searchbase.bv_val, scope ));
593 Debug( LDAP_DEBUG_TRACE,
594 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
595 searchbase.bv_val, scope, 0 );
598 be = select_backend( &searchbase, 0, 1 );
599 if(( be == NULL ) || ( be->be_search == NULL)) {
600 rc = LDAP_INAPPROPRIATE_AUTH;
603 suffix_alias( be, &searchbase );
609 op.o_tag = LDAP_REQ_SEARCH;
610 op.o_protocol = LDAP_VERSION3;
613 op.o_time = slap_get_time();
615 (*be->be_search)( be, /*conn=*/NULL, &op, /*base=*/NULL, &searchbase,
616 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
617 /*attrs=*/NULL, /*attrsonly=*/0 );
622 rc = LDAP_INAPPROPRIATE_AUTH;
625 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
626 if( filter ) filter_free( filter );
628 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
629 "slap_sasl_match: comparison returned %d\n", rc ));
631 Debug( LDAP_DEBUG_TRACE,
632 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
640 * This function answers the question, "Can this ID authorize to that ID?",
641 * based on authorization rules. The rules are stored in the *searchDN, in the
642 * attribute named by *attr. If any of those rules map to the *assertDN, the
643 * authorization is approved.
645 * The DNs should not have the dn: prefix
648 slap_sasl_check_authz(struct berval *searchDN, struct berval *assertDN, AttributeDescription *ad, struct berval *authc)
654 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
655 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
656 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val ));
658 Debug( LDAP_DEBUG_TRACE,
659 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
660 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
663 rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN, ad, &vals );
664 if( rc != LDAP_SUCCESS )
667 /* Check if the *assertDN matches any **vals */
668 for( i=0; vals[i].bv_val != NULL; i++ ) {
669 rc = slap_sasl_match( &vals[i], assertDN, authc );
670 if ( rc == LDAP_SUCCESS )
673 rc = LDAP_INAPPROPRIATE_AUTH;
676 if( vals ) ber_bvarray_free( vals );
679 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
680 "slap_sasl_check_authz: %s check returning %s\n", ad->ad_cname.bv_val, rc ));
682 Debug( LDAP_DEBUG_TRACE,
683 "<==slap_sasl_check_authz: %s check returning %d\n", ad->ad_cname.bv_val, rc, 0);
688 #endif /* HAVE_CYRUS_SASL */
691 /* Check if a bind can SASL authorize to another identity.
692 * The DNs should not have the dn: prefix
695 int slap_sasl_authorized( struct berval *authcDN, struct berval *authzDN )
697 int rc = LDAP_INAPPROPRIATE_AUTH;
699 #ifdef HAVE_CYRUS_SASL
700 /* User binding as anonymous */
701 if ( authzDN == NULL ) {
707 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
708 "slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val ));
710 Debug( LDAP_DEBUG_TRACE,
711 "==>slap_sasl_authorized: can %s become %s?\n", authcDN->bv_val, authzDN->bv_val, 0 );
714 /* If person is authorizing to self, succeed */
715 if ( dn_match( authcDN, authzDN ) ) {
720 /* Check source rules */
721 if( authz_policy & SASL_AUTHZ_TO ) {
722 rc = slap_sasl_check_authz( authcDN, authzDN,
723 slap_schema.si_ad_saslAuthzTo, authcDN );
724 if( rc == LDAP_SUCCESS ) {
729 /* Check destination rules */
730 if( authz_policy & SASL_AUTHZ_FROM ) {
731 rc = slap_sasl_check_authz( authzDN, authcDN,
732 slap_schema.si_ad_saslAuthzFrom, authcDN );
733 if( rc == LDAP_SUCCESS ) {
738 rc = LDAP_INAPPROPRIATE_AUTH;
744 LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
745 "slap_sasl_authorized: return %d\n", rc ));
747 Debug( LDAP_DEBUG_TRACE,
748 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );