blob: 0a47a0928638d8016123462c8c7e8ae30d574afb [file] [log] [blame]
/* authz.c : path-based access control
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
/*** Includes. ***/
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_fnmatch.h>
#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_repos.h"
#include "svn_config.h"
#include "svn_ctype.h"
#include "private/svn_atomic.h"
#include "private/svn_fspath.h"
#include "private/svn_repos_private.h"
#include "private/svn_sorts_private.h"
#include "private/svn_subr_private.h"
#include "repos.h"
#include "authz.h"
#include "config_file.h"
/*** Access rights. ***/
/* This structure describes the access rights given to a specific user by
* a path rule (actually the rule set specified for a path). I.e. there is
* one instance of this per path rule.
*/
typedef struct path_access_t
{
/* Sequence number of the path rule that this struct was derived from.
* If multiple rules apply to the same path (only possible with wildcard
* matching), the one with the highest SEQUENCE_NUMBER wins, i.e. the latest
* one defined in the authz file.
*
* A value of 0 denotes the default rule at the repository root denying
* access to everybody. User-defined path rules start with ID 1.
*/
int sequence_number;
/* Access rights of the respective user as defined by the rule set. */
authz_access_t rights;
} path_access_t;
/* Use this to indicate that no sequence ID has been assigned.
* It will automatically be inferior to (less than) any other sequence ID. */
#define NO_SEQUENCE_NUMBER (-1)
/* Convenience structure combining the node-local access rights with the
* min and max rights granted within the sub-tree. */
typedef struct limited_rights_t
{
/* Access granted to the current user. If the SEQUENCE_NUMBER member is
* NO_SEQUENCE_NUMBER, there has been no specific path rule for this PATH
* but only for some sub-path(s). There is always a rule at the root node.
*/
path_access_t access;
/* Minimal access rights that the user has on this or any other node in
* the sub-tree. This does not take inherited rights into account. */
authz_access_t min_rights;
/* Maximal access rights that the user has on this or any other node in
* the sub-tree. This does not take inherited rights into account. */
authz_access_t max_rights;
} limited_rights_t;
/* Return TRUE, if RIGHTS has local rights defined in the ACCESS member. */
static svn_boolean_t
has_local_rule(const limited_rights_t *rights)
{
return rights->access.sequence_number != NO_SEQUENCE_NUMBER;
}
/* Aggregate the ACCESS spec of TARGET and RIGHTS into TARGET. I.e. if both
* are specified, pick one in accordance to the precedence rules. */
static void
combine_access(limited_rights_t *target,
const limited_rights_t *rights)
{
/* This implies the check for NO_SEQUENCE_NUMBER, i.e no rights being
* specified. */
if (target->access.sequence_number < rights->access.sequence_number)
target->access = rights->access;
}
/* Aggregate the min / max access rights of TARGET and RIGHTS into TARGET. */
static void
combine_right_limits(limited_rights_t *target,
const limited_rights_t *rights)
{
target->max_rights |= rights->max_rights;
target->min_rights &= rights->min_rights;
}
/*** Authz cache access. ***/
/* All authz instances currently in use as well as all filtered authz
* instances in use will be cached here.
* Both caches will be instantiated at most once. */
static svn_object_pool__t *authz_pool = NULL;
static svn_object_pool__t *filtered_pool = NULL;
static svn_atomic_t authz_pool_initialized = FALSE;
/* Implements svn_atomic__err_init_func_t. */
static svn_error_t *
synchronized_authz_initialize(void *baton, apr_pool_t *pool)
{
#if APR_HAS_THREADS
svn_boolean_t multi_threaded = TRUE;
#else
svn_boolean_t multi_threaded = FALSE;
#endif
SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool));
SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_repos_authz_initialize(apr_pool_t *pool)
{
/* Protect against multiple calls. */
return svn_error_trace(svn_atomic__init_once(&authz_pool_initialized,
synchronized_authz_initialize,
NULL, pool));
}
/* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in RESULT_POOL.
* GROUPS_KEY may be NULL. This is the key for the AUTHZ_POOL.
*/
static svn_membuf_t *
construct_authz_key(const svn_checksum_t *authz_key,
const svn_checksum_t *groups_key,
apr_pool_t *result_pool)
{
svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
if (groups_key)
{
apr_size_t authz_size = svn_checksum_size(authz_key);
apr_size_t groups_size = svn_checksum_size(groups_key);
svn_membuf__create(result, authz_size + groups_size, result_pool);
result->size = authz_size + groups_size; /* exact length is required! */
memcpy(result->data, authz_key->digest, authz_size);
memcpy((char *)result->data + authz_size,
groups_key->digest, groups_size);
}
else
{
apr_size_t size = svn_checksum_size(authz_key);
svn_membuf__create(result, size, result_pool);
result->size = size; /* exact length is required! */
memcpy(result->data, authz_key->digest, size);
}
return result;
}
/* Return a combination of REPOS_NAME, USER and AUTHZ_ID, allocated in
* RESULT_POOL. USER may be NULL. This is the key for the FILTERED_POOL.
*/
static svn_membuf_t *
construct_filtered_key(const char *repos_name,
const char *user,
const svn_membuf_t *authz_id,
apr_pool_t *result_pool)
{
svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
size_t repos_len = strlen(repos_name);
size_t user_len = user ? strlen(user) : 1;
const char *nullable_user = user ? user : "\0";
size_t size = authz_id->size + repos_len + 1 + user_len + 1;
svn_membuf__create(result, size, result_pool);
result->size = size;
memcpy(result->data, repos_name, repos_len + 1);
size = repos_len + 1;
memcpy((char *)result->data + size, nullable_user, user_len + 1);
size += user_len + 1;
memcpy((char *)result->data + size, authz_id->data, authz_id->size);
return result;
}
/*** Constructing the prefix tree. ***/
/* Since prefix arrays may have more than one hit, we need to link them
* for fast lookup. */
typedef struct sorted_pattern_t
{
/* The filtered tree node carrying the prefix. */
struct node_t *node;
/* Entry that is a prefix to this one or NULL. */
struct sorted_pattern_t *next;
} sorted_pattern_t;
/* Substructure of node_t. It contains all sub-node that use patterns
* in the next segment level. We keep it separate to save a bit of memory
* and to be able to check for pattern presence in a single operation.
*/
typedef struct node_pattern_t
{
/* If not NULL, this represents the "*" follow-segment. */
struct node_t *any;
/* If not NULL, this represents the "**" follow-segment. */
struct node_t *any_var;
/* If not NULL, the segments of all sorted_pattern_t in this array are the
* prefix part of "prefix*" patterns. Sorted by segment prefix. */
apr_array_header_t *prefixes;
/* If not NULL, the segments of all sorted_pattern_t in this array are the
* reversed suffix part of "*suffix" patterns. Sorted by reversed
* segment suffix. */
apr_array_header_t *suffixes;
/* If not NULL, the segments of all sorted_pattern_t in this array contain
* wildcards and don't fit into any of the above categories.
* The NEXT members of the elements will not be used. */
apr_array_header_t *complex;
/* This node itself is a "**" segment and must therefore itself be added
* to the matching node list for the next level. */
svn_boolean_t repeat;
} node_pattern_t;
/* The pattern tree. All relevant path rules are being folded into this
* prefix tree, with a single, whole segment stored at each node. The whole
* tree applies to a single user only.
*/
typedef struct node_t
{
/* The segment as specified in the path rule. During the lookup tree walk,
* this will compared to the respective segment of the path to check. */
svn_string_t segment;
/* Immediate access rights granted by rules on this node and the min /
* max rights on any path in this sub-tree. */
limited_rights_t rights;
/* Map of sub-segment(const char *) to respective node (node_t) for all
* sub-segments that have rules on themselves or their respective subtrees.
* NULL, if there are no rules for sub-paths relevant to the user. */
apr_hash_t *sub_nodes;
/* If not NULL, this contains the pattern-based segment sub-nodes. */
node_pattern_t *pattern_sub_nodes;
} node_t;
/* Create a new tree node for SEGMENT.
Note: SEGMENT->pattern is always interned and therefore does not
have to be copied into the result pool. */
static node_t *
create_node(authz_rule_segment_t *segment,
apr_pool_t *result_pool)
{
node_t *result = apr_pcalloc(result_pool, sizeof(*result));
if (segment)
result->segment = segment->pattern;
else
{
result->segment.data = "";
result->segment.len = 0;
}
result->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
return result;
}
/* Auto-create a node in *NODE, make it apply to SEGMENT and return it. */
static node_t *
ensure_node(node_t **node,
authz_rule_segment_t *segment,
apr_pool_t *result_pool)
{
if (!*node)
*node = create_node(segment, result_pool);
return *node;
}
/* compare_func comparing segment names. It takes a sorted_pattern_t* as
* VOID_LHS and a const authz_rule_segment_t * as VOID_RHS.
*/
static int
compare_node_rule_segment(const void *void_lhs,
const void *void_rhs)
{
const sorted_pattern_t *element = void_lhs;
const authz_rule_segment_t *segment = void_rhs;
return strcmp(element->node->segment.data, segment->pattern.data);
}
/* compare_func comparing segment names. It takes a sorted_pattern_t* as
* VOID_LHS and a const char * as VOID_RHS.
*/
static int
compare_node_path_segment(const void *void_lhs,
const void *void_rhs)
{
const sorted_pattern_t *element = void_lhs;
const char *segment = void_rhs;
return strcmp(element->node->segment.data, segment);
}
/* Make sure a node_t* for SEGMENT exists in *ARRAY and return it.
* Auto-create either if they don't exist. Entries in *ARRAY are
* sorted by their segment strings.
*/
static node_t *
ensure_node_in_array(apr_array_header_t **array,
authz_rule_segment_t *segment,
apr_pool_t *result_pool)
{
int idx;
sorted_pattern_t entry;
sorted_pattern_t *entry_ptr;
/* Auto-create the array. */
if (!*array)
*array = apr_array_make(result_pool, 4, sizeof(sorted_pattern_t));
/* Find the node in ARRAY and the IDX at which it were to be inserted.
* Initialize IDX such that we won't attempt a hinted lookup (likely
* to fail and therefore pure overhead). */
idx = (*array)->nelts;
entry_ptr = svn_sort__array_lookup(*array, segment, &idx,
compare_node_rule_segment);
if (entry_ptr)
return entry_ptr->node;
/* There is no such node, yet.
* Create one and insert it into the sorted array. */
entry.node = create_node(segment, result_pool);
entry.next = NULL;
svn_error_clear(svn_sort__array_insert2(*array, &entry, idx));
return entry.node;
}
/* Auto-create the PATTERN_SUB_NODES sub-structure in *NODE and return it. */
static node_pattern_t *
ensure_pattern_sub_nodes(node_t *node,
apr_pool_t *result_pool)
{
if (node->pattern_sub_nodes == NULL)
node->pattern_sub_nodes = apr_pcalloc(result_pool,
sizeof(*node->pattern_sub_nodes));
return node->pattern_sub_nodes;
}
/* Combine an ACL rule segment with the corresponding node in our filtered
* data model. */
typedef struct node_segment_pair_t
{
authz_rule_segment_t *segment;
node_t *node;
} node_segment_pair_t;
/* Context object to be used with process_acl. It allows us to re-use
* information from previous insertions. */
typedef struct construction_context_t
{
/* Array of node_segment_pair_t. It contains all segments already
* processed of the current insertion together with the respective
* nodes in our filtered tree. Before the next lookup, the tree
* walk for the common prefix can be skipped. */
apr_array_header_t *path;
} construction_context_t;
/* Return a new context object allocated in RESULT_POOL. */
static construction_context_t *
create_construction_context(apr_pool_t *result_pool)
{
construction_context_t *result = apr_pcalloc(result_pool, sizeof(*result));
/* Array will be auto-extended but this initial size will make it rarely
* ever necessary. */
result->path = apr_array_make(result_pool, 32, sizeof(node_segment_pair_t));
return result;
}
/* Constructor utility: Below NODE, recursively insert sub-nodes for the
* path given as *SEGMENTS of length SEGMENT_COUNT. If matching nodes
* already exist, use those instead of creating new ones. Set the leave
* node's access rights spec to PATH_ACCESS. Update the context info in CTX.
*/
static void
insert_path(construction_context_t *ctx,
node_t *node,
path_access_t *path_access,
int segment_count,
authz_rule_segment_t *segment,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
node_t *sub_node;
node_segment_pair_t *node_segment;
/* End of path? */
if (segment_count == 0)
{
/* Set access rights. Note that there might be multiple rules for
* the same path due to non-repo-specific rules vs. repo-specific
* ones. Whichever gets defined last wins.
*/
limited_rights_t rights;
rights.access = *path_access;
rights.max_rights = path_access->rights;
rights.min_rights = path_access->rights;
combine_access(&node->rights, &rights);
return;
}
/* Any wildcards? They will go into a separate sub-structure. */
if (segment->kind != authz_rule_literal)
ensure_pattern_sub_nodes(node, result_pool);
switch (segment->kind)
{
/* A full wildcard segment? */
case authz_rule_any_segment:
sub_node = ensure_node(&node->pattern_sub_nodes->any,
segment, result_pool);
break;
/* One or more full wildcard segments? */
case authz_rule_any_recursive:
sub_node = ensure_node(&node->pattern_sub_nodes->any_var,
segment, result_pool);
ensure_pattern_sub_nodes(sub_node, result_pool)->repeat = TRUE;
break;
/* A single wildcard at the end of the segment? */
case authz_rule_prefix:
sub_node = ensure_node_in_array(&node->pattern_sub_nodes->prefixes,
segment, result_pool);
break;
/* A single wildcard at the start of segments? */
case authz_rule_suffix:
sub_node = ensure_node_in_array(&node->pattern_sub_nodes->suffixes,
segment, result_pool);
break;
/* General pattern? */
case authz_rule_fnmatch:
sub_node = ensure_node_in_array(&node->pattern_sub_nodes->complex,
segment, result_pool);
break;
/* Then it must be a literal. */
default:
SVN_ERR_ASSERT_NO_RETURN(segment->kind == authz_rule_literal);
if (!node->sub_nodes)
{
node->sub_nodes = svn_hash__make(result_pool);
sub_node = NULL;
}
else
{
sub_node = svn_hash_gets(node->sub_nodes, segment->pattern.data);
}
/* Auto-insert a sub-node for the current segment. */
if (!sub_node)
{
sub_node = create_node(segment, result_pool);
apr_hash_set(node->sub_nodes,
sub_node->segment.data,
sub_node->segment.len,
sub_node);
}
}
/* Update context. */
node_segment = apr_array_push(ctx->path);
node_segment->segment = segment;
node_segment->node = sub_node;
/* Continue at the sub-node with the next segment. */
insert_path(ctx, sub_node, path_access, segment_count - 1, segment + 1,
result_pool, scratch_pool);
}
/* If the ACL is relevant to the REPOSITORY and user (given as MEMBERSHIPS
* plus ANONYMOUS flag), insert the respective nodes into tree starting
* at ROOT. Use the context info of the previous call in CTX to eliminate
* repeated lookups. Allocate new nodes in RESULT_POOL and use SCRATCH_POOL
* for temporary allocations.
*/
static void
process_acl(construction_context_t *ctx,
const authz_acl_t *acl,
node_t *root,
const char *repository,
const char *user,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
path_access_t path_access;
int i;
node_t *node;
/* Skip ACLs that don't say anything about the current user
and/or repository. */
if (!svn_authz__get_acl_access(&path_access.rights, acl, user, repository))
return;
/* Insert the rule into the filtered tree. */
path_access.sequence_number = acl->sequence_number;
/* Try to reuse results from previous runs.
* Basically, skip the common prefix. */
node = root;
for (i = 0; i < ctx->path->nelts; ++i)
{
const node_segment_pair_t *step
= &APR_ARRAY_IDX(ctx->path, i, const node_segment_pair_t);
/* Exploit the fact that all strings in the authz model are unique /
* internized and can be identified by address alone. */
if ( !step->node
|| i >= acl->rule.len
|| step->segment->kind != acl->rule.path[i].kind
|| step->segment->pattern.data != acl->rule.path[i].pattern.data)
{
ctx->path->nelts = i;
break;
}
else
{
node = step->node;
}
}
/* Insert the path rule into the filtered tree. */
insert_path(ctx, node, &path_access,
acl->rule.len - i, acl->rule.path + i,
result_pool, scratch_pool);
}
/* Forward declaration ... */
static svn_boolean_t
trim_tree(node_t *node,
int latest_any_var,
apr_pool_t *scratch_pool);
/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
* node_t * and remove empty nodes from. *HASH may be NULL. If all nodes
* could be removed, set *HASH to NULL and return TRUE. Allocate temporary
* data in SCRATCH_POOL.
*/
static svn_boolean_t
trim_subnode_hash(apr_hash_t **hash,
int latest_any_var,
apr_pool_t *scratch_pool)
{
if (*hash)
{
apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
sizeof(node_t *));
apr_hash_index_t *hi;
for (hi = apr_hash_first(scratch_pool, *hash);
hi;
hi = apr_hash_next(hi))
{
node_t *node = apr_hash_this_val(hi);
if (trim_tree(node, latest_any_var, scratch_pool))
APR_ARRAY_PUSH(to_remove, node_t *) = node;
}
/* Are some nodes left? */
if (to_remove->nelts < apr_hash_count(*hash))
{
/* Remove empty nodes (if any). */
int i;
for (i = 0; i < to_remove->nelts; ++i)
{
node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
apr_hash_set(*hash, node->segment.data, node->segment.len,
NULL);
}
return FALSE;
}
/* No nodes left. A NULL hash is more efficient than an empty one. */
*hash = NULL;
}
return TRUE;
}
/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
* node_t * and remove empty nodes from. *ARRAY may be NULL. If all nodes
* could be removed, set *ARRAY to NULL and return TRUE. Allocate
* temporary data in SCRATCH_POOL.
*/
static svn_boolean_t
trim_subnode_array(apr_array_header_t **array,
int latest_any_var,
apr_pool_t *scratch_pool)
{
if (*array)
{
int i, dest;
for (i = 0, dest = 0; i < (*array)->nelts; ++i)
{
node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
if (!trim_tree(node, latest_any_var, scratch_pool))
{
if (i != dest)
APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
= APR_ARRAY_IDX(*array, i, sorted_pattern_t);
++dest;
}
}
/* Are some nodes left? */
if (dest)
{
/* Trim it to the number of valid entries. */
(*array)->nelts = dest;
return FALSE;
}
/* No nodes left. A NULL array is more efficient than an empty one. */
*array = NULL;
}
return TRUE;
}
/* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
* "any-var" rule with sequence number LATEST_ANY_VAR. Return TRUE, if
* there are no rules left in the sub-tree, including NODE.
* Allocate temporary data in SCRATCH_POOL.
*/
static svn_boolean_t
trim_tree(node_t *node,
int latest_any_var,
apr_pool_t *scratch_pool)
{
svn_boolean_t removed_all = TRUE;
/* For convenience, we allow NODE to be NULL: */
if (!node)
return TRUE;
/* Do we have a later "any_var" rule at this node. */
if ( node->pattern_sub_nodes
&& node->pattern_sub_nodes->any_var
&& node->pattern_sub_nodes->any_var->rights.access.sequence_number
> latest_any_var)
{
latest_any_var
= node->pattern_sub_nodes->any_var->rights.access.sequence_number;
}
/* Is there a local rule at this node that is not eclipsed by any_var? */
if (has_local_rule(&node->rights))
{
/* Remove the local rule, if it got eclipsed.
* Note that for the latest any_var node, the sequence number is equal. */
if (node->rights.access.sequence_number >= latest_any_var)
removed_all = FALSE;
else
node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
}
/* Process all sub-nodes. */
removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
scratch_pool);
if (node->pattern_sub_nodes)
{
if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
scratch_pool))
node->pattern_sub_nodes->any = NULL;
else
removed_all = FALSE;
if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
scratch_pool))
node->pattern_sub_nodes->any_var = NULL;
else
removed_all = FALSE;
removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
latest_any_var, scratch_pool);
removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
latest_any_var, scratch_pool);
removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
latest_any_var, scratch_pool);
/* Trim the tree as much as possible to speed up lookup(). */
if (removed_all)
node->pattern_sub_nodes = NULL;
}
return removed_all;
}
/* Forward declaration ... */
static void
finalize_tree(node_t *node,
limited_rights_t *sum,
apr_pool_t *scratch_pool);
/* Call finalize_tree() on all elements in the HASH of node_t *, passing
* SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
*/
static void
finalize_subnode_hash(apr_hash_t *hash,
limited_rights_t *sum,
apr_pool_t *scratch_pool)
{
if (hash)
{
apr_hash_index_t *hi;
for (hi = apr_hash_first(scratch_pool, hash);
hi;
hi = apr_hash_next(hi))
finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
}
}
/* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
* passing SUM along. ARRAY may be NULL. Use SCRATCH_POOL for temporary
* allocations.
*/
static void
finalize_subnode_array(apr_array_header_t *array,
limited_rights_t *sum,
apr_pool_t *scratch_pool)
{
if (array)
{
int i;
for (i = 0; i < array->nelts; ++i)
finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
scratch_pool);
}
}
/* Link prefixes within the sorted ARRAY. */
static void
link_prefix_patterns(apr_array_header_t *array)
{
int i;
if (!array)
return;
for (i = 1; i < array->nelts; ++i)
{
sorted_pattern_t *prev
= &APR_ARRAY_IDX(array, i - 1, sorted_pattern_t);
sorted_pattern_t *pattern
= &APR_ARRAY_IDX(array, i, sorted_pattern_t);
/* Does PATTERN potentially have a prefix in ARRAY?
* If so, at least the first char must match with the predecessor's
* because the array is sorted by that string. */
if (prev->node->segment.data[0] != pattern->node->segment.data[0])
continue;
/* Only the predecessor or any of its prefixes can be the closest
* prefix to PATTERN. */
for ( ; prev; prev = prev->next)
if ( prev->node->segment.len < pattern->node->segment.len
&& !memcmp(prev->node->segment.data,
pattern->node->segment.data,
prev->node->segment.len))
{
pattern->next = prev;
break;
}
}
}
/* Recursively finalization the tree node properties for NODE. Update SUM
* (of NODE's parent) by combining it with the recursive access rights info
* on NODE. Use SCRATCH_POOL for temporary allocations.
*/
static void
finalize_tree(node_t *node,
limited_rights_t *sum,
apr_pool_t *scratch_pool)
{
limited_rights_t *local_sum = &node->rights;
/* For convenience, we allow NODE to be NULL: */
if (!node)
return;
/* Sum of rights at NODE - so far. */
if (has_local_rule(local_sum))
{
local_sum->max_rights = local_sum->access.rights;
local_sum->min_rights = local_sum->access.rights;
}
else
{
local_sum->min_rights = authz_access_write;
local_sum->max_rights = authz_access_none;
}
/* Process all sub-nodes. */
finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
if (node->pattern_sub_nodes)
{
finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
scratch_pool);
finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
scratch_pool);
finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
scratch_pool);
/* Link up the prefixes / suffixes. */
link_prefix_patterns(node->pattern_sub_nodes->prefixes);
link_prefix_patterns(node->pattern_sub_nodes->suffixes);
}
/* Add our min / max info to the parent's info.
* Idempotent for parent == node (happens at root). */
combine_right_limits(sum, local_sum);
}
/* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
* Return the filtered rule tree.
*/
static node_t *
create_user_authz(authz_full_t *authz,
const char *repository,
const char *user,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
int i;
node_t *root = create_node(NULL, result_pool);
construction_context_t *ctx = create_construction_context(scratch_pool);
/* Use a separate sub-pool to keep memory usage tight. */
apr_pool_t *subpool = svn_pool_create(scratch_pool);
/* Find all ACLs for REPOSITORY. */
apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts,
sizeof(authz_acl_t *));
for (i = 0; i < authz->acls->nelts; ++i)
{
const authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
if (svn_authz__acl_applies_to_repo(acl, repository))
{
/* ACLs in the AUTHZ are sorted by path and repository.
* So, if there is a rule for the repo and a global rule for the
* same path, we will detect them here. */
if (acls->nelts)
{
const authz_acl_t *prev_acl
= APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *);
if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0)
{
svn_boolean_t global_acl_applies;
svn_boolean_t repos_acl_applies;
/* Previous ACL is a global rule. */
SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos,
AUTHZ_ANY_REPOSITORY));
/* Current ACL is a per-repository rule. */
SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos,
AUTHZ_ANY_REPOSITORY));
global_acl_applies =
svn_authz__get_acl_access(NULL, prev_acl, user, repository);
repos_acl_applies =
svn_authz__get_acl_access(NULL, acl, user, repository);
/* Prefer rules which apply to both this user and this path
* over rules which apply only to the path. In cases where
* both rules apply to user and path, always prefer the
* repository-specific rule. */
if (!global_acl_applies || repos_acl_applies)
{
apr_array_pop(acls);
APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
}
}
else
APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
}
else
APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
}
}
/* Filtering and tree construction. */
for (i = 0; i < acls->nelts; ++i)
process_acl(ctx, APR_ARRAY_IDX(acls, i, const authz_acl_t *),
root, repository, user, result_pool, subpool);
/* If there is no relevant rule at the root node, the "no access" default
* applies. Give it a SEQUENCE_NUMBER that will never overrule others. */
if (!has_local_rule(&root->rights))
{
root->rights.access.sequence_number = 0;
root->rights.access.rights = authz_access_none;
}
/* Trim the tree.
*
* We can't do pattern comparison, so for most pattern rules we cannot
* say that a set of rules "eclipses" / overrides a given other set of
* rules for all possible paths. That limits the accuracy of our check
* for recursive access in similar ways than for non-pattern rules.
*
* However, the user expects a rule ending with "**" to eclipse any older
* rule in that sub-tree recursively. So, this trim function removes
* eclipsed nodes from the tree.
*/
svn_pool_clear(subpool);
trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
/* Calculate recursive rights.
*
* This is a bottom-up calculation of the range of access rights
* specified anywhere in the respective sub-tree, including the base
* node itself.
*
* To prevent additional finalization passes, we piggy-back the addition
* of the ordering links of the prefix and suffix sub-node rules.
*/
svn_pool_clear(subpool);
finalize_tree(root, &root->rights, subpool);
/* Done. */
svn_pool_destroy(subpool);
return root;
}
/*** Lookup. ***/
/* Reusable lookup state object. It is easy to pass to functions and
* recycling it between lookups saves significant setup costs. */
typedef struct lookup_state_t
{
/* Rights immediately applying to this node and limits to the rights to
* any sub-path. */
limited_rights_t rights;
/* Nodes applying to the path followed so far. */
apr_array_header_t *current;
/* Temporary array containing the nodes applying to the next path
* segment (used to build up the next contents of CURRENT). */
apr_array_header_t *next;
/* Scratch pad for path operations. */
svn_stringbuf_t *scratch_pad;
/* After each lookup iteration, CURRENT and PARENT_RIGHTS will
* apply to this path. */
svn_stringbuf_t *parent_path;
/* Rights that apply at PARENT_PATH, if PARENT_PATH is not empty. */
limited_rights_t parent_rights;
} lookup_state_t;
/* Constructor for lookup_state_t. */
static lookup_state_t *
create_lookup_state(apr_pool_t *result_pool)
{
lookup_state_t *state = apr_pcalloc(result_pool, sizeof(*state));
state->next = apr_array_make(result_pool, 4, sizeof(node_t *));
state->current = apr_array_make(result_pool, 4, sizeof(node_t *));
/* Virtually all path segments should fit into this buffer. If they
* don't, the buffer gets automatically reallocated.
*
* Using a smaller initial size would be fine as well but does not
* buy us much for the increased risk of being expanded anyway - at
* some extra cost. */
state->scratch_pad = svn_stringbuf_create_ensure(200, result_pool);
/* Most paths should fit into this buffer. The same rationale as
* above applies. */
state->parent_path = svn_stringbuf_create_ensure(200, result_pool);
return state;
}
/* Clear the current contents of STATE and re-initialize it for ROOT.
* Check whether we can reuse a previous parent path lookup to shorten
* the current PATH walk. Return the full or remaining portion of
* PATH, respectively. PATH must not be NULL. */
static const char *
init_lockup_state(lookup_state_t *state,
node_t *root,
const char *path)
{
apr_size_t len = strlen(path);
if ( (len > state->parent_path->len)
&& state->parent_path->len
&& (path[state->parent_path->len] == '/')
&& !memcmp(path, state->parent_path->data, state->parent_path->len))
{
/* The PARENT_PATH of the previous lookup is actually a parent path
* of PATH. The CURRENT node list already matches the parent path
* and we only have to set the correct rights info. */
state->rights = state->parent_rights;
/* Tell the caller where to proceed. */
return path + state->parent_path->len;
}
/* Start lookup at ROOT for the full PATH. */
state->rights = root->rights;
state->parent_rights = root->rights;
apr_array_clear(state->next);
apr_array_clear(state->current);
APR_ARRAY_PUSH(state->current, node_t *) = root;
/* Var-segment rules match empty segments as well */
if (root->pattern_sub_nodes && root->pattern_sub_nodes->any_var)
{
node_t *node = root->pattern_sub_nodes->any_var;
/* This is non-recursive due to ACL normalization. */
combine_access(&state->rights, &node->rights);
combine_right_limits(&state->rights, &node->rights);
APR_ARRAY_PUSH(state->current, node_t *) = node;
}
svn_stringbuf_setempty(state->parent_path);
svn_stringbuf_setempty(state->scratch_pad);
return path;
}
/* Add NODE to the list of NEXT nodes in STATE. NODE may be NULL in which
* case this is a no-op. Also update and aggregate the access rights data
* for the next path segment.
*/
static void
add_next_node(lookup_state_t *state,
node_t *node)
{
/* Allowing NULL nodes simplifies the caller. */
if (node)
{
/* The rule with the highest sequence number is the one that applies.
* Not all nodes that we are following have rules that apply directly
* to this path but are mere intermediates that may only have some
* matching deep sub-node. */
combine_access(&state->rights, &node->rights);
/* The rule tree node can be seen as an overlay of all the nodes that
* we are following. Any of them _may_ match eventually, so the min/
* max possible access rights are a combination of all these sub-trees.
*/
combine_right_limits(&state->rights, &node->rights);
/* NODE is now enlisted as a (potential) match for the next segment. */
APR_ARRAY_PUSH(state->next, node_t *) = node;
/* Variable length sub-segment sequences apply to the same node as
* they match empty sequences as well. */
if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
{
node = node->pattern_sub_nodes->any_var;
/* This is non-recursive due to ACL normalization. */
combine_access(&state->rights, &node->rights);
combine_right_limits(&state->rights, &node->rights);
APR_ARRAY_PUSH(state->next, node_t *) = node;
}
}
}
/* If PREFIX is indeed a prefix (or exact match) or SEGMENT, add the
* node in PREFIX to STATE. */
static void
add_if_prefix_matches(lookup_state_t *state,
const sorted_pattern_t *prefix,
const svn_stringbuf_t *segment)
{
node_t *node = prefix->node;
if ( node->segment.len <= segment->len
&& !memcmp(node->segment.data, segment->data, node->segment.len))
add_next_node(state, node);
}
/* Scan the PREFIXES array of node_t* for all entries whose SEGMENT members
* are prefixes of SEGMENT. Add these to STATE for the next tree level. */
static void
add_prefix_matches(lookup_state_t *state,
const svn_stringbuf_t *segment,
apr_array_header_t *prefixes)
{
/* Index of the first node that might be a match. All matches will
* be at this and the immediately following indexes. */
int i = svn_sort__bsearch_lower_bound(prefixes, segment->data,
compare_node_path_segment);
/* The entry we found may be an exact match (but not a true prefix).
* The prefix matching test will still work. */
if (i < prefixes->nelts)
add_if_prefix_matches(state,
&APR_ARRAY_IDX(prefixes, i, sorted_pattern_t),
segment);
/* The immediate predecessor may be a true prefix and all potential
* prefixes can be found following the NEXT links between the array
* indexes. */
if (i > 0)
{
sorted_pattern_t *pattern;
for (pattern = &APR_ARRAY_IDX(prefixes, i - 1, sorted_pattern_t);
pattern;
pattern = pattern->next)
{
add_if_prefix_matches(state, pattern, segment);
}
}
}
/* Scan the PATTERNS array of node_t* for all entries whose SEGMENT members
* (usually containing wildcards) match SEGMENT. Add these to STATE for the
* next tree level. */
static void
add_complex_matches(lookup_state_t *state,
const svn_stringbuf_t *segment,
apr_array_header_t *patterns)
{
int i;
for (i = 0; i < patterns->nelts; ++i)
{
node_t *node = APR_ARRAY_IDX(patterns, i, sorted_pattern_t).node;
if (0 == apr_fnmatch(node->segment.data, segment->data, 0))
add_next_node(state, node);
}
}
/* Extract the next segment from PATH and copy it into SEGMENT, whose current
* contents get overwritten. Empty paths ("") are supported and leading '/'
* segment separators will be interpreted as an empty segment (""). Non-
* normalizes parts, i.e. sequences of '/', will be treated as a single '/'.
*
* Return the start of the next segment within PATH, skipping the '/'
* separator(s). Return NULL, if there are no further segments.
*
* The caller (only called by lookup(), ATM) must ensure that SEGMENT has
* enough room to store all of PATH.
*/
static const char *
next_segment(svn_stringbuf_t *segment,
const char *path)
{
apr_size_t len;
char c;
/* Read and scan PATH for NUL and '/' -- whichever comes first. */
for (len = 0, c = *path; c; c = path[++len])
if (c == '/')
{
/* End of segment. */
segment->data[len] = 0;
segment->len = len;
/* If PATH is not normalized, this is where we skip whole sequences
* of separators. */
while (path[++len] == '/')
;
/* Continue behind the last separator in the sequence. We will
* treat trailing '/' as indicating an empty trailing segment.
* Therefore, we never have to return NULL here. */
return path + len;
}
else
{
/* Copy segment contents directly into the result buffer.
* On many architectures, this is almost or entirely for free. */
segment->data[len] = c;
}
/* No separator found, so all of PATH has been the last segment. */
segment->data[len] = 0;
segment->len = len;
/* Tell the caller that this has been the last segment. */
return NULL;
}
/* Starting at the respective user's authz root node provided with STATE,
* follow PATH and return TRUE, iff the REQUIRED access has been granted to
* that user for this PATH. REQUIRED must not contain svn_authz_recursive.
* If RECURSIVE is set, all paths in the sub-tree at and below PATH must
* have REQUIRED access. PATH does not need to be normalized, may be empty
* but must not be NULL.
*/
static svn_boolean_t
lookup(lookup_state_t *state,
const char *path,
authz_access_t required,
svn_boolean_t recursive,
apr_pool_t *scratch_pool)
{
/* Create a scratch pad large enough to hold any of PATH's segments. */
apr_size_t path_len = strlen(path);
svn_stringbuf_ensure(state->scratch_pad, path_len);
/* Normalize start and end of PATH. Most paths will be fully normalized,
* so keep the overhead as low as possible. */
if (path_len && path[path_len-1] == '/')
{
do
{
--path_len;
}
while (path_len && path[path_len-1] == '/');
path = apr_pstrmemdup(scratch_pool, path, path_len);
}
while (path[0] == '/')
++path; /* Don't update PATH_LEN as we won't need it anymore. */
/* Actually walk the path rule tree following PATH until we run out of
* either tree or PATH. */
while (state->current->nelts && path)
{
apr_array_header_t *temp;
int i;
svn_stringbuf_t *segment = state->scratch_pad;
/* Shortcut 1: We could nowhere find enough rights in this sub-tree. */
if ((state->rights.max_rights & required) != required)
return FALSE;
/* Shortcut 2: We will find enough rights everywhere in this sub-tree. */
if ((state->rights.min_rights & required) == required)
return TRUE;
/* Extract the next segment. */
path = next_segment(segment, path);
/* Initial state for this segment. */
apr_array_clear(state->next);
state->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
state->rights.access.rights = authz_access_none;
/* These init values ensure that the first node's value will be used
* when combined with them. If there is no first node,
* state->access.sequence_number remains unchanged and we will use
* the parent's (i.e. inherited) access rights. */
state->rights.min_rights = authz_access_write;
state->rights.max_rights = authz_access_none;
/* Update the PARENT_PATH member in STATE to match the nodes in
* CURRENT at the end of this iteration, i.e. if and when NEXT
* has become CURRENT. */
if (path)
{
svn_stringbuf_appendbyte(state->parent_path, '/');
svn_stringbuf_appendbytes(state->parent_path, segment->data,
segment->len);
}
/* Scan follow all alternative routes to the next level. */
for (i = 0; i < state->current->nelts; ++i)
{
node_t *node = APR_ARRAY_IDX(state->current, i, node_t *);
if (node->sub_nodes)
add_next_node(state, apr_hash_get(node->sub_nodes, segment->data,
segment->len));
/* Process alternative, wildcard-based sub-nodes. */
if (node->pattern_sub_nodes)
{
add_next_node(state, node->pattern_sub_nodes->any);
/* If the current node represents a "**" pattern, it matches
* to all levels. So, add it to the list for the NEXT level. */
if (node->pattern_sub_nodes->repeat)
add_next_node(state, node);
/* Find all prefix pattern matches. */
if (node->pattern_sub_nodes->prefixes)
add_prefix_matches(state, segment,
node->pattern_sub_nodes->prefixes);
if (node->pattern_sub_nodes->complex)
add_complex_matches(state, segment,
node->pattern_sub_nodes->complex);
/* Find all suffux pattern matches.
* This must be the last check as it destroys SEGMENT. */
if (node->pattern_sub_nodes->suffixes)
{
/* Suffixes behave like reversed prefixes. */
svn_authz__reverse_string(segment->data, segment->len);
add_prefix_matches(state, segment,
node->pattern_sub_nodes->suffixes);
}
}
}
/* If no rule applied to this SEGMENT directly, the parent rights
* will apply to at least the SEGMENT node itself and possibly
* other parts deeper in it's subtree. */
if (!has_local_rule(&state->rights))
{
state->rights.access = state->parent_rights.access;
state->rights.min_rights &= state->parent_rights.access.rights;
state->rights.max_rights |= state->parent_rights.access.rights;
}
/* The list of nodes for SEGMENT is now complete. If we need to
* continue, make it the current and put the old one into the recycler.
*
* If this is the end of the path, keep the parent path and rights in
* STATE as are such that sibling lookups will benefit from it.
*/
if (path)
{
temp = state->current;
state->current = state->next;
state->next = temp;
/* In STATE, PARENT_PATH, PARENT_RIGHTS and CURRENT are now in sync. */
state->parent_rights = state->rights;
}
}
/* If we check recursively, none of the (potential) sub-paths must have
* less than the REQUIRED access rights. "Potential" because we don't
* verify that the respective paths actually exist in the repository.
*/
if (recursive)
return (state->rights.min_rights & required) == required;
/* Return whether the access rights on PATH fully include REQUIRED. */
return (state->rights.access.rights & required) == required;
}
/*** The authz data structure. ***/
/* An entry in svn_authz_t's USER_RULES cache. All members must be
* allocated in the POOL and the latter has to be cleared / destroyed
* before overwriting the entries' contents.
*/
struct authz_user_rules_t
{
/* User name for which we filtered the rules.
* User NULL for the anonymous user. */
const char *user;
/* Repository name for which we filtered the rules.
* May be empty but never NULL for used entries. */
const char *repository;
/* The combined min/max rights USER has on REPOSITORY. */
authz_rights_t global_rights;
/* Root of the filtered path rule tree.
* Will remain NULL until the first usage. */
node_t *root;
/* Reusable lookup state instance. */
lookup_state_t *lookup_state;
/* Pool from which all data within this struct got allocated.
* Can be destroyed or cleaned up with no further side-effects. */
apr_pool_t *pool;
};
/* Return TRUE, iff AUTHZ matches the pair of REPOS_NAME and USER.
* Note that USER may be NULL.
*/
static svn_boolean_t
matches_filtered_tree(const authz_user_rules_t *authz,
const char *repos_name,
const char *user)
{
/* Does the user match? */
if (user)
{
if (authz->user == NULL || strcmp(user, authz->user))
return FALSE;
}
else if (authz->user != NULL)
return FALSE;
/* Does the repository match as well? */
return strcmp(repos_name, authz->repository) == 0;
}
/* Check if AUTHZ's already contains a path rule tree filtered for this
* USER, REPOS_NAME combination. If that does not exist, yet, create one
* but don't construct the actual filtered tree, yet.
*/
static authz_user_rules_t *
get_user_rules(svn_authz_t *authz,
const char *repos_name,
const char *user)
{
apr_pool_t *pool;
/* Search our cache for a suitable previously filtered tree. */
if (authz->filtered)
{
/* Is this a suitable filtered tree? */
if (matches_filtered_tree(authz->filtered, repos_name, user))
return authz->filtered;
/* Drop the old filtered tree before creating a new one. */
svn_pool_destroy(authz->filtered->pool);
authz->filtered = NULL;
}
/* Global cache lookup. Filter the full model only if necessary. */
pool = svn_pool_create(authz->pool);
/* Write a new entry. */
authz->filtered = apr_palloc(pool, sizeof(*authz->filtered));
authz->filtered->pool = pool;
authz->filtered->repository = apr_pstrdup(pool, repos_name);
authz->filtered->user = user ? apr_pstrdup(pool, user) : NULL;
authz->filtered->lookup_state = create_lookup_state(pool);
authz->filtered->root = NULL;
svn_authz__get_global_rights(&authz->filtered->global_rights,
authz->full, user, repos_name);
return authz->filtered;
}
/* In AUTHZ's user rules, construct the actual filtered tree.
* Use SCRATCH_POOL for temporary allocations.
*/
static svn_error_t *
filter_tree(svn_authz_t *authz,
apr_pool_t *scratch_pool)
{
apr_pool_t *pool = authz->filtered->pool;
const char *repos_name = authz->filtered->repository;
const char *user = authz->filtered->user;
node_t *root;
if (filtered_pool)
{
svn_membuf_t *key = construct_filtered_key(repos_name, user,
authz->authz_id,
scratch_pool);
/* Cache lookup. */
SVN_ERR(svn_object_pool__lookup((void **)&root, filtered_pool, key,
pool));
if (!root)
{
apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
authz_full_t *add_ref = NULL;
/* Make sure the underlying full authz object lives as long as the
* filtered one that we are about to create. We do this by adding
* a reference to it in ITEM_POOL (which may live longer than AUTHZ).
*
* Note that we already have a reference to that full authz in
* AUTHZ->FULL. Assert that we actually don't created multiple
* instances of the same full model.
*/
svn_error_clear(svn_object_pool__lookup((void **)&add_ref,
authz_pool, authz->authz_id,
item_pool));
SVN_ERR_ASSERT(add_ref == authz->full);
/* Now construct the new filtered tree and cache it. */
root = create_user_authz(authz->full, repos_name, user, item_pool,
scratch_pool);
svn_error_clear(svn_object_pool__insert((void **)&root,
filtered_pool, key, root,
item_pool, pool));
}
}
else
{
root = create_user_authz(authz->full, repos_name, user, pool,
scratch_pool);
}
/* Write a new entry. */
authz->filtered->root = root;
return SVN_NO_ERROR;
}
/* Read authz configuration data from PATH into *AUTHZ_P, allocated in
RESULT_POOL. Return the cache key in *AUTHZ_ID. If GROUPS_PATH is set,
use the global groups parsed from it. Use SCRATCH_POOL for temporary
allocations.
PATH and GROUPS_PATH may be a dirent or an absolute file url. REPOS_HINT
may be specified to speed up access to in-repo authz files.
If PATH or GROUPS_PATH is not a valid authz rule file, then return
SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
is also an error. */
static svn_error_t *
authz_read(authz_full_t **authz_p,
svn_membuf_t **authz_id,
const char *path,
const char *groups_path,
svn_boolean_t must_exist,
svn_repos_t *repos_hint,
svn_repos_authz_warning_func_t warning_func,
void *warning_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_error_t* err = NULL;
svn_stream_t *rules_stream = NULL;
svn_stream_t *groups_stream = NULL;
svn_checksum_t *rules_checksum = NULL;
svn_checksum_t *groups_checksum = NULL;
config_access_t *config_access =
svn_repos__create_config_access(repos_hint, scratch_pool);
/* Open the main authz file */
SVN_ERR(svn_repos__get_config(&rules_stream, &rules_checksum, config_access,
path, must_exist, scratch_pool));
/* Open the optional groups file */
if (groups_path)
SVN_ERR(svn_repos__get_config(&groups_stream, &groups_checksum,
config_access, groups_path, must_exist,
scratch_pool));
/* The authz cache is optional. */
*authz_id = construct_authz_key(rules_checksum, groups_checksum,
result_pool);
if (authz_pool)
{
/* Cache lookup. */
SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool,
*authz_id, result_pool));
/* If not found, parse and add to cache. */
if (!*authz_p)
{
apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
/* Parse the configuration(s) and construct the full authz model
* from it. */
err = svn_authz__parse(authz_p, rules_stream, groups_stream,
warning_func, warning_baton,
item_pool, scratch_pool);
if (err != SVN_NO_ERROR)
{
/* That pool would otherwise never get destroyed. */
svn_pool_destroy(item_pool);
/* Add the URL / file name to the error stack since the parser
* doesn't have it. */
err = svn_error_quick_wrapf(err,
"Error while parsing config file: '%s':",
path);
}
else
{
SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool,
*authz_id, *authz_p,
item_pool, result_pool));
}
}
}
else
{
/* Parse the configuration(s) and construct the full authz model from
* it. */
err = svn_error_quick_wrapf(
svn_authz__parse(authz_p, rules_stream, groups_stream,
warning_func, warning_baton,
result_pool, scratch_pool),
"Error while parsing authz file: '%s':", path);
}
svn_repos__destroy_config_access(config_access);
return err;
}
/*** Public functions. ***/
svn_error_t *
svn_repos_authz_read4(svn_authz_t **authz_p,
const char *path,
const char *groups_path,
svn_boolean_t must_exist,
svn_repos_t *repos_hint,
svn_repos_authz_warning_func_t warning_func,
void *warning_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
authz->pool = result_pool;
SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path,
must_exist, repos_hint, warning_func, warning_baton,
result_pool, scratch_pool));
*authz_p = authz;
return SVN_NO_ERROR;
}
svn_error_t *
svn_repos_authz_parse2(svn_authz_t **authz_p,
svn_stream_t *stream,
svn_stream_t *groups_stream,
svn_repos_authz_warning_func_t warning_func,
void *warning_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
authz->pool = result_pool;
/* Parse the configuration and construct the full authz model from it. */
SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream,
warning_func, warning_baton,
result_pool, scratch_pool));
*authz_p = authz;
return SVN_NO_ERROR;
}
svn_error_t *
svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
const char *path, const char *user,
svn_repos_authz_access_t required_access,
svn_boolean_t *access_granted,
apr_pool_t *pool)
{
const authz_access_t required =
((required_access & svn_authz_read ? authz_access_read_flag : 0)
| (required_access & svn_authz_write ? authz_access_write_flag : 0));
/* Pick or create the suitable pre-filtered path rule tree. */
authz_user_rules_t *rules = get_user_rules(
authz,
(repos_name ? repos_name : AUTHZ_ANY_REPOSITORY),
user);
/* In many scenarios, users have uniform access to a repository
* (blanket access or no access at all).
*
* In these cases, don't bother creating or consulting the filtered tree.
*/
if ((rules->global_rights.min_access & required) == required)
{
*access_granted = TRUE;
return SVN_NO_ERROR;
}
if ((rules->global_rights.max_access & required) != required)
{
*access_granted = FALSE;
return SVN_NO_ERROR;
}
/* No specific path given, i.e. looking for anywhere in the tree? */
if (!path)
{
*access_granted =
((rules->global_rights.max_access & required) == required);
return SVN_NO_ERROR;
}
/* Rules tree lookup */
/* Did we already filter the data model? */
if (!rules->root)
SVN_ERR(filter_tree(authz, pool));
/* Re-use previous lookup results, if possible. */
path = init_lockup_state(authz->filtered->lookup_state,
authz->filtered->root, path);
/* Sanity check. */
SVN_ERR_ASSERT(path[0] == '/');
/* Determine the granted access for the requested path.
* PATH does not need to be normalized for lockup(). */
*access_granted = lookup(rules->lookup_state, path, required,
!!(required_access & svn_authz_recursive), pool);
return SVN_NO_ERROR;
}