authz_parse.c revision 362181
1/* authz_parse.c : Parser for 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#include <apr_fnmatch.h> 24#include <apr_tables.h> 25 26#include "svn_ctype.h" 27#include "svn_error.h" 28#include "svn_hash.h" 29#include "svn_iter.h" 30#include "svn_pools.h" 31#include "svn_repos.h" 32 33#include "private/svn_fspath.h" 34#include "private/svn_config_private.h" 35#include "private/svn_sorts_private.h" 36#include "private/svn_string_private.h" 37#include "private/svn_subr_private.h" 38 39#include "svn_private_config.h" 40 41#include "authz.h" 42 43 44/* Temporary ACL constructed by the parser. */ 45typedef struct parsed_acl_t 46{ 47 /* The global ACL. 48 The strings in ACL.rule are allocated from the result pool. 49 ACL.user_access is null during the parsing stage. */ 50 authz_acl_t acl; 51 52 /* The set of access control entries. In the second pass, aliases in 53 these entries will be expanded and equivalent entries will be 54 merged. The entries are allocated from the parser pool. */ 55 apr_hash_t *aces; 56 57 /* The set of access control entries that use aliases. In the second 58 pass, aliases in these entries will be expanded and merged into ACES. 59 The entries are allocated from the parser pool. */ 60 apr_hash_t *alias_aces; 61} parsed_acl_t; 62 63 64/* Temporary group definition constructed by the authz/group parser. 65 Once all groups and aliases are defined, a second pass over these 66 data will recursively expand group memberships. */ 67typedef struct parsed_group_t 68{ 69 svn_boolean_t local_group; 70 apr_array_header_t *members; 71} parsed_group_t; 72 73 74/* Baton for the parser constructor. */ 75typedef struct ctor_baton_t 76{ 77 /* The final output of the parser. */ 78 authz_full_t *authz; 79 80 /* Interned-string set, allocated in AUTHZ->pool. 81 Stores singleton instances of user, group and repository names, 82 which are used by members of the AUTHZ structure. By reusing the 83 same immutable string multiple times, we reduce the size of the 84 authz representation in the result pool. 85 86 N.B.: Whilst the strings are allocated from teh result pool, the 87 hash table itself is not. */ 88 apr_hash_t *strings; 89 90 /* A set of all the sections that were seen in the authz or global 91 groups file. Rules, aliases and groups may each only be defined 92 once in the authz file. The global groups file may only contain a 93 [groups] section. */ 94 apr_hash_t *sections; 95 96 /* The name of the section we're currently parsing. */ 97 const char *section; 98 99 /* TRUE iff we're parsing the global groups file. */ 100 svn_boolean_t parsing_groups; 101 102 /* TRUE iff we're parsing a [groups] section. */ 103 svn_boolean_t in_groups; 104 105 /* TRUE iff we're parsing an [aliases] section. */ 106 svn_boolean_t in_aliases; 107 108 /* A set of all the unique rules we parsed from the section names. */ 109 apr_hash_t *parsed_rules; 110 111 /* Temporary parsed-groups definitions. */ 112 apr_hash_t *parsed_groups; 113 114 /* Temporary alias mappings. */ 115 apr_hash_t *parsed_aliases; 116 117 /* Temporary parsed-acl definitions. */ 118 apr_array_header_t *parsed_acls; 119 120 /* Temporary expanded groups definitions. */ 121 apr_hash_t *expanded_groups; 122 123 /* The temporary ACL we're currently constructing. */ 124 parsed_acl_t *current_acl; 125 126 /* Temporary buffers used to parse a rule into segments. */ 127 svn_membuf_t rule_path_buffer; 128 svn_stringbuf_t *rule_string_buffer; 129 130 /* The warning callback and its baton. */ 131 svn_repos_authz_warning_func_t warning_func; 132 void *warning_baton; 133 134 /* The parser's scratch pool. This may not be the same pool as 135 passed to the constructor callbacks, that is supposed to be an 136 iteration pool maintained by the generic parser. 137 138 N.B.: The result pool is AUTHZ->pool. */ 139 apr_pool_t *parser_pool; 140} ctor_baton_t; 141 142 143/* An empty string with a known address. */ 144static const char interned_empty_string[] = ""; 145 146/* The name of the aliases section. */ 147static const char aliases_section[] = "aliases"; 148 149/* The name of the groups section. */ 150static const char groups_section[] = "groups"; 151 152/* The token indicating that an authz rule contains wildcards. */ 153static const char glob_rule_token[] = "glob"; 154 155/* The anonymous access token. */ 156static const char anon_access_token[] = "$anonymous"; 157 158/* The authenticated access token. */ 159static const char authn_access_token[] = "$authenticated"; 160 161/* Fake token for inverted rights. */ 162static const char neg_access_token[] = "~~$inverted"; 163 164/* Initialize a rights structure. 165 The minimum rights start with all available access and are later 166 bitwise-and'ed with actual access rights. The maximum rights begin 167 empty and are later bitwise-and'ed with actual rights. */ 168static void init_rights(authz_rights_t *rights) 169{ 170 rights->min_access = authz_access_write; 171 rights->max_access = authz_access_none; 172 } 173 174/* Initialize a global rights structure. 175 The USER string must be interned or statically initialized. */ 176static void 177init_global_rights(authz_global_rights_t *gr, const char *user, 178 apr_pool_t *result_pool) 179{ 180 gr->user = user; 181 init_rights(&gr->all_repos_rights); 182 init_rights(&gr->any_repos_rights); 183 gr->per_repos_rights = apr_hash_make(result_pool); 184} 185 186 187/* Insert the default global ACL into the parsed ACLs. */ 188static void 189insert_default_acl(ctor_baton_t *cb) 190{ 191 parsed_acl_t *acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t); 192 acl->acl.sequence_number = 0; 193 acl->acl.rule.repos = interned_empty_string; 194 acl->acl.rule.len = 0; 195 acl->acl.rule.path = NULL; 196 acl->acl.anon_access = authz_access_none; 197 acl->acl.has_anon_access = TRUE; 198 acl->acl.authn_access = authz_access_none; 199 acl->acl.has_authn_access = TRUE; 200 acl->acl.neg_access = authz_access_none; 201 acl->acl.has_neg_access = TRUE; 202 acl->acl.user_access = NULL; 203 acl->aces = svn_hash__make(cb->parser_pool); 204 acl->alias_aces = svn_hash__make(cb->parser_pool); 205} 206 207 208/* Initialize a constuctor baton. */ 209static ctor_baton_t * 210create_ctor_baton(svn_repos_authz_warning_func_t warning_func, 211 void *warning_baton, 212 apr_pool_t *result_pool, 213 apr_pool_t *scratch_pool) 214{ 215 apr_pool_t *const parser_pool = svn_pool_create(scratch_pool); 216 ctor_baton_t *const cb = apr_pcalloc(parser_pool, sizeof(*cb)); 217 218 authz_full_t *const authz = apr_pcalloc(result_pool, sizeof(*authz)); 219 init_global_rights(&authz->anon_rights, anon_access_token, result_pool); 220 init_global_rights(&authz->authn_rights, authn_access_token, result_pool); 221 init_global_rights(&authz->neg_rights, neg_access_token, result_pool); 222 authz->user_rights = svn_hash__make(result_pool); 223 authz->pool = result_pool; 224 225 cb->authz = authz; 226 cb->strings = svn_hash__make(parser_pool); 227 228 cb->sections = svn_hash__make(parser_pool); 229 cb->section = NULL; 230 cb->parsing_groups = FALSE; 231 cb->in_groups = FALSE; 232 cb->in_aliases = FALSE; 233 234 cb->parsed_rules = svn_hash__make(parser_pool); 235 cb->parsed_groups = svn_hash__make(parser_pool); 236 cb->parsed_aliases = svn_hash__make(parser_pool); 237 cb->parsed_acls = apr_array_make(parser_pool, 64, sizeof(parsed_acl_t)); 238 cb->current_acl = NULL; 239 240 svn_membuf__create(&cb->rule_path_buffer, 0, parser_pool); 241 cb->rule_string_buffer = svn_stringbuf_create_empty(parser_pool); 242 243 cb->warning_func = warning_func; 244 cb->warning_baton = warning_baton; 245 246 cb->parser_pool = parser_pool; 247 248 insert_default_acl(cb); 249 250 return cb; 251} 252 253 254/* Emit a warning. Clears ERROR */ 255static void 256emit_parser_warning(const ctor_baton_t *cb, 257 svn_error_t *error, 258 apr_pool_t *scratch_pool) 259{ 260 if (cb->warning_func) 261 cb->warning_func(cb->warning_baton, error, scratch_pool); 262 svn_error_clear(error); 263} 264 265/* Avoid creating an error struct if there is no warning function. */ 266#define SVN_AUTHZ_PARSE_WARN(cb, err, pool) \ 267 do { \ 268 if ((cb) && (cb)->warning_func) \ 269 emit_parser_warning((cb), (err), (pool)); \ 270 } while(0) 271 272 273/* Create and store per-user global rights. 274 The USER string must be interned or statically initialized. */ 275static void 276prepare_global_rights(ctor_baton_t *cb, const char *user) 277{ 278 authz_global_rights_t *gr = svn_hash_gets(cb->authz->user_rights, user); 279 if (!gr) 280 { 281 gr = apr_palloc(cb->authz->pool, sizeof(*gr)); 282 init_global_rights(gr, user, cb->authz->pool); 283 svn_hash_sets(cb->authz->user_rights, gr->user, gr); 284 } 285} 286 287 288/* Internalize a string that will be referenced by the parsed svn_authz_t. 289 If LEN is (apr_size_t)-1, assume the string is NUL-terminated. */ 290static const char * 291intern_string(ctor_baton_t *cb, const char *str, apr_size_t len) 292{ 293 const char *interned; 294 295 if (len == (apr_size_t)-1) 296 len = strlen(str); 297 298 interned = apr_hash_get(cb->strings, str, len); 299 if (!interned) 300 { 301 interned = apr_pstrmemdup(cb->authz->pool, str, len); 302 apr_hash_set(cb->strings, interned, len, interned); 303 } 304 return interned; 305} 306 307 308/* Helper for rules_open_section and groups_open_section. */ 309static svn_error_t * 310check_open_section(ctor_baton_t *cb, svn_stringbuf_t *section) 311{ 312 SVN_ERR_ASSERT(!cb->current_acl && !cb->section); 313 if (apr_hash_get(cb->sections, section->data, section->len)) 314 { 315 if (cb->parsing_groups) 316 return svn_error_createf( 317 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 318 _("Section appears more than once" 319 " in the global groups file: [%s]"), 320 section->data); 321 else 322 return svn_error_createf( 323 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 324 _("Section appears more than once" 325 " in the authz file: [%s]"), 326 section->data); 327 } 328 329 cb->section = apr_pstrmemdup(cb->parser_pool, section->data, section->len); 330 svn_hash_sets(cb->sections, cb->section, interned_empty_string); 331 return SVN_NO_ERROR; 332} 333 334 335/* Constructor callback: Begins the [groups] section. */ 336static svn_error_t * 337groups_open_section(void *baton, svn_stringbuf_t *section) 338{ 339 ctor_baton_t *const cb = baton; 340 341 if (cb->parsing_groups) 342 SVN_ERR(check_open_section(cb, section)); 343 344 if (0 == strcmp(section->data, groups_section)) 345 { 346 cb->in_groups = TRUE; 347 return SVN_NO_ERROR; 348 } 349 350 return svn_error_createf( 351 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 352 (cb->parsing_groups 353 ? _("Section is not valid in the global group file: [%s]") 354 : _("Section is not valid in the authz file: [%s]")), 355 section->data); 356} 357 358 359/* Constructor callback: Parses a group declaration. */ 360static svn_error_t * 361groups_add_value(void *baton, svn_stringbuf_t *section, 362 svn_stringbuf_t *option, svn_stringbuf_t *value) 363{ 364 ctor_baton_t *const cb = baton; 365 const char *group; 366 apr_size_t group_len; 367 368 SVN_ERR_ASSERT(cb->in_groups); 369 370 if (strchr("@$&*~", *option->data)) 371 { 372 if (cb->parsing_groups) 373 return svn_error_createf( 374 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 375 _("Global group name '%s' may not begin with '%c'"), 376 option->data, *option->data); 377 else 378 return svn_error_createf( 379 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 380 _("Group name '%s' may not begin with '%c'"), 381 option->data, *option->data); 382 } 383 384 /* Decorate the name to make lookups consistent. */ 385 group = apr_pstrcat(cb->parser_pool, "@", option->data, SVN_VA_NULL); 386 group_len = option->len + 1; 387 if (apr_hash_get(cb->parsed_groups, group, group_len)) 388 { 389 if (cb->parsing_groups) 390 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 391 _("Can't override definition" 392 " of global group '%s'"), 393 group); 394 else 395 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 396 _("Can't override definition" 397 " of group '%s'"), 398 group); 399 } 400 401 /* We store the whole group definition, so that we can use the 402 temporary groups in the baton hash later to fully expand group 403 memberships. 404 At this point, we can finally internalize the group name. */ 405 apr_hash_set(cb->parsed_groups, 406 intern_string(cb, group, group_len), group_len, 407 svn_cstring_split(value->data, ",", TRUE, cb->parser_pool)); 408 409 return SVN_NO_ERROR; 410} 411 412 413/* Remove escape sequences in-place. */ 414static void 415unescape_in_place(svn_stringbuf_t *buf) 416{ 417 char *p = buf->data; 418 apr_size_t i; 419 420 /* Skip the string up to the first escape sequence. */ 421 for (i = 0; i < buf->len; ++i) 422 { 423 if (*p == '\\') 424 break; 425 ++p; 426 } 427 428 if (i < buf->len) 429 { 430 /* Unescape the remainder of the string. */ 431 svn_boolean_t escape = TRUE; 432 const char *q; 433 434 for (q = p + 1, ++i; i < buf->len; ++i) 435 { 436 if (escape) 437 { 438 *p++ = *q++; 439 escape = FALSE; 440 } 441 else if (*q == '\\') 442 { 443 ++q; 444 escape = TRUE; 445 } 446 else 447 *p++ = *q++; 448 } 449 450 /* A trailing backslash is literal, so make it part of the pattern. */ 451 if (escape) 452 *p++ = '\\'; 453 *p = '\0'; 454 buf->len = p - buf->data; 455 } 456} 457 458 459/* Internalize a pattern. */ 460static void 461intern_pattern(ctor_baton_t *cb, 462 svn_string_t *pattern, 463 const char *string, 464 apr_size_t len) 465{ 466 pattern->data = intern_string(cb, string, len); 467 pattern->len = len; 468} 469 470 471/* Parse a rule path PATH up to PATH_LEN into *RULE. 472 If GLOB is TRUE, treat PATH as possibly containing wildcards. 473 SECTION is the whole rule in the authz file. 474 Use pools and buffers from CB to do the obvious thing. */ 475static svn_error_t * 476parse_rule_path(authz_rule_t *rule, 477 ctor_baton_t *cb, 478 svn_boolean_t glob, 479 const char *path, 480 apr_size_t path_len, 481 const char *section) 482{ 483 svn_stringbuf_t *const pattern = cb->rule_string_buffer; 484 const char *const path_end = path + path_len; 485 authz_rule_segment_t *segment; 486 const char *start; 487 const char *end; 488 int nseg; 489 490 SVN_ERR_ASSERT(*path == '/'); 491 492 nseg = 0; 493 for (start = path; start != path_end; start = end) 494 { 495 apr_size_t pattern_len; 496 497 /* Skip the leading slash and find the end of the segment. */ 498 end = memchr(++start, '/', path_len - 1); 499 if (!end) 500 end = path_end; 501 502 pattern_len = end - start; 503 path_len -= pattern_len + 1; 504 505 if (pattern_len == 0) 506 { 507 if (nseg == 0) 508 { 509 /* This is an empty (root) path. */ 510 rule->len = 0; 511 rule->path = NULL; 512 return SVN_NO_ERROR; 513 } 514 515 /* A path with two consecutive slashes is not canonical. */ 516 return svn_error_createf( 517 SVN_ERR_AUTHZ_INVALID_CONFIG, 518 svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 519 _("Found empty name in authz rule path")), 520 _("Non-canonical path '%s' in authz rule [%s]"), 521 path, section); 522 } 523 524 /* A path with . or .. segments is not canonical. */ 525 if (*start == '.' 526 && (pattern_len == 1 527 || (pattern_len == 2 && start[1] == '.'))) 528 return svn_error_createf( 529 SVN_ERR_AUTHZ_INVALID_CONFIG, 530 (end == start + 1 531 ? svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 532 _("Found '.' in authz rule path")) 533 : svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 534 _("Found '..' in authz rule path"))), 535 _("Non-canonical path '%s' in authz rule [%s]"), 536 path, section); 537 538 /* Make space for the current segment. */ 539 ++nseg; 540 svn_membuf__resize(&cb->rule_path_buffer, nseg * sizeof(*segment)); 541 segment = cb->rule_path_buffer.data; 542 segment += (nseg - 1); 543 544 if (!glob) 545 { 546 /* Trivial case: this is not a glob rule, so every segment 547 is a literal match. */ 548 segment->kind = authz_rule_literal; 549 intern_pattern(cb, &segment->pattern, start, pattern_len); 550 continue; 551 } 552 553 /* Copy the segment into the temporary buffer. */ 554 svn_stringbuf_setempty(pattern); 555 svn_stringbuf_appendbytes(pattern, start, pattern_len); 556 557 if (0 == apr_fnmatch_test(pattern->data)) 558 { 559 /* It's a literal match after all. */ 560 segment->kind = authz_rule_literal; 561 unescape_in_place(pattern); 562 intern_pattern(cb, &segment->pattern, pattern->data, pattern->len); 563 continue; 564 } 565 566 if (*pattern->data == '*') 567 { 568 if (pattern->len == 1 569 || (pattern->len == 2 && pattern->data[1] == '*')) 570 { 571 /* Process * and **, applying normalization as per 572 https://cwiki.apache.org/confluence/display/SVN/Authz+Improvements. */ 573 574 authz_rule_segment_t *const prev = 575 (nseg > 1 ? segment - 1 : NULL); 576 577 if (pattern_len == 1) 578 { 579 /* This is a *. Replace **|* with *|**. */ 580 if (prev && prev->kind == authz_rule_any_recursive) 581 { 582 prev->kind = authz_rule_any_segment; 583 segment->kind = authz_rule_any_recursive; 584 } 585 else 586 segment->kind = authz_rule_any_segment; 587 } 588 else 589 { 590 /* This is a **. Replace **|** with a single **. */ 591 if (prev && prev->kind == authz_rule_any_recursive) 592 { 593 /* Simply drop the redundant new segment. */ 594 --nseg; 595 continue; 596 } 597 else 598 segment->kind = authz_rule_any_recursive; 599 } 600 601 segment->pattern.data = interned_empty_string; 602 segment->pattern.len = 0; 603 continue; 604 } 605 606 /* Maybe it's a suffix match? */ 607 if (0 == apr_fnmatch_test(pattern->data + 1)) 608 { 609 svn_stringbuf_leftchop(pattern, 1); 610 segment->kind = authz_rule_suffix; 611 unescape_in_place(pattern); 612 svn_authz__reverse_string(pattern->data, pattern->len); 613 intern_pattern(cb, &segment->pattern, 614 pattern->data, pattern->len); 615 continue; 616 } 617 } 618 619 if (pattern->data[pattern->len - 1] == '*') 620 { 621 /* Might be a prefix match. Note that because of the 622 previous test, we already know that the pattern is longer 623 than one character. */ 624 if (pattern->data[pattern->len - 2] != '\\') 625 { 626 /* OK, the * wasn't escaped. Chop off the wildcard. */ 627 svn_stringbuf_chop(pattern, 1); 628 if (0 == apr_fnmatch_test(pattern->data)) 629 { 630 segment->kind = authz_rule_prefix; 631 unescape_in_place(pattern); 632 intern_pattern(cb, &segment->pattern, 633 pattern->data, pattern->len); 634 continue; 635 } 636 637 /* Restore the wildcard since it was not a prefix match. */ 638 svn_stringbuf_appendbyte(pattern, '*'); 639 } 640 } 641 642 /* It's a generic fnmatch pattern. */ 643 segment->kind = authz_rule_fnmatch; 644 intern_pattern(cb, &segment->pattern, pattern->data, pattern->len); 645 } 646 647 SVN_ERR_ASSERT(nseg > 0); 648 649 /* Copy the temporary segments array into the result pool. */ 650 { 651 const apr_size_t path_size = nseg * sizeof(*segment); 652 SVN_ERR_ASSERT(path_size <= cb->rule_path_buffer.size); 653 654 rule->len = nseg; 655 rule->path = apr_palloc(cb->authz->pool, path_size); 656 memcpy(rule->path, cb->rule_path_buffer.data, path_size); 657 } 658 659 return SVN_NO_ERROR; 660} 661 662 663/* Check that the parsed RULE is unique within the authz file. 664 With the introduction of wildcards, just looking at the SECTION 665 names is not sufficient to determine uniqueness. 666 Use pools and buffers from CB to do the obvious thing. */ 667static svn_error_t * 668check_unique_rule(ctor_baton_t *cb, 669 const authz_rule_t *rule, 670 const char *section) 671{ 672 svn_stringbuf_t *const buf = cb->rule_string_buffer; 673 const char *exists; 674 int i; 675 676 /* Construct the key for this rule */ 677 svn_stringbuf_setempty(buf); 678 svn_stringbuf_appendcstr(buf, rule->repos); 679 svn_stringbuf_appendbyte(buf, '\n'); 680 681 for (i = 0; i < rule->len; ++i) 682 { 683 authz_rule_segment_t *const seg = &rule->path[i]; 684 svn_stringbuf_appendbyte(buf, '@' + seg->kind); 685 svn_stringbuf_appendbytes(buf, seg->pattern.data, seg->pattern.len); 686 svn_stringbuf_appendbyte(buf, '\n'); 687 } 688 689 /* Check if the section exists. */ 690 exists = apr_hash_get(cb->parsed_rules, buf->data, buf->len); 691 if (exists) 692 return svn_error_createf( 693 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 694 _("Section [%s] describes the same rule as section [%s]"), 695 section, exists); 696 697 /* Insert the rule into the known rules set. */ 698 apr_hash_set(cb->parsed_rules, 699 apr_pstrmemdup(cb->parser_pool, buf->data, buf->len), 700 buf->len, 701 apr_pstrdup(cb->parser_pool, section)); 702 703 return SVN_NO_ERROR; 704} 705 706 707/* Constructor callback: Starts a rule or [aliases] section. */ 708static svn_error_t * 709rules_open_section(void *baton, svn_stringbuf_t *section) 710{ 711 ctor_baton_t *const cb = baton; 712 const char *rule = section->data; 713 apr_size_t rule_len = section->len; 714 svn_boolean_t glob; 715 const char *endp; 716 parsed_acl_t acl; 717 718 SVN_ERR(check_open_section(cb, section)); 719 720 /* Parse rule property tokens. */ 721 if (*rule != ':') 722 glob = FALSE; 723 else 724 { 725 /* This must be a wildcard rule. */ 726 apr_size_t token_len; 727 728 ++rule; --rule_len; 729 endp = memchr(rule, ':', rule_len); 730 if (!endp) 731 return svn_error_createf( 732 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 733 _("Empty repository name in authz rule [%s]"), 734 section->data); 735 736 /* Note: the size of glob_rule_token includes the NUL terminator. */ 737 token_len = endp - rule; 738 if (token_len != sizeof(glob_rule_token) - 1 739 || memcmp(rule, glob_rule_token, token_len)) 740 return svn_error_createf( 741 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 742 _("Invalid type token '%s' in authz rule [%s]"), 743 apr_pstrmemdup(cb->parser_pool, rule, token_len), 744 section->data); 745 746 glob = TRUE; 747 rule = endp + 1; 748 rule_len -= token_len + 1; 749 } 750 751 /* Parse the repository name. */ 752 endp = (*rule == '/' ? NULL : memchr(rule, ':', rule_len)); 753 if (!endp) 754 acl.acl.rule.repos = interned_empty_string; 755 else 756 { 757 const apr_size_t repos_len = endp - rule; 758 759 /* The rule contains a repository name. */ 760 if (0 == repos_len) 761 return svn_error_createf( 762 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 763 _("Empty repository name in authz rule [%s]"), 764 section->data); 765 766 acl.acl.rule.repos = intern_string(cb, rule, repos_len); 767 rule = endp + 1; 768 rule_len -= repos_len + 1; 769 } 770 771 /* Parse the actual rule. */ 772 if (*rule == '/') 773 { 774 SVN_ERR(parse_rule_path(&acl.acl.rule, cb, glob, rule, rule_len, 775 section->data)); 776 SVN_ERR(check_unique_rule(cb, &acl.acl.rule, section->data)); 777 } 778 else if (0 == strcmp(section->data, aliases_section)) 779 { 780 cb->in_aliases = TRUE; 781 return SVN_NO_ERROR; 782 } 783 else 784 { 785 /* This must be the [groups] section. */ 786 return groups_open_section(cb, section); 787 } 788 789 acl.acl.sequence_number = cb->parsed_acls->nelts; 790 acl.acl.anon_access = authz_access_none; 791 acl.acl.has_anon_access = FALSE; 792 acl.acl.authn_access = authz_access_none; 793 acl.acl.has_authn_access = FALSE; 794 acl.acl.neg_access = authz_access_none; 795 acl.acl.has_neg_access = FALSE; 796 acl.acl.user_access = NULL; 797 798 acl.aces = svn_hash__make(cb->parser_pool); 799 acl.alias_aces = svn_hash__make(cb->parser_pool); 800 801 cb->current_acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t); 802 *cb->current_acl = acl; 803 return SVN_NO_ERROR; 804} 805 806 807/* Parses an alias declaration. The definition (username) of the 808 alias will always be interned. */ 809static svn_error_t * 810add_alias_definition(ctor_baton_t *cb, 811 svn_stringbuf_t *option, svn_stringbuf_t *value) 812{ 813 const char *alias; 814 apr_size_t alias_len; 815 const char *user; 816 817 if (strchr("@$&*~", *option->data)) 818 return svn_error_createf( 819 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 820 _("Alias name '%s' may not begin with '%c'"), 821 option->data, *option->data); 822 823 /* Decorate the name to make lookups consistent. */ 824 alias = apr_pstrcat(cb->parser_pool, "&", option->data, SVN_VA_NULL); 825 alias_len = option->len + 1; 826 if (apr_hash_get(cb->parsed_aliases, alias, alias_len)) 827 return svn_error_createf( 828 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 829 _("Can't override definition of alias '%s'"), 830 alias); 831 832 user = intern_string(cb, value->data, value->len); 833 apr_hash_set(cb->parsed_aliases, alias, alias_len, user); 834 835 /* Prepare the global rights struct for this user. */ 836 prepare_global_rights(cb, user); 837 return SVN_NO_ERROR; 838} 839 840/* Parses an access entry. Groups and users in access entry names will 841 always be interned, aliases will never be. */ 842static svn_error_t * 843add_access_entry(ctor_baton_t *cb, svn_stringbuf_t *section, 844 svn_stringbuf_t *option, svn_stringbuf_t *value) 845{ 846 parsed_acl_t *const acl = cb->current_acl; 847 const char *name = option->data; 848 apr_size_t name_len = option->len; 849 const svn_boolean_t inverted = (*name == '~'); 850 svn_boolean_t anonymous = FALSE; 851 svn_boolean_t authenticated = FALSE; 852 authz_access_t access = authz_access_none; 853 authz_ace_t *ace; 854 int i; 855 856 SVN_ERR_ASSERT(acl != NULL); 857 858 if (inverted) 859 { 860 ++name; 861 --name_len; 862 } 863 864 /* Determine the access entry type. */ 865 switch (*name) 866 { 867 case '~': 868 return svn_error_createf( 869 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 870 _("Access entry '%s' has more than one inversion;" 871 " double negatives are not permitted"), 872 option->data); 873 break; 874 875 case '*': 876 if (name_len != 1) 877 return svn_error_createf( 878 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 879 _("Access entry '%s' is not valid;" 880 " it must be a single '*'"), 881 option->data); 882 883 if (inverted) 884 return svn_error_createf( 885 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 886 _("Access entry '~*' will never match")); 887 888 anonymous = TRUE; 889 authenticated = TRUE; 890 break; 891 892 case '$': 893 if (0 == strcmp(name, anon_access_token)) 894 { 895 if (inverted) 896 authenticated = TRUE; 897 else 898 anonymous = TRUE; 899 } 900 else if (0 == strcmp(name, authn_access_token)) 901 { 902 if (inverted) 903 anonymous = TRUE; 904 else 905 authenticated = TRUE; 906 } 907 else 908 return svn_error_createf( 909 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 910 _("Access entry token '%s' is not valid;" 911 " should be '%s' or '%s'"), 912 option->data, anon_access_token, authn_access_token); 913 break; 914 915 default: 916 /* A username, group name or alias. */; 917 } 918 919 /* Parse the access rights. */ 920 for (i = 0; i < value->len; ++i) 921 { 922 const char access_code = value->data[i]; 923 switch (access_code) 924 { 925 case 'r': 926 access |= authz_access_read_flag; 927 break; 928 929 case 'w': 930 access |= authz_access_write_flag; 931 break; 932 933 default: 934 if (!svn_ctype_isspace(access_code)) 935 return svn_error_createf( 936 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 937 _("The access mode '%c' in access entry '%s'" 938 " of rule [%s] is not valid"), 939 access_code, option->data, section->data); 940 } 941 } 942 943 /* We do not support write-only access. */ 944 if ((access & authz_access_write_flag) && !(access & authz_access_read_flag)) 945 return svn_error_createf( 946 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 947 _("Write-only access entry '%s' of rule [%s] is not valid"), 948 option->data, section->data); 949 950 /* Update the parsed ACL with this access entry. */ 951 if (anonymous || authenticated) 952 { 953 if (anonymous) 954 { 955 acl->acl.has_anon_access = TRUE; 956 acl->acl.anon_access |= access; 957 } 958 if (authenticated) 959 { 960 acl->acl.has_authn_access = TRUE; 961 acl->acl.authn_access |= access; 962 } 963 } 964 else 965 { 966 /* The inversion tag must be part of the key in the hash 967 table, otherwise we can't tell regular and inverted 968 entries appart. */ 969 const char *key = (inverted ? name - 1 : name); 970 const apr_size_t key_len = (inverted ? name_len + 1 : name_len); 971 const svn_boolean_t aliased = (*name == '&'); 972 apr_hash_t *aces = (aliased ? acl->alias_aces : acl->aces); 973 974 ace = apr_hash_get(aces, key, key_len); 975 if (ace) 976 ace->access |= access; 977 else 978 { 979 ace = apr_palloc(cb->parser_pool, sizeof(*ace)); 980 ace->name = (aliased 981 ? apr_pstrmemdup(cb->parser_pool, name, name_len) 982 : intern_string(cb, name, name_len)); 983 ace->members = NULL; 984 ace->inverted = inverted; 985 ace->access = access; 986 987 key = (inverted 988 ? apr_pstrmemdup(cb->parser_pool, key, key_len) 989 : ace->name); 990 apr_hash_set(aces, key, key_len, ace); 991 992 /* Prepare the global rights struct for this user. */ 993 if (!aliased && *ace->name != '@') 994 prepare_global_rights(cb, ace->name); 995 } 996 997 /* Propagate rights for inverted selectors to the global rights, otherwise 998 an access check can bail out early. See: SVN-4793 */ 999 if (inverted) 1000 { 1001 acl->acl.has_neg_access = TRUE; 1002 acl->acl.neg_access |= access; 1003 } 1004 } 1005 1006 return SVN_NO_ERROR; 1007} 1008 1009/* Constructor callback: Parse a rule, alias or group delcaration. */ 1010static svn_error_t * 1011rules_add_value(void *baton, svn_stringbuf_t *section, 1012 svn_stringbuf_t *option, svn_stringbuf_t *value) 1013{ 1014 ctor_baton_t *const cb = baton; 1015 1016 if (cb->in_groups) 1017 return groups_add_value(baton, section, option, value); 1018 1019 if (cb->in_aliases) 1020 return add_alias_definition(cb, option, value); 1021 1022 return add_access_entry(cb, section, option, value); 1023} 1024 1025 1026/* Constructor callback: Close a section. */ 1027static svn_error_t * 1028close_section(void *baton, svn_stringbuf_t *section) 1029{ 1030 ctor_baton_t *const cb = baton; 1031 1032 SVN_ERR_ASSERT(0 == strcmp(cb->section, section->data)); 1033 cb->section = NULL; 1034 cb->current_acl = NULL; 1035 cb->in_groups = FALSE; 1036 cb->in_aliases = FALSE; 1037 return SVN_NO_ERROR; 1038} 1039 1040 1041/* Add a user to GROUP. 1042 GROUP is never internalized, but USER always is. 1043 Adding a NULL user will create an empty group, if it doesn't exist. */ 1044static void 1045add_to_group(ctor_baton_t *cb, const char *group, const char *user) 1046{ 1047 apr_hash_t *members = svn_hash_gets(cb->expanded_groups, group); 1048 if (!members) 1049 { 1050 group = intern_string(cb, group, -1); 1051 members = svn_hash__make(cb->authz->pool); 1052 svn_hash_sets(cb->expanded_groups, group, members); 1053 } 1054 if (user) 1055 svn_hash_sets(members, user, interned_empty_string); 1056} 1057 1058 1059/* Hash iterator for expanding group definitions. 1060 WARNING: This function is recursive! */ 1061static svn_error_t * 1062expand_group_callback(void *baton, 1063 const void *key, 1064 apr_ssize_t klen, 1065 void *value, 1066 apr_pool_t *scratch_pool) 1067{ 1068 ctor_baton_t *const cb = baton; 1069 const char *const group = key; 1070 apr_array_header_t *members = value; 1071 int i; 1072 1073 if (0 == members->nelts) 1074 { 1075 /* Create the group with no members. */ 1076 add_to_group(cb, group, NULL); 1077 return SVN_NO_ERROR; 1078 } 1079 1080 for (i = 0; i < members->nelts; ++i) 1081 { 1082 const char *member = APR_ARRAY_IDX(members, i, const char*); 1083 if (0 == strcmp(member, group)) 1084 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1085 _("Recursive definition of group '%s'"), 1086 group); 1087 1088 if (*member == '&') 1089 { 1090 /* Add expanded alias to the group. 1091 N.B.: the user name is already internalized. */ 1092 const char *user = svn_hash_gets(cb->parsed_aliases, member); 1093 if (!user) 1094 return svn_error_createf( 1095 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1096 _("Alias '%s' was never defined"), 1097 member); 1098 1099 add_to_group(cb, group, user); 1100 } 1101 else if (*member != '@') 1102 { 1103 /* Add the member to the group. */ 1104 const char *user = intern_string(cb, member, -1); 1105 add_to_group(cb, group, user); 1106 1107 /* Prepare the global rights struct for this user. */ 1108 prepare_global_rights(cb, user); 1109 } 1110 else 1111 { 1112 /* Recursively expand the group membership */ 1113 apr_array_header_t *member_members 1114 = svn_hash_gets(cb->parsed_groups, member); 1115 if (!member_members) 1116 return svn_error_createf( 1117 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1118 _("Undefined group '%s'"), 1119 member); 1120 SVN_ERR(expand_group_callback(cb, key, klen, 1121 member_members, scratch_pool)); 1122 } 1123 } 1124 return SVN_NO_ERROR; 1125} 1126 1127 1128/* Hash iteration baton for merge_alias_ace. */ 1129typedef struct merge_alias_baton_t 1130{ 1131 apr_hash_t *aces; 1132 ctor_baton_t *cb; 1133} merge_alias_baton_t; 1134 1135/* Hash iterator for expanding and mergina alias-based ACEs 1136 into the user/group-based ACEs. */ 1137static svn_error_t * 1138merge_alias_ace(void *baton, 1139 const void *key, 1140 apr_ssize_t klen, 1141 void *value, 1142 apr_pool_t *scratch_pool) 1143{ 1144 merge_alias_baton_t *const mab = baton; 1145 authz_ace_t *aliased_ace = value; 1146 const char *alias = aliased_ace->name; 1147 const char *unaliased_key; 1148 const char *user; 1149 authz_ace_t *ace; 1150 1151 user = svn_hash_gets(mab->cb->parsed_aliases, alias); 1152 if (!user) 1153 return svn_error_createf( 1154 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1155 _("Alias '%s' was never defined"), 1156 alias); 1157 1158 /* N.B.: The user name is always internalized, 1159 but the inverted key may not be. */ 1160 if (!aliased_ace->inverted) 1161 unaliased_key = user; 1162 else 1163 { 1164 unaliased_key = apr_pstrcat(mab->cb->parser_pool, 1165 "~", user, SVN_VA_NULL); 1166 unaliased_key = intern_string(mab->cb, unaliased_key, -1); 1167 } 1168 1169 ace = svn_hash_gets(mab->aces, unaliased_key); 1170 if (!ace) 1171 { 1172 aliased_ace->name = user; 1173 svn_hash_sets(mab->aces, unaliased_key, aliased_ace); 1174 } 1175 else 1176 { 1177 SVN_ERR_ASSERT(!ace->inverted == !aliased_ace->inverted); 1178 ace->access |= aliased_ace->access; 1179 } 1180 1181 return SVN_NO_ERROR; 1182} 1183 1184 1185/* Hash iteration baton for array_insert_ace. */ 1186typedef struct insert_ace_baton_t 1187{ 1188 apr_array_header_t *ace_array; 1189 ctor_baton_t *cb; 1190} insert_ace_baton_t; 1191 1192/* Hash iterator, inserts an ACE into the ACLs array. */ 1193static svn_error_t * 1194array_insert_ace(void *baton, 1195 const void *key, 1196 apr_ssize_t klen, 1197 void *value, 1198 apr_pool_t *scratch_pool) 1199{ 1200 insert_ace_baton_t *iab = baton; 1201 authz_ace_t *ace = value; 1202 1203 /* Add group membership info to the ACE. */ 1204 if (*ace->name == '@') 1205 { 1206 SVN_ERR_ASSERT(ace->members == NULL); 1207 ace->members = svn_hash_gets(iab->cb->expanded_groups, ace->name); 1208 if (!ace->members) 1209 { 1210 return svn_error_createf( 1211 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1212 _("Access entry refers to undefined group '%s'"), 1213 ace->name); 1214 } 1215 else if (0 == apr_hash_count(ace->members)) 1216 { 1217 /* An ACE for an empty group has no effect, so ignore it. */ 1218 SVN_AUTHZ_PARSE_WARN( 1219 iab->cb, 1220 svn_error_createf( 1221 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1222 _("Ignoring access entry for empty group '%s'"), 1223 ace->name), 1224 scratch_pool); 1225 return SVN_NO_ERROR; 1226 } 1227 } 1228 1229 APR_ARRAY_PUSH(iab->ace_array, authz_ace_t) = *ace; 1230 return SVN_NO_ERROR; 1231} 1232 1233 1234/* Update accumulated RIGHTS from ACCESS. */ 1235static void 1236update_rights(authz_rights_t *rights, 1237 authz_access_t access) 1238{ 1239 rights->min_access &= access; 1240 rights->max_access |= access; 1241} 1242 1243 1244/* Update a global RIGHTS based on REPOS and ACCESS. */ 1245static void 1246update_global_rights(authz_global_rights_t *gr, 1247 const char *repos, 1248 authz_access_t access) 1249{ 1250 update_rights(&gr->all_repos_rights, access); 1251 if (0 == strcmp(repos, AUTHZ_ANY_REPOSITORY)) 1252 update_rights(&gr->any_repos_rights, access); 1253 else 1254 { 1255 authz_rights_t *rights = svn_hash_gets(gr->per_repos_rights, repos); 1256 if (rights) 1257 update_rights(rights, access); 1258 else 1259 { 1260 rights = apr_palloc(apr_hash_pool_get(gr->per_repos_rights), 1261 sizeof(*rights)); 1262 init_rights(rights); 1263 update_rights(rights, access); 1264 svn_hash_sets(gr->per_repos_rights, repos, rights); 1265 } 1266 } 1267} 1268 1269 1270/* Hash iterator to update global per-user rights from an ACL. */ 1271static svn_error_t * 1272update_user_rights(void *baton, 1273 const void *key, 1274 apr_ssize_t klen, 1275 void *value, 1276 apr_pool_t *scratch_pool) 1277{ 1278 const authz_acl_t *const acl = baton; 1279 const char *const user = key; 1280 authz_global_rights_t *const gr = value; 1281 authz_access_t access; 1282 svn_boolean_t has_access = 1283 svn_authz__get_acl_access(&access, acl, user, acl->rule.repos); 1284 1285 if (has_access) 1286 update_global_rights(gr, acl->rule.repos, access); 1287 return SVN_NO_ERROR; 1288} 1289 1290 1291/* List iterator, expands/merges a parsed ACL into its final form and 1292 appends it to the authz info's ACL array. */ 1293static svn_error_t * 1294expand_acl_callback(void *baton, 1295 void *item, 1296 apr_pool_t *scratch_pool) 1297{ 1298 ctor_baton_t *const cb = baton; 1299 parsed_acl_t *const pacl = item; 1300 authz_acl_t *const acl = &pacl->acl; 1301 1302 /* Expand and merge the aliased ACEs. */ 1303 if (apr_hash_count(pacl->alias_aces)) 1304 { 1305 merge_alias_baton_t mab; 1306 mab.aces = pacl->aces; 1307 mab.cb = cb; 1308 SVN_ERR(svn_iter_apr_hash(NULL, pacl->alias_aces, 1309 merge_alias_ace, &mab, scratch_pool)); 1310 } 1311 1312 /* Make an array from the merged hashes. */ 1313 acl->user_access = 1314 apr_array_make(cb->authz->pool, apr_hash_count(pacl->aces), 1315 sizeof(authz_ace_t)); 1316 { 1317 insert_ace_baton_t iab; 1318 iab.ace_array = acl->user_access; 1319 iab.cb = cb; 1320 SVN_ERR(svn_iter_apr_hash(NULL, pacl->aces, 1321 array_insert_ace, &iab, scratch_pool)); 1322 } 1323 1324 /* Store the completed ACL into authz. */ 1325 APR_ARRAY_PUSH(cb->authz->acls, authz_acl_t) = *acl; 1326 1327 /* Update global access rights for this ACL. */ 1328 if (acl->has_anon_access) 1329 { 1330 cb->authz->has_anon_rights = TRUE; 1331 update_global_rights(&cb->authz->anon_rights, 1332 acl->rule.repos, acl->anon_access); 1333 } 1334 if (acl->has_authn_access) 1335 { 1336 cb->authz->has_authn_rights = TRUE; 1337 update_global_rights(&cb->authz->authn_rights, 1338 acl->rule.repos, acl->authn_access); 1339 } 1340 if (acl->has_neg_access) 1341 { 1342 cb->authz->has_neg_rights = TRUE; 1343 update_global_rights(&cb->authz->neg_rights, 1344 acl->rule.repos, acl->neg_access); 1345 } 1346 SVN_ERR(svn_iter_apr_hash(NULL, cb->authz->user_rights, 1347 update_user_rights, acl, scratch_pool)); 1348 return SVN_NO_ERROR; 1349} 1350 1351 1352/* Compare two ACLs in rule lexical order, then repository order, then 1353 order of definition. This ensures that our default ACL is always 1354 first in the sorted array. */ 1355static int 1356compare_parsed_acls(const void *va, const void *vb) 1357{ 1358 const parsed_acl_t *const a = va; 1359 const parsed_acl_t *const b = vb; 1360 1361 int cmp = svn_authz__compare_rules(&a->acl.rule, &b->acl.rule); 1362 if (cmp == 0) 1363 cmp = a->acl.sequence_number - b->acl.sequence_number; 1364 return cmp; 1365} 1366 1367 1368svn_error_t * 1369svn_authz__parse(authz_full_t **authz, 1370 svn_stream_t *rules, 1371 svn_stream_t *groups, 1372 svn_repos_authz_warning_func_t warning_func, 1373 void *warning_baton, 1374 apr_pool_t *result_pool, 1375 apr_pool_t *scratch_pool) 1376{ 1377 ctor_baton_t *const cb = create_ctor_baton(warning_func, warning_baton, 1378 result_pool, scratch_pool); 1379 1380 /* 1381 * Pass 1: Parse the authz file. 1382 */ 1383 SVN_ERR(svn_config__parse_stream(rules, 1384 svn_config__constructor_create( 1385 rules_open_section, 1386 close_section, 1387 rules_add_value, 1388 cb->parser_pool), 1389 cb, cb->parser_pool)); 1390 1391 /* 1392 * Pass 1.6487: Parse the global groups file. 1393 */ 1394 if (groups) 1395 { 1396 /* Check that the authz file did not contain any groups. */ 1397 if (0 != apr_hash_count(cb->parsed_groups)) 1398 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, 1399 ("Authz file cannot contain any groups" 1400 " when global groups are being used.")); 1401 1402 apr_hash_clear(cb->sections); 1403 cb->parsing_groups = TRUE; 1404 SVN_ERR(svn_config__parse_stream(groups, 1405 svn_config__constructor_create( 1406 groups_open_section, 1407 close_section, 1408 groups_add_value, 1409 cb->parser_pool), 1410 cb, cb->parser_pool)); 1411 } 1412 1413 /* 1414 * Pass 2: Expand groups and construct the final svn_authz_t. 1415 */ 1416 cb->expanded_groups = svn_hash__make(cb->parser_pool); 1417 SVN_ERR(svn_iter_apr_hash(NULL, cb->parsed_groups, 1418 expand_group_callback, cb, cb->parser_pool)); 1419 1420 1421 /* Sort the parsed ACLs in rule lexical order and pop off the 1422 default global ACL iff an equivalent ACL was defined in the authz 1423 file. */ 1424 if (cb->parsed_acls->nelts > 1) 1425 { 1426 parsed_acl_t *defacl; 1427 parsed_acl_t *nxtacl; 1428 1429 svn_sort__array(cb->parsed_acls, compare_parsed_acls); 1430 defacl = &APR_ARRAY_IDX(cb->parsed_acls, 0, parsed_acl_t); 1431 nxtacl = &APR_ARRAY_IDX(cb->parsed_acls, 1, parsed_acl_t); 1432 1433 /* If the first ACL is not our default thingamajig, there's a 1434 bug in our comparator. */ 1435 SVN_ERR_ASSERT( 1436 defacl->acl.sequence_number == 0 && defacl->acl.rule.len == 0 1437 && 0 == strcmp(defacl->acl.rule.repos, AUTHZ_ANY_REPOSITORY)); 1438 1439 /* Pop the default ACL off the array if another equivalent 1440 exists, after merging the default rights. */ 1441 if (0 == svn_authz__compare_rules(&defacl->acl.rule, &nxtacl->acl.rule)) 1442 { 1443 nxtacl->acl.has_anon_access = TRUE; 1444 nxtacl->acl.has_authn_access = TRUE; 1445 cb->parsed_acls->elts = (char*)(nxtacl); 1446 --cb->parsed_acls->nelts; 1447 } 1448 } 1449 1450 cb->authz->acls = apr_array_make(cb->authz->pool, cb->parsed_acls->nelts, 1451 sizeof(authz_acl_t)); 1452 SVN_ERR(svn_iter_apr_array(NULL, cb->parsed_acls, 1453 expand_acl_callback, cb, cb->parser_pool)); 1454 1455 *authz = cb->authz; 1456 apr_pool_destroy(cb->parser_pool); 1457 return SVN_NO_ERROR; 1458} 1459 1460 1461void 1462svn_authz__reverse_string(char *string, apr_size_t len) 1463{ 1464 char *left = string; 1465 char *right = string + len - 1; 1466 for (; left < right; ++left, --right) 1467 { 1468 char c = *left; 1469 *left = *right; 1470 *right = c; 1471 } 1472} 1473 1474 1475int 1476svn_authz__compare_paths(const authz_rule_t *a, const authz_rule_t *b) 1477{ 1478 const int min_len = (a->len > b->len ? b->len : a->len); 1479 int i; 1480 1481 for (i = 0; i < min_len; ++i) 1482 { 1483 int cmp = a->path[i].kind - b->path[i].kind; 1484 if (0 == cmp) 1485 { 1486 const char *const aseg = a->path[i].pattern.data; 1487 const char *const bseg = b->path[i].pattern.data; 1488 1489 /* Exploit the fact that segment patterns are interned. */ 1490 if (aseg != bseg) 1491 cmp = strcmp(aseg, bseg); 1492 else 1493 cmp = 0; 1494 } 1495 if (0 != cmp) 1496 return cmp; 1497 } 1498 1499 /* Sort shorter rules first. */ 1500 if (a->len != b->len) 1501 return a->len - b->len; 1502 1503 return 0; 1504} 1505 1506int 1507svn_authz__compare_rules(const authz_rule_t *a, const authz_rule_t *b) 1508{ 1509 int diff = svn_authz__compare_paths(a, b); 1510 if (diff) 1511 return diff; 1512 1513 /* Repository names are interned, too. */ 1514 if (a->repos != b->repos) 1515 return strcmp(a->repos, b->repos); 1516 1517 return 0; 1518} 1519