aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_repos/authz.c
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commit32547653cc5376642e1231fb644db99933ac8db4 (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_repos/authz.c
downloadsrc-32547653cc5376642e1231fb644db99933ac8db4.tar.gz
src-32547653cc5376642e1231fb644db99933ac8db4.zip
Import trimmed svn-1.8.0-rc3vendor/subversion/subversion-1.8.0-rc3
Notes
Notes: svn path=/vendor/subversion/dist/; revision=251881 svn path=/vendor/subversion/subversion-1.8.0-rc3/; revision=251882; tag=vendor/subversion/subversion-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_repos/authz.c')
-rw-r--r--subversion/libsvn_repos/authz.c1075
1 files changed, 1075 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
new file mode 100644
index 000000000000..af4a1f255c59
--- /dev/null
+++ b/subversion/libsvn_repos/authz.c
@@ -0,0 +1,1075 @@
+/* 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 "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_fspath.h"
+#include "repos.h"
+
+
+/*** Structures. ***/
+
+/* Information for the config enumerators called during authz
+ lookup. */
+struct authz_lookup_baton {
+ /* The authz configuration. */
+ svn_config_t *config;
+
+ /* The user to authorize. */
+ const char *user;
+
+ /* Explicitly granted rights. */
+ svn_repos_authz_access_t allow;
+ /* Explicitly denied rights. */
+ svn_repos_authz_access_t deny;
+
+ /* The rights required by the caller of the lookup. */
+ svn_repos_authz_access_t required_access;
+
+ /* The following are used exclusively in recursive lookups. */
+
+ /* The path in the repository (an fspath) to authorize. */
+ const char *repos_path;
+ /* repos_path prefixed by the repository name and a colon. */
+ const char *qualified_repos_path;
+
+ /* Whether, at the end of a recursive lookup, access is granted. */
+ svn_boolean_t access;
+};
+
+/* Information for the config enumeration functions called during the
+ validation process. */
+struct authz_validate_baton {
+ svn_config_t *config; /* The configuration file being validated. */
+ svn_error_t *err; /* The error being thrown out of the
+ enumerator, if any. */
+};
+
+/* Currently this structure is just a wrapper around a
+ svn_config_t. */
+struct svn_authz_t
+{
+ svn_config_t *cfg;
+};
+
+
+
+/*** Checking access. ***/
+
+/* Determine whether the REQUIRED access is granted given what authz
+ * to ALLOW or DENY. Return TRUE if the REQUIRED access is
+ * granted.
+ *
+ * Access is granted either when no required access is explicitly
+ * denied (implicit grant), or when the required access is explicitly
+ * granted, overriding any denials.
+ */
+static svn_boolean_t
+authz_access_is_granted(svn_repos_authz_access_t allow,
+ svn_repos_authz_access_t deny,
+ svn_repos_authz_access_t required)
+{
+ svn_repos_authz_access_t stripped_req =
+ required & (svn_authz_read | svn_authz_write);
+
+ if ((deny & required) == svn_authz_none)
+ return TRUE;
+ else if ((allow & required) == stripped_req)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+
+/* Decide whether the REQUIRED access has been conclusively
+ * determined. Return TRUE if the given ALLOW/DENY authz are
+ * conclusive regarding the REQUIRED authz.
+ *
+ * Conclusive determination occurs when any of the REQUIRED authz are
+ * granted or denied by ALLOW/DENY.
+ */
+static svn_boolean_t
+authz_access_is_determined(svn_repos_authz_access_t allow,
+ svn_repos_authz_access_t deny,
+ svn_repos_authz_access_t required)
+{
+ if ((deny & required) || (allow & required))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/* Return TRUE is USER equals ALIAS. The alias definitions are in the
+ "aliases" sections of CFG. Use POOL for temporary allocations during
+ the lookup. */
+static svn_boolean_t
+authz_alias_is_user(svn_config_t *cfg,
+ const char *alias,
+ const char *user,
+ apr_pool_t *pool)
+{
+ const char *value;
+
+ svn_config_get(cfg, &value, "aliases", alias, NULL);
+ if (!value)
+ return FALSE;
+
+ if (strcmp(value, user) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/* Return TRUE if USER is in GROUP. The group definitions are in the
+ "groups" section of CFG. Use POOL for temporary allocations during
+ the lookup. */
+static svn_boolean_t
+authz_group_contains_user(svn_config_t *cfg,
+ const char *group,
+ const char *user,
+ apr_pool_t *pool)
+{
+ const char *value;
+ apr_array_header_t *list;
+ int i;
+
+ svn_config_get(cfg, &value, "groups", group, NULL);
+
+ list = svn_cstring_split(value, ",", TRUE, pool);
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *group_user = APR_ARRAY_IDX(list, i, char *);
+
+ /* If the 'user' is a subgroup, recurse into it. */
+ if (*group_user == '@')
+ {
+ if (authz_group_contains_user(cfg, &group_user[1],
+ user, pool))
+ return TRUE;
+ }
+
+ /* If the 'user' is an alias, verify it. */
+ else if (*group_user == '&')
+ {
+ if (authz_alias_is_user(cfg, &group_user[1],
+ user, pool))
+ return TRUE;
+ }
+
+ /* If the user matches, stop. */
+ else if (strcmp(user, group_user) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Determines whether an authz rule applies to the current
+ * user, given the name part of the rule's name-value pair
+ * in RULE_MATCH_STRING and the authz_lookup_baton object
+ * B with the username in question.
+ */
+static svn_boolean_t
+authz_line_applies_to_user(const char *rule_match_string,
+ struct authz_lookup_baton *b,
+ apr_pool_t *pool)
+{
+ /* If the rule has an inversion, recurse and invert the result. */
+ if (rule_match_string[0] == '~')
+ return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
+
+ /* Check for special tokens. */
+ if (strcmp(rule_match_string, "$anonymous") == 0)
+ return (b->user == NULL);
+ if (strcmp(rule_match_string, "$authenticated") == 0)
+ return (b->user != NULL);
+
+ /* Check for a wildcard rule. */
+ if (strcmp(rule_match_string, "*") == 0)
+ return TRUE;
+
+ /* If we get here, then the rule is:
+ * - Not an inversion rule.
+ * - Not an authz token rule.
+ * - Not a wildcard rule.
+ *
+ * All that's left over is regular user or group specifications.
+ */
+
+ /* If the session is anonymous, then a user/group
+ * rule definitely won't match.
+ */
+ if (b->user == NULL)
+ return FALSE;
+
+ /* Process the rule depending on whether it is
+ * a user, alias or group rule.
+ */
+ if (rule_match_string[0] == '@')
+ return authz_group_contains_user(
+ b->config, &rule_match_string[1], b->user, pool);
+ else if (rule_match_string[0] == '&')
+ return authz_alias_is_user(
+ b->config, &rule_match_string[1], b->user, pool);
+ else
+ return (strcmp(b->user, rule_match_string) == 0);
+}
+
+
+/* Callback to parse one line of an authz file and update the
+ * authz_baton accordingly.
+ */
+static svn_boolean_t
+authz_parse_line(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+
+ /* Stop if the rule doesn't apply to this user. */
+ if (!authz_line_applies_to_user(name, b, pool))
+ return TRUE;
+
+ /* Set the access grants for the rule. */
+ if (strchr(value, 'r'))
+ b->allow |= svn_authz_read;
+ else
+ b->deny |= svn_authz_read;
+
+ if (strchr(value, 'w'))
+ b->allow |= svn_authz_write;
+ else
+ b->deny |= svn_authz_write;
+
+ return TRUE;
+}
+
+
+/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
+ * (which is a repository name, colon, and repository fspath, such as
+ * "myrepos:/trunk/foo").
+ */
+static svn_boolean_t
+is_applicable_section(const char *path_spec,
+ const char *section_name)
+{
+ apr_size_t path_spec_len = strlen(path_spec);
+
+ return ((strncmp(path_spec, section_name, path_spec_len) == 0)
+ && (path_spec[path_spec_len - 1] == '/'
+ || section_name[path_spec_len] == '/'
+ || section_name[path_spec_len] == '\0'));
+}
+
+
+/* Callback to parse a section and update the authz_baton if the
+ * section denies access to the subtree the baton describes.
+ */
+static svn_boolean_t
+authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+ svn_boolean_t conclusive;
+
+ /* Does the section apply to us? */
+ if (!is_applicable_section(b->qualified_repos_path, section_name)
+ && !is_applicable_section(b->repos_path, section_name))
+ return TRUE;
+
+ /* Work out what this section grants. */
+ b->allow = b->deny = 0;
+ svn_config_enumerate2(b->config, section_name,
+ authz_parse_line, b, pool);
+
+ /* Has the section explicitly determined an access? */
+ conclusive = authz_access_is_determined(b->allow, b->deny,
+ b->required_access);
+
+ /* Is access granted OR inconclusive? */
+ b->access = authz_access_is_granted(b->allow, b->deny,
+ b->required_access)
+ || !conclusive;
+
+ /* As long as access isn't conclusively denied, carry on. */
+ return b->access;
+}
+
+
+/* Validate access to the given user for the given path. This
+ * function checks rules for exactly the given path, and first tries
+ * to access a section specific to the given repository before falling
+ * back to pan-repository rules.
+ *
+ * Update *access_granted to inform the caller of the outcome of the
+ * lookup. Return a boolean indicating whether the access rights were
+ * successfully determined.
+ */
+static svn_boolean_t
+authz_get_path_access(svn_config_t *cfg, 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 char *qualified_path;
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+
+ /* Try to locate a repository-specific block first. */
+ qualified_path = apr_pstrcat(pool, repos_name, ":", path, (char *)NULL);
+ svn_config_enumerate2(cfg, qualified_path,
+ authz_parse_line, &baton, pool);
+
+ *access_granted = authz_access_is_granted(baton.allow, baton.deny,
+ required_access);
+
+ /* If the first test has determined access, stop now. */
+ if (authz_access_is_determined(baton.allow, baton.deny,
+ required_access))
+ return TRUE;
+
+ /* No repository specific rule, try pan-repository rules. */
+ svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
+
+ *access_granted = authz_access_is_granted(baton.allow, baton.deny,
+ required_access);
+ return authz_access_is_determined(baton.allow, baton.deny,
+ required_access);
+}
+
+
+/* Validate access to the given user for the subtree starting at the
+ * given path. This function walks the whole authz file in search of
+ * rules applying to paths in the requested subtree which deny the
+ * requested access.
+ *
+ * As soon as one is found, or else when the whole ACL file has been
+ * searched, return the updated authorization status.
+ */
+static svn_boolean_t
+authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
+ const char *path, const char *user,
+ svn_repos_authz_access_t required_access,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+ baton.required_access = required_access;
+ baton.repos_path = path;
+ baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
+ ":", path, (char *)NULL);
+ /* Default to access granted if no rules say otherwise. */
+ baton.access = TRUE;
+
+ svn_config_enumerate_sections2(cfg, authz_parse_section,
+ &baton, pool);
+
+ return baton.access;
+}
+
+
+/* Callback to parse sections of the configuration file, looking for
+ any kind of granted access. Implements the
+ svn_config_section_enumerator2_t interface. */
+static svn_boolean_t
+authz_get_any_access_parser_cb(const char *section_name, void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+
+ /* Does the section apply to the query? */
+ if (section_name[0] == '/'
+ || strncmp(section_name, b->qualified_repos_path,
+ strlen(b->qualified_repos_path)) == 0)
+ {
+ b->allow = b->deny = svn_authz_none;
+
+ svn_config_enumerate2(b->config, section_name,
+ authz_parse_line, baton, pool);
+ b->access = authz_access_is_granted(b->allow, b->deny,
+ b->required_access);
+
+ /* Continue as long as we don't find a determined, granted access. */
+ return !(b->access
+ && authz_access_is_determined(b->allow, b->deny,
+ b->required_access));
+ }
+
+ return TRUE;
+}
+
+
+/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
+ * to any path within the REPOSITORY. Return TRUE if so. Use POOL
+ * for temporary allocations. */
+static svn_boolean_t
+authz_get_any_access(svn_config_t *cfg, const char *repos_name,
+ const char *user,
+ svn_repos_authz_access_t required_access,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+ baton.required_access = required_access;
+ baton.access = FALSE; /* Deny access by default. */
+ baton.repos_path = "/";
+ baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
+ ":/", (char *)NULL);
+
+ /* We could have used svn_config_enumerate2 for "repos_name:/".
+ * However, this requires access for root explicitly (which the user
+ * may not always have). So we end up enumerating the sections in
+ * the authz CFG and stop on the first match with some access for
+ * this user. */
+ svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
+ &baton, pool);
+
+ /* If walking the configuration was inconclusive, deny access. */
+ if (!authz_access_is_determined(baton.allow,
+ baton.deny, baton.required_access))
+ return FALSE;
+
+ return baton.access;
+}
+
+
+
+/*** Validating the authz file. ***/
+
+/* Check for errors in GROUP's definition of CFG. The errors
+ * detected are references to non-existent groups and circular
+ * dependencies between groups. If an error is found, return
+ * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary
+ * allocations only.
+ *
+ * CHECKED_GROUPS should be an empty (it is used for recursive calls).
+ */
+static svn_error_t *
+authz_group_walk(svn_config_t *cfg,
+ const char *group,
+ apr_hash_t *checked_groups,
+ apr_pool_t *pool)
+{
+ const char *value;
+ apr_array_header_t *list;
+ int i;
+
+ svn_config_get(cfg, &value, "groups", group, NULL);
+ /* Having a non-existent group in the ACL configuration might be the
+ sign of a typo. Refuse to perform authz on uncertain rules. */
+ if (!value)
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to group '%s', "
+ "which is undefined",
+ group);
+
+ list = svn_cstring_split(value, ",", TRUE, pool);
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *group_user = APR_ARRAY_IDX(list, i, char *);
+
+ /* If the 'user' is a subgroup, recurse into it. */
+ if (*group_user == '@')
+ {
+ /* A circular dependency between groups is a Bad Thing. We
+ don't do authz with invalid ACL files. */
+ if (svn_hash_gets(checked_groups, &group_user[1]))
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
+ NULL,
+ "Circular dependency between "
+ "groups '%s' and '%s'",
+ &group_user[1], group);
+
+ /* Add group to hash of checked groups. */
+ svn_hash_sets(checked_groups, &group_user[1], "");
+
+ /* Recurse on that group. */
+ SVN_ERR(authz_group_walk(cfg, &group_user[1],
+ checked_groups, pool));
+
+ /* Remove group from hash of checked groups, so that we don't
+ incorrectly report an error if we see it again as part of
+ another group. */
+ svn_hash_sets(checked_groups, &group_user[1], NULL);
+ }
+ else if (*group_user == '&')
+ {
+ const char *alias;
+
+ svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
+ /* Having a non-existent alias in the ACL configuration might be the
+ sign of a typo. Refuse to perform authz on uncertain rules. */
+ if (!alias)
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to alias '%s', "
+ "which is undefined",
+ &group_user[1]);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Callback to perform some simple sanity checks on an authz rule.
+ *
+ * - If RULE_MATCH_STRING references a group or an alias, verify that
+ * the group or alias definition exists.
+ * - If RULE_MATCH_STRING specifies a token (starts with $), verify
+ * that the token name is valid.
+ * - If RULE_MATCH_STRING is using inversion, verify that it isn't
+ * doing it more than once within the one rule, and that it isn't
+ * "~*", as that would never match.
+ * - Check that VALUE part of the rule specifies only allowed rule
+ * flag characters ('r' and 'w').
+ *
+ * Return TRUE if the rule has no errors. Use BATON for context and
+ * error reporting.
+ */
+static svn_boolean_t authz_validate_rule(const char *rule_match_string,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *val;
+ const char *match = rule_match_string;
+ struct authz_validate_baton *b = baton;
+
+ /* Make sure the user isn't using double-negatives. */
+ if (match[0] == '~')
+ {
+ /* Bump the pointer past the inversion for the other checks. */
+ match++;
+
+ /* Another inversion is a double negative; we can't not stop. */
+ if (match[0] == '~')
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Rule '%s' has more than one "
+ "inversion; double negatives are "
+ "not permitted.",
+ rule_match_string);
+ return FALSE;
+ }
+
+ /* Make sure that the rule isn't "~*", which won't ever match. */
+ if (strcmp(match, "*") == 0)
+ {
+ b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Authz rules with match string '~*' "
+ "are not allowed, because they never "
+ "match anyone.");
+ return FALSE;
+ }
+ }
+
+ /* If the rule applies to a group, check its existence. */
+ if (match[0] == '@')
+ {
+ const char *group = &match[1];
+
+ svn_config_get(b->config, &val, "groups", group, NULL);
+
+ /* Having a non-existent group in the ACL configuration might be
+ the sign of a typo. Refuse to perform authz on uncertain
+ rules. */
+ if (!val)
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to group "
+ "'%s', which is undefined",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ /* If the rule applies to an alias, check its existence. */
+ if (match[0] == '&')
+ {
+ const char *alias = &match[1];
+
+ svn_config_get(b->config, &val, "aliases", alias, NULL);
+
+ if (!val)
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to alias "
+ "'%s', which is undefined",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ /* If the rule specifies a token, check its validity. */
+ if (match[0] == '$')
+ {
+ const char *token_name = &match[1];
+
+ if ((strcmp(token_name, "anonymous") != 0)
+ && (strcmp(token_name, "authenticated") != 0))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Unrecognized authz token '%s'.",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ val = value;
+
+ while (*val)
+ {
+ if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "The character '%c' in rule '%s' is not "
+ "allowed in authz rules", *val,
+ rule_match_string);
+ return FALSE;
+ }
+
+ ++val;
+ }
+
+ return TRUE;
+}
+
+/* Callback to check ALIAS's definition for validity. Use
+ BATON for context and error reporting. */
+static svn_boolean_t authz_validate_alias(const char *alias,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ /* No checking at the moment, every alias is valid */
+ return TRUE;
+}
+
+
+/* Callback to check GROUP's definition for cyclic dependancies. Use
+ BATON for context and error reporting. */
+static svn_boolean_t authz_validate_group(const char *group,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_validate_baton *b = baton;
+
+ b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
+ if (b->err)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Callback to check the contents of the configuration section given
+ by NAME. Use BATON for context and error reporting. */
+static svn_boolean_t authz_validate_section(const char *name,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_validate_baton *b = baton;
+
+ /* Use the group checking callback for the "groups" section... */
+ if (strcmp(name, "groups") == 0)
+ svn_config_enumerate2(b->config, name, authz_validate_group,
+ baton, pool);
+ /* ...and the alias checking callback for "aliases"... */
+ else if (strcmp(name, "aliases") == 0)
+ svn_config_enumerate2(b->config, name, authz_validate_alias,
+ baton, pool);
+ /* ...but for everything else use the rule checking callback. */
+ else
+ {
+ /* Validate the section's name. Skip the optional REPOS_NAME. */
+ const char *fspath = strchr(name, ':');
+ if (fspath)
+ fspath++;
+ else
+ fspath = name;
+ if (! svn_fspath__is_canonical(fspath))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Section name '%s' contains non-canonical "
+ "fspath '%s'",
+ name, fspath);
+ return FALSE;
+ }
+
+ svn_config_enumerate2(b->config, name, authz_validate_rule,
+ baton, pool);
+ }
+
+ if (b->err)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Walk the configuration in AUTHZ looking for any errors. */
+static svn_error_t *
+authz_validate(svn_authz_t *authz, apr_pool_t *pool)
+{
+ struct authz_validate_baton baton = { 0 };
+
+ baton.err = SVN_NO_ERROR;
+ baton.config = authz->cfg;
+
+ /* Step through the entire rule file stopping on error. */
+ svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
+ &baton, pool);
+ SVN_ERR(baton.err);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
+ * file placing the result into CFG_P allocated in POOL.
+ *
+ * If DIRENT cannot be parsed as a config file then an error is returned. The
+ * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
+ * authz file is also an error.
+ *
+ * SCRATCH_POOL will be used for temporary allocations. */
+static svn_error_t *
+authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
+ svn_boolean_t must_exist,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_repos_t *repos;
+ const char *repos_root_dirent;
+ const char *fs_path;
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ svn_stream_t *contents;
+
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+ if (!repos_root_dirent)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
+ "Unable to find repository at '%s'", dirent);
+
+ /* Attempt to open a repository at repos_root_dirent. */
+ SVN_ERR(svn_repos_open2(&repos, repos_root_dirent, NULL, scratch_pool));
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Root path is always a directory so no reason to go any further */
+ if (*fs_path == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'/' is not a file in repo '%s'",
+ repos_root_dirent);
+
+ /* We skip some things that are non-important for how we're going to use
+ * this repo connection. We do not set any capabilities since none of
+ * the current ones are important for what we're doing. We also do not
+ * setup the environment that repos hooks would run under since we won't
+ * be triggering any. */
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(repos);
+
+ /* Find HEAD and the revision root */
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind == svn_node_none)
+ {
+ if (!must_exist)
+ {
+ SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' path not found in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
+ err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool);
+
+ /* Add the URL to the error stack since the parser doesn't have it. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error while parsing config file: '%s' in repo '%s':",
+ fs_path, repos_root_dirent);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a PATH which might be a relative repo URL (^/), an absolute
+ * local repo URL (file://), an absolute path outside of the repo
+ * or a location in the Windows registry.
+ *
+ * Retrieve the configuration data that PATH points at and parse it into
+ * CFG_P allocated in POOL.
+ *
+ * If PATH cannot be parsed as a config file then an error is returned. The
+ * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
+ * authz file is also an error.
+ *
+ * REPOS_ROOT points at the root of the repos you are
+ * going to apply the authz against, can be NULL if you are sure that you
+ * don't have a repos relative URL in PATH. */
+static svn_error_t *
+authz_retrieve_config(svn_config_t **cfg_p, const char *path,
+ svn_boolean_t must_exist, apr_pool_t *pool)
+{
+ if (svn_path_is_url(path))
+ {
+ const char *dirent;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
+
+ if (err == SVN_NO_ERROR)
+ err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, pool,
+ scratch_pool);
+
+ /* Close the repos and streams we opened. */
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+ }
+ else
+ {
+ /* Outside of repo file or Windows registry*/
+ SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Callback to copy (name, value) group into the "groups" section
+ of another configuration. */
+static svn_boolean_t
+authz_copy_group(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ svn_config_t *authz_cfg = baton;
+
+ svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+
+ return TRUE;
+}
+
+/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
+ * If AUTHZ already contains any group definition, report an error.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
+ apr_pool_t *pool)
+{
+ /* Easy out: we prohibit local groups in the authz file when global
+ groups are being used. */
+ if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+ {
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Authz file cannot contain any groups "
+ "when global groups are being used.");
+ }
+
+ svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
+ authz_copy_group, authz->cfg, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ svn_boolean_t accept_urls, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Load the authz file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool));
+ else
+ SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool));
+
+ if (groups_path)
+ {
+ svn_config_t *groups_cfg;
+ svn_error_t *err;
+
+ /* Load the groups file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
+ pool));
+ else
+ SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
+ TRUE, TRUE, pool));
+
+ /* Copy the groups from groups_cfg into authz. */
+ err = authz_copy_groups(authz, groups_cfg, pool);
+
+ /* Add the paths to the error stack since the authz_copy_groups
+ routine knows nothing about them. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error reading authz file '%s' with "
+ "groups file '%s':", path, groups_path);
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, pool));
+
+ *authz_p = authz;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public functions. ***/
+
+svn_error_t *
+svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
+ svn_stream_t *groups_stream, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Parse the authz stream */
+ SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
+
+ if (groups_stream)
+ {
+ svn_config_t *groups_cfg;
+
+ /* Parse the groups stream */
+ SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
+
+ SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, 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 char *current_path;
+
+ if (!repos_name)
+ repos_name = "";
+
+ /* If PATH is NULL, check if the user has *any* access. */
+ if (!path)
+ {
+ *access_granted = authz_get_any_access(authz->cfg, repos_name,
+ user, required_access, pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Sanity check. */
+ SVN_ERR_ASSERT(path[0] == '/');
+
+ /* Determine the granted access for the requested path. */
+ path = svn_fspath__canonicalize(path, pool);
+ current_path = path;
+
+ while (!authz_get_path_access(authz->cfg, repos_name,
+ current_path, user,
+ required_access,
+ access_granted,
+ pool))
+ {
+ /* Stop if the loop hits the repository root with no
+ results. */
+ if (current_path[0] == '/' && current_path[1] == '\0')
+ {
+ /* Deny access by default. */
+ *access_granted = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Work back to the parent path. */
+ current_path = svn_fspath__dirname(current_path, pool);
+ }
+
+ /* If the caller requested recursive access, we need to walk through
+ the entire authz config to see whether any child paths are denied
+ to the requested user. */
+ if (*access_granted && (required_access & svn_authz_recursive))
+ *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
+ user, required_access, pool);
+
+ return SVN_NO_ERROR;
+}