1/* authz.c : path-based access control 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23 24/*** Includes. ***/ 25 26#include <apr_pools.h> 27#include <apr_file_io.h> 28 29#include "svn_hash.h" 30#include "svn_pools.h" 31#include "svn_error.h" 32#include "svn_dirent_uri.h" 33#include "svn_path.h" 34#include "svn_repos.h" 35#include "svn_config.h" 36#include "svn_ctype.h" 37#include "private/svn_fspath.h" 38#include "repos.h" 39 40 41/*** Structures. ***/ 42 43/* Information for the config enumerators called during authz 44 lookup. */ 45struct authz_lookup_baton { 46 /* The authz configuration. */ 47 svn_config_t *config; 48 49 /* The user to authorize. */ 50 const char *user; 51 52 /* Explicitly granted rights. */ 53 svn_repos_authz_access_t allow; 54 /* Explicitly denied rights. */ 55 svn_repos_authz_access_t deny; 56 57 /* The rights required by the caller of the lookup. */ 58 svn_repos_authz_access_t required_access; 59 60 /* The following are used exclusively in recursive lookups. */ 61 62 /* The path in the repository (an fspath) to authorize. */ 63 const char *repos_path; 64 /* repos_path prefixed by the repository name and a colon. */ 65 const char *qualified_repos_path; 66 67 /* Whether, at the end of a recursive lookup, access is granted. */ 68 svn_boolean_t access; 69}; 70 71/* Information for the config enumeration functions called during the 72 validation process. */ 73struct authz_validate_baton { 74 svn_config_t *config; /* The configuration file being validated. */ 75 svn_error_t *err; /* The error being thrown out of the 76 enumerator, if any. */ 77}; 78 79/* Currently this structure is just a wrapper around a 80 svn_config_t. */ 81struct svn_authz_t 82{ 83 svn_config_t *cfg; 84}; 85 86 87 88/*** Checking access. ***/ 89 90/* Determine whether the REQUIRED access is granted given what authz 91 * to ALLOW or DENY. Return TRUE if the REQUIRED access is 92 * granted. 93 * 94 * Access is granted either when no required access is explicitly 95 * denied (implicit grant), or when the required access is explicitly 96 * granted, overriding any denials. 97 */ 98static svn_boolean_t 99authz_access_is_granted(svn_repos_authz_access_t allow, 100 svn_repos_authz_access_t deny, 101 svn_repos_authz_access_t required) 102{ 103 svn_repos_authz_access_t stripped_req = 104 required & (svn_authz_read | svn_authz_write); 105 106 if ((deny & required) == svn_authz_none) 107 return TRUE; 108 else if ((allow & required) == stripped_req) 109 return TRUE; 110 else 111 return FALSE; 112} 113 114 115/* Decide whether the REQUIRED access has been conclusively 116 * determined. Return TRUE if the given ALLOW/DENY authz are 117 * conclusive regarding the REQUIRED authz. 118 * 119 * Conclusive determination occurs when any of the REQUIRED authz are 120 * granted or denied by ALLOW/DENY. 121 */ 122static svn_boolean_t 123authz_access_is_determined(svn_repos_authz_access_t allow, 124 svn_repos_authz_access_t deny, 125 svn_repos_authz_access_t required) 126{ 127 if ((deny & required) || (allow & required)) 128 return TRUE; 129 else 130 return FALSE; 131} 132 133/* Return TRUE is USER equals ALIAS. The alias definitions are in the 134 "aliases" sections of CFG. Use POOL for temporary allocations during 135 the lookup. */ 136static svn_boolean_t 137authz_alias_is_user(svn_config_t *cfg, 138 const char *alias, 139 const char *user, 140 apr_pool_t *pool) 141{ 142 const char *value; 143 144 svn_config_get(cfg, &value, "aliases", alias, NULL); 145 if (!value) 146 return FALSE; 147 148 if (strcmp(value, user) == 0) 149 return TRUE; 150 151 return FALSE; 152} 153 154 155/* Return TRUE if USER is in GROUP. The group definitions are in the 156 "groups" section of CFG. Use POOL for temporary allocations during 157 the lookup. */ 158static svn_boolean_t 159authz_group_contains_user(svn_config_t *cfg, 160 const char *group, 161 const char *user, 162 apr_pool_t *pool) 163{ 164 const char *value; 165 apr_array_header_t *list; 166 int i; 167 168 svn_config_get(cfg, &value, "groups", group, NULL); 169 170 list = svn_cstring_split(value, ",", TRUE, pool); 171 172 for (i = 0; i < list->nelts; i++) 173 { 174 const char *group_user = APR_ARRAY_IDX(list, i, char *); 175 176 /* If the 'user' is a subgroup, recurse into it. */ 177 if (*group_user == '@') 178 { 179 if (authz_group_contains_user(cfg, &group_user[1], 180 user, pool)) 181 return TRUE; 182 } 183 184 /* If the 'user' is an alias, verify it. */ 185 else if (*group_user == '&') 186 { 187 if (authz_alias_is_user(cfg, &group_user[1], 188 user, pool)) 189 return TRUE; 190 } 191 192 /* If the user matches, stop. */ 193 else if (strcmp(user, group_user) == 0) 194 return TRUE; 195 } 196 197 return FALSE; 198} 199 200 201/* Determines whether an authz rule applies to the current 202 * user, given the name part of the rule's name-value pair 203 * in RULE_MATCH_STRING and the authz_lookup_baton object 204 * B with the username in question. 205 */ 206static svn_boolean_t 207authz_line_applies_to_user(const char *rule_match_string, 208 struct authz_lookup_baton *b, 209 apr_pool_t *pool) 210{ 211 /* If the rule has an inversion, recurse and invert the result. */ 212 if (rule_match_string[0] == '~') 213 return !authz_line_applies_to_user(&rule_match_string[1], b, pool); 214 215 /* Check for special tokens. */ 216 if (strcmp(rule_match_string, "$anonymous") == 0) 217 return (b->user == NULL); 218 if (strcmp(rule_match_string, "$authenticated") == 0) 219 return (b->user != NULL); 220 221 /* Check for a wildcard rule. */ 222 if (strcmp(rule_match_string, "*") == 0) 223 return TRUE; 224 225 /* If we get here, then the rule is: 226 * - Not an inversion rule. 227 * - Not an authz token rule. 228 * - Not a wildcard rule. 229 * 230 * All that's left over is regular user or group specifications. 231 */ 232 233 /* If the session is anonymous, then a user/group 234 * rule definitely won't match. 235 */ 236 if (b->user == NULL) 237 return FALSE; 238 239 /* Process the rule depending on whether it is 240 * a user, alias or group rule. 241 */ 242 if (rule_match_string[0] == '@') 243 return authz_group_contains_user( 244 b->config, &rule_match_string[1], b->user, pool); 245 else if (rule_match_string[0] == '&') 246 return authz_alias_is_user( 247 b->config, &rule_match_string[1], b->user, pool); 248 else 249 return (strcmp(b->user, rule_match_string) == 0); 250} 251 252 253/* Callback to parse one line of an authz file and update the 254 * authz_baton accordingly. 255 */ 256static svn_boolean_t 257authz_parse_line(const char *name, const char *value, 258 void *baton, apr_pool_t *pool) 259{ 260 struct authz_lookup_baton *b = baton; 261 262 /* Stop if the rule doesn't apply to this user. */ 263 if (!authz_line_applies_to_user(name, b, pool)) 264 return TRUE; 265 266 /* Set the access grants for the rule. */ 267 if (strchr(value, 'r')) 268 b->allow |= svn_authz_read; 269 else 270 b->deny |= svn_authz_read; 271 272 if (strchr(value, 'w')) 273 b->allow |= svn_authz_write; 274 else 275 b->deny |= svn_authz_write; 276 277 return TRUE; 278} 279 280 281/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC 282 * (which is a repository name, colon, and repository fspath, such as 283 * "myrepos:/trunk/foo"). 284 */ 285static svn_boolean_t 286is_applicable_section(const char *path_spec, 287 const char *section_name) 288{ 289 apr_size_t path_spec_len = strlen(path_spec); 290 291 return ((strncmp(path_spec, section_name, path_spec_len) == 0) 292 && (path_spec[path_spec_len - 1] == '/' 293 || section_name[path_spec_len] == '/' 294 || section_name[path_spec_len] == '\0')); 295} 296 297 298/* Callback to parse a section and update the authz_baton if the 299 * section denies access to the subtree the baton describes. 300 */ 301static svn_boolean_t 302authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool) 303{ 304 struct authz_lookup_baton *b = baton; 305 svn_boolean_t conclusive; 306 307 /* Does the section apply to us? */ 308 if (!is_applicable_section(b->qualified_repos_path, section_name) 309 && !is_applicable_section(b->repos_path, section_name)) 310 return TRUE; 311 312 /* Work out what this section grants. */ 313 b->allow = b->deny = 0; 314 svn_config_enumerate2(b->config, section_name, 315 authz_parse_line, b, pool); 316 317 /* Has the section explicitly determined an access? */ 318 conclusive = authz_access_is_determined(b->allow, b->deny, 319 b->required_access); 320 321 /* Is access granted OR inconclusive? */ 322 b->access = authz_access_is_granted(b->allow, b->deny, 323 b->required_access) 324 || !conclusive; 325 326 /* As long as access isn't conclusively denied, carry on. */ 327 return b->access; 328} 329 330 331/* Validate access to the given user for the given path. This 332 * function checks rules for exactly the given path, and first tries 333 * to access a section specific to the given repository before falling 334 * back to pan-repository rules. 335 * 336 * Update *access_granted to inform the caller of the outcome of the 337 * lookup. Return a boolean indicating whether the access rights were 338 * successfully determined. 339 */ 340static svn_boolean_t 341authz_get_path_access(svn_config_t *cfg, const char *repos_name, 342 const char *path, const char *user, 343 svn_repos_authz_access_t required_access, 344 svn_boolean_t *access_granted, 345 apr_pool_t *pool) 346{ 347 const char *qualified_path; 348 struct authz_lookup_baton baton = { 0 }; 349 350 baton.config = cfg; 351 baton.user = user; 352 353 /* Try to locate a repository-specific block first. */ 354 qualified_path = apr_pstrcat(pool, repos_name, ":", path, (char *)NULL); 355 svn_config_enumerate2(cfg, qualified_path, 356 authz_parse_line, &baton, pool); 357 358 *access_granted = authz_access_is_granted(baton.allow, baton.deny, 359 required_access); 360 361 /* If the first test has determined access, stop now. */ 362 if (authz_access_is_determined(baton.allow, baton.deny, 363 required_access)) 364 return TRUE; 365 366 /* No repository specific rule, try pan-repository rules. */ 367 svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool); 368 369 *access_granted = authz_access_is_granted(baton.allow, baton.deny, 370 required_access); 371 return authz_access_is_determined(baton.allow, baton.deny, 372 required_access); 373} 374 375 376/* Validate access to the given user for the subtree starting at the 377 * given path. This function walks the whole authz file in search of 378 * rules applying to paths in the requested subtree which deny the 379 * requested access. 380 * 381 * As soon as one is found, or else when the whole ACL file has been 382 * searched, return the updated authorization status. 383 */ 384static svn_boolean_t 385authz_get_tree_access(svn_config_t *cfg, const char *repos_name, 386 const char *path, const char *user, 387 svn_repos_authz_access_t required_access, 388 apr_pool_t *pool) 389{ 390 struct authz_lookup_baton baton = { 0 }; 391 392 baton.config = cfg; 393 baton.user = user; 394 baton.required_access = required_access; 395 baton.repos_path = path; 396 baton.qualified_repos_path = apr_pstrcat(pool, repos_name, 397 ":", path, (char *)NULL); 398 /* Default to access granted if no rules say otherwise. */ 399 baton.access = TRUE; 400 401 svn_config_enumerate_sections2(cfg, authz_parse_section, 402 &baton, pool); 403 404 return baton.access; 405} 406 407 408/* Callback to parse sections of the configuration file, looking for 409 any kind of granted access. Implements the 410 svn_config_section_enumerator2_t interface. */ 411static svn_boolean_t 412authz_get_any_access_parser_cb(const char *section_name, void *baton, 413 apr_pool_t *pool) 414{ 415 struct authz_lookup_baton *b = baton; 416 417 /* Does the section apply to the query? */ 418 if (section_name[0] == '/' 419 || strncmp(section_name, b->qualified_repos_path, 420 strlen(b->qualified_repos_path)) == 0) 421 { 422 b->allow = b->deny = svn_authz_none; 423 424 svn_config_enumerate2(b->config, section_name, 425 authz_parse_line, baton, pool); 426 b->access = authz_access_is_granted(b->allow, b->deny, 427 b->required_access); 428 429 /* Continue as long as we don't find a determined, granted access. */ 430 return !(b->access 431 && authz_access_is_determined(b->allow, b->deny, 432 b->required_access)); 433 } 434 435 return TRUE; 436} 437 438 439/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS 440 * to any path within the REPOSITORY. Return TRUE if so. Use POOL 441 * for temporary allocations. */ 442static svn_boolean_t 443authz_get_any_access(svn_config_t *cfg, const char *repos_name, 444 const char *user, 445 svn_repos_authz_access_t required_access, 446 apr_pool_t *pool) 447{ 448 struct authz_lookup_baton baton = { 0 }; 449 450 baton.config = cfg; 451 baton.user = user; 452 baton.required_access = required_access; 453 baton.access = FALSE; /* Deny access by default. */ 454 baton.repos_path = "/"; 455 baton.qualified_repos_path = apr_pstrcat(pool, repos_name, 456 ":/", (char *)NULL); 457 458 /* We could have used svn_config_enumerate2 for "repos_name:/". 459 * However, this requires access for root explicitly (which the user 460 * may not always have). So we end up enumerating the sections in 461 * the authz CFG and stop on the first match with some access for 462 * this user. */ 463 svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb, 464 &baton, pool); 465 466 /* If walking the configuration was inconclusive, deny access. */ 467 if (!authz_access_is_determined(baton.allow, 468 baton.deny, baton.required_access)) 469 return FALSE; 470 471 return baton.access; 472} 473 474 475 476/*** Validating the authz file. ***/ 477 478/* Check for errors in GROUP's definition of CFG. The errors 479 * detected are references to non-existent groups and circular 480 * dependencies between groups. If an error is found, return 481 * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary 482 * allocations only. 483 * 484 * CHECKED_GROUPS should be an empty (it is used for recursive calls). 485 */ 486static svn_error_t * 487authz_group_walk(svn_config_t *cfg, 488 const char *group, 489 apr_hash_t *checked_groups, 490 apr_pool_t *pool) 491{ 492 const char *value; 493 apr_array_header_t *list; 494 int i; 495 496 svn_config_get(cfg, &value, "groups", group, NULL); 497 /* Having a non-existent group in the ACL configuration might be the 498 sign of a typo. Refuse to perform authz on uncertain rules. */ 499 if (!value) 500 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 501 "An authz rule refers to group '%s', " 502 "which is undefined", 503 group); 504 505 list = svn_cstring_split(value, ",", TRUE, pool); 506 507 for (i = 0; i < list->nelts; i++) 508 { 509 const char *group_user = APR_ARRAY_IDX(list, i, char *); 510 511 /* If the 'user' is a subgroup, recurse into it. */ 512 if (*group_user == '@') 513 { 514 /* A circular dependency between groups is a Bad Thing. We 515 don't do authz with invalid ACL files. */ 516 if (svn_hash_gets(checked_groups, &group_user[1])) 517 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, 518 NULL, 519 "Circular dependency between " 520 "groups '%s' and '%s'", 521 &group_user[1], group); 522 523 /* Add group to hash of checked groups. */ 524 svn_hash_sets(checked_groups, &group_user[1], ""); 525 526 /* Recurse on that group. */ 527 SVN_ERR(authz_group_walk(cfg, &group_user[1], 528 checked_groups, pool)); 529 530 /* Remove group from hash of checked groups, so that we don't 531 incorrectly report an error if we see it again as part of 532 another group. */ 533 svn_hash_sets(checked_groups, &group_user[1], NULL); 534 } 535 else if (*group_user == '&') 536 { 537 const char *alias; 538 539 svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL); 540 /* Having a non-existent alias in the ACL configuration might be the 541 sign of a typo. Refuse to perform authz on uncertain rules. */ 542 if (!alias) 543 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 544 "An authz rule refers to alias '%s', " 545 "which is undefined", 546 &group_user[1]); 547 } 548 } 549 550 return SVN_NO_ERROR; 551} 552 553 554/* Callback to perform some simple sanity checks on an authz rule. 555 * 556 * - If RULE_MATCH_STRING references a group or an alias, verify that 557 * the group or alias definition exists. 558 * - If RULE_MATCH_STRING specifies a token (starts with $), verify 559 * that the token name is valid. 560 * - If RULE_MATCH_STRING is using inversion, verify that it isn't 561 * doing it more than once within the one rule, and that it isn't 562 * "~*", as that would never match. 563 * - Check that VALUE part of the rule specifies only allowed rule 564 * flag characters ('r' and 'w'). 565 * 566 * Return TRUE if the rule has no errors. Use BATON for context and 567 * error reporting. 568 */ 569static svn_boolean_t authz_validate_rule(const char *rule_match_string, 570 const char *value, 571 void *baton, 572 apr_pool_t *pool) 573{ 574 const char *val; 575 const char *match = rule_match_string; 576 struct authz_validate_baton *b = baton; 577 578 /* Make sure the user isn't using double-negatives. */ 579 if (match[0] == '~') 580 { 581 /* Bump the pointer past the inversion for the other checks. */ 582 match++; 583 584 /* Another inversion is a double negative; we can't not stop. */ 585 if (match[0] == '~') 586 { 587 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 588 "Rule '%s' has more than one " 589 "inversion; double negatives are " 590 "not permitted.", 591 rule_match_string); 592 return FALSE; 593 } 594 595 /* Make sure that the rule isn't "~*", which won't ever match. */ 596 if (strcmp(match, "*") == 0) 597 { 598 b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 599 "Authz rules with match string '~*' " 600 "are not allowed, because they never " 601 "match anyone."); 602 return FALSE; 603 } 604 } 605 606 /* If the rule applies to a group, check its existence. */ 607 if (match[0] == '@') 608 { 609 const char *group = &match[1]; 610 611 svn_config_get(b->config, &val, "groups", group, NULL); 612 613 /* Having a non-existent group in the ACL configuration might be 614 the sign of a typo. Refuse to perform authz on uncertain 615 rules. */ 616 if (!val) 617 { 618 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 619 "An authz rule refers to group " 620 "'%s', which is undefined", 621 rule_match_string); 622 return FALSE; 623 } 624 } 625 626 /* If the rule applies to an alias, check its existence. */ 627 if (match[0] == '&') 628 { 629 const char *alias = &match[1]; 630 631 svn_config_get(b->config, &val, "aliases", alias, NULL); 632 633 if (!val) 634 { 635 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 636 "An authz rule refers to alias " 637 "'%s', which is undefined", 638 rule_match_string); 639 return FALSE; 640 } 641 } 642 643 /* If the rule specifies a token, check its validity. */ 644 if (match[0] == '$') 645 { 646 const char *token_name = &match[1]; 647 648 if ((strcmp(token_name, "anonymous") != 0) 649 && (strcmp(token_name, "authenticated") != 0)) 650 { 651 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 652 "Unrecognized authz token '%s'.", 653 rule_match_string); 654 return FALSE; 655 } 656 } 657 658 val = value; 659 660 while (*val) 661 { 662 if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val)) 663 { 664 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 665 "The character '%c' in rule '%s' is not " 666 "allowed in authz rules", *val, 667 rule_match_string); 668 return FALSE; 669 } 670 671 ++val; 672 } 673 674 return TRUE; 675} 676 677/* Callback to check ALIAS's definition for validity. Use 678 BATON for context and error reporting. */ 679static svn_boolean_t authz_validate_alias(const char *alias, 680 const char *value, 681 void *baton, 682 apr_pool_t *pool) 683{ 684 /* No checking at the moment, every alias is valid */ 685 return TRUE; 686} 687 688 689/* Callback to check GROUP's definition for cyclic dependancies. Use 690 BATON for context and error reporting. */ 691static svn_boolean_t authz_validate_group(const char *group, 692 const char *value, 693 void *baton, 694 apr_pool_t *pool) 695{ 696 struct authz_validate_baton *b = baton; 697 698 b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool); 699 if (b->err) 700 return FALSE; 701 702 return TRUE; 703} 704 705 706/* Callback to check the contents of the configuration section given 707 by NAME. Use BATON for context and error reporting. */ 708static svn_boolean_t authz_validate_section(const char *name, 709 void *baton, 710 apr_pool_t *pool) 711{ 712 struct authz_validate_baton *b = baton; 713 714 /* Use the group checking callback for the "groups" section... */ 715 if (strcmp(name, "groups") == 0) 716 svn_config_enumerate2(b->config, name, authz_validate_group, 717 baton, pool); 718 /* ...and the alias checking callback for "aliases"... */ 719 else if (strcmp(name, "aliases") == 0) 720 svn_config_enumerate2(b->config, name, authz_validate_alias, 721 baton, pool); 722 /* ...but for everything else use the rule checking callback. */ 723 else 724 { 725 /* Validate the section's name. Skip the optional REPOS_NAME. */ 726 const char *fspath = strchr(name, ':'); 727 if (fspath) 728 fspath++; 729 else 730 fspath = name; 731 if (! svn_fspath__is_canonical(fspath)) 732 { 733 b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 734 "Section name '%s' contains non-canonical " 735 "fspath '%s'", 736 name, fspath); 737 return FALSE; 738 } 739 740 svn_config_enumerate2(b->config, name, authz_validate_rule, 741 baton, pool); 742 } 743 744 if (b->err) 745 return FALSE; 746 747 return TRUE; 748} 749 750 751/* Walk the configuration in AUTHZ looking for any errors. */ 752static svn_error_t * 753authz_validate(svn_authz_t *authz, apr_pool_t *pool) 754{ 755 struct authz_validate_baton baton = { 0 }; 756 757 baton.err = SVN_NO_ERROR; 758 baton.config = authz->cfg; 759 760 /* Step through the entire rule file stopping on error. */ 761 svn_config_enumerate_sections2(authz->cfg, authz_validate_section, 762 &baton, pool); 763 SVN_ERR(baton.err); 764 765 return SVN_NO_ERROR; 766} 767 768 769/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config 770 * file placing the result into CFG_P allocated in POOL. 771 * 772 * If DIRENT cannot be parsed as a config file then an error is returned. The 773 * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing 774 * authz file is also an error. 775 * 776 * SCRATCH_POOL will be used for temporary allocations. */ 777static svn_error_t * 778authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent, 779 svn_boolean_t must_exist, 780 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 781{ 782 svn_error_t *err; 783 svn_repos_t *repos; 784 const char *repos_root_dirent; 785 const char *fs_path; 786 svn_fs_t *fs; 787 svn_fs_root_t *root; 788 svn_revnum_t youngest_rev; 789 svn_node_kind_t node_kind; 790 svn_stream_t *contents; 791 792 /* Search for a repository in the full path. */ 793 repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool); 794 if (!repos_root_dirent) 795 return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL, 796 "Unable to find repository at '%s'", dirent); 797 798 /* Attempt to open a repository at repos_root_dirent. */ 799 SVN_ERR(svn_repos_open2(&repos, repos_root_dirent, NULL, scratch_pool)); 800 801 fs_path = &dirent[strlen(repos_root_dirent)]; 802 803 /* Root path is always a directory so no reason to go any further */ 804 if (*fs_path == '\0') 805 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 806 "'/' is not a file in repo '%s'", 807 repos_root_dirent); 808 809 /* We skip some things that are non-important for how we're going to use 810 * this repo connection. We do not set any capabilities since none of 811 * the current ones are important for what we're doing. We also do not 812 * setup the environment that repos hooks would run under since we won't 813 * be triggering any. */ 814 815 /* Get the filesystem. */ 816 fs = svn_repos_fs(repos); 817 818 /* Find HEAD and the revision root */ 819 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool)); 820 SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool)); 821 822 SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool)); 823 if (node_kind == svn_node_none) 824 { 825 if (!must_exist) 826 { 827 SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool)); 828 return SVN_NO_ERROR; 829 } 830 else 831 { 832 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 833 "'%s' path not found in repo '%s'", fs_path, 834 repos_root_dirent); 835 } 836 } 837 else if (node_kind != svn_node_file) 838 { 839 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 840 "'%s' is not a file in repo '%s'", fs_path, 841 repos_root_dirent); 842 } 843 844 SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool)); 845 err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool); 846 847 /* Add the URL to the error stack since the parser doesn't have it. */ 848 if (err != SVN_NO_ERROR) 849 return svn_error_createf(err->apr_err, err, 850 "Error while parsing config file: '%s' in repo '%s':", 851 fs_path, repos_root_dirent); 852 853 return SVN_NO_ERROR; 854} 855 856/* Given a PATH which might be a relative repo URL (^/), an absolute 857 * local repo URL (file://), an absolute path outside of the repo 858 * or a location in the Windows registry. 859 * 860 * Retrieve the configuration data that PATH points at and parse it into 861 * CFG_P allocated in POOL. 862 * 863 * If PATH cannot be parsed as a config file then an error is returned. The 864 * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing 865 * authz file is also an error. 866 * 867 * REPOS_ROOT points at the root of the repos you are 868 * going to apply the authz against, can be NULL if you are sure that you 869 * don't have a repos relative URL in PATH. */ 870static svn_error_t * 871authz_retrieve_config(svn_config_t **cfg_p, const char *path, 872 svn_boolean_t must_exist, apr_pool_t *pool) 873{ 874 if (svn_path_is_url(path)) 875 { 876 const char *dirent; 877 svn_error_t *err; 878 apr_pool_t *scratch_pool = svn_pool_create(pool); 879 880 err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool); 881 882 if (err == SVN_NO_ERROR) 883 err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, pool, 884 scratch_pool); 885 886 /* Close the repos and streams we opened. */ 887 svn_pool_destroy(scratch_pool); 888 889 return err; 890 } 891 else 892 { 893 /* Outside of repo file or Windows registry*/ 894 SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool)); 895 } 896 897 return SVN_NO_ERROR; 898} 899 900 901/* Callback to copy (name, value) group into the "groups" section 902 of another configuration. */ 903static svn_boolean_t 904authz_copy_group(const char *name, const char *value, 905 void *baton, apr_pool_t *pool) 906{ 907 svn_config_t *authz_cfg = baton; 908 909 svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value); 910 911 return TRUE; 912} 913 914/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ. 915 * If AUTHZ already contains any group definition, report an error. 916 * Use POOL for temporary allocations. */ 917static svn_error_t * 918authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg, 919 apr_pool_t *pool) 920{ 921 /* Easy out: we prohibit local groups in the authz file when global 922 groups are being used. */ 923 if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS)) 924 { 925 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 926 "Authz file cannot contain any groups " 927 "when global groups are being used."); 928 } 929 930 svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS, 931 authz_copy_group, authz->cfg, pool); 932 933 return SVN_NO_ERROR; 934} 935 936svn_error_t * 937svn_repos__authz_read(svn_authz_t **authz_p, const char *path, 938 const char *groups_path, svn_boolean_t must_exist, 939 svn_boolean_t accept_urls, apr_pool_t *pool) 940{ 941 svn_authz_t *authz = apr_palloc(pool, sizeof(*authz)); 942 943 /* Load the authz file */ 944 if (accept_urls) 945 SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool)); 946 else 947 SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool)); 948 949 if (groups_path) 950 { 951 svn_config_t *groups_cfg; 952 svn_error_t *err; 953 954 /* Load the groups file */ 955 if (accept_urls) 956 SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist, 957 pool)); 958 else 959 SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist, 960 TRUE, TRUE, pool)); 961 962 /* Copy the groups from groups_cfg into authz. */ 963 err = authz_copy_groups(authz, groups_cfg, pool); 964 965 /* Add the paths to the error stack since the authz_copy_groups 966 routine knows nothing about them. */ 967 if (err != SVN_NO_ERROR) 968 return svn_error_createf(err->apr_err, err, 969 "Error reading authz file '%s' with " 970 "groups file '%s':", path, groups_path); 971 } 972 973 /* Make sure there are no errors in the configuration. */ 974 SVN_ERR(authz_validate(authz, pool)); 975 976 *authz_p = authz; 977 return SVN_NO_ERROR; 978} 979 980 981 982/*** Public functions. ***/ 983 984svn_error_t * 985svn_repos_authz_read2(svn_authz_t **authz_p, const char *path, 986 const char *groups_path, svn_boolean_t must_exist, 987 apr_pool_t *pool) 988{ 989 return svn_repos__authz_read(authz_p, path, groups_path, must_exist, 990 TRUE, pool); 991} 992 993 994svn_error_t * 995svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream, 996 svn_stream_t *groups_stream, apr_pool_t *pool) 997{ 998 svn_authz_t *authz = apr_palloc(pool, sizeof(*authz)); 999 1000 /* Parse the authz stream */ 1001 SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool)); 1002 1003 if (groups_stream) 1004 { 1005 svn_config_t *groups_cfg; 1006 1007 /* Parse the groups stream */ 1008 SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool)); 1009 1010 SVN_ERR(authz_copy_groups(authz, groups_cfg, pool)); 1011 } 1012 1013 /* Make sure there are no errors in the configuration. */ 1014 SVN_ERR(authz_validate(authz, pool)); 1015 1016 *authz_p = authz; 1017 return SVN_NO_ERROR; 1018} 1019 1020 1021svn_error_t * 1022svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name, 1023 const char *path, const char *user, 1024 svn_repos_authz_access_t required_access, 1025 svn_boolean_t *access_granted, 1026 apr_pool_t *pool) 1027{ 1028 const char *current_path; 1029 1030 if (!repos_name) 1031 repos_name = ""; 1032 1033 /* If PATH is NULL, check if the user has *any* access. */ 1034 if (!path) 1035 { 1036 *access_granted = authz_get_any_access(authz->cfg, repos_name, 1037 user, required_access, pool); 1038 return SVN_NO_ERROR; 1039 } 1040 1041 /* Sanity check. */ 1042 SVN_ERR_ASSERT(path[0] == '/'); 1043 1044 /* Determine the granted access for the requested path. */ 1045 path = svn_fspath__canonicalize(path, pool); 1046 current_path = path; 1047 1048 while (!authz_get_path_access(authz->cfg, repos_name, 1049 current_path, user, 1050 required_access, 1051 access_granted, 1052 pool)) 1053 { 1054 /* Stop if the loop hits the repository root with no 1055 results. */ 1056 if (current_path[0] == '/' && current_path[1] == '\0') 1057 { 1058 /* Deny access by default. */ 1059 *access_granted = FALSE; 1060 return SVN_NO_ERROR; 1061 } 1062 1063 /* Work back to the parent path. */ 1064 current_path = svn_fspath__dirname(current_path, pool); 1065 } 1066 1067 /* If the caller requested recursive access, we need to walk through 1068 the entire authz config to see whether any child paths are denied 1069 to the requested user. */ 1070 if (*access_granted && (required_access & svn_authz_recursive)) 1071 *access_granted = authz_get_tree_access(authz->cfg, repos_name, path, 1072 user, required_access, pool); 1073 1074 return SVN_NO_ERROR; 1075} 1076