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