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