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