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 *searchbase, int *scope, Filter **filter )
75 assert( uri != NULL && uri->bv_val != NULL );
76 searchbase->bv_val = NULL;
77 searchbase->bv_len = 0;
82 LDAP_LOG( TRANSPORT, ENTRY,
83 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
85 Debug( LDAP_DEBUG_TRACE,
86 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
89 /* If it does not look like a URI, assume it is a DN */
90 if( !strncasecmp( uri->bv_val, "dn:", sizeof("dn:")-1 ) ) {
91 bv.bv_val = uri->bv_val + sizeof("dn:")-1;
92 bv.bv_val += strspn( bv.bv_val, " " );
94 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
96 rc = dnNormalize( 0, NULL, NULL, &bv, searchbase, op->o_tmpmemctx );
97 if( rc == LDAP_SUCCESS ) {
98 *scope = LDAP_SCOPE_BASE;
103 rc = ldap_url_parse( uri->bv_val, &ludp );
104 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
105 bv.bv_val = uri->bv_val;
109 if ( rc != LDAP_URL_SUCCESS ) {
110 return LDAP_PROTOCOL_ERROR;
113 if (( ludp->lud_host && *ludp->lud_host )
114 || ludp->lud_attrs || ludp->lud_exts )
116 /* host part must be empty */
117 /* attrs and extensions parts must be empty */
118 return LDAP_PROTOCOL_ERROR;
122 *scope = ludp->lud_scope;
124 /* Grab the filter */
125 if ( ludp->lud_filter ) {
126 *filter = str2filter_x( op, ludp->lud_filter );
127 if ( *filter == NULL ) {
128 rc = LDAP_PROTOCOL_ERROR;
133 /* Grab the searchbase */
134 bv.bv_val = ludp->lud_dn;
135 bv.bv_len = strlen( bv.bv_val );
136 rc = dnNormalize( 0, NULL, NULL, &bv, searchbase, op->o_tmpmemctx );
139 if( rc != LDAP_SUCCESS ) {
140 if( *filter ) filter_free_x( op, *filter );
143 ldap_free_urldesc( ludp );
147 static int slap_sasl_rx_off(char *rep, int *off)
152 /* Precompile replace pattern. Find the $<n> placeholders */
155 for ( c = rep; *c; c++ ) {
156 if ( *c == '\\' && c[1] ) {
161 if ( n == SASLREGEX_REPLACE ) {
163 LDAP_LOG( TRANSPORT, ERR,
164 "slap_sasl_rx_off: \"%s\" has too many $n "
165 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
167 Debug( LDAP_DEBUG_ANY,
168 "SASL replace pattern %s has too many $n "
169 "placeholders (max %d)\n",
170 rep, SASLREGEX_REPLACE, 0 );
173 return( LDAP_OTHER );
180 /* Final placeholder, after the last $n */
184 return( LDAP_SUCCESS );
187 int slap_sasl_regexp_config( const char *match, const char *replace )
192 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
193 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
195 reg = &SaslRegexp[nSaslRegexp];
197 reg->sr_match = ch_strdup( match );
198 reg->sr_replace = ch_strdup( replace );
200 /* Precompile matching pattern */
201 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
204 LDAP_LOG( TRANSPORT, ERR,
205 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
206 reg->sr_match, 0, 0 );
208 Debug( LDAP_DEBUG_ANY,
209 "SASL match pattern %s could not be compiled by regexp engine\n",
210 reg->sr_match, 0, 0 );
213 return( LDAP_OTHER );
216 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
217 if ( rc != LDAP_SUCCESS ) return rc;
220 return( LDAP_SUCCESS );
224 /* Perform replacement on regexp matches */
225 static void slap_sasl_rx_exp(
229 const char *saslname,
233 int i, n, len, insert;
235 /* Get the total length of the final URI */
239 while( off[n] >= 0 ) {
240 /* Len of next section from replacement string (x,y,z above) */
241 len += off[n] - off[n-1] - 2;
245 /* Len of string from saslname that matched next $i (b,d above) */
246 i = rep[ off[n] + 1 ] - '0';
247 len += str[i].rm_eo - str[i].rm_so;
250 out->bv_val = sl_malloc( len + 1, ctx );
253 /* Fill in URI with replace string, replacing $i as we go */
256 while( off[n] >= 0) {
257 /* Paste in next section from replacement string (x,y,z above) */
258 len = off[n] - off[n-1] - 2;
259 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
264 /* Paste in string from saslname that matched next $i (b,d above) */
265 i = rep[ off[n] + 1 ] - '0';
266 len = str[i].rm_eo - str[i].rm_so;
267 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
273 out->bv_val[insert] = '\0';
276 /* Take the passed in SASL name and attempt to convert it into an
277 LDAP URI to find the matching LDAP entry, using the pattern matching
278 strings given in the saslregexp config file directive(s) */
280 static int slap_sasl_regexp( struct berval *in, struct berval *out, void *ctx )
282 char *saslname = in->bv_val;
284 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
287 memset( out, 0, sizeof( *out ) );
290 LDAP_LOG( TRANSPORT, ENTRY,
291 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
293 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
297 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
301 /* Match the normalized SASL name to the saslregexp patterns */
302 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
303 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
304 sr_strings, 0) == 0 )
308 if( i >= nSaslRegexp ) return( 0 );
311 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
312 * replace pattern of the form "x$1y$2z". The returned string needs
313 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
315 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
316 sr_strings, saslname, out, ctx );
319 LDAP_LOG( TRANSPORT, ENTRY,
320 "slap_sasl_regexp: converted SASL name to %s\n",
321 out->bv_len ? out->bv_val : "", 0, 0 );
323 Debug( LDAP_DEBUG_TRACE,
324 "slap_sasl_regexp: converted SASL name to %s\n",
325 out->bv_len ? out->bv_val : "", 0, 0 );
331 /* This callback actually does some work...*/
332 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
334 struct berval *ndn = o->o_callback->sc_private;
336 if (rs->sr_type != REP_SEARCH) return 0;
338 /* We only want to be called once */
340 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
344 LDAP_LOG( TRANSPORT, DETAIL1,
345 "slap_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
347 Debug( LDAP_DEBUG_TRACE,
348 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
353 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
358 typedef struct smatch_info {
363 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
365 smatch_info *sm = o->o_callback->sc_private;
367 if (rs->sr_type != REP_SEARCH) return 0;
369 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
371 return -1; /* short-circuit the search */
378 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
379 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
380 * the rule must be used as an internal search for entries. If that search
381 * returns the *assertDN entry, the match is successful.
383 * The assertDN should not have the dn: prefix
387 int slap_sasl_match( Operation *opx, struct berval *rule,
388 struct berval *assertDN, struct berval *authc )
393 slap_callback cb = { sasl_sc_smatch, NULL };
395 SlapReply rs = {REP_RESULT};
398 LDAP_LOG( TRANSPORT, ENTRY,
399 "slap_sasl_match: comparing DN %s to rule %s\n",
400 assertDN->bv_val, rule->bv_val,0 );
402 Debug( LDAP_DEBUG_TRACE,
403 "===>slap_sasl_match: comparing DN %s to rule %s\n",
404 assertDN->bv_val, rule->bv_val, 0 );
407 rc = slap_parseURI( opx, rule,
408 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter );
409 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
411 /* Massive shortcut: search scope == base */
412 if( op.oq_search.rs_scope == LDAP_SCOPE_BASE ) {
413 rc = regcomp(®, op.o_req_ndn.bv_val,
414 REG_EXTENDED|REG_ICASE|REG_NOSUB);
416 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
422 rc = LDAP_INAPPROPRIATE_AUTH;
427 /* Must run an internal search. */
430 LDAP_LOG( TRANSPORT, DETAIL1,
431 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
432 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
434 Debug( LDAP_DEBUG_TRACE,
435 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
436 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
439 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
440 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
441 rc = LDAP_INAPPROPRIATE_AUTH;
449 op.o_tag = LDAP_REQ_SEARCH;
450 op.o_protocol = LDAP_VERSION3;
453 op.o_time = slap_get_time();
454 op.o_do_not_cache = 1;
455 op.o_is_auth_check = 1;
456 op.o_threadctx = opx->o_threadctx;
457 op.o_tmpmemctx = opx->o_tmpmemctx;
458 op.o_tmpmfuncs = opx->o_tmpmfuncs;
462 op.o_conn = opx->o_conn;
463 op.o_connid = opx->o_connid;
465 op.o_bd->be_search( &op, &rs );
470 rc = LDAP_INAPPROPRIATE_AUTH;
474 if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
475 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
478 LDAP_LOG( TRANSPORT, ENTRY,
479 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
481 Debug( LDAP_DEBUG_TRACE,
482 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
490 * This function answers the question, "Can this ID authorize to that ID?",
491 * based on authorization rules. The rules are stored in the *searchDN, in the
492 * attribute named by *attr. If any of those rules map to the *assertDN, the
493 * authorization is approved.
495 * The DNs should not have the dn: prefix
498 slap_sasl_check_authz( Operation *op,
499 struct berval *searchDN,
500 struct berval *assertDN,
501 AttributeDescription *ad,
502 struct berval *authc )
508 LDAP_LOG( TRANSPORT, ENTRY,
509 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
510 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
512 Debug( LDAP_DEBUG_TRACE,
513 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
514 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
517 rc = backend_attribute( op, NULL,
518 searchDN, ad, &vals );
519 if( rc != LDAP_SUCCESS ) goto COMPLETE;
521 /* Check if the *assertDN matches any **vals */
523 for( i=0; vals[i].bv_val != NULL; i++ ) {
524 rc = slap_sasl_match( op, &vals[i], assertDN, authc );
525 if ( rc == LDAP_SUCCESS ) goto COMPLETE;
528 rc = LDAP_INAPPROPRIATE_AUTH;
531 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
534 LDAP_LOG( TRANSPORT, RESULTS,
535 "slap_sasl_check_authz: %s check returning %s\n",
536 ad->ad_cname.bv_val, rc, 0 );
538 Debug( LDAP_DEBUG_TRACE,
539 "<==slap_sasl_check_authz: %s check returning %d\n",
540 ad->ad_cname.bv_val, rc, 0);
547 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
548 * return the LDAP DN to which it matches. The SASL regexp rules in the config
549 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
550 * search with scope=base), just return the URI (or its searchbase). Otherwise
551 * an internal search must be done, and if that search returns exactly one
552 * entry, return the DN of that one entry.
554 void slap_sasl2dn( Operation *opx,
555 struct berval *saslname, struct berval *sasldn )
558 slap_callback cb = { sasl_sc_sasl2dn, NULL };
560 SlapReply rs = {REP_RESULT};
561 struct berval regout = { 0, NULL };
564 LDAP_LOG( TRANSPORT, ENTRY,
565 "slap_sasl2dn: converting SASL name %s to DN.\n",
566 saslname->bv_val, 0, 0 );
568 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
569 "converting SASL name %s to a DN\n",
570 saslname->bv_val, 0,0 );
573 sasldn->bv_val = NULL;
575 cb.sc_private = sasldn;
577 /* Convert the SASL name into a minimal URI */
578 if( !slap_sasl_regexp( saslname, ®out, opx->o_tmpmemctx ) ) {
582 rc = slap_parseURI( opx, ®out,
583 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter );
584 if( regout.bv_val ) sl_free( regout.bv_val, opx->o_tmpmemctx );
585 if( rc != LDAP_SUCCESS ) {
589 /* Must do an internal search */
590 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
592 /* Massive shortcut: search scope == base */
593 if( op.oq_search.rs_scope == LDAP_SCOPE_BASE ) {
594 *sasldn = op.o_req_ndn;
595 op.o_req_ndn.bv_len = 0;
596 op.o_req_ndn.bv_val = NULL;
601 LDAP_LOG( TRANSPORT, DETAIL1,
602 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
603 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
605 Debug( LDAP_DEBUG_TRACE,
606 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
607 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
610 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
614 op.o_conn = opx->o_conn;
615 op.o_connid = opx->o_connid;
616 op.o_tag = LDAP_REQ_SEARCH;
617 op.o_protocol = LDAP_VERSION3;
618 op.o_ndn = opx->o_conn->c_ndn;
620 op.o_time = slap_get_time();
621 op.o_do_not_cache = 1;
622 op.o_is_auth_check = 1;
623 op.o_threadctx = opx->o_threadctx;
624 op.o_tmpmemctx = opx->o_tmpmemctx;
625 op.o_tmpmfuncs = opx->o_tmpmfuncs;
629 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
630 op.oq_search.rs_slimit = 1;
631 op.oq_search.rs_attrsonly = 1;
633 op.o_bd->be_search( &op, &rs );
636 if( sasldn->bv_len ) {
637 opx->o_conn->c_authz_backend = op.o_bd;
639 if( op.o_req_ndn.bv_len ) ch_free( op.o_req_ndn.bv_val );
640 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
643 LDAP_LOG( TRANSPORT, ENTRY,
644 "slap_sasl2dn: Converted SASL name to %s\n",
645 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
647 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
648 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
655 /* Check if a bind can SASL authorize to another identity.
656 * The DNs should not have the dn: prefix
659 int slap_sasl_authorized( Operation *op,
660 struct berval *authcDN, struct berval *authzDN )
662 int rc = LDAP_INAPPROPRIATE_AUTH;
664 /* User binding as anonymous */
665 if ( authzDN == NULL ) {
671 LDAP_LOG( TRANSPORT, ENTRY,
672 "slap_sasl_authorized: can %s become %s?\n",
673 authcDN->bv_val, authzDN->bv_val, 0 );
675 Debug( LDAP_DEBUG_TRACE,
676 "==>slap_sasl_authorized: can %s become %s?\n",
677 authcDN->bv_val, authzDN->bv_val, 0 );
680 /* If person is authorizing to self, succeed */
681 if ( dn_match( authcDN, authzDN ) ) {
686 /* Allow the manager to authorize as any DN. */
687 if( op->o_conn->c_authz_backend && be_isroot( op->o_conn->c_authz_backend, authcDN )) {
692 /* Check source rules */
693 if( authz_policy & SASL_AUTHZ_TO ) {
694 rc = slap_sasl_check_authz( op, authcDN, authzDN,
695 slap_schema.si_ad_saslAuthzTo, authcDN );
696 if( rc == LDAP_SUCCESS ) {
701 /* Check destination rules */
702 if( authz_policy & SASL_AUTHZ_FROM ) {
703 rc = slap_sasl_check_authz( op, authzDN, authcDN,
704 slap_schema.si_ad_saslAuthzFrom, authcDN );
705 if( rc == LDAP_SUCCESS ) {
710 rc = LDAP_INAPPROPRIATE_AUTH;
715 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
717 Debug( LDAP_DEBUG_TRACE,
718 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );