]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
Fix build errors
[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, *searchbase=NULL, *DN=NULL;
300         int rc, scope;
301         Backend *be;
302         Filter *filter=NULL;
303         Connection *conn=NULL;
304         LDAP *client=NULL;
305         LDAPMessage *res=NULL, *msg;
306
307
308 #ifdef NEW_LOGGING
309         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
310                    "slap_sasl2dn: converting SASL name %s to DN.\n", saslname ));
311 #else
312         Debug( LDAP_DEBUG_TRACE,
313           "==>slap_sasl2dn: Converting SASL name %s to a DN\n", saslname, 0,0 );
314 #endif
315
316
317         /* Convert the SASL name into an LDAP URI */
318         uri = slap_sasl_regexp( saslname );
319         if( uri == NULL )
320                 goto FINISHED;
321
322         rc = slap_parseURI( uri, &searchbase, &scope, &filter );
323         if( rc )
324                 goto FINISHED;
325
326         /* Massive shortcut: search scope == base */
327         if( scope == LDAP_SCOPE_BASE ) {
328                 DN = ch_strdup( searchbase );
329                 goto FINISHED;
330         }
331
332         /* Must do an internal search */
333
334 #ifdef NEW_LOGGING
335         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
336                    "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
337                    searchbase, scope ));
338 #else
339         Debug( LDAP_DEBUG_TRACE,
340            "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
341            searchbase, scope, 0 );
342 #endif
343
344
345         be = select_backend( searchbase, 0 );
346         if(( be == NULL ) || ( be->be_search == NULL))
347                 goto FINISHED;
348         searchbase = suffix_alias( be, searchbase );
349
350         rc = connection_internal_open( &conn, &client, saslname );
351         if( rc != LDAP_SUCCESS )
352                 goto FINISHED;
353
354         (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
355            scope, /*deref=*/1, /*sizelimit=*/1, /*time=*/0, filter, /*fstr=*/NULL,
356            /*attrs=*/NULL, /*attrsonly=*/0 );
357
358
359         /* Read the client side of the internal search */
360         rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
361         if( rc == -1 )
362                 goto FINISHED;
363
364         /* Make sure exactly one entry was returned */
365         rc = ldap_count_entries( client, res );
366 #ifdef NEW_LOGGING
367         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
368                    "slap_sasl2dn: search DN returned %d entries\n", rc ));
369 #else
370         Debug( LDAP_DEBUG_TRACE,
371            "slap_sasl2dn: search DN returned %d entries\n", rc,0,0 );
372 #endif
373
374         if( rc != 1 )
375                 goto FINISHED;
376
377         msg = ldap_first_entry( client, res );
378         DN = ldap_get_dn( client, msg );
379
380 FINISHED:
381         if( searchbase ) ch_free( searchbase );
382         if( filter ) filter_free( filter );
383         if( uri ) ch_free( uri );
384         if( conn ) connection_internal_close( conn );
385         if( res ) ldap_msgfree( res );
386         if( client  ) ldap_unbind( client );
387         if( DN ) dn_normalize( DN );
388 #ifdef NEW_LOGGING
389         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
390                    "slap_sasl2dn: Converted SASL name to %s\n", DN ? DN : "<nothing>" ));
391 #else
392         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
393            DN ? DN : "<nothing>", 0, 0 );
394 #endif
395
396         return( DN );
397 }
398
399
400
401
402
403 /*
404  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
405  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
406  * the rule must be used as an internal search for entries. If that search
407  * returns the *assertDN entry, the match is successful.
408  *
409  * The assertDN should not have the dn: prefix
410  */
411
412 static
413 int slap_sasl_match( char *rule, char *assertDN, char *authc )
414 {
415         char *searchbase=NULL, *dn=NULL;
416         int rc, scope;
417         Backend *be;
418         Filter *filter=NULL;
419         Connection *conn=NULL;
420         LDAP *client=NULL;
421         LDAPMessage *res=NULL, *msg;
422         regex_t reg;
423
424
425 #ifdef NEW_LOGGING
426         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
427                    "slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule ));
428 #else
429         Debug( LDAP_DEBUG_TRACE,
430            "===>slap_sasl_match: comparing DN %s to rule %s\n", assertDN, rule, 0 );
431 #endif
432
433
434         rc = slap_parseURI( rule, &searchbase, &scope, &filter );
435         if( rc != LDAP_SUCCESS )
436                 goto CONCLUDED;
437
438         /* Massive shortcut: search scope == base */
439         if( scope == LDAP_SCOPE_BASE ) {
440                 dn_normalize( searchbase );
441                 rc = regcomp(&reg, searchbase, REG_EXTENDED|REG_ICASE|REG_NOSUB);
442                 if ( rc == 0 ) {
443                         rc = regexec(&reg, assertDN, 0, NULL, 0);
444                         regfree( &reg );
445                 }
446                 if ( rc == 0 )
447                         rc = LDAP_SUCCESS;
448                 else
449                         rc = LDAP_INAPPROPRIATE_AUTH;
450                 goto CONCLUDED;
451         }
452
453         /* Must run an internal search. */
454
455 #ifdef NEW_LOGGING
456         LDAP_LOG(( "sasl", LDAP_LEVEL_DETAIL1,
457                    "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
458                    searchbase, scope ));
459 #else
460         Debug( LDAP_DEBUG_TRACE,
461            "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
462            searchbase, scope, 0 );
463 #endif
464
465
466         be = select_backend( searchbase, 0 );
467         if(( be == NULL ) || ( be->be_search == NULL)) {
468                 rc = LDAP_INAPPROPRIATE_AUTH;
469                 goto CONCLUDED;
470         }
471         searchbase = suffix_alias( be, searchbase );
472
473         /* Make an internal connection on which to run the search */
474         rc = connection_internal_open( &conn, &client, authc );
475         if( rc != LDAP_SUCCESS )
476                 goto CONCLUDED;
477
478         (*be->be_search)( be, conn, conn->c_ops, /*base=*/NULL, searchbase,
479            scope, /*deref=*/1, /*sizelimit=*/0, /*time=*/0, filter, /*fstr=*/NULL,
480            /*attrs=*/NULL, /*attrsonly=*/0 );
481
482
483         /* On the client side of the internal search, read the results. Check
484            if the assertDN matches any of the DN's returned by the search */
485         rc = ldap_result( client, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &res );
486         if( rc == -1 )
487                 goto CONCLUDED;
488
489         for( msg=ldap_first_entry( client, res );
490               msg;
491               msg=ldap_next_entry( client, msg ) )   {
492                 dn = ldap_get_dn( client, msg );
493                 dn_normalize( dn );
494                 rc = strcmp( dn, assertDN );
495                 ch_free( dn );
496                 if( rc == 0 ) {
497                         rc = LDAP_SUCCESS;
498                         goto CONCLUDED;
499                 }
500         }
501         rc = LDAP_INAPPROPRIATE_AUTH;
502
503 CONCLUDED:
504         if( searchbase ) ch_free( searchbase );
505         if( filter ) filter_free( filter );
506         if( conn ) connection_internal_close( conn );
507         if( res ) ldap_msgfree( res );
508         if( client  ) ldap_unbind( client );
509 #ifdef NEW_LOGGING
510         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
511                    "slap_sasl_match: comparison returned %d\n", rc ));
512 #else
513         Debug( LDAP_DEBUG_TRACE,
514            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
515 #endif
516
517         return( rc );
518 }
519
520
521
522
523
524 /*
525  * This function answers the question, "Can this ID authorize to that ID?",
526  * based on authorization rules. The rules are stored in the *searchDN, in the
527  * attribute named by *attr. If any of those rules map to the *assertDN, the
528  * authorization is approved.
529  *
530  * DN's passed in should have a dn: prefix
531  */
532
533 static int
534 slap_sasl_check_authz(char *searchDN, char *assertDN, char *attr, char *authc)
535 {
536         const char *errmsg;
537         int i, rc;
538         struct berval **vals=NULL;
539         AttributeDescription *ad=NULL;
540
541
542 #ifdef NEW_LOGGING
543         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
544                    "slap_sasl_check_authz: does %s match %s rule in %s?\n",
545                    assertDN, attr, searchDN ));
546 #else
547         Debug( LDAP_DEBUG_TRACE,
548            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
549            assertDN, attr, searchDN);
550 #endif
551
552         rc = slap_str2ad( attr, &ad, &errmsg );
553         if( rc != LDAP_SUCCESS )
554                 goto COMPLETE;
555
556         rc = backend_attribute( NULL, NULL, NULL, NULL, searchDN+3, ad, &vals );
557         if( rc != LDAP_SUCCESS )
558                 goto COMPLETE;
559
560         /* Check if the *assertDN matches any **vals */
561         for( i=0; vals[i] != NULL; i++ ) {
562                 rc = slap_sasl_match( vals[i]->bv_val, assertDN+3, authc );
563                 if ( rc == LDAP_SUCCESS )
564                         goto COMPLETE;
565         }
566         rc = LDAP_INAPPROPRIATE_AUTH;
567
568 COMPLETE:
569         if( vals ) ber_bvecfree( vals );
570         if( ad ) ad_free( ad, 1 );
571
572 #ifdef NEW_LOGGING
573         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
574                    "slap_sasl_check_authz: %s check returning %s\n", attr, rc ));
575 #else
576         Debug( LDAP_DEBUG_TRACE,
577            "<==slap_sasl_check_authz: %s check returning %d\n", attr, rc, 0);
578 #endif
579
580         return( rc );
581 }
582
583
584
585 #endif  /* HAVE_CYRUS_SASL */
586
587
588
589
590
591 /* Check if a bind can SASL authorize to another identity.
592    Accepts authorization DN's with "dn:" prefix */
593
594 int slap_sasl_authorized( char *authcDN, char *authzDN )
595 {
596         int rc;
597
598 #ifdef HAVE_CYRUS_SASL
599         /* User binding as anonymous */
600         if ( authzDN == NULL ) {
601                 rc = LDAP_SUCCESS;
602                 goto DONE;
603         }
604
605 #ifdef NEW_LOGGING
606         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
607                    "slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN ));
608 #else
609         Debug( LDAP_DEBUG_TRACE,
610            "==>slap_sasl_authorized: can %s become %s?\n", authcDN, authzDN, 0 );
611 #endif
612
613
614         /* If person is authorizing to self, succeed */
615         if ( !strcmp( authcDN, authzDN ) ) {
616                 rc = LDAP_SUCCESS;
617                 goto DONE;
618         }
619
620         /* Check source rules */
621         rc = slap_sasl_check_authz( authcDN, authzDN, SASL_AUTHZ_SOURCE_ATTR,
622            authcDN );
623         if( rc == LDAP_SUCCESS )
624                 goto DONE;
625
626         /* Check destination rules */
627         rc = slap_sasl_check_authz( authzDN, authcDN, SASL_AUTHZ_DEST_ATTR,
628            authcDN );
629         if( rc == LDAP_SUCCESS )
630                 goto DONE;
631
632 #endif
633         rc = LDAP_INAPPROPRIATE_AUTH;
634
635 DONE:
636 #ifdef NEW_LOGGING
637         LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
638                    "slap_sasl_authorized: return %d\n", rc ));
639 #else
640         Debug( LDAP_DEBUG_TRACE, "<== slap_sasl_authorized: return %d\n",rc,0,0 );
641 #endif
642
643         return( rc );
644 }