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