/* aclparse.c - routines to parse and check acl's */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 1998-2004 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ /* Portions Copyright (c) 1995 Regents of the University of Michigan. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that this notice is preserved and that due credit is given * to the University of Michigan at Ann Arbor. The name of the University * may not be used to endorse or promote products derived from this * software without specific prior written permission. This software * is provided ``as is'' without express or implied warranty. */ #include "portable.h" #include #include #include #include #include #include #include "slap.h" #include "lber_pvt.h" #include "lutil.h" static char *style_strings[] = { "regex", "expand", "base", "one", "subtree", "children", "attrof", "ip", "path", NULL }; static void split(char *line, int splitchar, char **left, char **right); static void access_append(Access **l, Access *a); static void acl_usage(void) LDAP_GCCATTR((noreturn)); static void acl_regex_normalized_dn(const char *src, struct berval *pat); #ifdef LDAP_DEBUG static void print_acl(Backend *be, AccessControl *a); static void print_access(Access *b); #endif #ifdef LDAP_DEVEL static int check_scope( BackendDB *be, AccessControl *a ); #endif /* LDAP_DEVEL */ static void regtest(const char *fname, int lineno, char *pat) { int e; regex_t re; char buf[512]; unsigned size; char *sp; char *dp; int flag; sp = pat; dp = buf; size = 0; buf[0] = '\0'; for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) { if (flag) { if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) { *dp++ = *sp; size++; } flag = 0; } else { if (*sp == '$') { flag = 1; } else { *dp++ = *sp; size++; } } } *dp = '\0'; if ( size >= (sizeof(buf)-1) ) { fprintf( stderr, "%s: line %d: regular expression \"%s\" too large\n", fname, lineno, pat ); acl_usage(); } if ((e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE))) { char error[512]; regerror(e, &re, error, sizeof(error)); fprintf( stderr, "%s: line %d: regular expression \"%s\" bad because of %s\n", fname, lineno, pat, error ); acl_usage(); } regfree(&re); } #ifdef LDAP_DEVEL /* * Experimental * * Check if the pattern of an ACL, if any, matches the scope * of the backend it is defined within. */ #define ACL_SCOPE_UNKNOWN (-2) #define ACL_SCOPE_ERR (-1) #define ACL_SCOPE_OK (0) #define ACL_SCOPE_PARTIAL (1) #define ACL_SCOPE_WARN (2) static int check_scope( BackendDB *be, AccessControl *a ) { int patlen; struct berval dn; dn = be->be_nsuffix[ 0 ]; if ( a->acl_dn_pat.bv_len || ( a->acl_dn_style != ACL_STYLE_REGEX ) ) { slap_style_t style = a->acl_dn_style; if ( style == ACL_STYLE_REGEX ) { char dnbuf[ SLAP_LDAPDN_MAXLEN + 2 ]; char rebuf[ SLAP_LDAPDN_MAXLEN + 1 ]; regex_t re; int rc; /* add trailing '$' */ AC_MEMCPY( dnbuf, be->be_nsuffix[ 0 ].bv_val, be->be_nsuffix[ 0 ].bv_len ); dnbuf[ be->be_nsuffix[ 0 ].bv_len ] = '$'; dnbuf[ be->be_nsuffix[ 0 ].bv_len + 1 ] = '\0'; if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) { return ACL_SCOPE_WARN; } /* remove trailing '$' */ AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, a->acl_dn_pat.bv_len + 1 ); if ( a->acl_dn_pat.bv_val[ a->acl_dn_pat.bv_len - 1 ] == '$' ) { rebuf[ a->acl_dn_pat.bv_len - 1 ] = '\0'; } /* not a clear indication of scoping error, though */ rc = regexec( &re, rebuf, 0, NULL, 0 ) ? ACL_SCOPE_WARN : ACL_SCOPE_OK; regfree( &re ); return rc; } patlen = a->acl_dn_pat.bv_len; /* If backend suffix is longer than pattern, * it is a potential mismatch (in the sense * that a superior naming context could * match */ if ( dn.bv_len > patlen ) { /* base is blatantly wrong */ if ( style == ACL_STYLE_BASE ) { return ACL_SCOPE_ERR; } /* one can be wrong if there is more * than one level between the suffix * and the pattern */ if ( style == ACL_STYLE_ONE ) { int rdnlen = -1, sep = 0; if ( patlen > 0 ) { if ( !DN_SEPARATOR( dn.bv_val[ dn.bv_len - patlen - 1 ] ) ) return ACL_SCOPE_ERR; sep = 1; } rdnlen = dn_rdnlen( NULL, &dn ); if ( rdnlen != dn.bv_len - patlen - sep ) return ACL_SCOPE_ERR; } /* if the trailing part doesn't match, * then it's an error */ if ( strcmp( a->acl_dn_pat.bv_val, &dn.bv_val[ dn.bv_len - patlen ] ) != 0 ) { return ACL_SCOPE_ERR; } return ACL_SCOPE_PARTIAL; } switch ( style ) { case ACL_STYLE_BASE: case ACL_STYLE_ONE: case ACL_STYLE_CHILDREN: case ACL_STYLE_SUBTREE: break; default: assert( 0 ); break; } if ( dn.bv_len < patlen && !DN_SEPARATOR( a->acl_dn_pat.bv_val[ patlen -dn.bv_len - 1 ] ) ) { return ACL_SCOPE_ERR; } if ( strcmp( &a->acl_dn_pat.bv_val[ patlen - dn.bv_len ], dn.bv_val ) != 0 ) { return ACL_SCOPE_ERR; } return ACL_SCOPE_OK; } return ACL_SCOPE_UNKNOWN; } #endif /* LDAP_DEVEL */ void parse_acl( Backend *be, const char *fname, int lineno, int argc, char **argv ) { int i; char *left, *right, *style; struct berval bv; AccessControl *a; Access *b; int rc; const char *text; a = NULL; for ( i = 1; i < argc; i++ ) { /* to clause - select which entries are protected */ if ( strcasecmp( argv[i], "to" ) == 0 ) { if ( a != NULL ) { fprintf( stderr, "%s: line %d: " "only one to clause allowed in access line\n", fname, lineno ); acl_usage(); } a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) ); for ( ++i; i < argc; i++ ) { if ( strcasecmp( argv[i], "by" ) == 0 ) { i--; break; } if ( strcasecmp( argv[i], "*" ) == 0 ) { if( a->acl_dn_pat.bv_len || ( a->acl_dn_style != ACL_STYLE_REGEX ) ) { fprintf( stderr, "%s: line %d: dn pattern" " already specified in to clause.\n", fname, lineno ); acl_usage(); } a->acl_dn_pat.bv_val = ch_strdup( "*" ); a->acl_dn_pat.bv_len = 1; continue; } split( argv[i], '=', &left, &right ); split( left, '.', &left, &style ); if ( right == NULL ) { fprintf( stderr, "%s: line %d: " "missing \"=\" in \"%s\" in to clause\n", fname, lineno, left ); acl_usage(); } if ( strcasecmp( left, "dn" ) == 0 ) { if( a->acl_dn_pat.bv_len != 0 || ( a->acl_dn_style != ACL_STYLE_REGEX ) ) { fprintf( stderr, "%s: line %d: dn pattern" " already specified in to clause.\n", fname, lineno ); acl_usage(); } if ( style == NULL || *style == '\0' || ( strcasecmp( style, "base" ) == 0 ) || ( strcasecmp( style, "exact" ) == 0 )) { a->acl_dn_style = ACL_STYLE_BASE; ber_str2bv( right, 0, 1, &a->acl_dn_pat ); } else if ( strcasecmp( style, "onelevel" ) == 0 || strcasecmp( style, "one" ) == 0 ) { a->acl_dn_style = ACL_STYLE_ONE; ber_str2bv( right, 0, 1, &a->acl_dn_pat ); } else if ( strcasecmp( style, "subtree" ) == 0 || strcasecmp( style, "sub" ) == 0 ) { if( *right == '\0' ) { a->acl_dn_pat.bv_val = ch_strdup( "*" ); a->acl_dn_pat.bv_len = 1; } else { a->acl_dn_style = ACL_STYLE_SUBTREE; ber_str2bv( right, 0, 1, &a->acl_dn_pat ); } } else if ( strcasecmp( style, "children" ) == 0 ) { a->acl_dn_style = ACL_STYLE_CHILDREN; ber_str2bv( right, 0, 1, &a->acl_dn_pat ); } else if ( strcasecmp( style, "regex" ) == 0 ) { a->acl_dn_style = ACL_STYLE_REGEX; if ( *right == '\0' ) { /* empty regex should match empty DN */ a->acl_dn_style = ACL_STYLE_BASE; ber_str2bv( right, 0, 1, &a->acl_dn_pat ); } else if ( strcmp(right, "*") == 0 || strcmp(right, ".*") == 0 || strcmp(right, ".*$") == 0 || strcmp(right, "^.*") == 0 || strcmp(right, "^.*$") == 0 || strcmp(right, ".*$$") == 0 || strcmp(right, "^.*$$") == 0 ) { a->acl_dn_pat.bv_val = ch_strdup( "*" ); a->acl_dn_pat.bv_len = sizeof("*")-1; } else { acl_regex_normalized_dn( right, &a->acl_dn_pat ); } } else { fprintf( stderr, "%s: line %d: " "unknown dn style \"%s\" in to clause\n", fname, lineno, style ); acl_usage(); } continue; } if ( strcasecmp( left, "filter" ) == 0 ) { if ( (a->acl_filter = str2filter( right )) == NULL ) { fprintf( stderr, "%s: line %d: bad filter \"%s\" in to clause\n", fname, lineno, right ); acl_usage(); } } else if ( strcasecmp( left, "attr" ) == 0 || strcasecmp( left, "attrs" ) == 0 ) { a->acl_attrs = str2anlist( a->acl_attrs, right, "," ); if ( a->acl_attrs == NULL ) { fprintf( stderr, "%s: line %d: unknown attr \"%s\" in to clause\n", fname, lineno, right ); acl_usage(); } } else if ( strncasecmp( left, "val", 3 ) == 0 ) { if ( a->acl_attrval.bv_len ) { fprintf( stderr, "%s: line %d: attr val already specified in to clause.\n", fname, lineno ); acl_usage(); } if ( a->acl_attrs == NULL || a->acl_attrs[1].an_name.bv_val ) { fprintf( stderr, "%s: line %d: attr val requires a single attribute.\n", fname, lineno ); acl_usage(); } ber_str2bv( right, 0, 1, &a->acl_attrval ); if ( style && strcasecmp( style, "regex" ) == 0 ) { int e = regcomp( &a->acl_attrval_re, a->acl_attrval.bv_val, REG_EXTENDED | REG_ICASE | REG_NOSUB ); if ( e ) { char buf[512]; regerror( e, &a->acl_attrval_re, buf, sizeof(buf) ); fprintf( stderr, "%s: line %d: " "regular expression \"%s\" bad because of %s\n", fname, lineno, right, buf ); acl_usage(); } a->acl_attrval_style = ACL_STYLE_REGEX; } else { /* FIXME: if the attribute has DN syntax, * we might allow one, subtree and children styles as well */ if ( !strcasecmp( style, "exact" ) ) { a->acl_attrval_style = ACL_STYLE_BASE; } else if ( a->acl_attrs[0].an_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) { if ( !strcasecmp( style, "base" ) ) { a->acl_attrval_style = ACL_STYLE_BASE; } else if ( !strcasecmp( style, "onelevel" ) || !strcasecmp( style, "one" ) ) { a->acl_attrval_style = ACL_STYLE_ONE; } else if ( !strcasecmp( style, "subtree" ) || !strcasecmp( style, "sub" ) ) { a->acl_attrval_style = ACL_STYLE_SUBTREE; } else if ( !strcasecmp( style, "children" ) ) { a->acl_attrval_style = ACL_STYLE_CHILDREN; } else { fprintf( stderr, "%s: line %d: unknown val.