]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
Sync with HEAD
[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 = { uri->bv_len, (char *)buf },
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                 strncpy( buf, uri->bv_val, sizeof( buf ) );
282
283                 rc = slap_parse_user( &id, &user, &realm, &mech );
284                 if ( rc != LDAP_SUCCESS ) {
285                         return rc;
286                 }
287
288                 if ( mech.bv_val ) {
289                         c.c_sasl_bind_mech = mech;
290                 } else {
291                         c.c_sasl_bind_mech.bv_val = "AUTHZ";
292                         c.c_sasl_bind_mech.bv_len = sizeof( "AUTHZ" ) - 1;
293                 }
294                 
295                 rc = slap_sasl_getdn( &c, op, user.bv_val, user.bv_len,
296                                 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
297
298                 if ( rc == LDAP_SUCCESS ) {
299                         *scope = LDAP_X_SCOPE_EXACT;
300                 }
301
302                 return rc;
303         }
304                 
305         rc = ldap_url_parse( uri->bv_val, &ludp );
306         if ( rc == LDAP_URL_ERR_BADSCHEME ) {
307                 /* last chance: assume it's a(n exact) DN ... */
308                 bv.bv_val = uri->bv_val;
309                 *scope = LDAP_X_SCOPE_EXACT;
310                 goto is_dn;
311         }
312
313         if ( rc != LDAP_URL_SUCCESS ) {
314                 return LDAP_PROTOCOL_ERROR;
315         }
316
317         if (( ludp->lud_host && *ludp->lud_host )
318                 || ludp->lud_attrs || ludp->lud_exts )
319         {
320                 /* host part must be empty */
321                 /* attrs and extensions parts must be empty */
322                 rc = LDAP_PROTOCOL_ERROR;
323                 goto done;
324         }
325
326         /* Grab the scope */
327         *scope = ludp->lud_scope;
328
329         /* Grab the filter */
330         if ( ludp->lud_filter ) {
331                 *filter = str2filter_x( op, ludp->lud_filter );
332                 if ( *filter == NULL ) {
333                         rc = LDAP_PROTOCOL_ERROR;
334                         goto done;
335                 }
336                 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
337         }
338
339         /* Grab the searchbase */
340         ber_str2bv( ludp->lud_dn, 0, 0, base );
341         rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
342
343 done:
344         if( rc != LDAP_SUCCESS ) {
345                 if( *filter ) filter_free_x( op, *filter );
346                 base->bv_val = NULL;
347                 base->bv_len = 0;
348                 fstr->bv_val = NULL;
349                 fstr->bv_len = 0;
350         } else {
351                 /* Don't free these, return them to caller */
352                 ludp->lud_filter = NULL;
353                 ludp->lud_dn = NULL;
354         }
355
356         ldap_free_urldesc( ludp );
357         return( rc );
358 }
359
360 static int slap_sasl_rx_off(char *rep, int *off)
361 {
362         const char *c;
363         int n;
364
365         /* Precompile replace pattern. Find the $<n> placeholders */
366         off[0] = -2;
367         n = 1;
368         for ( c = rep;   *c;  c++ ) {
369                 if ( *c == '\\' && c[1] ) {
370                         c++;
371                         continue;
372                 }
373                 if ( *c == '$' ) {
374                         if ( n == SASLREGEX_REPLACE ) {
375 #ifdef NEW_LOGGING
376                                 LDAP_LOG( TRANSPORT, ERR, 
377                                         "slap_sasl_rx_off: \"%s\" has too many $n "
378                                         "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0  );
379 #else
380                                 Debug( LDAP_DEBUG_ANY,
381                                         "SASL replace pattern %s has too many $n "
382                                                 "placeholders (max %d)\n",
383                                         rep, SASLREGEX_REPLACE, 0 );
384 #endif
385
386                                 return( LDAP_OTHER );
387                         }
388                         off[n] = c - rep;
389                         n++;
390                 }
391         }
392
393         /* Final placeholder, after the last $n */
394         off[n] = c - rep;
395         n++;
396         off[n] = -1;
397         return( LDAP_SUCCESS );
398 }
399
400 int slap_sasl_regexp_config( const char *match, const char *replace )
401 {
402         int rc;
403         SaslRegexp_t *reg;
404
405         SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
406           (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
407
408         reg = &SaslRegexp[nSaslRegexp];
409
410         reg->sr_match = ch_strdup( match );
411         reg->sr_replace = ch_strdup( replace );
412
413         /* Precompile matching pattern */
414         rc = regcomp( &reg->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
415         if ( rc ) {
416 #ifdef NEW_LOGGING
417                 LDAP_LOG( TRANSPORT, ERR, 
418                         "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
419                         reg->sr_match, 0, 0 );
420 #else
421                 Debug( LDAP_DEBUG_ANY,
422                 "SASL match pattern %s could not be compiled by regexp engine\n",
423                 reg->sr_match, 0, 0 );
424 #endif
425
426                 return( LDAP_OTHER );
427         }
428
429         rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
430         if ( rc != LDAP_SUCCESS ) return rc;
431
432         nSaslRegexp++;
433         return( LDAP_SUCCESS );
434 }
435
436
437 /* Perform replacement on regexp matches */
438 static void slap_sasl_rx_exp(
439         const char *rep,
440         const int *off,
441         regmatch_t *str,
442         const char *saslname,
443         struct berval *out,
444         void *ctx )
445 {
446         int i, n, len, insert;
447
448         /* Get the total length of the final URI */
449
450         n=1;
451         len = 0;
452         while( off[n] >= 0 ) {
453                 /* Len of next section from replacement string (x,y,z above) */
454                 len += off[n] - off[n-1] - 2;
455                 if( off[n+1] < 0)
456                         break;
457
458                 /* Len of string from saslname that matched next $i  (b,d above) */
459                 i = rep[ off[n] + 1 ]   - '0';
460                 len += str[i].rm_eo - str[i].rm_so;
461                 n++;
462         }
463         out->bv_val = sl_malloc( len + 1, ctx );
464         out->bv_len = len;
465
466         /* Fill in URI with replace string, replacing $i as we go */
467         n=1;
468         insert = 0;
469         while( off[n] >= 0) {
470                 /* Paste in next section from replacement string (x,y,z above) */
471                 len = off[n] - off[n-1] - 2;
472                 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
473                 insert += len;
474                 if( off[n+1] < 0)
475                         break;
476
477                 /* Paste in string from saslname that matched next $i  (b,d above) */
478                 i = rep[ off[n] + 1 ]   - '0';
479                 len = str[i].rm_eo - str[i].rm_so;
480                 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
481                 insert += len;
482
483                 n++;
484         }
485
486         out->bv_val[insert] = '\0';
487 }
488
489 /* Take the passed in SASL name and attempt to convert it into an
490    LDAP URI to find the matching LDAP entry, using the pattern matching
491    strings given in the saslregexp config file directive(s) */
492
493 static int slap_sasl_regexp( struct berval *in, struct berval *out,
494                 int flags, void *ctx )
495 {
496         char *saslname = in->bv_val;
497         SaslRegexp_t *reg;
498         regmatch_t sr_strings[SASLREGEX_REPLACE];       /* strings matching $1,$2 ... */
499         int i;
500
501         memset( out, 0, sizeof( *out ) );
502
503 #ifdef NEW_LOGGING
504         LDAP_LOG( TRANSPORT, ENTRY, 
505                 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
506 #else
507         Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
508            saslname, 0, 0 );
509 #endif
510
511         if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
512                 return( 0 );
513         }
514
515         /* Match the normalized SASL name to the saslregexp patterns */
516         for( reg = SaslRegexp,i=0;  i<nSaslRegexp;  i++,reg++ ) {
517                 if ( regexec( &reg->sr_workspace, saslname, SASLREGEX_REPLACE,
518                   sr_strings, 0)  == 0 )
519                         break;
520         }
521
522         if( i >= nSaslRegexp ) return( 0 );
523
524         /*
525          * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
526          * replace pattern of the form "x$1y$2z". The returned string needs
527          * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
528          */
529         slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
530                 sr_strings, saslname, out, ctx );
531
532 #ifdef NEW_LOGGING
533         LDAP_LOG( TRANSPORT, ENTRY, 
534                 "slap_sasl_regexp: converted SASL name to %s\n",
535                 out->bv_len ? out->bv_val : "", 0, 0 );
536 #else
537         Debug( LDAP_DEBUG_TRACE,
538                 "slap_sasl_regexp: converted SASL name to %s\n",
539                 out->bv_len ? out->bv_val : "", 0, 0 );
540 #endif
541
542         return( 1 );
543 }
544
545 /* This callback actually does some work...*/
546 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
547 {
548         struct berval *ndn = o->o_callback->sc_private;
549
550         if (rs->sr_type != REP_SEARCH) return 0;
551
552         /* We only want to be called once */
553         if( ndn->bv_val ) {
554                 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
555                 ndn->bv_val = NULL;
556
557 #ifdef NEW_LOGGING
558                 LDAP_LOG( TRANSPORT, DETAIL1,
559                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
560 #else
561                 Debug( LDAP_DEBUG_TRACE,
562                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
563 #endif
564                 return -1;
565         }
566
567         ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
568         return 0;
569 }
570
571
572 typedef struct smatch_info {
573         struct berval *dn;
574         int match;
575 } smatch_info;
576
577 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
578 {
579         smatch_info *sm = o->o_callback->sc_private;
580
581         if (rs->sr_type != REP_SEARCH) return 0;
582
583         if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
584                 sm->match = 1;
585                 return -1;      /* short-circuit the search */
586         }
587
588         return 1;
589 }
590
591 /*
592  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
593  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
594  * the rule must be used as an internal search for entries. If that search
595  * returns the *assertDN entry, the match is successful.
596  *
597  * The assertDN should not have the dn: prefix
598  */
599
600 static
601 int slap_sasl_match( Operation *opx, struct berval *rule,
602         struct berval *assertDN, struct berval *authc )
603 {
604         int rc; 
605         regex_t reg;
606         smatch_info sm;
607         slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
608         Operation op = {0};
609         SlapReply rs = {REP_RESULT};
610
611 #ifdef NEW_LOGGING
612         LDAP_LOG( TRANSPORT, ENTRY, 
613                 "slap_sasl_match: comparing DN %s to rule %s\n", 
614                 assertDN->bv_val, rule->bv_val,0 );
615 #else
616         Debug( LDAP_DEBUG_TRACE,
617            "===>slap_sasl_match: comparing DN %s to rule %s\n",
618                 assertDN->bv_val, rule->bv_val, 0 );
619 #endif
620
621         rc = slap_parseURI( opx, rule, &op.o_req_dn,
622                 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
623                 &op.ors_filterstr );
624         if( rc != LDAP_SUCCESS ) goto CONCLUDED;
625
626         /* Massive shortcut: search scope == base */
627         switch ( op.oq_search.rs_scope ) {
628         case LDAP_SCOPE_BASE:
629         case LDAP_X_SCOPE_EXACT:
630 exact_match:
631                 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
632                         rc = LDAP_SUCCESS;
633                 } else {
634                         rc = LDAP_INAPPROPRIATE_AUTH;
635                 }
636                 goto CONCLUDED;
637
638         case LDAP_X_SCOPE_CHILDREN:
639         case LDAP_X_SCOPE_SUBTREE:
640         {
641                 int     d = assertDN->bv_len - op.o_req_ndn.bv_len;
642
643                 rc = LDAP_INAPPROPRIATE_AUTH;
644
645                 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
646                         goto exact_match;
647
648                 } else if ( d > 0 ) {
649                         struct berval bv = { op.o_req_ndn.bv_len, assertDN->bv_val + d };
650
651                         if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
652                                 rc = LDAP_SUCCESS;
653                         }
654                 }
655                 goto CONCLUDED;
656         }
657
658         case LDAP_X_SCOPE_REGEX:
659                 rc = regcomp(&reg, op.o_req_ndn.bv_val,
660                         REG_EXTENDED|REG_ICASE|REG_NOSUB);
661                 if ( rc == 0 ) {
662                         rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
663                         regfree( &reg );
664                 }
665                 if ( rc == 0 ) {
666                         rc = LDAP_SUCCESS;
667                 } else {
668                         rc = LDAP_INAPPROPRIATE_AUTH;
669                 }
670                 goto CONCLUDED;
671
672         default:
673                 break;
674         }
675
676         /* Must run an internal search. */
677         if ( op.oq_search.rs_filter == NULL ) {
678                 rc = LDAP_FILTER_ERROR;
679                 goto CONCLUDED;
680         }
681
682 #ifdef NEW_LOGGING
683         LDAP_LOG( TRANSPORT, DETAIL1, 
684                 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
685                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
686 #else
687         Debug( LDAP_DEBUG_TRACE,
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 #endif
691
692         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
693         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
694                 rc = LDAP_INAPPROPRIATE_AUTH;
695                 goto CONCLUDED;
696         }
697
698         sm.dn = assertDN;
699         sm.match = 0;
700         cb.sc_private = &sm;
701
702         op.o_tag = LDAP_REQ_SEARCH;
703         op.o_protocol = LDAP_VERSION3;
704         op.o_ndn = *authc;
705         op.o_callback = &cb;
706         op.o_time = slap_get_time();
707         op.o_do_not_cache = 1;
708         op.o_is_auth_check = 1;
709         op.o_threadctx = opx->o_threadctx;
710         op.o_tmpmemctx = opx->o_tmpmemctx;
711         op.o_tmpmfuncs = opx->o_tmpmfuncs;
712 #ifdef LDAP_SLAPI
713         op.o_pb = opx->o_pb;
714 #endif
715         op.o_conn = opx->o_conn;
716         op.o_connid = opx->o_connid;
717         op.o_req_dn = op.o_req_ndn;
718
719         op.o_bd->be_search( &op, &rs );
720
721         if (sm.match) {
722                 rc = LDAP_SUCCESS;
723         } else {
724                 rc = LDAP_INAPPROPRIATE_AUTH;
725         }
726
727 CONCLUDED:
728         if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
729         if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
730         if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
731         if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
732
733 #ifdef NEW_LOGGING
734         LDAP_LOG( TRANSPORT, ENTRY, 
735                 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
736 #else
737         Debug( LDAP_DEBUG_TRACE,
738            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
739 #endif
740
741         return( rc );
742 }
743
744
745 /*
746  * This function answers the question, "Can this ID authorize to that ID?",
747  * based on authorization rules. The rules are stored in the *searchDN, in the
748  * attribute named by *attr. If any of those rules map to the *assertDN, the
749  * authorization is approved.
750  *
751  * The DNs should not have the dn: prefix
752  */
753 static int
754 slap_sasl_check_authz( Operation *op,
755         struct berval *searchDN,
756         struct berval *assertDN,
757         AttributeDescription *ad,
758         struct berval *authc )
759 {
760         int i, rc;
761         BerVarray vals=NULL;
762
763 #ifdef NEW_LOGGING
764         LDAP_LOG( TRANSPORT, ENTRY, 
765                 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
766             assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
767 #else
768         Debug( LDAP_DEBUG_TRACE,
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 #endif
772
773         rc = backend_attribute( op, NULL,
774                 searchDN, ad, &vals );
775         if( rc != LDAP_SUCCESS ) goto COMPLETE;
776
777         /* Check if the *assertDN matches any **vals */
778         if( vals != NULL ) {
779                 for( i=0; vals[i].bv_val != NULL; i++ ) {
780                         rc = slap_sasl_match( op, &vals[i], assertDN, authc );
781                         if ( rc == LDAP_SUCCESS ) goto COMPLETE;
782                 }
783         }
784         rc = LDAP_INAPPROPRIATE_AUTH;
785
786 COMPLETE:
787         if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
788
789 #ifdef NEW_LOGGING
790         LDAP_LOG( TRANSPORT, RESULTS, 
791                 "slap_sasl_check_authz: %s check returning %s\n", 
792                 ad->ad_cname.bv_val, rc, 0 );
793 #else
794         Debug( LDAP_DEBUG_TRACE,
795            "<==slap_sasl_check_authz: %s check returning %d\n",
796                 ad->ad_cname.bv_val, rc, 0);
797 #endif
798
799         return( rc );
800 }
801
802 /*
803  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
804  * return the LDAP DN to which it matches. The SASL regexp rules in the config
805  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
806  * search with scope=base), just return the URI (or its searchbase). Otherwise
807  * an internal search must be done, and if that search returns exactly one
808  * entry, return the DN of that one entry.
809  */
810 void slap_sasl2dn( Operation *opx,
811         struct berval *saslname, struct berval *sasldn, int flags )
812 {
813         int rc;
814         slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
815         Operation op = {0};
816         SlapReply rs = {REP_RESULT};
817         struct berval regout = { 0, NULL };
818
819 #ifdef NEW_LOGGING
820         LDAP_LOG( TRANSPORT, ENTRY, 
821                 "slap_sasl2dn: converting SASL name %s to DN.\n",
822                 saslname->bv_val, 0, 0 );
823 #else
824         Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
825                 "converting SASL name %s to a DN\n",
826                 saslname->bv_val, 0,0 );
827 #endif
828
829         sasldn->bv_val = NULL;
830         sasldn->bv_len = 0;
831         cb.sc_private = sasldn;
832
833         /* Convert the SASL name into a minimal URI */
834         if( !slap_sasl_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
835                 goto FINISHED;
836         }
837
838         rc = slap_parseURI( opx, &regout, &op.o_req_dn,
839                 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
840                 &op.ors_filterstr );
841         if( regout.bv_val ) sl_free( regout.bv_val, opx->o_tmpmemctx );
842         if( rc != LDAP_SUCCESS ) {
843                 goto FINISHED;
844         }
845
846         /* Must do an internal search */
847         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
848
849         /* Massive shortcut: search scope == base */
850         switch ( op.oq_search.rs_scope ) {
851         case LDAP_SCOPE_BASE:
852         case LDAP_X_SCOPE_EXACT:
853                 *sasldn = op.o_req_ndn;
854                 op.o_req_ndn.bv_len = 0;
855                 op.o_req_ndn.bv_val = NULL;
856                 /* intentionally continue to next case */
857
858         case LDAP_X_SCOPE_REGEX:
859         case LDAP_X_SCOPE_SUBTREE:
860         case LDAP_X_SCOPE_CHILDREN:
861                 /* correctly parsed, but illegal */
862                 goto FINISHED;
863
864         case LDAP_SCOPE_ONELEVEL:
865         case LDAP_SCOPE_SUBTREE:
866                 /* do a search */
867                 break;
868
869         default:
870                 /* catch unhandled cases (there shouldn't be) */
871                 assert( 0 );
872         }
873
874 #ifdef NEW_LOGGING
875         LDAP_LOG( TRANSPORT, DETAIL1, 
876                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
877                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
878 #else
879         Debug( LDAP_DEBUG_TRACE,
880                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
881                 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
882 #endif
883
884         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
885                 goto FINISHED;
886         }
887
888         op.o_conn = opx->o_conn;
889         op.o_connid = opx->o_connid;
890         op.o_tag = LDAP_REQ_SEARCH;
891         op.o_protocol = LDAP_VERSION3;
892         op.o_ndn = opx->o_conn->c_ndn;
893         op.o_callback = &cb;
894         op.o_time = slap_get_time();
895         op.o_do_not_cache = 1;
896         op.o_is_auth_check = 1;
897         op.o_threadctx = opx->o_threadctx;
898         op.o_tmpmemctx = opx->o_tmpmemctx;
899         op.o_tmpmfuncs = opx->o_tmpmfuncs;
900 #ifdef LDAP_SLAPI
901         op.o_pb = opx->o_pb;
902 #endif
903         op.oq_search.rs_deref = LDAP_DEREF_NEVER;
904         op.oq_search.rs_slimit = 1;
905         op.oq_search.rs_attrsonly = 1;
906         op.o_req_dn = op.o_req_ndn;
907
908         op.o_bd->be_search( &op, &rs );
909         
910 FINISHED:
911         if( sasldn->bv_len ) {
912                 opx->o_conn->c_authz_backend = op.o_bd;
913         }
914         if( op.o_req_dn.bv_len ) ch_free( op.o_req_dn.bv_val );
915         if( op.o_req_ndn.bv_len ) sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
916         if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
917         if( op.ors_filterstr.bv_len ) ch_free( op.ors_filterstr.bv_val );
918
919 #ifdef NEW_LOGGING
920         LDAP_LOG( TRANSPORT, ENTRY, 
921                 "slap_sasl2dn: Converted SASL name to %s\n",
922                 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
923 #else
924         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
925                 sasldn->bv_len ? sasldn->bv_val : "<nothing>", 0, 0 );
926 #endif
927
928         return;
929 }
930
931
932 /* Check if a bind can SASL authorize to another identity.
933  * The DNs should not have the dn: prefix
934  */
935
936 int slap_sasl_authorized( Operation *op,
937         struct berval *authcDN, struct berval *authzDN )
938 {
939         int rc = LDAP_INAPPROPRIATE_AUTH;
940
941         /* User binding as anonymous */
942         if ( authzDN == NULL ) {
943                 rc = LDAP_SUCCESS;
944                 goto DONE;
945         }
946
947 #ifdef NEW_LOGGING
948         LDAP_LOG( TRANSPORT, ENTRY, 
949                 "slap_sasl_authorized: can %s become %s?\n", 
950                 authcDN->bv_val, authzDN->bv_val, 0 );
951 #else
952         Debug( LDAP_DEBUG_TRACE,
953            "==>slap_sasl_authorized: can %s become %s?\n",
954                 authcDN->bv_val, authzDN->bv_val, 0 );
955 #endif
956
957         /* If person is authorizing to self, succeed */
958         if ( dn_match( authcDN, authzDN ) ) {
959                 rc = LDAP_SUCCESS;
960                 goto DONE;
961         }
962
963         /* Allow the manager to authorize as any DN. */
964         if( op->o_conn->c_authz_backend && be_isroot( op->o_conn->c_authz_backend, authcDN )) {
965                 rc = LDAP_SUCCESS;
966                 goto DONE;
967         }
968
969         /* Check source rules */
970         if( authz_policy & SASL_AUTHZ_TO ) {
971                 rc = slap_sasl_check_authz( op, authcDN, authzDN,
972                         slap_schema.si_ad_saslAuthzTo, authcDN );
973                 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
974                         goto DONE;
975                 }
976         }
977
978         /* Check destination rules */
979         if( authz_policy & SASL_AUTHZ_FROM ) {
980                 rc = slap_sasl_check_authz( op, authzDN, authcDN,
981                         slap_schema.si_ad_saslAuthzFrom, authcDN );
982                 if( rc == LDAP_SUCCESS ) {
983                         goto DONE;
984                 }
985         }
986
987         rc = LDAP_INAPPROPRIATE_AUTH;
988
989 DONE:
990
991 #ifdef NEW_LOGGING
992         LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
993 #else
994         Debug( LDAP_DEBUG_TRACE,
995                 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );
996 #endif
997
998         return( rc );
999 }