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