#include <assert.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_repos.h"
#include "svn_config.h"
#include "svn_ctype.h"
struct authz_lookup_baton {
svn_config_t *config;
const char *user;
svn_repos_authz_access_t allow;
svn_repos_authz_access_t deny;
svn_repos_authz_access_t required_access;
const char *repos_path;
const char *qualified_repos_path;
svn_boolean_t access;
};
struct authz_validate_baton {
svn_config_t *config;
svn_error_t *err;
};
struct svn_authz_t
{
svn_config_t *cfg;
};
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;
}
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;
}
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 (*group_user == '@')
{
if (authz_group_contains_user(cfg, &group_user[1],
user, pool))
return TRUE;
}
else if (strcmp(user, group_user) == 0)
return TRUE;
}
return FALSE;
}
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;
if (strcmp(name, "*") != 0)
{
if (!b->user)
return TRUE;
if (*name == '@')
{
if (!authz_group_contains_user(b->config, &name[1],
b->user, pool))
return TRUE;
}
else if (strcmp(name, b->user) != 0)
return TRUE;
}
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;
}
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;
if (svn_path_is_ancestor(b->qualified_repos_path,
section_name) == FALSE
&& svn_path_is_ancestor(b->repos_path,
section_name) == FALSE)
return TRUE;
b->allow = b->deny = 0;
svn_config_enumerate2(b->config, section_name,
authz_parse_line, b, pool);
conclusive = authz_access_is_determined(b->allow, b->deny,
b->required_access);
b->access = authz_access_is_granted(b->allow, b->deny,
b->required_access)
|| !conclusive;
return b->access;
}
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;
qualified_path = apr_pstrcat(pool, repos_name, ":", path, 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 (authz_access_is_determined(baton.allow, baton.deny,
required_access))
return TRUE;
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);
}
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, NULL);
baton.access = TRUE;
svn_config_enumerate_sections2(cfg, authz_parse_section,
&baton, pool);
return baton.access;
}
static svn_boolean_t
authz_global_parse_section(const char *section_name, void *baton,
apr_pool_t *pool)
{
struct authz_lookup_baton *b = baton;
if (section_name[0] == '/'
|| strncmp(section_name, b->repos_path,
strlen(b->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);
return !b->access;
}
return TRUE;
}
static svn_boolean_t
authz_get_global_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;
baton.repos_path = apr_pstrcat(pool, repos_name, ":/", NULL);
svn_config_enumerate_sections2(cfg, authz_global_parse_section,
&baton, pool);
return baton.access;
}
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);
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 (*group_user == '@')
{
if (apr_hash_get(checked_groups, &group_user[1],
APR_HASH_KEY_STRING))
return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
NULL,
"Circular dependency between "
"groups '%s' and '%s'",
&group_user[1], group);
apr_hash_set(checked_groups, &group_user[1],
APR_HASH_KEY_STRING, "");
SVN_ERR(authz_group_walk(cfg, &group_user[1],
checked_groups, pool));
apr_hash_set(checked_groups, &group_user[1],
APR_HASH_KEY_STRING, NULL);
}
}
return SVN_NO_ERROR;
}
static svn_boolean_t authz_validate_rule(const char *group,
const char *value,
void *baton,
apr_pool_t *pool)
{
const char *val;
struct authz_validate_baton *b = baton;
if (*group == '@')
{
svn_config_get(b->config, &val, "groups", &group[1], NULL);
if (!val)
{
b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
"An authz rule refers to group "
"'%s', which is undefined",
group);
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, group);
return FALSE;
}
++val;
}
return TRUE;
}
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;
}
static svn_boolean_t authz_validate_section(const char *name,
void *baton,
apr_pool_t *pool)
{
struct authz_validate_baton *b = baton;
if (strncmp(name, "groups", 6) == 0)
svn_config_enumerate2(b->config, name, authz_validate_group,
baton, pool);
else
svn_config_enumerate2(b->config, name, authz_validate_rule,
baton, pool);
if (b->err)
return FALSE;
return TRUE;
}
svn_error_t *
svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
svn_boolean_t must_exist, apr_pool_t *pool)
{
svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
struct authz_validate_baton baton = { 0 };
baton.err = SVN_NO_ERROR;
SVN_ERR(svn_config_read(&authz->cfg, file, must_exist, pool));
baton.config = authz->cfg;
svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
&baton, pool);
SVN_ERR(baton.err);
*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 = path;
if (!path)
{
*access_granted = authz_get_global_access(authz->cfg, repos_name,
user, required_access,
pool);
return SVN_NO_ERROR;
}
while (!authz_get_path_access(authz->cfg, repos_name,
current_path, user,
required_access,
access_granted,
pool))
{
if (current_path[0] == '/' && current_path[1] == '\0')
{
*access_granted = FALSE;
return SVN_NO_ERROR;
}
svn_path_split(current_path, ¤t_path, NULL, pool);
}
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;
}