]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
57b204ee73f9b886940d2dad593ed25871119ffe
[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
545
546 #ifdef NEW_LOGGING
547         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
548                    "slap_sasl_check_authz: does %s match %s rule in %s?\n",
549                    assertDN, attr, searchDN ));
550 #else
551         Debug( LDAP_DEBUG_TRACE,
552            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
553            assertDN, attr, searchDN);
554 #endif
555
556         rc = slap_str2ad( attr, &ad, &errmsg );
557         if( rc != LDAP_SUCCESS )
558                 goto COMPLETE;
559
560         rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN+3, ad, &vals );
561         if( rc != LDAP_SUCCESS )
562                 goto COMPLETE;
563
564         /* Check if the *assertDN matches any **vals */
565         for( i=0; vals[i] != NULL; i++ ) {
566                 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
567                 if ( rc == LDAP_SUCCESS )
568                         goto COMPLETE;
569         }
570         rc = LDAP_INAPPROPRIATE_AUTH;
571
572 COMPLETE:
573         if( vals ) ber_bvecfree( vals );
574
575 #ifdef NEW_LOGGING
576         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
577                    "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
578 #else
579         Debug( LDAP_DEBUG_TRACE,
580            "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
581 #endif
582
583         return( rc );
584 }
585
586
587
588 #endif  /* HAVE_CYRUS_SASL */
589
590
591
592
593
594 /* Check if a bind can SASL authorize to another identity.
595    Accepts authorization DN's with "dn:" prefix */
596
597 int slap_sasl_authorized( char *authcDN, char *authzDN )
598 {
599         int rc = LDAP_INAPPROPRIATE_AUTH;
600
601 #ifdef HAVE_CYRUS_SASL
602         /* User binding as anonymous */
603         if ( authzDN == NULL ) {
604                 rc = LDAP_SUCCESS;
605                 goto DONE;
606         }
607
608 #ifdef NEW_LOGGING
609         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
610                    "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
611 #else
612         Debug( LDAP_DEBUG_TRACE,
613            "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
614 #endif
615
616
617         /* If person is authorizing to self, succeed */
618         if ( !strcmp( authcDN, authzDN ) ) {
619                 rc = LDAP_SUCCESS;
620                 goto DONE;
621         }
622
623         /* Check source rules */
624         rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
625            authcDN );
626         if( rc == LDAP_SUCCESS ) {
627                 goto DONE;
628         }
629
630         /* Check destination rules */
631         rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
632            authcDN );
633         if( rc == LDAP_SUCCESS ) {
634                 goto DONE;
635         }
636
637         rc = LDAP_INAPPROPRIATE_AUTH;
638
639 DONE:
640 #endif
641
642 #ifdef NEW_LOGGING
643         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
644                    "slap_sasl_authorized: return %d\n", rc ));
645 #else
646         Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );
647 #endif
648
649         return( rc );
650 }