3 * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved.
4 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
7 * Copyright (c) 2000, Mark Adamson, Carnegie Mellon. All rights reserved.
8 * This software is not subject to any license of Carnegie Mellon University.
10 * Redistribution and use in source and binary forms are permitted without
11 * restriction or fee of any kind as long as this notice is preserved.
13 * The name "Carnegie Mellon" must not be used to endorse or promote
14 * products derived from this software without prior written permission.
22 #include <ac/stdlib.h>
23 #include <ac/string.h>
31 #define SASLREGEX_REPLACE 10
33 typedef struct sasl_regexp {
34 char *sr_match; /* regexp match pattern */
35 char *sr_replace; /* regexp replace pattern */
36 regex_t sr_workspace; /* workspace for regexp engine */
37 int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
40 static int nSaslRegexp = 0;
41 static SaslRegexp_t *SaslRegexp = NULL;
43 /* What SASL proxy authorization policies are allowed? */
44 #define SASL_AUTHZ_NONE 0
45 #define SASL_AUTHZ_FROM 1
46 #define SASL_AUTHZ_TO 2
48 static int authz_policy = SASL_AUTHZ_NONE;
50 int slap_sasl_setpolicy( const char *arg )
52 int rc = LDAP_SUCCESS;
54 if ( strcasecmp( arg, "none" ) == 0 ) {
55 authz_policy = SASL_AUTHZ_NONE;
56 } else if ( strcasecmp( arg, "from" ) == 0 ) {
57 authz_policy = SASL_AUTHZ_FROM;
58 } else if ( strcasecmp( arg, "to" ) == 0 ) {
59 authz_policy = SASL_AUTHZ_TO;
60 } else if ( strcasecmp( arg, "both" ) == 0 ) {
61 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
68 static int slap_parseURI( Operation *op, struct berval *uri,
69 struct berval *base, struct berval *nbase,
70 int *scope, Filter **filter, struct berval *fstr )
76 assert( uri != NULL && uri->bv_val != NULL );
87 LDAP_LOG( TRANSPORT, ENTRY,
88 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
90 Debug( LDAP_DEBUG_TRACE,
91 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
94 /* If it does not look like a URI, assume it is a DN */
95 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
96 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
97 bv.bv_val += strspn( bv.bv_val, " " );
99 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
101 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
102 if( rc == LDAP_SUCCESS ) {
103 *scope = LDAP_SCOPE_BASE;
108 rc = ldap_url_parse( uri->bv_val, &ludp );
109 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
110 bv.bv_val = uri->bv_val;
114 if ( rc != LDAP_URL_SUCCESS ) {
115 return LDAP_PROTOCOL_ERROR;
118 if (( ludp->lud_host && *ludp->lud_host )
119 || ludp->lud_attrs || ludp->lud_exts )
121 /* host part must be empty */
122 /* attrs and extensions parts must be empty */
123 rc = LDAP_PROTOCOL_ERROR;
128 *scope = ludp->lud_scope;
130 /* Grab the filter */
131 if ( ludp->lud_filter ) {
132 *filter = str2filter_x( op, ludp->lud_filter );
133 if ( *filter == NULL ) {
134 rc = LDAP_PROTOCOL_ERROR;
137 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
140 /* Grab the searchbase */
141 ber_str2bv( ludp->lud_dn, 0, 0, base );
142 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
145 if( rc != LDAP_SUCCESS ) {
146 if( *filter ) filter_free_x( op, *filter );
152 /* Don't free these, return them to caller */
153 ludp->lud_filter = NULL;
157 ldap_free_urldesc( ludp );
161 static int slap_sasl_rx_off(char *rep, int *off)
166 /* Precompile replace pattern. Find the $<n> placeholders */
169 for ( c = rep; *c; c++ ) {
170 if ( *c == '\\' && c[1] ) {
175 if ( n == SASLREGEX_REPLACE ) {
177 LDAP_LOG( TRANSPORT, ERR,
178 "slap_sasl_rx_off: \"%s\" has too many $n "
179 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
181 Debug( LDAP_DEBUG_ANY,
182 "SASL replace pattern %s has too many $n "
183 "placeholders (max %d)\n",
184 rep, SASLREGEX_REPLACE, 0 );
187 return( LDAP_OTHER );
194 /* Final placeholder, after the last $n */
198 return( LDAP_SUCCESS );
201 int slap_sasl_regexp_config( const char *match, const char *replace )
206 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
207 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
209 reg = &SaslRegexp[nSaslRegexp];
211 reg->sr_match = ch_strdup( match );
212 reg->sr_replace = ch_strdup( replace );
214 /* Precompile matching pattern */
215 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
218 LDAP_LOG( TRANSPORT, ERR,
219 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
220 reg->sr_match, 0, 0 );
222 Debug( LDAP_DEBUG_ANY,
223 "SASL match pattern %s could not be compiled by regexp engine\n",
224 reg->sr_match, 0, 0 );
227 return( LDAP_OTHER );
230 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
231 if ( rc != LDAP_SUCCESS ) return rc;
234 return( LDAP_SUCCESS );
238 /* Perform replacement on regexp matches */
239 static void slap_sasl_rx_exp(
243 const char *saslname,
247 int i, n, len, insert;
249 /* Get the total length of the final URI */
253 while( off[n] >= 0 ) {
254 /* Len of next section from replacement string (x,y,z above) */
255 len += off[n] - off[n-1] - 2;
259 /* Len of string from saslname that matched next $i (b,d above) */
260 i = rep[ off[n] + 1 ] - '0';
261 len += str[i].rm_eo - str[i].rm_so;
264 out->bv_val = sl_malloc( len + 1, ctx );
267 /* Fill in URI with replace string, replacing $i as we go */
270 while( off[n] >= 0) {
271 /* Paste in next section from replacement string (x,y,z above) */
272 len = off[n] - off[n-1] - 2;
273 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
278 /* Paste in string from saslname that matched next $i (b,d above) */
279 i = rep[ off[n] + 1 ] - '0';
280 len = str[i].rm_eo - str[i].rm_so;
281 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
287 out->bv_val[insert] = '\0';
290 /* Take the passed in SASL name and attempt to convert it into an
291 LDAP URI to find the matching LDAP entry, using the pattern matching
292 strings given in the saslregexp config file directive(s) */
294 static int slap_sasl_regexp( struct berval *in, struct berval *out, void *ctx )
296 char *saslname = in->bv_val;
298 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
301 memset( out, 0, sizeof( *out ) );
304 LDAP_LOG( TRANSPORT, ENTRY,
305 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
307 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
311 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
315 /* Match the normalized SASL name to the saslregexp patterns */
316 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
317 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
318 sr_strings, 0) == 0 )
322 if( i >= nSaslRegexp ) return( 0 );
325 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
326 * replace pattern of the form "x$1y$2z". The returned string needs
327 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
329 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
330 sr_strings, saslname, out, ctx );
333 LDAP_LOG( TRANSPORT, ENTRY,
334 "slap_sasl_regexp: converted SASL name to %s\n",
335 out->bv_len ? out->bv_val : "", 0, 0 );
337 Debug( LDAP_DEBUG_TRACE,
338 "slap_sasl_regexp: converted SASL name to %s\n",
339 out->bv_len ? out->bv_val : "", 0, 0 );
345 /* This callback actually does some work...*/
346 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
348 struct berval *ndn = o->o_callback->sc_private;
350 if (rs->sr_type != REP_SEARCH) return 0;
352 /* We only want to be called once */
354 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
358 LDAP_LOG( TRANSPORT, DETAIL1,
359 "slap_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
361 Debug( LDAP_DEBUG_TRACE,
362 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
367 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
372 typedef struct smatch_info {
377 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
379 smatch_info *sm = o->o_callback->sc_private;
381 if (rs->sr_type != REP_SEARCH) return 0;
383 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
385 return -1; /* short-circuit the search */
392 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
393 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
394 * the rule must be used as an internal search for entries. If that search
395 * returns the *assertDN entry, the match is successful.
397 * The assertDN should not have the dn: prefix
401 int slap_sasl_match( Operation *opx, struct berval *rule,
402 struct berval *assertDN, struct berval *authc )
407 slap_callback cb = { sasl_sc_smatch, NULL };
409 SlapReply rs = {REP_RESULT};
412 LDAP_LOG( TRANSPORT, ENTRY,
413 "slap_sasl_match: comparing DN %s to rule %s\n",
414 assertDN->bv_val, rule->bv_val,0 );
416 Debug( LDAP_DEBUG_TRACE,
417 "===>slap_sasl_match: comparing DN %s to rule %s\n",
418 assertDN->bv_val, rule->bv_val, 0 );
421 rc = slap_parseURI( opx, rule, &op.o_req_dn,
422 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
424 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
426 /* Massive shortcut: search scope == base */
427 if( op.oq_search.rs_scope == LDAP_SCOPE_BASE ) {
428 rc = regcomp(®, op.o_req_ndn.bv_val,
429 REG_EXTENDED|REG_ICASE|REG_NOSUB);
431 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
437 rc = LDAP_INAPPROPRIATE_AUTH;
442 /* Must run an internal search. */
445 LDAP_LOG( TRANSPORT, DETAIL1,
446 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
447 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
449 Debug( LDAP_DEBUG_TRACE,
450 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
451 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
454 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
455 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
456 rc = LDAP_INAPPROPRIATE_AUTH;
464 op.o_tag = LDAP_REQ_SEARCH;
465 op.o_protocol = LDAP_VERSION3;
468 op.o_time = slap_get_time();
469 op.o_do_not_cache = 1;
470 op.o_is_auth_check = 1;
471 op.o_threadctx = opx->o_threadctx;
472 op.o_tmpmemctx = opx->o_tmpmemctx;
473 op.o_tmpmfuncs = opx->o_tmpmfuncs;
477 op.o_conn = opx->o_conn;
478 op.o_connid = opx->o_connid;
479 op.o_req_dn = op.o_req_ndn;
481 op.o_bd->be_search( &op, &rs );
486 rc = LDAP_INAPPROPRIATE_AUTH;
490 if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
491 if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
492 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
493 if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
496 LDAP_LOG( TRANSPORT, ENTRY,
497 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
499 Debug( LDAP_DEBUG_TRACE,
500 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
508 * This function answers the question, "Can this ID authorize to that ID?",
509 * based on authorization rules. The rules are stored in the *searchDN, in the
510 * attribute named by *attr. If any of those rules map to the *assertDN, the
511 * authorization is approved.
513 * The DNs should not have the dn: prefix
516 slap_sasl_check_authz( Operation *op,
517 struct berval *searchDN,
518 struct berval *assertDN,
519 AttributeDescription *ad,
520 struct berval *authc )
526 LDAP_LOG( TRANSPORT, ENTRY,
527 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
528 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
530 Debug( LDAP_DEBUG_TRACE,
531 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
532 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
535 rc = backend_attribute( op, NULL,
536 searchDN, ad, &vals );
537 if( rc != LDAP_SUCCESS ) goto COMPLETE;
539 /* Check if the *assertDN matches any **vals */
541 for( i=0; vals[i].bv_val != NULL; i++ ) {
542 rc = slap_sasl_match( op, &vals[i], assertDN, authc );
543 if ( rc == LDAP_SUCCESS ) goto COMPLETE;
546 rc = LDAP_INAPPROPRIATE_AUTH;
549 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
552 LDAP_LOG( TRANSPORT, RESULTS,
553 "slap_sasl_check_authz: %s check returning %s\n",
554 ad->ad_cname.bv_val, rc, 0 );
556 Debug( LDAP_DEBUG_TRACE,
557 "<==slap_sasl_check_authz: %s check returning %d\n",
558 ad->ad_cname.bv_val, rc, 0);
565 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
566 * return the LDAP DN to which it matches. The SASL regexp rules in the config
567 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
568 * search with scope=base), just return the URI (or its searchbase). Otherwise
569 * an internal search must be done, and if that search returns exactly one
570 * entry, return the DN of that one entry.
572 void slap_sasl2dn( Operation *opx,
573 struct berval *saslname, struct berval *sasldn )
576 slap_callback cb = { sasl_sc_sasl2dn, NULL };
578 SlapReply rs = {REP_RESULT};
579 struct berval regout = { 0, NULL };
582 LDAP_LOG( TRANSPORT, ENTRY,
583 "slap_sasl2dn: converting SASL name %s to DN.\n",
584 saslname->bv_val, 0, 0 );
586 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
587 "converting SASL name %s to a DN\n",
588 saslname->bv_val, 0,0 );
591 sasldn->bv_val = NULL;
593 cb.sc_private = sasldn;
595 /* Convert the SASL name into a minimal URI */
596 if( !slap_sasl_regexp( saslname, ®out, opx->o_tmpmemctx ) ) {
600 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
601 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
603 if( regout.bv_val ) sl_free( regout.bv_val, opx->o_tmpmemctx );
604 if( rc != LDAP_SUCCESS ) {
608 /* Must do an internal search */
609 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
611 /* Massive shortcut: search scope == base */
612 if( op.oq_search.rs_scope == LDAP_SCOPE_BASE ) {
613 *sasldn = op.o_req_ndn;
614 op.o_req_ndn.bv_len = 0;
615 op.o_req_ndn.bv_val = NULL;
620 LDAP_LOG( TRANSPORT, DETAIL1,
621 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
622 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
624 Debug( LDAP_DEBUG_TRACE,
625 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
626 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
629 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
633 op.o_conn = opx->o_conn;
634 op.o_connid = opx->o_connid;
635 op.o_tag = LDAP_REQ_SEARCH;
636 op.o_protocol = LDAP_VERSION3;
637 op.o_ndn = opx->o_conn->c_ndn;
639 op.o_time = slap_get_time();
640 op.o_do_not_cache = 1;
641 op.o_is_auth_check = 1;
642 op.o_threadctx = opx->o_threadctx;
643 op.o_tmpmemctx = opx->o_tmpmemctx;
644 op.o_tmpmfuncs = opx->o_tmpmfuncs;
648 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
649 op.oq_search.rs_slimit = 1;
650 op.oq_search.rs_attrsonly = 1;
651 op.o_req_dn = op.o_req_ndn;
653 op.o_bd->be_search( &op, &rs );
656 if( sasldn->bv_len ) {
657 opx->o_conn->c_authz_backend = op.o_bd;
659 if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
660 if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
661 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
662 if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
665 LDAP_LOG( TRANSPORT, ENTRY,
666 "slap_sasl2dn: Converted SASL name to %s\n",
667 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
669 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
670 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
677 /* Check if a bind can SASL authorize to another identity.
678 * The DNs should not have the dn: prefix
681 int slap_sasl_authorized( Operation *op,
682 struct berval *authcDN, struct berval *authzDN )
684 int rc = LDAP_INAPPROPRIATE_AUTH;
686 /* User binding as anonymous */
687 if ( authzDN == NULL ) {
693 LDAP_LOG( TRANSPORT, ENTRY,
694 "slap_sasl_authorized: can %s become %s?\n",
695 authcDN->bv_val, authzDN->bv_val, 0 );
697 Debug( LDAP_DEBUG_TRACE,
698 "==>slap_sasl_authorized: can %s become %s?\n",
699 authcDN->bv_val, authzDN->bv_val, 0 );
702 /* If person is authorizing to self, succeed */
703 if ( dn_match( authcDN, authzDN ) ) {
708 /* Allow the manager to authorize as any DN. */
709 if( op->o_conn->c_authz_backend && be_isroot( op->o_conn->c_authz_backend, authcDN )) {
714 /* Check source rules */
715 if( authz_policy & SASL_AUTHZ_TO ) {
716 rc = slap_sasl_check_authz( op, authcDN, authzDN,
717 slap_schema.si_ad_saslAuthzTo, authcDN );
718 if( rc == LDAP_SUCCESS ) {
723 /* Check destination rules */
724 if( authz_policy & SASL_AUTHZ_FROM ) {
725 rc = slap_sasl_check_authz( op, authzDN, authcDN,
726 slap_schema.si_ad_saslAuthzFrom, authcDN );
727 if( rc == LDAP_SUCCESS ) {
732 rc = LDAP_INAPPROPRIATE_AUTH;
737 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
739 Debug( LDAP_DEBUG_TRACE,
740 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );