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