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>
30 #define SASLREGEX_REPLACE 10
32 typedef struct sasl_regexp {
33 char *sr_match; /* regexp match pattern */
34 char *sr_replace; /* regexp replace pattern */
35 regex_t sr_workspace; /* workspace for regexp engine */
36 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
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;
67 /* URI format: ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]] */
69 static int slap_parseURI( struct berval *uri,
70 struct berval *searchbase, int *scope, Filter **filter )
76 assert( uri != NULL && uri->bv_val != NULL );
77 searchbase->bv_val = NULL;
78 searchbase->bv_len = 0;
83 LDAP_LOG( TRANSPORT, ENTRY,
84 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
86 Debug( LDAP_DEBUG_TRACE, "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 = dnNormalize2( NULL, &bv, searchbase );
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 should be empty */
117 /* attrs and extensions parts should be empty */
118 return LDAP_PROTOCOL_ERROR;
122 *scope = ludp->lud_scope;
124 /* Grab the filter */
125 if ( ludp->lud_filter ) {
126 *filter = str2filter( 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 = dnNormalize2( NULL, &bv, searchbase );
139 if( rc != LDAP_SUCCESS ) {
140 if( *filter ) filter_free( *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,
232 int i, n, len, insert;
234 /* Get the total length of the final URI */
238 while( off[n] >= 0 ) {
239 /* Len of next section from replacement string (x,y,z above) */
240 len += off[n] - off[n-1] - 2;
244 /* Len of string from saslname that matched next $i (b,d above) */
245 i = rep[ off[n] + 1 ] - '0';
246 len += str[i].rm_eo - str[i].rm_so;
249 out->bv_val = ch_malloc( len + 1 );
252 /* Fill in URI with replace string, replacing $i as we go */
255 while( off[n] >= 0) {
256 /* Paste in next section from replacement string (x,y,z above) */
257 len = off[n] - off[n-1] - 2;
258 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
263 /* Paste in string from saslname that matched next $i (b,d above) */
264 i = rep[ off[n] + 1 ] - '0';
265 len = str[i].rm_eo - str[i].rm_so;
266 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
272 out->bv_val[insert] = '\0';
275 /* Take the passed in SASL name and attempt to convert it into an
276 LDAP URI to find the matching LDAP entry, using the pattern matching
277 strings given in the saslregexp config file directive(s) */
279 static int slap_sasl_regexp( struct berval *in, struct berval *out )
281 char *saslname = in->bv_val;
285 memset( out, 0, sizeof( *out ) );
288 LDAP_LOG( TRANSPORT, ENTRY,
289 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
291 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
295 if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
298 /* Match the normalized SASL name to the saslregexp patterns */
299 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
300 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
301 reg->sr_strings, 0) == 0 )
305 if( i >= nSaslRegexp )
309 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
310 * replace pattern of the form "x$1y$2z". The returned string needs
311 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
313 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
314 reg->sr_strings, saslname, out );
317 LDAP_LOG( TRANSPORT, ENTRY,
318 "slap_sasl_regexp: converted SASL name to %s\n",
319 out->bv_len ? out->bv_val : "", 0, 0 );
321 Debug( LDAP_DEBUG_TRACE,
322 "slap_sasl_regexp: converted SASL name to %s\n",
323 out->bv_len ? out->bv_val : "", 0, 0 );
329 /* Two empty callback functions to avoid sending results */
330 void slap_cb_null_response( Connection *conn, Operation *o, ber_tag_t tag,
331 ber_int_t msgid, ber_int_t err, const char *matched,
332 const char *text, BerVarray ref, const char *resoid,
333 struct berval *resdata, struct berval *sasldata, LDAPControl **c)
337 void slap_cb_null_sresult( Connection *conn, Operation *o, ber_int_t err,
338 const char *matched, const char *text, BerVarray refs, LDAPControl **c,
343 /* This callback actually does some work...*/
344 static int sasl_sc_sasl2dn( BackendDB *be, Connection *conn, Operation *o,
345 Entry *e, AttributeName *an, int ao, LDAPControl **c)
347 struct berval *ndn = o->o_callback->sc_private;
349 /* We only want to be called once */
355 LDAP_LOG( TRANSPORT, DETAIL1,
356 "slap_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
358 Debug( LDAP_DEBUG_TRACE,
359 "slap_sasl2dn: search DN returned more than 1 entry\n", 0,0,0 );
364 ber_dupbv(ndn, &e->e_nname);
369 typedef struct smatch_info {
374 static int sasl_sc_smatch( BackendDB *be, Connection *conn, Operation *o,
375 Entry *e, AttributeName *an, int ao, LDAPControl **c)
377 smatch_info *sm = o->o_callback->sc_private;
379 if (dn_match(sm->dn, &e->e_nname)) {
381 return -1; /* short-circuit the search */
388 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
389 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
390 * the rule must be used as an internal search for entries. If that search
391 * returns the *assertDN entry, the match is successful.
393 * The assertDN should not have the dn: prefix
397 int slap_sasl_match(Connection *conn, struct berval *rule, struct berval *assertDN, struct berval *authc )
399 struct berval searchbase = {0, NULL};
405 slap_callback cb = { slap_cb_null_response, slap_cb_null_sresult, sasl_sc_smatch, NULL };
409 LDAP_LOG( TRANSPORT, ENTRY,
410 "slap_sasl_match: comparing DN %s to rule %s\n",
411 assertDN->bv_val, rule->bv_val,0 );
413 Debug( LDAP_DEBUG_TRACE,
414 "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN->bv_val, rule->bv_val, 0 );
417 rc = slap_parseURI( rule, &searchbase, &scope, &filter );
418 if( rc != LDAP_SUCCESS )
421 /* Massive shortcut: search scope == base */
422 if( scope == LDAP_SCOPE_BASE ) {
423 rc = regcomp(®, searchbase.bv_val,
424 REG_EXTENDED|REG_ICASE|REG_NOSUB);
426 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
432 rc = LDAP_INAPPROPRIATE_AUTH;
436 /* Must run an internal search. */
439 LDAP_LOG( TRANSPORT, DETAIL1,
440 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
441 searchbase.bv_val, scope,0 );
443 Debug( LDAP_DEBUG_TRACE,
444 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
445 searchbase.bv_val, scope, 0 );
448 be = select_backend( &searchbase, 0, 1 );
449 if(( be == NULL ) || ( be->be_search == NULL)) {
450 rc = LDAP_INAPPROPRIATE_AUTH;
453 suffix_alias( be, &searchbase );
459 op.o_tag = LDAP_REQ_SEARCH;
460 op.o_protocol = LDAP_VERSION3;
463 op.o_time = slap_get_time();
464 op.o_do_not_cache = 1;
465 op.o_threadctx = conn->c_sasl_bindop->o_threadctx;
467 (*be->be_search)( be, conn, &op, /*base=*/NULL, &searchbase,
468 scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
469 /*attrs=*/NULL, /*attrsonly=*/0 );
474 rc = LDAP_INAPPROPRIATE_AUTH;
477 if( searchbase.bv_len ) ch_free( searchbase.bv_val );
478 if( filter ) filter_free( filter );
480 LDAP_LOG( TRANSPORT, ENTRY,
481 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
483 Debug( LDAP_DEBUG_TRACE,
484 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
492 * This function answers the question, "Can this ID authorize to that ID?",
493 * based on authorization rules. The rules are stored in the *searchDN, in the
494 * attribute named by *attr. If any of those rules map to the *assertDN, the
495 * authorization is approved.
497 * The DNs should not have the dn: prefix
500 slap_sasl_check_authz( Connection *conn,
501 struct berval *searchDN,
502 struct berval *assertDN,
503 AttributeDescription *ad,
504 struct berval *authc )
510 LDAP_LOG( TRANSPORT, ENTRY,
511 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
512 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
514 Debug( LDAP_DEBUG_TRACE,
515 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
516 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
519 rc = backend_attribute( NULL, NULL, conn->c_sasl_bindop, NULL,
520 searchDN, ad, &vals );
521 if( rc != LDAP_SUCCESS )
524 /* Check if the *assertDN matches any **vals */
525 for( i=0; vals[i].bv_val != NULL; i++ ) {
526 rc = slap_sasl_match( conn, &vals[i], assertDN, authc );
527 if ( rc == LDAP_SUCCESS )
530 rc = LDAP_INAPPROPRIATE_AUTH;
533 if( vals ) ber_bvarray_free( vals );
536 LDAP_LOG( TRANSPORT, RESULTS,
537 "slap_sasl_check_authz: %s check returning %s\n",
538 ad->ad_cname.bv_val, rc, 0 );
540 Debug( LDAP_DEBUG_TRACE,
541 "<==slap_sasl_check_authz: %s check returning %d\n",
542 ad->ad_cname.bv_val, rc, 0);
549 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
550 * return the LDAP DN to which it matches. The SASL regexp rules in the config
551 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
552 * search with scope=base), just return the URI (or its searchbase). Otherwise
553 * an internal search must be done, and if that search returns exactly one
554 * entry, return the DN of that one entry.
556 void slap_sasl2dn( Connection *conn,
557 struct berval *saslname, struct berval *sasldn )
561 struct berval dn = { 0, NULL };
562 int scope = LDAP_SCOPE_BASE;
563 Filter *filter = NULL;
564 slap_callback cb = { slap_cb_null_response,
565 slap_cb_null_sresult, sasl_sc_sasl2dn, NULL};
567 struct berval regout = { 0, NULL };
570 LDAP_LOG( TRANSPORT, ENTRY,
571 "slap_sasl2dn: converting SASL name %s to DN.\n",
572 saslname->bv_val, 0, 0 );
574 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
575 "converting SASL name %s to a DN\n",
576 saslname->bv_val, 0,0 );
579 sasldn->bv_val = NULL;
581 cb.sc_private = sasldn;
583 /* Convert the SASL name into a minimal URI */
584 if( !slap_sasl_regexp( saslname, ®out ) ) {
588 rc = slap_parseURI( ®out, &dn, &scope, &filter );
589 if( rc != LDAP_SUCCESS ) {
593 /* Must do an internal search */
594 be = select_backend( &dn, 0, 1 );
596 /* Massive shortcut: search scope == base */
597 if( scope == LDAP_SCOPE_BASE ) {
605 LDAP_LOG( TRANSPORT, DETAIL1,
606 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
607 dn.bv_val, scope, 0 );
609 Debug( LDAP_DEBUG_TRACE,
610 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
611 dn.bv_val, scope, 0 );
614 if(( be == NULL ) || ( be->be_search == NULL)) {
617 suffix_alias( be, &dn );
619 op.o_tag = LDAP_REQ_SEARCH;
620 op.o_protocol = LDAP_VERSION3;
621 op.o_ndn = conn->c_ndn;
623 op.o_time = slap_get_time();
624 op.o_do_not_cache = 1;
625 op.o_threadctx = conn->c_sasl_bindop->o_threadctx;
627 (*be->be_search)( be, conn, &op, NULL, &dn,
628 scope, LDAP_DEREF_NEVER, 1, 0,
629 filter, NULL, NULL, 1 );
632 if( sasldn->bv_len ) {
633 conn->c_authz_backend = be;
635 if( dn.bv_len ) ch_free( dn.bv_val );
636 if( filter ) filter_free( filter );
639 LDAP_LOG( TRANSPORT, ENTRY,
640 "slap_sasl2dn: Converted SASL name to %s\n",
641 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
643 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
644 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
651 /* Check if a bind can SASL authorize to another identity.
652 * The DNs should not have the dn: prefix
655 int slap_sasl_authorized( Connection *conn,
656 struct berval *authcDN, struct berval *authzDN )
658 int rc = LDAP_INAPPROPRIATE_AUTH;
660 /* User binding as anonymous */
661 if ( authzDN == NULL ) {
667 LDAP_LOG( TRANSPORT, ENTRY,
668 "slap_sasl_authorized: can %s become %s?\n",
669 authcDN->bv_val, authzDN->bv_val, 0 );
671 Debug( LDAP_DEBUG_TRACE,
672 "==>slap_sasl_authorized: can %s become %s?\n",
673 authcDN->bv_val, authzDN->bv_val, 0 );
676 /* If person is authorizing to self, succeed */
677 if ( dn_match( authcDN, authzDN ) ) {
682 /* Check source rules */
683 if( authz_policy & SASL_AUTHZ_TO ) {
684 rc = slap_sasl_check_authz( conn, authcDN, authzDN,
685 slap_schema.si_ad_saslAuthzTo, authcDN );
686 if( rc == LDAP_SUCCESS ) {
691 /* Check destination rules */
692 if( authz_policy & SASL_AUTHZ_FROM ) {
693 rc = slap_sasl_check_authz( conn, authzDN, authcDN,
694 slap_schema.si_ad_saslAuthzFrom, authcDN );
695 if( rc == LDAP_SUCCESS ) {
700 rc = LDAP_INAPPROPRIATE_AUTH;
705 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
707 Debug( LDAP_DEBUG_TRACE,
708 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );