1251881Speter/* authz.c : path-based access control 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23251881Speter 24251881Speter/*** Includes. ***/ 25251881Speter 26251881Speter#include <apr_pools.h> 27251881Speter#include <apr_file_io.h> 28251881Speter 29251881Speter#include "svn_hash.h" 30251881Speter#include "svn_pools.h" 31251881Speter#include "svn_error.h" 32251881Speter#include "svn_dirent_uri.h" 33251881Speter#include "svn_path.h" 34251881Speter#include "svn_repos.h" 35251881Speter#include "svn_config.h" 36251881Speter#include "svn_ctype.h" 37251881Speter#include "private/svn_fspath.h" 38299742Sdim#include "private/svn_repos_private.h" 39251881Speter#include "repos.h" 40251881Speter 41251881Speter 42251881Speter/*** Structures. ***/ 43251881Speter 44251881Speter/* Information for the config enumerators called during authz 45251881Speter lookup. */ 46251881Speterstruct authz_lookup_baton { 47251881Speter /* The authz configuration. */ 48251881Speter svn_config_t *config; 49251881Speter 50251881Speter /* The user to authorize. */ 51251881Speter const char *user; 52251881Speter 53251881Speter /* Explicitly granted rights. */ 54251881Speter svn_repos_authz_access_t allow; 55251881Speter /* Explicitly denied rights. */ 56251881Speter svn_repos_authz_access_t deny; 57251881Speter 58251881Speter /* The rights required by the caller of the lookup. */ 59251881Speter svn_repos_authz_access_t required_access; 60251881Speter 61251881Speter /* The following are used exclusively in recursive lookups. */ 62251881Speter 63251881Speter /* The path in the repository (an fspath) to authorize. */ 64251881Speter const char *repos_path; 65251881Speter /* repos_path prefixed by the repository name and a colon. */ 66251881Speter const char *qualified_repos_path; 67251881Speter 68251881Speter /* Whether, at the end of a recursive lookup, access is granted. */ 69251881Speter svn_boolean_t access; 70251881Speter}; 71251881Speter 72251881Speter/* Information for the config enumeration functions called during the 73251881Speter validation process. */ 74251881Speterstruct authz_validate_baton { 75251881Speter svn_config_t *config; /* The configuration file being validated. */ 76251881Speter svn_error_t *err; /* The error being thrown out of the 77251881Speter enumerator, if any. */ 78251881Speter}; 79251881Speter 80299742Sdim/* Currently this structure is just a wrapper around a svn_config_t. 81299742Sdim Please update authz_pool if you modify this structure. */ 82251881Speterstruct svn_authz_t 83251881Speter{ 84251881Speter svn_config_t *cfg; 85251881Speter}; 86251881Speter 87251881Speter 88251881Speter 89251881Speter/*** Checking access. ***/ 90251881Speter 91251881Speter/* Determine whether the REQUIRED access is granted given what authz 92251881Speter * to ALLOW or DENY. Return TRUE if the REQUIRED access is 93251881Speter * granted. 94251881Speter * 95251881Speter * Access is granted either when no required access is explicitly 96251881Speter * denied (implicit grant), or when the required access is explicitly 97251881Speter * granted, overriding any denials. 98251881Speter */ 99251881Speterstatic svn_boolean_t 100251881Speterauthz_access_is_granted(svn_repos_authz_access_t allow, 101251881Speter svn_repos_authz_access_t deny, 102251881Speter svn_repos_authz_access_t required) 103251881Speter{ 104251881Speter svn_repos_authz_access_t stripped_req = 105251881Speter required & (svn_authz_read | svn_authz_write); 106251881Speter 107251881Speter if ((deny & required) == svn_authz_none) 108251881Speter return TRUE; 109251881Speter else if ((allow & required) == stripped_req) 110251881Speter return TRUE; 111251881Speter else 112251881Speter return FALSE; 113251881Speter} 114251881Speter 115251881Speter 116251881Speter/* Decide whether the REQUIRED access has been conclusively 117251881Speter * determined. Return TRUE if the given ALLOW/DENY authz are 118251881Speter * conclusive regarding the REQUIRED authz. 119251881Speter * 120251881Speter * Conclusive determination occurs when any of the REQUIRED authz are 121251881Speter * granted or denied by ALLOW/DENY. 122251881Speter */ 123251881Speterstatic svn_boolean_t 124251881Speterauthz_access_is_determined(svn_repos_authz_access_t allow, 125251881Speter svn_repos_authz_access_t deny, 126251881Speter svn_repos_authz_access_t required) 127251881Speter{ 128251881Speter if ((deny & required) || (allow & required)) 129251881Speter return TRUE; 130251881Speter else 131251881Speter return FALSE; 132251881Speter} 133251881Speter 134251881Speter/* Return TRUE is USER equals ALIAS. The alias definitions are in the 135251881Speter "aliases" sections of CFG. Use POOL for temporary allocations during 136251881Speter the lookup. */ 137251881Speterstatic svn_boolean_t 138251881Speterauthz_alias_is_user(svn_config_t *cfg, 139251881Speter const char *alias, 140251881Speter const char *user, 141251881Speter apr_pool_t *pool) 142251881Speter{ 143251881Speter const char *value; 144251881Speter 145251881Speter svn_config_get(cfg, &value, "aliases", alias, NULL); 146251881Speter if (!value) 147251881Speter return FALSE; 148251881Speter 149251881Speter if (strcmp(value, user) == 0) 150251881Speter return TRUE; 151251881Speter 152251881Speter return FALSE; 153251881Speter} 154251881Speter 155251881Speter 156251881Speter/* Return TRUE if USER is in GROUP. The group definitions are in the 157251881Speter "groups" section of CFG. Use POOL for temporary allocations during 158251881Speter the lookup. */ 159251881Speterstatic svn_boolean_t 160251881Speterauthz_group_contains_user(svn_config_t *cfg, 161251881Speter const char *group, 162251881Speter const char *user, 163251881Speter apr_pool_t *pool) 164251881Speter{ 165251881Speter const char *value; 166251881Speter apr_array_header_t *list; 167251881Speter int i; 168251881Speter 169251881Speter svn_config_get(cfg, &value, "groups", group, NULL); 170251881Speter 171251881Speter list = svn_cstring_split(value, ",", TRUE, pool); 172251881Speter 173251881Speter for (i = 0; i < list->nelts; i++) 174251881Speter { 175251881Speter const char *group_user = APR_ARRAY_IDX(list, i, char *); 176251881Speter 177251881Speter /* If the 'user' is a subgroup, recurse into it. */ 178251881Speter if (*group_user == '@') 179251881Speter { 180251881Speter if (authz_group_contains_user(cfg, &group_user[1], 181251881Speter user, pool)) 182251881Speter return TRUE; 183251881Speter } 184251881Speter 185251881Speter /* If the 'user' is an alias, verify it. */ 186251881Speter else if (*group_user == '&') 187251881Speter { 188251881Speter if (authz_alias_is_user(cfg, &group_user[1], 189251881Speter user, pool)) 190251881Speter return TRUE; 191251881Speter } 192251881Speter 193251881Speter /* If the user matches, stop. */ 194251881Speter else if (strcmp(user, group_user) == 0) 195251881Speter return TRUE; 196251881Speter } 197251881Speter 198251881Speter return FALSE; 199251881Speter} 200251881Speter 201251881Speter 202251881Speter/* Determines whether an authz rule applies to the current 203251881Speter * user, given the name part of the rule's name-value pair 204251881Speter * in RULE_MATCH_STRING and the authz_lookup_baton object 205251881Speter * B with the username in question. 206251881Speter */ 207251881Speterstatic svn_boolean_t 208251881Speterauthz_line_applies_to_user(const char *rule_match_string, 209251881Speter struct authz_lookup_baton *b, 210251881Speter apr_pool_t *pool) 211251881Speter{ 212251881Speter /* If the rule has an inversion, recurse and invert the result. */ 213251881Speter if (rule_match_string[0] == '~') 214251881Speter return !authz_line_applies_to_user(&rule_match_string[1], b, pool); 215251881Speter 216251881Speter /* Check for special tokens. */ 217251881Speter if (strcmp(rule_match_string, "$anonymous") == 0) 218251881Speter return (b->user == NULL); 219251881Speter if (strcmp(rule_match_string, "$authenticated") == 0) 220251881Speter return (b->user != NULL); 221251881Speter 222251881Speter /* Check for a wildcard rule. */ 223251881Speter if (strcmp(rule_match_string, "*") == 0) 224251881Speter return TRUE; 225251881Speter 226251881Speter /* If we get here, then the rule is: 227251881Speter * - Not an inversion rule. 228251881Speter * - Not an authz token rule. 229251881Speter * - Not a wildcard rule. 230251881Speter * 231251881Speter * All that's left over is regular user or group specifications. 232251881Speter */ 233251881Speter 234251881Speter /* If the session is anonymous, then a user/group 235251881Speter * rule definitely won't match. 236251881Speter */ 237251881Speter if (b->user == NULL) 238251881Speter return FALSE; 239251881Speter 240251881Speter /* Process the rule depending on whether it is 241251881Speter * a user, alias or group rule. 242251881Speter */ 243251881Speter if (rule_match_string[0] == '@') 244251881Speter return authz_group_contains_user( 245251881Speter b->config, &rule_match_string[1], b->user, pool); 246251881Speter else if (rule_match_string[0] == '&') 247251881Speter return authz_alias_is_user( 248251881Speter b->config, &rule_match_string[1], b->user, pool); 249251881Speter else 250251881Speter return (strcmp(b->user, rule_match_string) == 0); 251251881Speter} 252251881Speter 253251881Speter 254251881Speter/* Callback to parse one line of an authz file and update the 255251881Speter * authz_baton accordingly. 256251881Speter */ 257251881Speterstatic svn_boolean_t 258251881Speterauthz_parse_line(const char *name, const char *value, 259251881Speter void *baton, apr_pool_t *pool) 260251881Speter{ 261251881Speter struct authz_lookup_baton *b = baton; 262251881Speter 263251881Speter /* Stop if the rule doesn't apply to this user. */ 264251881Speter if (!authz_line_applies_to_user(name, b, pool)) 265251881Speter return TRUE; 266251881Speter 267251881Speter /* Set the access grants for the rule. */ 268251881Speter if (strchr(value, 'r')) 269251881Speter b->allow |= svn_authz_read; 270251881Speter else 271251881Speter b->deny |= svn_authz_read; 272251881Speter 273251881Speter if (strchr(value, 'w')) 274251881Speter b->allow |= svn_authz_write; 275251881Speter else 276251881Speter b->deny |= svn_authz_write; 277251881Speter 278251881Speter return TRUE; 279251881Speter} 280251881Speter 281251881Speter 282251881Speter/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC 283251881Speter * (which is a repository name, colon, and repository fspath, such as 284251881Speter * "myrepos:/trunk/foo"). 285251881Speter */ 286251881Speterstatic svn_boolean_t 287251881Speteris_applicable_section(const char *path_spec, 288251881Speter const char *section_name) 289251881Speter{ 290251881Speter apr_size_t path_spec_len = strlen(path_spec); 291251881Speter 292251881Speter return ((strncmp(path_spec, section_name, path_spec_len) == 0) 293251881Speter && (path_spec[path_spec_len - 1] == '/' 294251881Speter || section_name[path_spec_len] == '/' 295251881Speter || section_name[path_spec_len] == '\0')); 296251881Speter} 297251881Speter 298251881Speter 299251881Speter/* Callback to parse a section and update the authz_baton if the 300251881Speter * section denies access to the subtree the baton describes. 301251881Speter */ 302251881Speterstatic svn_boolean_t 303251881Speterauthz_parse_section(const char *section_name, void *baton, apr_pool_t *pool) 304251881Speter{ 305251881Speter struct authz_lookup_baton *b = baton; 306251881Speter svn_boolean_t conclusive; 307251881Speter 308251881Speter /* Does the section apply to us? */ 309251881Speter if (!is_applicable_section(b->qualified_repos_path, section_name) 310251881Speter && !is_applicable_section(b->repos_path, section_name)) 311251881Speter return TRUE; 312251881Speter 313251881Speter /* Work out what this section grants. */ 314251881Speter b->allow = b->deny = 0; 315251881Speter svn_config_enumerate2(b->config, section_name, 316251881Speter authz_parse_line, b, pool); 317251881Speter 318251881Speter /* Has the section explicitly determined an access? */ 319251881Speter conclusive = authz_access_is_determined(b->allow, b->deny, 320251881Speter b->required_access); 321251881Speter 322251881Speter /* Is access granted OR inconclusive? */ 323251881Speter b->access = authz_access_is_granted(b->allow, b->deny, 324251881Speter b->required_access) 325251881Speter || !conclusive; 326251881Speter 327251881Speter /* As long as access isn't conclusively denied, carry on. */ 328251881Speter return b->access; 329251881Speter} 330251881Speter 331251881Speter 332251881Speter/* Validate access to the given user for the given path. This 333251881Speter * function checks rules for exactly the given path, and first tries 334251881Speter * to access a section specific to the given repository before falling 335251881Speter * back to pan-repository rules. 336251881Speter * 337251881Speter * Update *access_granted to inform the caller of the outcome of the 338251881Speter * lookup. Return a boolean indicating whether the access rights were 339251881Speter * successfully determined. 340251881Speter */ 341251881Speterstatic svn_boolean_t 342251881Speterauthz_get_path_access(svn_config_t *cfg, const char *repos_name, 343251881Speter const char *path, const char *user, 344251881Speter svn_repos_authz_access_t required_access, 345251881Speter svn_boolean_t *access_granted, 346251881Speter apr_pool_t *pool) 347251881Speter{ 348251881Speter const char *qualified_path; 349251881Speter struct authz_lookup_baton baton = { 0 }; 350251881Speter 351251881Speter baton.config = cfg; 352251881Speter baton.user = user; 353251881Speter 354251881Speter /* Try to locate a repository-specific block first. */ 355299742Sdim qualified_path = apr_pstrcat(pool, repos_name, ":", path, SVN_VA_NULL); 356251881Speter svn_config_enumerate2(cfg, qualified_path, 357251881Speter authz_parse_line, &baton, pool); 358251881Speter 359251881Speter *access_granted = authz_access_is_granted(baton.allow, baton.deny, 360251881Speter required_access); 361251881Speter 362251881Speter /* If the first test has determined access, stop now. */ 363251881Speter if (authz_access_is_determined(baton.allow, baton.deny, 364251881Speter required_access)) 365251881Speter return TRUE; 366251881Speter 367251881Speter /* No repository specific rule, try pan-repository rules. */ 368251881Speter svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool); 369251881Speter 370251881Speter *access_granted = authz_access_is_granted(baton.allow, baton.deny, 371251881Speter required_access); 372251881Speter return authz_access_is_determined(baton.allow, baton.deny, 373251881Speter required_access); 374251881Speter} 375251881Speter 376251881Speter 377251881Speter/* Validate access to the given user for the subtree starting at the 378251881Speter * given path. This function walks the whole authz file in search of 379251881Speter * rules applying to paths in the requested subtree which deny the 380251881Speter * requested access. 381251881Speter * 382251881Speter * As soon as one is found, or else when the whole ACL file has been 383251881Speter * searched, return the updated authorization status. 384251881Speter */ 385251881Speterstatic svn_boolean_t 386251881Speterauthz_get_tree_access(svn_config_t *cfg, const char *repos_name, 387251881Speter const char *path, const char *user, 388251881Speter svn_repos_authz_access_t required_access, 389251881Speter apr_pool_t *pool) 390251881Speter{ 391251881Speter struct authz_lookup_baton baton = { 0 }; 392251881Speter 393251881Speter baton.config = cfg; 394251881Speter baton.user = user; 395251881Speter baton.required_access = required_access; 396251881Speter baton.repos_path = path; 397251881Speter baton.qualified_repos_path = apr_pstrcat(pool, repos_name, 398299742Sdim ":", path, SVN_VA_NULL); 399251881Speter /* Default to access granted if no rules say otherwise. */ 400251881Speter baton.access = TRUE; 401251881Speter 402251881Speter svn_config_enumerate_sections2(cfg, authz_parse_section, 403251881Speter &baton, pool); 404251881Speter 405251881Speter return baton.access; 406251881Speter} 407251881Speter 408251881Speter 409251881Speter/* Callback to parse sections of the configuration file, looking for 410251881Speter any kind of granted access. Implements the 411251881Speter svn_config_section_enumerator2_t interface. */ 412251881Speterstatic svn_boolean_t 413251881Speterauthz_get_any_access_parser_cb(const char *section_name, void *baton, 414251881Speter apr_pool_t *pool) 415251881Speter{ 416251881Speter struct authz_lookup_baton *b = baton; 417251881Speter 418251881Speter /* Does the section apply to the query? */ 419251881Speter if (section_name[0] == '/' 420251881Speter || strncmp(section_name, b->qualified_repos_path, 421251881Speter strlen(b->qualified_repos_path)) == 0) 422251881Speter { 423251881Speter b->allow = b->deny = svn_authz_none; 424251881Speter 425251881Speter svn_config_enumerate2(b->config, section_name, 426251881Speter authz_parse_line, baton, pool); 427251881Speter b->access = authz_access_is_granted(b->allow, b->deny, 428251881Speter b->required_access); 429251881Speter 430251881Speter /* Continue as long as we don't find a determined, granted access. */ 431251881Speter return !(b->access 432251881Speter && authz_access_is_determined(b->allow, b->deny, 433251881Speter b->required_access)); 434251881Speter } 435251881Speter 436251881Speter return TRUE; 437251881Speter} 438251881Speter 439251881Speter 440251881Speter/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS 441251881Speter * to any path within the REPOSITORY. Return TRUE if so. Use POOL 442251881Speter * for temporary allocations. */ 443251881Speterstatic svn_boolean_t 444251881Speterauthz_get_any_access(svn_config_t *cfg, const char *repos_name, 445251881Speter const char *user, 446251881Speter svn_repos_authz_access_t required_access, 447251881Speter apr_pool_t *pool) 448251881Speter{ 449251881Speter struct authz_lookup_baton baton = { 0 }; 450251881Speter 451251881Speter baton.config = cfg; 452251881Speter baton.user = user; 453251881Speter baton.required_access = required_access; 454251881Speter baton.access = FALSE; /* Deny access by default. */ 455251881Speter baton.repos_path = "/"; 456251881Speter baton.qualified_repos_path = apr_pstrcat(pool, repos_name, 457299742Sdim ":/", SVN_VA_NULL); 458251881Speter 459251881Speter /* We could have used svn_config_enumerate2 for "repos_name:/". 460251881Speter * However, this requires access for root explicitly (which the user 461251881Speter * may not always have). So we end up enumerating the sections in 462251881Speter * the authz CFG and stop on the first match with some access for 463251881Speter * this user. */ 464251881Speter svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb, 465251881Speter &baton, pool); 466251881Speter 467251881Speter /* If walking the configuration was inconclusive, deny access. */ 468251881Speter if (!authz_access_is_determined(baton.allow, 469251881Speter baton.deny, baton.required_access)) 470251881Speter return FALSE; 471251881Speter 472251881Speter return baton.access; 473251881Speter} 474251881Speter 475251881Speter 476251881Speter 477251881Speter/*** Validating the authz file. ***/ 478251881Speter 479251881Speter/* Check for errors in GROUP's definition of CFG. The errors 480251881Speter * detected are references to non-existent groups and circular 481251881Speter * dependencies between groups. If an error is found, return 482251881Speter * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary 483251881Speter * allocations only. 484251881Speter * 485251881Speter * CHECKED_GROUPS should be an empty (it is used for recursive calls). 486251881Speter */ 487251881Speterstatic svn_error_t * 488251881Speterauthz_group_walk(svn_config_t *cfg, 489251881Speter const char *group, 490251881Speter apr_hash_t *checked_groups, 491251881Speter apr_pool_t *pool) 492251881Speter{ 493251881Speter const char *value; 494251881Speter apr_array_header_t *list; 495251881Speter int i; 496251881Speter 497251881Speter svn_config_get(cfg, &value, "groups", group, NULL); 498251881Speter /* Having a non-existent group in the ACL configuration might be the 499251881Speter sign of a typo. Refuse to perform authz on uncertain rules. */ 500251881Speter if (!value) 501251881Speter return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 502251881Speter "An authz rule refers to group '%s', " 503251881Speter "which is undefined", 504251881Speter group); 505251881Speter 506251881Speter list = svn_cstring_split(value, ",", TRUE, pool); 507251881Speter 508251881Speter for (i = 0; i < list->nelts; i++) 509251881Speter { 510251881Speter const char *group_user = APR_ARRAY_IDX(list, i, char *); 511251881Speter 512251881Speter /* If the 'user' is a subgroup, recurse into it. */ 513251881Speter if (*group_user == '@') 514251881Speter { 515251881Speter /* A circular dependency between groups is a Bad Thing. We 516251881Speter don't do authz with invalid ACL files. */ 517251881Speter if (svn_hash_gets(checked_groups, &group_user[1])) 518251881Speter return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, 519251881Speter NULL, 520251881Speter "Circular dependency between " 521251881Speter "groups '%s' and '%s'", 522251881Speter &group_user[1], group); 523251881Speter 524251881Speter /* Add group to hash of checked groups. */ 525251881Speter svn_hash_sets(checked_groups, &group_user[1], ""); 526251881Speter 527251881Speter /* Recurse on that group. */ 528251881Speter SVN_ERR(authz_group_walk(cfg, &group_user[1], 529251881Speter checked_groups, pool)); 530251881Speter 531251881Speter /* Remove group from hash of checked groups, so that we don't 532251881Speter incorrectly report an error if we see it again as part of 533251881Speter another group. */ 534251881Speter svn_hash_sets(checked_groups, &group_user[1], NULL); 535251881Speter } 536251881Speter else if (*group_user == '&') 537251881Speter { 538251881Speter const char *alias; 539251881Speter 540251881Speter svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL); 541251881Speter /* Having a non-existent alias in the ACL configuration might be the 542251881Speter sign of a typo. Refuse to perform authz on uncertain rules. */ 543251881Speter if (!alias) 544251881Speter return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 545251881Speter "An authz rule refers to alias '%s', " 546251881Speter "which is undefined", 547251881Speter &group_user[1]); 548251881Speter } 549251881Speter } 550251881Speter 551251881Speter return SVN_NO_ERROR; 552251881Speter} 553251881Speter 554251881Speter 555251881Speter/* Callback to perform some simple sanity checks on an authz rule. 556251881Speter * 557251881Speter * - If RULE_MATCH_STRING references a group or an alias, verify that 558251881Speter * the group or alias definition exists. 559251881Speter * - If RULE_MATCH_STRING specifies a token (starts with $), verify 560251881Speter * that the token name is valid. 561251881Speter * - If RULE_MATCH_STRING is using inversion, verify that it isn't 562251881Speter * doing it more than once within the one rule, and that it isn't 563251881Speter * "~*", as that would never match. 564251881Speter * - Check that VALUE part of the rule specifies only allowed rule 565251881Speter * flag characters ('r' and 'w'). 566251881Speter * 567251881Speter * Return TRUE if the rule has no errors. Use BATON for context and 568251881Speter * error reporting. 569251881Speter */ 570251881Speterstatic svn_boolean_t authz_validate_rule(const char *rule_match_string, 571251881Speter const char *value, 572251881Speter void *baton, 573251881Speter apr_pool_t *pool) 574251881Speter{ 575251881Speter const char *val; 576251881Speter const char *match = rule_match_string; 577251881Speter struct authz_validate_baton *b = baton; 578251881Speter 579251881Speter /* Make sure the user isn't using double-negatives. */ 580251881Speter if (match[0] == '~') 581251881Speter { 582251881Speter /* Bump the pointer past the inversion for the other checks. */ 583251881Speter match++; 584251881Speter 585251881Speter /* Another inversion is a double negative; we can't not stop. */ 586251881Speter if (match[0] == '~') 587251881Speter { 588251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 589251881Speter "Rule '%s' has more than one " 590251881Speter "inversion; double negatives are " 591251881Speter "not permitted.", 592251881Speter rule_match_string); 593251881Speter return FALSE; 594251881Speter } 595251881Speter 596251881Speter /* Make sure that the rule isn't "~*", which won't ever match. */ 597251881Speter if (strcmp(match, "*") == 0) 598251881Speter { 599251881Speter b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 600251881Speter "Authz rules with match string '~*' " 601251881Speter "are not allowed, because they never " 602251881Speter "match anyone."); 603251881Speter return FALSE; 604251881Speter } 605251881Speter } 606251881Speter 607251881Speter /* If the rule applies to a group, check its existence. */ 608251881Speter if (match[0] == '@') 609251881Speter { 610251881Speter const char *group = &match[1]; 611251881Speter 612251881Speter svn_config_get(b->config, &val, "groups", group, NULL); 613251881Speter 614251881Speter /* Having a non-existent group in the ACL configuration might be 615251881Speter the sign of a typo. Refuse to perform authz on uncertain 616251881Speter rules. */ 617251881Speter if (!val) 618251881Speter { 619251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 620251881Speter "An authz rule refers to group " 621251881Speter "'%s', which is undefined", 622251881Speter rule_match_string); 623251881Speter return FALSE; 624251881Speter } 625251881Speter } 626251881Speter 627251881Speter /* If the rule applies to an alias, check its existence. */ 628251881Speter if (match[0] == '&') 629251881Speter { 630251881Speter const char *alias = &match[1]; 631251881Speter 632251881Speter svn_config_get(b->config, &val, "aliases", alias, NULL); 633251881Speter 634251881Speter if (!val) 635251881Speter { 636251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 637251881Speter "An authz rule refers to alias " 638251881Speter "'%s', which is undefined", 639251881Speter rule_match_string); 640251881Speter return FALSE; 641251881Speter } 642251881Speter } 643251881Speter 644251881Speter /* If the rule specifies a token, check its validity. */ 645251881Speter if (match[0] == '$') 646251881Speter { 647251881Speter const char *token_name = &match[1]; 648251881Speter 649251881Speter if ((strcmp(token_name, "anonymous") != 0) 650251881Speter && (strcmp(token_name, "authenticated") != 0)) 651251881Speter { 652251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 653251881Speter "Unrecognized authz token '%s'.", 654251881Speter rule_match_string); 655251881Speter return FALSE; 656251881Speter } 657251881Speter } 658251881Speter 659251881Speter val = value; 660251881Speter 661251881Speter while (*val) 662251881Speter { 663251881Speter if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val)) 664251881Speter { 665251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 666251881Speter "The character '%c' in rule '%s' is not " 667251881Speter "allowed in authz rules", *val, 668251881Speter rule_match_string); 669251881Speter return FALSE; 670251881Speter } 671251881Speter 672251881Speter ++val; 673251881Speter } 674251881Speter 675251881Speter return TRUE; 676251881Speter} 677251881Speter 678251881Speter/* Callback to check ALIAS's definition for validity. Use 679251881Speter BATON for context and error reporting. */ 680251881Speterstatic svn_boolean_t authz_validate_alias(const char *alias, 681251881Speter const char *value, 682251881Speter void *baton, 683251881Speter apr_pool_t *pool) 684251881Speter{ 685251881Speter /* No checking at the moment, every alias is valid */ 686251881Speter return TRUE; 687251881Speter} 688251881Speter 689251881Speter 690251881Speter/* Callback to check GROUP's definition for cyclic dependancies. Use 691251881Speter BATON for context and error reporting. */ 692251881Speterstatic svn_boolean_t authz_validate_group(const char *group, 693251881Speter const char *value, 694251881Speter void *baton, 695251881Speter apr_pool_t *pool) 696251881Speter{ 697251881Speter struct authz_validate_baton *b = baton; 698251881Speter 699251881Speter b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool); 700251881Speter if (b->err) 701251881Speter return FALSE; 702251881Speter 703251881Speter return TRUE; 704251881Speter} 705251881Speter 706251881Speter 707251881Speter/* Callback to check the contents of the configuration section given 708251881Speter by NAME. Use BATON for context and error reporting. */ 709251881Speterstatic svn_boolean_t authz_validate_section(const char *name, 710251881Speter void *baton, 711251881Speter apr_pool_t *pool) 712251881Speter{ 713251881Speter struct authz_validate_baton *b = baton; 714251881Speter 715251881Speter /* Use the group checking callback for the "groups" section... */ 716251881Speter if (strcmp(name, "groups") == 0) 717251881Speter svn_config_enumerate2(b->config, name, authz_validate_group, 718251881Speter baton, pool); 719251881Speter /* ...and the alias checking callback for "aliases"... */ 720251881Speter else if (strcmp(name, "aliases") == 0) 721251881Speter svn_config_enumerate2(b->config, name, authz_validate_alias, 722251881Speter baton, pool); 723251881Speter /* ...but for everything else use the rule checking callback. */ 724251881Speter else 725251881Speter { 726251881Speter /* Validate the section's name. Skip the optional REPOS_NAME. */ 727251881Speter const char *fspath = strchr(name, ':'); 728251881Speter if (fspath) 729251881Speter fspath++; 730251881Speter else 731251881Speter fspath = name; 732251881Speter if (! svn_fspath__is_canonical(fspath)) 733251881Speter { 734251881Speter b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 735251881Speter "Section name '%s' contains non-canonical " 736251881Speter "fspath '%s'", 737251881Speter name, fspath); 738251881Speter return FALSE; 739251881Speter } 740251881Speter 741251881Speter svn_config_enumerate2(b->config, name, authz_validate_rule, 742251881Speter baton, pool); 743251881Speter } 744251881Speter 745251881Speter if (b->err) 746251881Speter return FALSE; 747251881Speter 748251881Speter return TRUE; 749251881Speter} 750251881Speter 751251881Speter 752299742Sdimsvn_error_t * 753299742Sdimsvn_repos__authz_validate(svn_authz_t *authz, apr_pool_t *pool) 754251881Speter{ 755251881Speter struct authz_validate_baton baton = { 0 }; 756251881Speter 757251881Speter baton.err = SVN_NO_ERROR; 758251881Speter baton.config = authz->cfg; 759251881Speter 760251881Speter /* Step through the entire rule file stopping on error. */ 761251881Speter svn_config_enumerate_sections2(authz->cfg, authz_validate_section, 762251881Speter &baton, pool); 763251881Speter SVN_ERR(baton.err); 764251881Speter 765251881Speter return SVN_NO_ERROR; 766251881Speter} 767251881Speter 768251881Speter 769251881Speter/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config 770251881Speter * file placing the result into CFG_P allocated in POOL. 771251881Speter * 772251881Speter * If DIRENT cannot be parsed as a config file then an error is returned. The 773251881Speter * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing 774299742Sdim * authz file is also an error. The CASE_SENSITIVE controls the lookup 775299742Sdim * behavior for section and option names alike. 776251881Speter * 777251881Speter * SCRATCH_POOL will be used for temporary allocations. */ 778251881Speterstatic svn_error_t * 779299742Sdimauthz_retrieve_config_repo(svn_config_t **cfg_p, 780299742Sdim const char *dirent, 781299742Sdim svn_boolean_t must_exist, 782299742Sdim svn_boolean_t case_sensitive, 783299742Sdim apr_pool_t *result_pool, 784299742Sdim apr_pool_t *scratch_pool) 785251881Speter{ 786251881Speter svn_error_t *err; 787251881Speter svn_repos_t *repos; 788251881Speter const char *repos_root_dirent; 789251881Speter const char *fs_path; 790251881Speter svn_fs_t *fs; 791251881Speter svn_fs_root_t *root; 792251881Speter svn_revnum_t youngest_rev; 793251881Speter svn_node_kind_t node_kind; 794251881Speter svn_stream_t *contents; 795251881Speter 796251881Speter /* Search for a repository in the full path. */ 797251881Speter repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool); 798251881Speter if (!repos_root_dirent) 799251881Speter return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL, 800251881Speter "Unable to find repository at '%s'", dirent); 801251881Speter 802251881Speter /* Attempt to open a repository at repos_root_dirent. */ 803299742Sdim SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL, scratch_pool, 804299742Sdim scratch_pool)); 805251881Speter 806251881Speter fs_path = &dirent[strlen(repos_root_dirent)]; 807251881Speter 808251881Speter /* Root path is always a directory so no reason to go any further */ 809251881Speter if (*fs_path == '\0') 810251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 811251881Speter "'/' is not a file in repo '%s'", 812251881Speter repos_root_dirent); 813251881Speter 814251881Speter /* We skip some things that are non-important for how we're going to use 815251881Speter * this repo connection. We do not set any capabilities since none of 816251881Speter * the current ones are important for what we're doing. We also do not 817251881Speter * setup the environment that repos hooks would run under since we won't 818251881Speter * be triggering any. */ 819251881Speter 820251881Speter /* Get the filesystem. */ 821251881Speter fs = svn_repos_fs(repos); 822251881Speter 823251881Speter /* Find HEAD and the revision root */ 824251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool)); 825251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool)); 826251881Speter 827251881Speter SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool)); 828251881Speter if (node_kind == svn_node_none) 829251881Speter { 830251881Speter if (!must_exist) 831251881Speter { 832299742Sdim SVN_ERR(svn_config_create2(cfg_p, case_sensitive, case_sensitive, 833299742Sdim result_pool)); 834251881Speter return SVN_NO_ERROR; 835251881Speter } 836251881Speter else 837251881Speter { 838251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 839251881Speter "'%s' path not found in repo '%s'", fs_path, 840251881Speter repos_root_dirent); 841251881Speter } 842251881Speter } 843251881Speter else if (node_kind != svn_node_file) 844251881Speter { 845251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 846251881Speter "'%s' is not a file in repo '%s'", fs_path, 847251881Speter repos_root_dirent); 848251881Speter } 849251881Speter 850251881Speter SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool)); 851299742Sdim err = svn_config_parse(cfg_p, contents, case_sensitive, case_sensitive, 852299742Sdim result_pool); 853251881Speter 854251881Speter /* Add the URL to the error stack since the parser doesn't have it. */ 855251881Speter if (err != SVN_NO_ERROR) 856251881Speter return svn_error_createf(err->apr_err, err, 857251881Speter "Error while parsing config file: '%s' in repo '%s':", 858251881Speter fs_path, repos_root_dirent); 859251881Speter 860251881Speter return SVN_NO_ERROR; 861251881Speter} 862251881Speter 863299742Sdimsvn_error_t * 864299742Sdimsvn_repos__retrieve_config(svn_config_t **cfg_p, 865299742Sdim const char *path, 866299742Sdim svn_boolean_t must_exist, 867299742Sdim svn_boolean_t case_sensitive, 868299742Sdim apr_pool_t *pool) 869251881Speter{ 870251881Speter if (svn_path_is_url(path)) 871251881Speter { 872251881Speter const char *dirent; 873251881Speter svn_error_t *err; 874251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 875251881Speter 876251881Speter err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool); 877251881Speter 878251881Speter if (err == SVN_NO_ERROR) 879299742Sdim err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, 880299742Sdim case_sensitive, pool, scratch_pool); 881251881Speter 882251881Speter /* Close the repos and streams we opened. */ 883251881Speter svn_pool_destroy(scratch_pool); 884251881Speter 885251881Speter return err; 886251881Speter } 887251881Speter else 888251881Speter { 889251881Speter /* Outside of repo file or Windows registry*/ 890299742Sdim SVN_ERR(svn_config_read3(cfg_p, path, must_exist, case_sensitive, 891299742Sdim case_sensitive, pool)); 892251881Speter } 893251881Speter 894251881Speter return SVN_NO_ERROR; 895251881Speter} 896251881Speter 897251881Speter 898251881Speter/* Callback to copy (name, value) group into the "groups" section 899251881Speter of another configuration. */ 900251881Speterstatic svn_boolean_t 901251881Speterauthz_copy_group(const char *name, const char *value, 902251881Speter void *baton, apr_pool_t *pool) 903251881Speter{ 904251881Speter svn_config_t *authz_cfg = baton; 905251881Speter 906251881Speter svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value); 907251881Speter 908251881Speter return TRUE; 909251881Speter} 910251881Speter 911251881Speter/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ. 912251881Speter * If AUTHZ already contains any group definition, report an error. 913251881Speter * Use POOL for temporary allocations. */ 914251881Speterstatic svn_error_t * 915251881Speterauthz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg, 916251881Speter apr_pool_t *pool) 917251881Speter{ 918251881Speter /* Easy out: we prohibit local groups in the authz file when global 919251881Speter groups are being used. */ 920251881Speter if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS)) 921251881Speter { 922251881Speter return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 923251881Speter "Authz file cannot contain any groups " 924251881Speter "when global groups are being used."); 925251881Speter } 926251881Speter 927251881Speter svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS, 928251881Speter authz_copy_group, authz->cfg, pool); 929251881Speter 930251881Speter return SVN_NO_ERROR; 931251881Speter} 932251881Speter 933251881Spetersvn_error_t * 934251881Spetersvn_repos__authz_read(svn_authz_t **authz_p, const char *path, 935251881Speter const char *groups_path, svn_boolean_t must_exist, 936251881Speter svn_boolean_t accept_urls, apr_pool_t *pool) 937251881Speter{ 938251881Speter svn_authz_t *authz = apr_palloc(pool, sizeof(*authz)); 939251881Speter 940251881Speter /* Load the authz file */ 941251881Speter if (accept_urls) 942299742Sdim SVN_ERR(svn_repos__retrieve_config(&authz->cfg, path, must_exist, TRUE, 943299742Sdim pool)); 944251881Speter else 945299742Sdim SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, 946299742Sdim pool)); 947251881Speter 948251881Speter if (groups_path) 949251881Speter { 950251881Speter svn_config_t *groups_cfg; 951251881Speter svn_error_t *err; 952251881Speter 953251881Speter /* Load the groups file */ 954251881Speter if (accept_urls) 955299742Sdim SVN_ERR(svn_repos__retrieve_config(&groups_cfg, groups_path, 956299742Sdim must_exist, TRUE, pool)); 957251881Speter else 958251881Speter SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist, 959251881Speter TRUE, TRUE, pool)); 960251881Speter 961251881Speter /* Copy the groups from groups_cfg into authz. */ 962251881Speter err = authz_copy_groups(authz, groups_cfg, pool); 963251881Speter 964251881Speter /* Add the paths to the error stack since the authz_copy_groups 965251881Speter routine knows nothing about them. */ 966251881Speter if (err != SVN_NO_ERROR) 967251881Speter return svn_error_createf(err->apr_err, err, 968251881Speter "Error reading authz file '%s' with " 969251881Speter "groups file '%s':", path, groups_path); 970251881Speter } 971251881Speter 972251881Speter /* Make sure there are no errors in the configuration. */ 973299742Sdim SVN_ERR(svn_repos__authz_validate(authz, pool)); 974251881Speter 975251881Speter *authz_p = authz; 976251881Speter return SVN_NO_ERROR; 977251881Speter} 978251881Speter 979251881Speter 980251881Speter 981251881Speter/*** Public functions. ***/ 982251881Speter 983251881Spetersvn_error_t * 984251881Spetersvn_repos_authz_read2(svn_authz_t **authz_p, const char *path, 985251881Speter const char *groups_path, svn_boolean_t must_exist, 986251881Speter apr_pool_t *pool) 987251881Speter{ 988251881Speter return svn_repos__authz_read(authz_p, path, groups_path, must_exist, 989251881Speter TRUE, pool); 990251881Speter} 991251881Speter 992251881Speter 993251881Spetersvn_error_t * 994251881Spetersvn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream, 995251881Speter svn_stream_t *groups_stream, apr_pool_t *pool) 996251881Speter{ 997251881Speter svn_authz_t *authz = apr_palloc(pool, sizeof(*authz)); 998251881Speter 999251881Speter /* Parse the authz stream */ 1000251881Speter SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool)); 1001251881Speter 1002251881Speter if (groups_stream) 1003251881Speter { 1004251881Speter svn_config_t *groups_cfg; 1005251881Speter 1006251881Speter /* Parse the groups stream */ 1007251881Speter SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool)); 1008251881Speter 1009251881Speter SVN_ERR(authz_copy_groups(authz, groups_cfg, pool)); 1010251881Speter } 1011251881Speter 1012251881Speter /* Make sure there are no errors in the configuration. */ 1013299742Sdim SVN_ERR(svn_repos__authz_validate(authz, pool)); 1014251881Speter 1015251881Speter *authz_p = authz; 1016251881Speter return SVN_NO_ERROR; 1017251881Speter} 1018251881Speter 1019251881Speter 1020251881Spetersvn_error_t * 1021251881Spetersvn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name, 1022251881Speter const char *path, const char *user, 1023251881Speter svn_repos_authz_access_t required_access, 1024251881Speter svn_boolean_t *access_granted, 1025251881Speter apr_pool_t *pool) 1026251881Speter{ 1027251881Speter const char *current_path; 1028251881Speter 1029251881Speter if (!repos_name) 1030251881Speter repos_name = ""; 1031251881Speter 1032251881Speter /* If PATH is NULL, check if the user has *any* access. */ 1033251881Speter if (!path) 1034251881Speter { 1035251881Speter *access_granted = authz_get_any_access(authz->cfg, repos_name, 1036251881Speter user, required_access, pool); 1037251881Speter return SVN_NO_ERROR; 1038251881Speter } 1039251881Speter 1040251881Speter /* Sanity check. */ 1041251881Speter SVN_ERR_ASSERT(path[0] == '/'); 1042251881Speter 1043251881Speter /* Determine the granted access for the requested path. */ 1044251881Speter path = svn_fspath__canonicalize(path, pool); 1045251881Speter current_path = path; 1046251881Speter 1047251881Speter while (!authz_get_path_access(authz->cfg, repos_name, 1048251881Speter current_path, user, 1049251881Speter required_access, 1050251881Speter access_granted, 1051251881Speter pool)) 1052251881Speter { 1053251881Speter /* Stop if the loop hits the repository root with no 1054251881Speter results. */ 1055251881Speter if (current_path[0] == '/' && current_path[1] == '\0') 1056251881Speter { 1057251881Speter /* Deny access by default. */ 1058251881Speter *access_granted = FALSE; 1059251881Speter return SVN_NO_ERROR; 1060251881Speter } 1061251881Speter 1062251881Speter /* Work back to the parent path. */ 1063251881Speter current_path = svn_fspath__dirname(current_path, pool); 1064251881Speter } 1065251881Speter 1066251881Speter /* If the caller requested recursive access, we need to walk through 1067251881Speter the entire authz config to see whether any child paths are denied 1068251881Speter to the requested user. */ 1069251881Speter if (*access_granted && (required_access & svn_authz_recursive)) 1070251881Speter *access_granted = authz_get_tree_access(authz->cfg, repos_name, path, 1071251881Speter user, required_access, pool); 1072251881Speter 1073251881Speter return SVN_NO_ERROR; 1074251881Speter} 1075