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