]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
Changed ma_rule_text to struct berval.
[openldap] / servers / slapd / saslauthz.c
1 /*
2  * Copyright (c) 2000, Mark Adamson, Carnegie Mellon.  All rights reserved.
3  * This software is not subject to any license of Carnegie Mellon University.
4  *
5  * Redistribution and use in source and binary forms are permitted without 
6  * restriction or fee of any kind as long as this notice is preserved.
7  *
8  * The name "Carnegie Mellon" must not be used to endorse or promote
9  * products derived from this software without prior written permission.
10  *
11  */
12
13 #include "portable.h"
14
15 #include <ac/stdlib.h>
16 #include <stdio.h>
17
18 #include <ac/string.h>
19
20 #include "slap.h"
21
22 #ifdef HAVE_CYRUS_SASL
23 #include <limits.h>
24 #include <sasl.h>
25 #include <ldap_pvt.h>
26 #endif
27
28 /* URI format:  ldap://<host>/<base>[?[<attrs>][?[<scope>][?[<filter>]]]]   */
29
30 int slap_parseURI( char *uri, char **searchbase, int *scope, Filter **filter )
31 {
32         char *start, *end;
33
34
35         assert( uri != NULL );
36         *searchbase = NULL;
37         *scope = -1;
38         *filter = NULL;
39
40 #ifdef NEW_LOGGING
41         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
42                    "slap_parseURI: parsing %s\n", uri ));
43 #else
44         Debug( LDAP_DEBUG_TRACE, "slap_parseURI: parsing %s\n", uri, 0, 0 );
45 #endif
46
47
48         /* If it does not look like a URI, assume it is a DN */
49         if( !strncasecmp( uri, "dn:", 3 ) ) {
50                 uri += 3;
51                 uri += strspn( uri, " " );
52                 *searchbase = ch_strdup( uri );
53                 dn_normalize( *searchbase );
54                 *scope = LDAP_SCOPE_BASE;
55                 return( LDAP_SUCCESS );
56         }
57         if( strncasecmp( uri, "ldap://", 7 ) ) {
58                 *searchbase = ch_strdup( uri );
59                 dn_normalize( *searchbase );
60                 *scope = LDAP_SCOPE_BASE;
61                 return( LDAP_SUCCESS );
62         }
63
64         end = strchr( uri + 7, '/' );
65         if ( end == NULL )
66                 return( LDAP_PROTOCOL_ERROR );
67
68         /* could check the hostname here */
69
70         /* Grab the searchbase */
71         start = end+1;
72         end = strchr( start, '?' );
73         if( end == NULL ) {
74                 *searchbase = ch_strdup( start );
75                 dn_normalize( *searchbase );
76                 return( LDAP_SUCCESS );
77         }
78         *end = '\0';
79         *searchbase = ch_strdup( start );
80         *end = '?';
81         dn_normalize( *searchbase );
82
83         /* Skip the attrs */
84         start = end+1;
85         end = strchr( start, '?' );
86         if( end == NULL ) {
87                 return( LDAP_SUCCESS );
88         }
89
90         /* Grab the scope */
91         start = end+1;
92         if( !strncasecmp( start, "base?", 5 )) {
93                 *scope = LDAP_SCOPE_BASE;
94                 start += 5;
95         }
96         else if( !strncasecmp( start, "one?", 4 )) {
97                 *scope = LDAP_SCOPE_ONELEVEL;
98                 start += 4;
99         }
100         else if( !strncasecmp( start, "sub?", 3 )) {
101                 *scope = LDAP_SCOPE_SUBTREE;
102                 start += 4;
103         }
104         else {
105                 ch_free( *searchbase );
106                 *searchbase = NULL;
107                 return( LDAP_PROTOCOL_ERROR );
108         }
109
110         /* Grab the filter */
111         *filter = str2filter( start );
112
113         return( LDAP_SUCCESS );
114 }
115
116
117
118
119
120 int slap_sasl_regexp_config( const char *match, const char *replace )
121 {
122 #ifdef HAVE_CYRUS_SASL
123         const char *c;
124         int rc, n;
125         SaslRegexp_t *reg;
126
127         SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
128           (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
129         reg = &( SaslRegexp[nSaslRegexp] );
130         reg->match = ch_strdup( match );
131         reg->replace = ch_strdup( replace );
132         dn_normalize( reg->match );
133         dn_normalize( reg->replace );
134
135         /* Precompile matching pattern */
136         rc = regcomp( &reg->workspace, reg->match, REG_EXTENDED|REG_ICASE );
137         if ( rc ) {
138 #ifdef NEW_LOGGING
139                 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
140                            "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
141                            reg->match ));
142 #else
143                 Debug( LDAP_DEBUG_ANY,
144                 "SASL match pattern %s could not be compiled by regexp engine\n",
145                 reg->match, 0, 0 );
146 #endif
147
148                 return( LDAP_OPERATIONS_ERROR );
149         }
150
151         /* Precompile replace pattern. Find the $<n> placeholders */
152         reg->offset[0] = -2;
153         n = 1;
154         for ( c = reg->replace;  *c;  c++ ) {
155                 if ( *c == '\\' ) {
156                         c++;
157                         continue;
158                 }
159                 if ( *c == '$' ) {
160                         if ( n == SASLREGEX_REPLACE ) {
161 #ifdef NEW_LOGGING
162                                 LDAP_LOG(( "sasl", LDAP_LEVEL_ERR,
163                                            "slap_sasl_regexp_config: \"%s\" has too many $n placeholders (max %d)\n",
164                                            reg->replace, SASLREGEX_REPLACE ));
165 #else
166                                 Debug( LDAP_DEBUG_ANY,
167                                    "SASL replace pattern %s has too many $n placeholders (max %d)\n",
168                                    reg->replace, SASLREGEX_REPLACE, 0 );
169 #endif
170
171                                 return( LDAP_OPERATIONS_ERROR );
172                         }
173                         reg->offset[n] = c - reg->replace;
174                         n++;
175                 }
176         }
177
178         /* Final placeholder, after the last $n */
179         reg->offset[n] = c - reg->replace;
180         n++;
181         reg->offset[n] = -1;
182
183         nSaslRegexp++;
184 #endif
185         return( LDAP_SUCCESS );
186 }
187
188
189
190
191
192 #ifdef HAVE_CYRUS_SASL
193
194
195
196 /* Take the passed in SASL name and attempt to convert it into an
197    LDAP URI to find the matching LDAP entry, using the pattern matching
198    strings given in the saslregexp config file directive(s) */
199 static
200 char *slap_sasl_regexp( char *saslname )
201 {
202         char *uri=NULL;
203         int i, n, len, insert;
204         SaslRegexp_t *reg;
205
206
207 #ifdef NEW_LOGGING
208         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
209                    "slap_sasl_regexp: converting SASL name %s\n", saslname ));
210 #else
211         Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
212            saslname, 0, 0 );
213 #endif
214
215         if (( saslname == NULL ) || ( nSaslRegexp == 0 ))
216                 return( NULL );
217
218         /* Match the normalized SASL name to the saslregexp patterns */
219         for( reg = SaslRegexp,i=0;  i<nSaslRegexp;  i++,reg++ ) {
220                 if ( regexec( &reg->workspace, saslname, SASLREGEX_REPLACE,
221                   reg->strings, 0)  == 0 )
222                         break;
223         }
224
225         if( i >= nSaslRegexp )
226                 return( NULL );
227
228         /*
229          * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
230          * replace pattern of the form "x$1y$2z". The returned string needs
231          * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
232          */
233
234
235         /* Get the total length of the final URI */
236
237         n=1;
238         len = 0;
239         while( reg->offset[n] >= 0 ) {
240                 /* Len of next section from replacement string (x,y,z above) */
241                 len += reg->offset[n] - reg->offset[n-1] - 2;
242                 if( reg->offset[n+1] < 0)
243                         break;
244
245                 /* Len of string from saslname that matched next $i  (b,d above) */
246                 i = reg->replace[ reg->offset[n] + 1 ]  - '0';
247                 len += reg->strings[i].rm_eo - reg->strings[i].rm_so;
248                 n++;
249         }
250         uri = ch_malloc( len + 1 );
251
252         /* Fill in URI with replace string, replacing $i as we go */
253         n=1;
254         insert = 0;
255         while( reg->offset[n] >= 0) {
256                 /* Paste in next section from replacement string (x,y,z above) */
257                 len = reg->offset[n] - reg->offset[n-1] - 2;
258                 strncpy( uri+insert, reg->replace + reg->offset[n-1] + 2, len);
259                 insert += len;
260                 if( reg->offset[n+1] < 0)
261                         break;
262
263                 /* Paste in string from saslname that matched next $i  (b,d above) */
264                 i = reg->replace[ reg->offset[n] + 1 ]  - '0';
265                 len = reg->strings[i].rm_eo - reg->strings[i].rm_so;
266                 strncpy( uri+insert, saslname + reg->strings[i].rm_so, len );
267                 insert += len;
268
269                 n++;
270         }
271
272         uri[insert] = '\0';
273 #ifdef NEW_LOGGING
274         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
275                    "slap_sasl_regexp: converted SASL name to %s\n", uri ));
276 #else
277         Debug( LDAP_DEBUG_TRACE,
278            "slap_sasl_regexp: converted SASL name to %s\n", uri, 0, 0 );
279 #endif
280
281         return( uri );
282 }
283
284
285
286
287
288 /*
289  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
290  * return the LDAP DN to which it matches. The SASL regexp rules in the config
291  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
292  * search with scope=base), just return the URI (or its searchbase). Otherwise
293  * an internal search must be done, and if that search returns exactly one
294  * entry, return the DN of that one entry.
295  */
296
297 char *slap_sasl2dn( char *saslname )
298 {
299         char *uri=NULL, *DN=NULL;
300         struct berval searchbase = {0, NULL};
301         int rc, scope;
302         Backend *be;
303         Filter *filter=NULL;
304         Connection *conn=NULL;
305         LDAP *client=NULL;
306         LDAPMessage *res=NULL, *msg;
307
308
309 #ifdef NEW_LOGGING
310         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
311                    "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
312 #else
313         Debug( LDAP_DEBUG_TRACE,
314           "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
315 #endif
316
317
318         /* Convert the SASL name into an LDAP URI */
319         uri = slap_sasl_regexp( saslname );
320         if( uri == NULL )
321                 goto FINISHED;
322
323         rc = slap_parseURI( uri, &searchbase.bv_val, &scope, &filter );
324         if( rc )
325                 goto FINISHED;
326
327         searchbase.bv_len = strlen( searchbase.bv_val );
328         /* Massive shortcut: search scope == base */
329         if( scope == LDAP_SCOPE_BASE ) {
330                 DN = ch_strdup( searchbase.bv_val );
331                 goto FINISHED;
332         }
333
334         /* Must do an internal search */
335
336 #ifdef NEW_LOGGING
337         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
338                    "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
339                    searchbase.bv_val, scope ));
340 #else
341         Debug( LDAP_DEBUG_TRACE,
342            "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
343            searchbase.bv_val, scope, 0 );
344 #endif
345
346
347         be = select_backend( &searchbase, 0, 1 );
348         if(( be == NULL ) || ( be->be_search == NULL))
349                 goto FINISHED;
350         suffix_alias( be, &searchbase );
351
352         rc = connection_internal_open( &conn, &client, saslname );
353         if( rc != LDAP_SUCCESS )
354                 goto FINISHED;
355
356         (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase.bv_val,
357            scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
358            /*attrs=*/NULL, /*attrsonly=*/0 );
359
360
361         /* Read the client side of the internal search */
362         rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
363         if( rc == -1 )
364                 goto FINISHED;
365
366         /* Make sure exactly one entry was returned */
367         rc = ldap_count_entries( client, res );
368 #ifdef NEW_LOGGING
369         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
370                    "slap_sasl2dn: search DN returned %d entries\n", rc ));
371 #else
372         Debug( LDAP_DEBUG_TRACE,
373            "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
374 #endif
375
376         if( rc != 1 )
377                 goto FINISHED;
378
379         msg = ldap_first_entry( client, res );
380         DN = ldap_get_dn( client, msg );
381
382 FINISHED:
383         if( searchbase.bv_len ) ch_free( searchbase.bv_val );
384         if( filter ) filter_free( filter );
385         if( uri ) ch_free( uri );
386         if( conn ) connection_internal_close( conn );
387         if( res ) ldap_msgfree( res );
388         if( client  ) ldap_unbind( client );
389         if( DN ) dn_normalize( DN );
390 #ifdef NEW_LOGGING
391         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
392                    "slap_sasl2dn: Converted SASL name to %s\n", DN ? DN : "<nothing>" ));
393 #else
394         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
395            DN ? DN : "<nothing>", 0, 0 );
396 #endif
397
398         return( DN );
399 }
400
401
402
403
404
405 /*
406  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
407  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
408  * the rule must be used as an internal search for entries. If that search
409  * returns the *assertDN entry, the match is successful.
410  *
411  * The assertDN should not have the dn: prefix
412  */
413
414 static
415 int slap_sasl_match( char *rule, char *assertDN, char *authc )
416 {
417         char *dn=NULL;
418         struct berval searchbase = {0, NULL};
419         int rc, scope;
420         Backend *be;
421         Filter *filter=NULL;
422         Connection *conn=NULL;
423         LDAP *client=NULL;
424         LDAPMessage *res=NULL, *msg;
425         regex_t reg;
426
427
428 #ifdef NEW_LOGGING
429         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
430                    "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
431 #else
432         Debug( LDAP_DEBUG_TRACE,
433            "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
434 #endif
435
436
437         rc = slap_parseURI( rule, &searchbase.bv_val, &scope, &filter );
438         if( rc != LDAP_SUCCESS )
439                 goto CONCLUDED;
440
441         searchbase.bv_len = strlen( searchbase.bv_val );
442         /* Massive shortcut: search scope == base */
443         if( scope == LDAP_SCOPE_BASE ) {
444                 dn_normalize( searchbase.bv_val );
445                 rc = regcomp(&reg, searchbase.bv_val, REG_EXTENDED|REG_ICASE|REG_NOSUB);
446                 if ( rc == 0 ) {
447                         rc = regexec(&reg, assertDN, 0, NULL, 0);
448                         regfree( &reg );
449                 }
450                 if ( rc == 0 )
451                         rc = LDAP_SUCCESS;
452                 else
453                         rc = LDAP_INAPPROPRIATE_AUTH;
454                 goto CONCLUDED;
455         }
456
457         /* Must run an internal search. */
458
459 #ifdef NEW_LOGGING
460         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
461                    "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
462                    searchbase.bv_val, scope ));
463 #else
464         Debug( LDAP_DEBUG_TRACE,
465            "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
466            searchbase.bv_val, scope, 0 );
467 #endif
468
469
470         be = select_backend( &searchbase, 0, 1 );
471         if(( be == NULL ) || ( be->be_search == NULL)) {
472                 rc = LDAP_INAPPROPRIATE_AUTH;
473                 goto CONCLUDED;
474         }
475         suffix_alias( be, &searchbase );
476
477         /* Make an internal connection on which to run the search */
478         rc = connection_internal_open( &conn, &client, authc );
479         if( rc != LDAP_SUCCESS )
480                 goto CONCLUDED;
481
482         (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase.bv_val,
483            scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
484            /*attrs=*/NULL, /*attrsonly=*/0 );
485
486
487         /* On the client side of the internal search, read the results. Check
488            if the assertDN matches any of the DN's returned by the search */
489         rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
490         if( rc == -1 )
491                 goto CONCLUDED;
492
493         for( msg=ldap_first_entry( client, res );
494               msg;
495               msg=ldap_next_entry( client, msg ) )   {
496                 dn = ldap_get_dn( client, msg );
497                 dn_normalize( dn );
498                 rc = strcmp( dn, assertDN );
499                 ch_free( dn );
500                 if( rc == 0 ) {
501                         rc = LDAP_SUCCESS;
502                         goto CONCLUDED;
503                 }
504         }
505         rc = LDAP_INAPPROPRIATE_AUTH;
506
507 CONCLUDED:
508         if( searchbase.bv_len ) ch_free( searchbase.bv_val );
509         if( filter ) filter_free( filter );
510         if( conn ) connection_internal_close( conn );
511         if( res ) ldap_msgfree( res );
512         if( client  ) ldap_unbind( client );
513 #ifdef NEW_LOGGING
514         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
515                    "slap_sasl_match: comparison returned %d\n", rc ));
516 #else
517         Debug( LDAP_DEBUG_TRACE,
518            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
519 #endif
520
521         return( rc );
522 }
523
524
525
526
527
528 /*
529  * This function answers the question, "Can this ID authorize to that ID?",
530  * based on authorization rules. The rules are stored in the *searchDN, in the
531  * attribute named by *attr. If any of those rules map to the *assertDN, the
532  * authorization is approved.
533  *
534  * DN's passed in should have a dn: prefix
535  */
536
537 static int
538 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
539 {
540         const char *errmsg;
541         int i, rc;
542         struct berval **vals=NULL;
543         AttributeDescription *ad=NULL;
544         struct berval bv;
545
546
547 #ifdef NEW_LOGGING
548         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
549                    "slap_sasl_check_authz: does %s match %s rule in %s?\n",
550                    assertDN, attr, searchDN ));
551 #else
552         Debug( LDAP_DEBUG_TRACE,
553            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
554            assertDN, attr, searchDN);
555 #endif
556
557         rc = slap_str2ad( attr, &ad, &errmsg );
558         if( rc != LDAP_SUCCESS )
559                 goto COMPLETE;
560
561         bv.bv_val = searchDN+3;
562         bv.bv_len = strlen(bv.bv_val);
563         rc = backend_attribute( NULL, NULL, NULL, NULL, &bv, ad, &vals );
564         if( rc != LDAP_SUCCESS )
565                 goto COMPLETE;
566
567         /* Check if the *assertDN matches any **vals */
568         for( i=0; vals[i] != NULL; i++ ) {
569                 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
570                 if ( rc == LDAP_SUCCESS )
571                         goto COMPLETE;
572         }
573         rc = LDAP_INAPPROPRIATE_AUTH;
574
575 COMPLETE:
576         if( vals ) ber_bvecfree( vals );
577
578 #ifdef NEW_LOGGING
579         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
580                    "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
581 #else
582         Debug( LDAP_DEBUG_TRACE,
583            "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
584 #endif
585
586         return( rc );
587 }
588
589
590
591 #endif  /* HAVE_CYRUS_SASL */
592
593
594
595
596
597 /* Check if a bind can SASL authorize to another identity.
598    Accepts authorization DN's with "dn:" prefix */
599
600 int slap_sasl_authorized( char *authcDN, char *authzDN )
601 {
602         int rc = LDAP_INAPPROPRIATE_AUTH;
603
604 #ifdef HAVE_CYRUS_SASL
605         /* User binding as anonymous */
606         if ( authzDN == NULL ) {
607                 rc = LDAP_SUCCESS;
608                 goto DONE;
609         }
610
611 #ifdef NEW_LOGGING
612         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
613                    "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
614 #else
615         Debug( LDAP_DEBUG_TRACE,
616            "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
617 #endif
618
619
620         /* If person is authorizing to self, succeed */
621         if ( !strcmp( authcDN, authzDN ) ) {
622                 rc = LDAP_SUCCESS;
623                 goto DONE;
624         }
625
626         /* Check source rules */
627         rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
628            authcDN );
629         if( rc == LDAP_SUCCESS ) {
630                 goto DONE;
631         }
632
633         /* Check destination rules */
634         rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
635            authcDN );
636         if( rc == LDAP_SUCCESS ) {
637                 goto DONE;
638         }
639
640         rc = LDAP_INAPPROPRIATE_AUTH;
641
642 DONE:
643 #endif
644
645 #ifdef NEW_LOGGING
646         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
647                    "slap_sasl_authorized: return %d\n", rc ));
648 #else
649         Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );
650 #endif
651
652         return( rc );
653 }