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