]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
allow 'all' vs. 'any' sasl-authz-policy
[openldap] / servers / slapd / saslauthz.c
1 /* $OpenLDAP$ */
2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3  *
4  * Copyright 1998-2003 The OpenLDAP Foundation.
5  * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16
17 #include "portable.h"
18
19 #include <stdio.h>
20
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
23
24 #include "slap.h"
25
26 #include <limits.h>
27
28 #include <ldap_pvt.h>
29
30 #define SASLREGEX_REPLACE 10
31
32 #define LDAP_X_SCOPE_EXACT      ((ber_int_t) 0x0010)
33 #define LDAP_X_SCOPE_REGEX      ((ber_int_t) 0x0020)
34 #define LDAP_X_SCOPE_CHILDREN   ((ber_int_t) 0x0030)
35 #define LDAP_X_SCOPE_SUBTREE    ((ber_int_t) 0x0040)
36
37 /*
38  * IDs in DNauthzid form can now have a type specifier, that
39  * influences how they are used in related operations.
40  *
41  * syntax: dn[.{exact|regex}]:<val>
42  *
43  * dn.exact:    the value must pass normalization and is used 
44  *              in exact DN match.
45  * dn.regex:    the value is treated as a regular expression 
46  *              in matching DN values in saslAuthz{To|From}
47  *              attributes.
48  * dn:          for backwards compatibility reasons, the value 
49  *              is treated as a regular expression, and thus 
50  *              it is not normalized nor validated; it is used
51  *              in exact or regex comparisons based on the 
52  *              context.
53  *
54  * IDs in DNauthzid form can now have a type specifier, that
55  * influences how they are used in related operations.
56  *
57  * syntax: u[.mech[/realm]]:<val>
58  * 
59  * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
60  * and realm is mechanism specific realm (separate to those
61  * which are representable as part of the principal).
62  */
63
64 typedef struct sasl_regexp {
65   char *sr_match;                                               /* regexp match pattern */
66   char *sr_replace;                                     /* regexp replace pattern */
67   regex_t sr_workspace;                                 /* workspace for regexp engine */
68   int sr_offset[SASLREGEX_REPLACE+2];   /* offsets of $1,$2... in *replace */
69 } SaslRegexp_t;
70
71 static int nSaslRegexp = 0;
72 static SaslRegexp_t *SaslRegexp = NULL;
73
74 /* What SASL proxy authorization policies are allowed? */
75 #define SASL_AUTHZ_NONE 0x00
76 #define SASL_AUTHZ_FROM 0x01
77 #define SASL_AUTHZ_TO   0x02
78 #define SASL_AUTHZ_AND  0x10
79
80 static int authz_policy = SASL_AUTHZ_NONE;
81
82 int slap_sasl_setpolicy( const char *arg )
83 {
84         int rc = LDAP_SUCCESS;
85
86         if ( strcasecmp( arg, "none" ) == 0 ) {
87                 authz_policy = SASL_AUTHZ_NONE;
88         } else if ( strcasecmp( arg, "from" ) == 0 ) {
89                 authz_policy = SASL_AUTHZ_FROM;
90         } else if ( strcasecmp( arg, "to" ) == 0 ) {
91                 authz_policy = SASL_AUTHZ_TO;
92         } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
93                 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
94         } else if ( strcasecmp( arg, "all" ) == 0 ) {
95                 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
96         } else {
97                 rc = LDAP_OTHER;
98         }
99         return rc;
100 }
101
102 int slap_parse_user( struct berval *id, struct berval *user,
103                 struct berval *realm, struct berval *mech )
104 {
105         char    u;
106         
107         assert( id );
108         assert( id->bv_val );
109         assert( user );
110         assert( realm );
111         assert( mech );
112
113         u = id->bv_val[ 0 ];
114         
115         if ( u != 'u' && u != 'U' ) {
116                 /* called with something other than u: */
117                 return LDAP_PROTOCOL_ERROR;
118         }
119
120         /* uauthzid form:
121          *              u[.mech[/realm]]:user
122          */
123         
124         user->bv_val = strchr( id->bv_val, ':' );
125         if ( user->bv_val == NULL ) {
126                 return LDAP_PROTOCOL_ERROR;
127         }
128         user->bv_val[ 0 ] = '\0';
129         user->bv_val++;
130         user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
131
132         mech->bv_val = strchr( id->bv_val, '.' );
133         if ( mech->bv_val != NULL ) {
134                 mech->bv_val[ 0 ] = '\0';
135                 mech->bv_val++;
136
137                 realm->bv_val = strchr( mech->bv_val, '/' );
138
139                 if ( realm->bv_val ) {
140                         realm->bv_val[ 0 ] = '\0';
141                         realm->bv_val++;
142                         mech->bv_len = realm->bv_val - mech->bv_val - 1;
143                         realm->bv_len = user->bv_val - realm->bv_val - 1;
144                 } else {
145                         mech->bv_len = user->bv_val - mech->bv_val - 1;
146                 }
147
148         } else {
149                 realm->bv_val = NULL;
150         }
151
152         if ( id->bv_val[ 1 ] != '\0' ) {
153                 return LDAP_PROTOCOL_ERROR;
154         }
155
156         if ( mech->bv_val != NULL ) {
157                 assert( mech->bv_val == id->bv_val + 2 );
158
159                 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
160                 mech->bv_val -= 2;
161         }
162
163         if ( realm->bv_val ) {
164                 assert( realm->bv_val >= id->bv_val + 2 );
165
166                 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
167                 realm->bv_val -= 2;
168         }
169
170         /* leave "u:" before user */
171         user->bv_val -= 2;
172         user->bv_len += 2;
173         user->bv_val[ 0 ] = u;
174         user->bv_val[ 1 ] = ':';
175
176         return LDAP_SUCCESS;
177 }
178
179 static int slap_parseURI( Operation *op, struct berval *uri,
180         struct berval *base, struct berval *nbase,
181         int *scope, Filter **filter, struct berval *fstr )
182 {
183         struct berval bv;
184         int rc;
185         LDAPURLDesc *ludp;
186
187         assert( uri != NULL && uri->bv_val != NULL );
188         base->bv_val = NULL;
189         base->bv_len = 0;
190         nbase->bv_val = NULL;
191         nbase->bv_len = 0;
192         fstr->bv_val = NULL;
193         fstr->bv_len = 0;
194         *scope = -1;
195         *filter = NULL;
196
197 #ifdef NEW_LOGGING
198         LDAP_LOG( TRANSPORT, ENTRY, 
199                 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
200 #else
201         Debug( LDAP_DEBUG_TRACE,
202                 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
203 #endif
204
205         rc = LDAP_PROTOCOL_ERROR;
206         if ( !strncasecmp( uri->bv_val, "dn", sizeof( "dn" ) - 1 ) ) {
207                 bv.bv_val = uri->bv_val + sizeof( "dn" ) - 1;
208
209                 if ( bv.bv_val[ 0 ] == '.' ) {
210                         bv.bv_val++;
211
212                         if ( !strncasecmp( bv.bv_val, "exact:", sizeof( "exact:" ) - 1 ) ) {
213                                 bv.bv_val += sizeof( "exact" ) - 1;
214                                 *scope = LDAP_X_SCOPE_EXACT;
215
216                         } else if ( !strncasecmp( bv.bv_val, "regex:", sizeof( "regex:" ) - 1 ) ) {
217                                 bv.bv_val += sizeof( "regex" ) - 1;
218                                 *scope = LDAP_X_SCOPE_REGEX;
219
220                         } else if ( !strncasecmp( bv.bv_val, "children:", sizeof( "chldren:" ) - 1 ) ) {
221                                 bv.bv_val += sizeof( "children" ) - 1;
222                                 *scope = LDAP_X_SCOPE_CHILDREN;
223
224                         } else if ( !strncasecmp( bv.bv_val, "subtree:", sizeof( "subtree:" ) - 1 ) ) {
225                                 bv.bv_val += sizeof( "subtree" ) - 1;
226                                 *scope = LDAP_X_SCOPE_SUBTREE;
227
228                         } else {
229                                 return LDAP_PROTOCOL_ERROR;
230                         }
231                 }
232
233                 if ( bv.bv_val[ 0 ] != ':' ) {
234                         return LDAP_PROTOCOL_ERROR;
235                 }
236                 bv.bv_val++;
237
238                 bv.bv_val += strspn( bv.bv_val, " " );
239                 /* jump here in case no type specification was present
240                  * and uir was not an URI... HEADS-UP: assuming EXACT */
241 is_dn:          bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
242
243                 switch ( *scope ) {
244                 case LDAP_X_SCOPE_EXACT:
245                 case LDAP_X_SCOPE_CHILDREN:
246                 case LDAP_X_SCOPE_SUBTREE:
247                         rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
248                         if( rc != LDAP_SUCCESS ) {
249                                 *scope = -1;
250                         }
251                         break;
252
253                 case LDAP_X_SCOPE_REGEX:
254                         ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
255                         rc = LDAP_SUCCESS;
256                         break;
257
258                 default:
259                         *scope = -1;
260                         break;
261                 }
262
263                 return rc;
264
265         } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
266                         && ( uri->bv_val[ 1 ] == ':' 
267                                 || uri->bv_val[ 1 ] == '/' 
268                                 || uri->bv_val[ 1 ] == '.' ) )
269         {
270                 Connection      c = *op->o_conn;
271                 char            buf[ SLAP_LDAPDN_MAXLEN ];
272                 struct berval   id,
273                                 user = { 0, NULL },
274                                 realm = { 0, NULL },
275                                 mech = { 0, NULL };
276
277                 if ( sizeof( buf ) <= uri->bv_len ) {
278                         return LDAP_INVALID_SYNTAX;
279                 }
280
281                 id.bv_len = uri->bv_len;
282                 id.bv_val = buf;
283                 strncpy( buf, uri->bv_val, sizeof( buf ) );
284
285                 rc = slap_parse_user( &id, &user, &realm, &mech );
286                 if ( rc != LDAP_SUCCESS ) {
287                         return rc;
288                 }
289
290                 if ( mech.bv_val ) {
291                         c.c_sasl_bind_mech = mech;
292                 } else {
293                         c.c_sasl_bind_mech.bv_val = "AUTHZ";
294                         c.c_sasl_bind_mech.bv_len = sizeof( "AUTHZ" ) - 1;
295                 }
296                 
297                 rc = slap_sasl_getdn( &c, op, user.bv_val, user.bv_len,
298                                 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
299
300                 if ( rc == LDAP_SUCCESS ) {
301                         *scope = LDAP_X_SCOPE_EXACT;
302                 }
303
304                 return rc;
305         }
306                 
307         rc = ldap_url_parse( uri->bv_val, &ludp );
308         if ( rc == LDAP_URL_ERR_BADSCHEME ) {
309                 /* last chance: assume it's a(n exact) DN ... */
310                 bv.bv_val = uri->bv_val;
311                 *scope = LDAP_X_SCOPE_EXACT;
312                 goto is_dn;
313         }
314
315         if ( rc != LDAP_URL_SUCCESS ) {
316                 return LDAP_PROTOCOL_ERROR;
317         }
318
319         if (( ludp->lud_host && *ludp->lud_host )
320                 || ludp->lud_attrs || ludp->lud_exts )
321         {
322                 /* host part must be empty */
323                 /* attrs and extensions parts must be empty */
324                 rc = LDAP_PROTOCOL_ERROR;
325                 goto done;
326         }
327
328         /* Grab the scope */
329         *scope = ludp->lud_scope;
330
331         /* Grab the filter */
332         if ( ludp->lud_filter ) {
333                 *filter = str2filter_x( op, ludp->lud_filter );
334                 if ( *filter == NULL ) {
335                         rc = LDAP_PROTOCOL_ERROR;
336                         goto done;
337                 }
338                 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
339         }
340
341         /* Grab the searchbase */
342         ber_str2bv( ludp->lud_dn, 0, 0, base );
343         rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
344
345 done:
346         if( rc != LDAP_SUCCESS ) {
347                 if( *filter ) filter_free_x( op, *filter );
348                 base->bv_val = NULL;
349                 base->bv_len = 0;
350                 fstr->bv_val = NULL;
351                 fstr->bv_len = 0;
352         } else {
353                 /* Don't free these, return them to caller */
354                 ludp->lud_filter = NULL;
355                 ludp->lud_dn = NULL;
356         }
357
358         ldap_free_urldesc( ludp );
359         return( rc );
360 }
361
362 static int slap_sasl_rx_off(char *rep, int *off)
363 {
364         const char *c;
365         int n;
366
367         /* Precompile replace pattern. Find the $<n> placeholders */
368         off[0] = -2;
369         n = 1;
370         for ( c = rep;   *c;  c++ ) {
371                 if ( *c == '\\' && c[1] ) {
372                         c++;
373                         continue;
374                 }
375                 if ( *c == '$' ) {
376                         if ( n == SASLREGEX_REPLACE ) {
377 #ifdef NEW_LOGGING
378                                 LDAP_LOG( TRANSPORT, ERR, 
379                                         "slap_sasl_rx_off: \"%s\" has too many $n "
380                                         "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0  );
381 #else
382                                 Debug( LDAP_DEBUG_ANY,
383                                         "SASL replace pattern %s has too many $n "
384                                                 "placeholders (max %d)\n",
385                                         rep, SASLREGEX_REPLACE, 0 );
386 #endif
387
388                                 return( LDAP_OTHER );
389                         }
390                         off[n] = c - rep;
391                         n++;
392                 }
393         }
394
395         /* Final placeholder, after the last $n */
396         off[n] = c - rep;
397         n++;
398         off[n] = -1;
399         return( LDAP_SUCCESS );
400 }
401
402 int slap_sasl_regexp_config( const char *match, const char *replace )
403 {
404         int rc;
405         SaslRegexp_t *reg;
406
407         SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
408           (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
409
410         reg = &SaslRegexp[nSaslRegexp];
411
412         reg->sr_match = ch_strdup( match );
413         reg->sr_replace = ch_strdup( replace );
414
415         /* Precompile matching pattern */
416         rc = regcomp( &reg->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
417         if ( rc ) {
418 #ifdef NEW_LOGGING
419                 LDAP_LOG( TRANSPORT, ERR, 
420                         "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
421                         reg->sr_match, 0, 0 );
422 #else
423                 Debug( LDAP_DEBUG_ANY,
424                 "SASL match pattern %s could not be compiled by regexp engine\n",
425                 reg->sr_match, 0, 0 );
426 #endif
427
428                 return( LDAP_OTHER );
429         }
430
431         rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
432         if ( rc != LDAP_SUCCESS ) return rc;
433
434         nSaslRegexp++;
435         return( LDAP_SUCCESS );
436 }
437
438
439 /* Perform replacement on regexp matches */
440 static void slap_sasl_rx_exp(
441         const char *rep,
442         const int *off,
443         regmatch_t *str,
444         const char *saslname,
445         struct berval *out,
446         void *ctx )
447 {
448         int i, n, len, insert;
449
450         /* Get the total length of the final URI */
451
452         n=1;
453         len = 0;
454         while( off[n] >= 0 ) {
455                 /* Len of next section from replacement string (x,y,z above) */
456                 len += off[n] - off[n-1] - 2;
457                 if( off[n+1] < 0)
458                         break;
459
460                 /* Len of string from saslname that matched next $i  (b,d above) */
461                 i = rep[ off[n] + 1 ]   - '0';
462                 len += str[i].rm_eo - str[i].rm_so;
463                 n++;
464         }
465         out->bv_val = sl_malloc( len + 1, ctx );
466         out->bv_len = len;
467
468         /* Fill in URI with replace string, replacing $i as we go */
469         n=1;
470         insert = 0;
471         while( off[n] >= 0) {
472                 /* Paste in next section from replacement string (x,y,z above) */
473                 len = off[n] - off[n-1] - 2;
474                 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
475                 insert += len;
476                 if( off[n+1] < 0)
477                         break;
478
479                 /* Paste in string from saslname that matched next $i  (b,d above) */
480                 i = rep[ off[n] + 1 ]   - '0';
481                 len = str[i].rm_eo - str[i].rm_so;
482                 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
483                 insert += len;
484
485                 n++;
486         }
487
488         out->bv_val[insert] = '\0';
489 }
490
491 /* Take the passed in SASL name and attempt to convert it into an
492    LDAP URI to find the matching LDAP entry, using the pattern matching
493    strings given in the saslregexp config file directive(s) */
494
495 static int slap_sasl_regexp( struct berval *in, struct berval *out, void *ctx )
496 {
497         char *saslname = in->bv_val;
498         SaslRegexp_t *reg;
499         regmatch_t sr_strings[SASLREGEX_REPLACE];       /* strings matching $1,$2 ... */
500         int i;
501
502         memset( out, 0, sizeof( *out ) );
503
504 #ifdef NEW_LOGGING
505         LDAP_LOG( TRANSPORT, ENTRY, 
506                 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
507 #else
508         Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
509            saslname, 0, 0 );
510 #endif
511
512         if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
513                 return( 0 );
514         }
515
516         /* Match the normalized SASL name to the saslregexp patterns */
517         for( reg = SaslRegexp,i=0;  i<nSaslRegexp;  i++,reg++ ) {
518                 if ( regexec( &reg->sr_workspace, saslname, SASLREGEX_REPLACE,
519                   sr_strings, 0)  == 0 )
520                         break;
521         }
522
523         if( i >= nSaslRegexp ) return( 0 );
524
525         /*
526          * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
527          * replace pattern of the form "x$1y$2z". The returned string needs
528          * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
529          */
530         slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
531                 sr_strings, saslname, out, ctx );
532
533 #ifdef NEW_LOGGING
534         LDAP_LOG( TRANSPORT, ENTRY, 
535                 "slap_sasl_regexp: converted SASL name to %s\n",
536                 out->bv_len ? out->bv_val : "", 0, 0 );
537 #else
538         Debug( LDAP_DEBUG_TRACE,
539                 "slap_sasl_regexp: converted SASL name to %s\n",
540                 out->bv_len ? out->bv_val : "", 0, 0 );
541 #endif
542
543         return( 1 );
544 }
545
546 /* This callback actually does some work...*/
547 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
548 {
549         struct berval *ndn = o->o_callback->sc_private;
550
551         if (rs->sr_type != REP_SEARCH) return 0;
552
553         /* We only want to be called once */
554         if( ndn->bv_val ) {
555                 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
556                 ndn->bv_val = NULL;
557
558 #ifdef NEW_LOGGING
559                 LDAP_LOG( TRANSPORT, DETAIL1,
560                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
561 #else
562                 Debug( LDAP_DEBUG_TRACE,
563                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
564 #endif
565                 return -1;
566         }
567
568         ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
569         return 0;
570 }
571
572
573 typedef struct smatch_info {
574         struct berval *dn;
575         int match;
576 } smatch_info;
577
578 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
579 {
580         smatch_info *sm = o->o_callback->sc_private;
581
582         if (rs->sr_type != REP_SEARCH) return 0;
583
584         if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
585                 sm->match = 1;
586                 return -1;      /* short-circuit the search */
587         }
588
589         return 1;
590 }
591
592 /*
593  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
594  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
595  * the rule must be used as an internal search for entries. If that search
596  * returns the *assertDN entry, the match is successful.
597  *
598  * The assertDN should not have the dn: prefix
599  */
600
601 static
602 int slap_sasl_match( Operation *opx, struct berval *rule,
603         struct berval *assertDN, struct berval *authc )
604 {
605         int rc; 
606         regex_t reg;
607         smatch_info sm;
608         slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
609         Operation op = {0};
610         SlapReply rs = {REP_RESULT};
611
612 #ifdef NEW_LOGGING
613         LDAP_LOG( TRANSPORT, ENTRY, 
614                 "slap_sasl_match: comparing DN %s to rule %s\n", 
615                 assertDN->bv_val, rule->bv_val,0 );
616 #else
617         Debug( LDAP_DEBUG_TRACE,
618            "===>slap_sasl_match: comparing DN %s to rule %s\n",
619                 assertDN->bv_val, rule->bv_val, 0 );
620 #endif
621
622         rc = slap_parseURI( opx, rule, &op.o_req_dn,
623                 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
624                 &op.ors_filterstr );
625         if( rc != LDAP_SUCCESS ) goto CONCLUDED;
626
627         /* Massive shortcut: search scope == base */
628         switch ( op.oq_search.rs_scope ) {
629         case LDAP_SCOPE_BASE:
630         case LDAP_X_SCOPE_EXACT:
631 exact_match:
632                 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
633                         rc = LDAP_SUCCESS;
634                 } else {
635                         rc = LDAP_INAPPROPRIATE_AUTH;
636                 }
637                 goto CONCLUDED;
638
639         case LDAP_X_SCOPE_CHILDREN:
640         case LDAP_X_SCOPE_SUBTREE:
641         {
642                 int     d = assertDN->bv_len - op.o_req_ndn.bv_len;
643
644                 rc = LDAP_INAPPROPRIATE_AUTH;
645
646                 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
647                         goto exact_match;
648
649                 } else if ( d > 0 ) {
650                         struct berval bv;
651
652                         bv.bv_len = op.o_req_ndn.bv_len;
653                         bv.bv_val = assertDN->bv_val + d;
654
655                         if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
656                                 rc = LDAP_SUCCESS;
657                         }
658                 }
659                 goto CONCLUDED;
660         }
661
662         case LDAP_X_SCOPE_REGEX:
663                 rc = regcomp(&reg, op.o_req_ndn.bv_val,
664                         REG_EXTENDED|REG_ICASE|REG_NOSUB);
665                 if ( rc == 0 ) {
666                         rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
667                         regfree( &reg );
668                 }
669                 if ( rc == 0 ) {
670                         rc = LDAP_SUCCESS;
671                 } else {
672                         rc = LDAP_INAPPROPRIATE_AUTH;
673                 }
674                 goto CONCLUDED;
675
676         default:
677                 break;
678         }
679
680         /* Must run an internal search. */
681         if ( op.oq_search.rs_filter == NULL ) {
682                 rc = LDAP_FILTER_ERROR;
683                 goto CONCLUDED;
684         }
685
686 #ifdef NEW_LOGGING
687         LDAP_LOG( TRANSPORT, DETAIL1, 
688                 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
689                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
690 #else
691         Debug( LDAP_DEBUG_TRACE,
692            "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
693            op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
694 #endif
695
696         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
697         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
698                 rc = LDAP_INAPPROPRIATE_AUTH;
699                 goto CONCLUDED;
700         }
701
702         sm.dn = assertDN;
703         sm.match = 0;
704         cb.sc_private = &sm;
705
706         op.o_tag = LDAP_REQ_SEARCH;
707         op.o_protocol = LDAP_VERSION3;
708         op.o_ndn = *authc;
709         op.o_callback = &cb;
710         op.o_time = slap_get_time();
711         op.o_do_not_cache = 1;
712         op.o_is_auth_check = 1;
713         op.o_threadctx = opx->o_threadctx;
714         op.o_tmpmemctx = opx->o_tmpmemctx;
715         op.o_tmpmfuncs = opx->o_tmpmfuncs;
716 #ifdef LDAP_SLAPI
717         op.o_pb = opx->o_pb;
718 #endif
719         op.o_conn = opx->o_conn;
720         op.o_connid = opx->o_connid;
721         op.o_req_dn = op.o_req_ndn;
722
723         op.o_bd->be_search( &op, &rs );
724
725         if (sm.match) {
726                 rc = LDAP_SUCCESS;
727         } else {
728                 rc = LDAP_INAPPROPRIATE_AUTH;
729         }
730
731 CONCLUDED:
732         if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
733         if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
734         if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
735         if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
736
737 #ifdef NEW_LOGGING
738         LDAP_LOG( TRANSPORT, ENTRY, 
739                 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
740 #else
741         Debug( LDAP_DEBUG_TRACE,
742            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
743 #endif
744
745         return( rc );
746 }
747
748
749 /*
750  * This function answers the question, "Can this ID authorize to that ID?",
751  * based on authorization rules. The rules are stored in the *searchDN, in the
752  * attribute named by *attr. If any of those rules map to the *assertDN, the
753  * authorization is approved.
754  *
755  * The DNs should not have the dn: prefix
756  */
757 static int
758 slap_sasl_check_authz( Operation *op,
759         struct berval *searchDN,
760         struct berval *assertDN,
761         AttributeDescription *ad,
762         struct berval *authc )
763 {
764         int i, rc;
765         BerVarray vals=NULL;
766
767 #ifdef NEW_LOGGING
768         LDAP_LOG( TRANSPORT, ENTRY, 
769                 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
770             assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
771 #else
772         Debug( LDAP_DEBUG_TRACE,
773            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
774            assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
775 #endif
776
777         rc = backend_attribute( op, NULL,
778                 searchDN, ad, &vals );
779         if( rc != LDAP_SUCCESS ) goto COMPLETE;
780
781         /* Check if the *assertDN matches any **vals */
782         if( vals != NULL ) {
783                 for( i=0; vals[i].bv_val != NULL; i++ ) {
784                         rc = slap_sasl_match( op, &vals[i], assertDN, authc );
785                         if ( rc == LDAP_SUCCESS ) goto COMPLETE;
786                 }
787         }
788         rc = LDAP_INAPPROPRIATE_AUTH;
789
790 COMPLETE:
791         if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
792
793 #ifdef NEW_LOGGING
794         LDAP_LOG( TRANSPORT, RESULTS, 
795                 "slap_sasl_check_authz: %s check returning %s\n", 
796                 ad->ad_cname.bv_val, rc, 0 );
797 #else
798         Debug( LDAP_DEBUG_TRACE,
799            "<==slap_sasl_check_authz: %s check returning %d\n",
800                 ad->ad_cname.bv_val, rc, 0);
801 #endif
802
803         return( rc );
804 }
805
806 /*
807  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
808  * return the LDAP DN to which it matches. The SASL regexp rules in the config
809  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
810  * search with scope=base), just return the URI (or its searchbase). Otherwise
811  * an internal search must be done, and if that search returns exactly one
812  * entry, return the DN of that one entry.
813  */
814 void slap_sasl2dn( Operation *opx,
815         struct berval *saslname, struct berval *sasldn )
816 {
817         int rc;
818         slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
819         Operation op = {0};
820         SlapReply rs = {REP_RESULT};
821         struct berval regout = { 0, NULL };
822
823 #ifdef NEW_LOGGING
824         LDAP_LOG( TRANSPORT, ENTRY, 
825                 "slap_sasl2dn: converting SASL name %s to DN.\n",
826                 saslname->bv_val, 0, 0 );
827 #else
828         Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
829                 "converting SASL name %s to a DN\n",
830                 saslname->bv_val, 0,0 );
831 #endif
832
833         sasldn->bv_val = NULL;
834         sasldn->bv_len = 0;
835         cb.sc_private = sasldn;
836
837         /* Convert the SASL name into a minimal URI */
838         if( !slap_sasl_regexp( saslname, &regout, opx->o_tmpmemctx ) ) {
839                 goto FINISHED;
840         }
841
842         rc = slap_parseURI( opx, &regout, &op.o_req_dn,
843                 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
844                 &op.ors_filterstr );
845         if( regout.bv_val ) sl_free( regout.bv_val, opx->o_tmpmemctx );
846         if( rc != LDAP_SUCCESS ) {
847                 goto FINISHED;
848         }
849
850         /* Must do an internal search */
851         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
852
853         /* Massive shortcut: search scope == base */
854         switch ( op.oq_search.rs_scope ) {
855         case LDAP_SCOPE_BASE:
856         case LDAP_X_SCOPE_EXACT:
857                 *sasldn = op.o_req_ndn;
858                 op.o_req_ndn.bv_len = 0;
859                 op.o_req_ndn.bv_val = NULL;
860                 /* intentionally continue to next case */
861
862         case LDAP_X_SCOPE_REGEX:
863         case LDAP_X_SCOPE_SUBTREE:
864         case LDAP_X_SCOPE_CHILDREN:
865                 /* correctly parsed, but illegal */
866                 goto FINISHED;
867
868         case LDAP_SCOPE_ONELEVEL:
869         case LDAP_SCOPE_SUBTREE:
870         case LDAP_SCOPE_SUBORDINATE:
871                 /* do a search */
872                 break;
873
874         default:
875                 /* catch unhandled cases (there shouldn't be) */
876                 assert( 0 );
877         }
878
879 #ifdef NEW_LOGGING
880         LDAP_LOG( TRANSPORT, DETAIL1, 
881                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
882                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
883 #else
884         Debug( LDAP_DEBUG_TRACE,
885                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
886                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
887 #endif
888
889         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
890                 goto FINISHED;
891         }
892
893         op.o_conn = opx->o_conn;
894         op.o_connid = opx->o_connid;
895         op.o_tag = LDAP_REQ_SEARCH;
896         op.o_protocol = LDAP_VERSION3;
897         op.o_ndn = opx->o_conn->c_ndn;
898         op.o_callback = &cb;
899         op.o_time = slap_get_time();
900         op.o_do_not_cache = 1;
901         op.o_is_auth_check = 1;
902         op.o_threadctx = opx->o_threadctx;
903         op.o_tmpmemctx = opx->o_tmpmemctx;
904         op.o_tmpmfuncs = opx->o_tmpmfuncs;
905 #ifdef LDAP_SLAPI
906         op.o_pb = opx->o_pb;
907 #endif
908         op.oq_search.rs_deref = LDAP_DEREF_NEVER;
909         op.oq_search.rs_slimit = 1;
910         op.oq_search.rs_attrsonly = 1;
911         op.o_req_dn = op.o_req_ndn;
912
913         op.o_bd->be_search( &op, &rs );
914         
915 FINISHED:
916         if( sasldn->bv_len ) {
917                 opx->o_conn->c_authz_backend = op.o_bd;
918         }
919         if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
920         if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
921         if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
922         if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
923
924 #ifdef NEW_LOGGING
925         LDAP_LOG( TRANSPORT, ENTRY, 
926                 "slap_sasl2dn: Converted SASL name to %s\n",
927                 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
928 #else
929         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
930                 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
931 #endif
932
933         return;
934 }
935
936
937 /* Check if a bind can SASL authorize to another identity.
938  * The DNs should not have the dn: prefix
939  */
940
941 int slap_sasl_authorized( Operation *op,
942         struct berval *authcDN, struct berval *authzDN )
943 {
944         int rc = LDAP_INAPPROPRIATE_AUTH;
945
946         /* User binding as anonymous */
947         if ( authzDN == NULL ) {
948                 rc = LDAP_SUCCESS;
949                 goto DONE;
950         }
951
952 #ifdef NEW_LOGGING
953         LDAP_LOG( TRANSPORT, ENTRY, 
954                 "slap_sasl_authorized: can %s become %s?\n", 
955                 authcDN->bv_val, authzDN->bv_val, 0 );
956 #else
957         Debug( LDAP_DEBUG_TRACE,
958            "==>slap_sasl_authorized: can %s become %s?\n",
959                 authcDN->bv_val, authzDN->bv_val, 0 );
960 #endif
961
962         /* If person is authorizing to self, succeed */
963         if ( dn_match( authcDN, authzDN ) ) {
964                 rc = LDAP_SUCCESS;
965                 goto DONE;
966         }
967
968         /* Allow the manager to authorize as any DN. */
969         if( op->o_conn->c_authz_backend && be_isroot( op->o_conn->c_authz_backend, authcDN )) {
970                 rc = LDAP_SUCCESS;
971                 goto DONE;
972         }
973
974         /* Check source rules */
975         if( authz_policy & SASL_AUTHZ_TO ) {
976                 rc = slap_sasl_check_authz( op, authcDN, authzDN,
977                         slap_schema.si_ad_saslAuthzTo, authcDN );
978                 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
979                         goto DONE;
980                 }
981         }
982
983         /* Check destination rules */
984         if( authz_policy & SASL_AUTHZ_FROM ) {
985                 rc = slap_sasl_check_authz( op, authzDN, authcDN,
986                         slap_schema.si_ad_saslAuthzFrom, authcDN );
987                 if( rc == LDAP_SUCCESS ) {
988                         goto DONE;
989                 }
990         }
991
992         rc = LDAP_INAPPROPRIATE_AUTH;
993
994 DONE:
995
996 #ifdef NEW_LOGGING
997         LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
998 #else
999         Debug( LDAP_DEBUG_TRACE,
1000                 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );
1001 #endif
1002
1003         return( rc );
1004 }