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