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