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