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