1 /* authz_parse.c : Parser for path-based access control
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
23 #include <apr_fnmatch.h>
24 #include <apr_tables.h>
26 #include "svn_ctype.h"
27 #include "svn_error.h"
30 #include "svn_pools.h"
31 #include "svn_repos.h"
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"
39 #include "svn_private_config.h"
44 /* Temporary ACL constructed by the parser. */
45 typedef struct parsed_acl_t
48 The strings in ACL.rule are allocated from the result pool.
49 ACL.user_access is null during the parsing stage. */
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. */
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;
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. */
67 typedef struct parsed_group_t
69 svn_boolean_t local_group;
70 apr_array_header_t *members;
74 /* Baton for the parser constructor. */
75 typedef struct ctor_baton_t
77 /* The final output of the parser. */
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.
86 N.B.: Whilst the strings are allocated from teh result pool, the
87 hash table itself is not. */
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
96 /* The name of the section we're currently parsing. */
99 /* TRUE iff we're parsing the global groups file. */
100 svn_boolean_t parsing_groups;
102 /* TRUE iff we're parsing a [groups] section. */
103 svn_boolean_t in_groups;
105 /* TRUE iff we're parsing an [aliases] section. */
106 svn_boolean_t in_aliases;
108 /* A set of all the unique rules we parsed from the section names. */
109 apr_hash_t *parsed_rules;
111 /* Temporary parsed-groups definitions. */
112 apr_hash_t *parsed_groups;
114 /* Temporary alias mappings. */
115 apr_hash_t *parsed_aliases;
117 /* Temporary parsed-acl definitions. */
118 apr_array_header_t *parsed_acls;
120 /* Temporary expanded groups definitions. */
121 apr_hash_t *expanded_groups;
123 /* The temporary ACL we're currently constructing. */
124 parsed_acl_t *current_acl;
126 /* Temporary buffers used to parse a rule into segments. */
127 svn_membuf_t rule_path_buffer;
128 svn_stringbuf_t *rule_string_buffer;
130 /* The parser's scratch pool. This may not be the same pool as
131 passed to the constructor callbacks, that is supposed to be an
132 iteration pool maintained by the generic parser.
134 N.B.: The result pool is AUTHZ->pool. */
135 apr_pool_t *parser_pool;
139 /* An empty string with a known address. */
140 static const char interned_empty_string[] = "";
142 /* The name of the aliases section. */
143 static const char aliases_section[] = "aliases";
145 /* The name of the groups section. */
146 static const char groups_section[] = "groups";
148 /* The token indicating that an authz rule contains wildcards. */
149 static const char glob_rule_token[] = "glob";
151 /* The anonymous access token. */
152 static const char anon_access_token[] = "$anonymous";
154 /* The authenticated access token. */
155 static const char authn_access_token[] = "$authenticated";
158 /* Initialize a rights structure.
159 The minimum rights start with all available access and are later
160 bitwise-and'ed with actual access rights. The maximum rights begin
161 empty and are later bitwise-and'ed with actual rights. */
162 static void init_rights(authz_rights_t *rights)
164 rights->min_access = authz_access_write;
165 rights->max_access = authz_access_none;
168 /* Initialize a global rights structure.
169 The USER string must be interned or statically initialized. */
171 init_global_rights(authz_global_rights_t *gr, const char *user,
172 apr_pool_t *result_pool)
175 init_rights(&gr->all_repos_rights);
176 init_rights(&gr->any_repos_rights);
177 gr->per_repos_rights = apr_hash_make(result_pool);
181 /* Insert the default global ACL into the parsed ACLs. */
183 insert_default_acl(ctor_baton_t *cb)
185 parsed_acl_t *acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t);
186 acl->acl.sequence_number = 0;
187 acl->acl.rule.repos = interned_empty_string;
188 acl->acl.rule.len = 0;
189 acl->acl.rule.path = NULL;
190 acl->acl.anon_access = authz_access_none;
191 acl->acl.has_anon_access = TRUE;
192 acl->acl.authn_access = authz_access_none;
193 acl->acl.has_authn_access = TRUE;
194 acl->acl.user_access = NULL;
195 acl->aces = svn_hash__make(cb->parser_pool);
196 acl->alias_aces = svn_hash__make(cb->parser_pool);
200 /* Initialize a constuctor baton. */
201 static ctor_baton_t *
202 create_ctor_baton(apr_pool_t *result_pool,
203 apr_pool_t *scratch_pool)
205 apr_pool_t *const parser_pool = svn_pool_create(scratch_pool);
206 ctor_baton_t *const cb = apr_pcalloc(parser_pool, sizeof(*cb));
208 authz_full_t *const authz = apr_pcalloc(result_pool, sizeof(*authz));
209 init_global_rights(&authz->anon_rights, anon_access_token, result_pool);
210 init_global_rights(&authz->authn_rights, authn_access_token, result_pool);
211 authz->user_rights = svn_hash__make(result_pool);
212 authz->pool = result_pool;
215 cb->strings = svn_hash__make(parser_pool);
217 cb->sections = svn_hash__make(parser_pool);
219 cb->parsing_groups = FALSE;
220 cb->in_groups = FALSE;
221 cb->in_aliases = FALSE;
223 cb->parsed_rules = svn_hash__make(parser_pool);
224 cb->parsed_groups = svn_hash__make(parser_pool);
225 cb->parsed_aliases = svn_hash__make(parser_pool);
226 cb->parsed_acls = apr_array_make(parser_pool, 64, sizeof(parsed_acl_t));
227 cb->current_acl = NULL;
229 svn_membuf__create(&cb->rule_path_buffer, 0, parser_pool);
230 cb->rule_string_buffer = svn_stringbuf_create_empty(parser_pool);
232 cb->parser_pool = parser_pool;
234 insert_default_acl(cb);
240 /* Create and store per-user global rights.
241 The USER string must be interned or statically initialized. */
243 prepare_global_rights(ctor_baton_t *cb, const char *user)
245 authz_global_rights_t *gr = svn_hash_gets(cb->authz->user_rights, user);
248 gr = apr_palloc(cb->authz->pool, sizeof(*gr));
249 init_global_rights(gr, user, cb->authz->pool);
250 svn_hash_sets(cb->authz->user_rights, gr->user, gr);
255 /* Internalize a string that will be referenced by the parsed svn_authz_t.
256 If LEN is (apr_size_t)-1, assume the string is NUL-terminated. */
258 intern_string(ctor_baton_t *cb, const char *str, apr_size_t len)
260 const char *interned;
262 if (len == (apr_size_t)-1)
265 interned = apr_hash_get(cb->strings, str, len);
268 interned = apr_pstrmemdup(cb->authz->pool, str, len);
269 apr_hash_set(cb->strings, interned, len, interned);
275 /* Helper for rules_open_section and groups_open_section. */
277 check_open_section(ctor_baton_t *cb, svn_stringbuf_t *section)
279 SVN_ERR_ASSERT(!cb->current_acl && !cb->section);
280 if (apr_hash_get(cb->sections, section->data, section->len))
282 if (cb->parsing_groups)
283 return svn_error_createf(
284 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
285 _("Section appears more than once"
286 " in the global groups file: [%s]"),
289 return svn_error_createf(
290 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
291 _("Section appears more than once"
292 " in the authz file: [%s]"),
296 cb->section = apr_pstrmemdup(cb->parser_pool, section->data, section->len);
297 svn_hash_sets(cb->sections, cb->section, interned_empty_string);
302 /* Constructor callback: Begins the [groups] section. */
304 groups_open_section(void *baton, svn_stringbuf_t *section)
306 ctor_baton_t *const cb = baton;
308 if (cb->parsing_groups)
309 SVN_ERR(check_open_section(cb, section));
311 if (0 == strcmp(section->data, groups_section))
313 cb->in_groups = TRUE;
317 return svn_error_createf(
318 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
320 ? _("Section is not valid in the global group file: [%s]")
321 : _("Section is not valid in the authz file: [%s]")),
326 /* Constructor callback: Parses a group declaration. */
328 groups_add_value(void *baton, svn_stringbuf_t *section,
329 svn_stringbuf_t *option, svn_stringbuf_t *value)
331 ctor_baton_t *const cb = baton;
333 apr_size_t group_len;
335 SVN_ERR_ASSERT(cb->in_groups);
337 if (strchr("@$&*~", *option->data))
339 if (cb->parsing_groups)
340 return svn_error_createf(
341 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
342 _("Global group name '%s' may not begin with '%c'"),
343 option->data, *option->data);
345 return svn_error_createf(
346 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
347 _("Group name '%s' may not begin with '%c'"),
348 option->data, *option->data);
351 /* Decorate the name to make lookups consistent. */
352 group = apr_pstrcat(cb->parser_pool, "@", option->data, SVN_VA_NULL);
353 group_len = option->len + 1;
354 if (apr_hash_get(cb->parsed_groups, group, group_len))
356 if (cb->parsing_groups)
357 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
358 _("Can't override definition"
359 " of global group '%s'"),
362 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
363 _("Can't override definition"
368 /* We store the whole group definition, so that we can use the
369 temporary groups in the baton hash later to fully expand group
371 At this point, we can finally internalize the group name. */
372 apr_hash_set(cb->parsed_groups,
373 intern_string(cb, group, group_len), group_len,
374 svn_cstring_split(value->data, ",", TRUE, cb->parser_pool));
380 /* Remove escape sequences in-place. */
382 unescape_in_place(svn_stringbuf_t *buf)
387 /* Skip the string up to the first escape sequence. */
388 for (i = 0; i < buf->len; ++i)
397 /* Unescape the remainder of the string. */
398 svn_boolean_t escape = TRUE;
401 for (q = p + 1, ++i; i < buf->len; ++i)
417 /* A trailing backslash is literal, so make it part of the pattern. */
421 buf->len = p - buf->data;
426 /* Internalize a pattern. */
428 intern_pattern(ctor_baton_t *cb,
429 svn_string_t *pattern,
433 pattern->data = intern_string(cb, string, len);
438 /* Parse a rule path PATH up to PATH_LEN into *RULE.
439 If GLOB is TRUE, treat PATH as possibly containing wildcards.
440 SECTION is the whole rule in the authz file.
441 Use pools and buffers from CB to do the obvious thing. */
443 parse_rule_path(authz_rule_t *rule,
450 svn_stringbuf_t *const pattern = cb->rule_string_buffer;
451 const char *const path_end = path + path_len;
452 authz_rule_segment_t *segment;
457 SVN_ERR_ASSERT(*path == '/');
460 for (start = path; start != path_end; start = end)
462 apr_size_t pattern_len;
464 /* Skip the leading slash and find the end of the segment. */
465 end = memchr(++start, '/', path_len - 1);
469 pattern_len = end - start;
470 path_len -= pattern_len + 1;
472 if (pattern_len == 0)
476 /* This is an empty (root) path. */
482 /* A path with two consecutive slashes is not canonical. */
483 return svn_error_createf(
484 SVN_ERR_AUTHZ_INVALID_CONFIG,
485 svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
486 _("Found empty name in authz rule path")),
487 _("Non-canonical path '%s' in authz rule [%s]"),
491 /* A path with . or .. segments is not canonical. */
494 || (pattern_len == 2 && start[1] == '.')))
495 return svn_error_createf(
496 SVN_ERR_AUTHZ_INVALID_CONFIG,
498 ? svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
499 _("Found '.' in authz rule path"))
500 : svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
501 _("Found '..' in authz rule path"))),
502 _("Non-canonical path '%s' in authz rule [%s]"),
505 /* Make space for the current segment. */
507 svn_membuf__resize(&cb->rule_path_buffer, nseg * sizeof(*segment));
508 segment = cb->rule_path_buffer.data;
509 segment += (nseg - 1);
513 /* Trivial case: this is not a glob rule, so every segment
514 is a literal match. */
515 segment->kind = authz_rule_literal;
516 intern_pattern(cb, &segment->pattern, start, pattern_len);
520 /* Copy the segment into the temporary buffer. */
521 svn_stringbuf_setempty(pattern);
522 svn_stringbuf_appendbytes(pattern, start, pattern_len);
524 if (0 == apr_fnmatch_test(pattern->data))
526 /* It's a literal match after all. */
527 segment->kind = authz_rule_literal;
528 unescape_in_place(pattern);
529 intern_pattern(cb, &segment->pattern, pattern->data, pattern->len);
533 if (*pattern->data == '*')
535 if (pattern->len == 1
536 || (pattern->len == 2 && pattern->data[1] == '*'))
538 /* Process * and **, applying normalization as per
539 https://wiki.apache.org/subversion/AuthzImprovements. */
541 authz_rule_segment_t *const prev =
542 (nseg > 1 ? segment - 1 : NULL);
544 if (pattern_len == 1)
546 /* This is a *. Replace **|* with *|**. */
547 if (prev && prev->kind == authz_rule_any_recursive)
549 prev->kind = authz_rule_any_segment;
550 segment->kind = authz_rule_any_recursive;
553 segment->kind = authz_rule_any_segment;
557 /* This is a **. Replace **|** with a single **. */
558 if (prev && prev->kind == authz_rule_any_recursive)
560 /* Simply drop the redundant new segment. */
565 segment->kind = authz_rule_any_recursive;
568 segment->pattern.data = interned_empty_string;
569 segment->pattern.len = 0;
573 /* Maybe it's a suffix match? */
574 if (0 == apr_fnmatch_test(pattern->data + 1))
576 svn_stringbuf_leftchop(pattern, 1);
577 segment->kind = authz_rule_suffix;
578 unescape_in_place(pattern);
579 svn_authz__reverse_string(pattern->data, pattern->len);
580 intern_pattern(cb, &segment->pattern,
581 pattern->data, pattern->len);
586 if (pattern->data[pattern->len - 1] == '*')
588 /* Might be a prefix match. Note that because of the
589 previous test, we already know that the pattern is longer
590 than one character. */
591 if (pattern->data[pattern->len - 2] != '\\')
593 /* OK, the * wasn't escaped. Chop off the wildcard. */
594 svn_stringbuf_chop(pattern, 1);
595 if (0 == apr_fnmatch_test(pattern->data))
597 segment->kind = authz_rule_prefix;
598 unescape_in_place(pattern);
599 intern_pattern(cb, &segment->pattern,
600 pattern->data, pattern->len);
604 /* Restore the wildcard since it was not a prefix match. */
605 svn_stringbuf_appendbyte(pattern, '*');
609 /* It's a generic fnmatch pattern. */
610 segment->kind = authz_rule_fnmatch;
611 intern_pattern(cb, &segment->pattern, pattern->data, pattern->len);
614 SVN_ERR_ASSERT(nseg > 0);
616 /* Copy the temporary segments array into the result pool. */
618 const apr_size_t path_size = nseg * sizeof(*segment);
619 SVN_ERR_ASSERT(path_size <= cb->rule_path_buffer.size);
622 rule->path = apr_palloc(cb->authz->pool, path_size);
623 memcpy(rule->path, cb->rule_path_buffer.data, path_size);
630 /* Check that the parsed RULE is unique within the authz file.
631 With the introduction of wildcards, just looking at the SECTION
632 names is not sufficient to determine uniqueness.
633 Use pools and buffers from CB to do the obvious thing. */
635 check_unique_rule(ctor_baton_t *cb,
636 const authz_rule_t *rule,
639 svn_stringbuf_t *const buf = cb->rule_string_buffer;
643 /* Construct the key for this rule */
644 svn_stringbuf_setempty(buf);
645 svn_stringbuf_appendcstr(buf, rule->repos);
646 svn_stringbuf_appendbyte(buf, '\n');
648 for (i = 0; i < rule->len; ++i)
650 authz_rule_segment_t *const seg = &rule->path[i];
651 svn_stringbuf_appendbyte(buf, '@' + seg->kind);
652 svn_stringbuf_appendbytes(buf, seg->pattern.data, seg->pattern.len);
653 svn_stringbuf_appendbyte(buf, '\n');
656 /* Check if the section exists. */
657 exists = apr_hash_get(cb->parsed_rules, buf->data, buf->len);
659 return svn_error_createf(
660 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
661 _("Section [%s] describes the same rule as section [%s]"),
664 /* Insert the rule into the known rules set. */
665 apr_hash_set(cb->parsed_rules,
666 apr_pstrmemdup(cb->parser_pool, buf->data, buf->len),
668 apr_pstrdup(cb->parser_pool, section));
674 /* Constructor callback: Starts a rule or [aliases] section. */
676 rules_open_section(void *baton, svn_stringbuf_t *section)
678 ctor_baton_t *const cb = baton;
679 const char *rule = section->data;
680 apr_size_t rule_len = section->len;
685 SVN_ERR(check_open_section(cb, section));
687 /* Parse rule property tokens. */
692 /* This must be a wildcard rule. */
693 apr_size_t token_len;
696 endp = memchr(rule, ':', rule_len);
698 return svn_error_createf(
699 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
700 _("Empty repository name in authz rule [%s]"),
703 /* Note: the size of glob_rule_token includes the NUL terminator. */
704 token_len = endp - rule;
705 if (token_len != sizeof(glob_rule_token) - 1
706 || memcmp(rule, glob_rule_token, token_len))
707 return svn_error_createf(
708 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
709 _("Invalid type token '%s' in authz rule [%s]"),
710 apr_pstrmemdup(cb->parser_pool, rule, token_len),
715 rule_len -= token_len + 1;
718 /* Parse the repository name. */
719 endp = (*rule == '/' ? NULL : memchr(rule, ':', rule_len));
721 acl.acl.rule.repos = interned_empty_string;
724 const apr_size_t repos_len = endp - rule;
726 /* The rule contains a repository name. */
728 return svn_error_createf(
729 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
730 _("Empty repository name in authz rule [%s]"),
733 acl.acl.rule.repos = intern_string(cb, rule, repos_len);
735 rule_len -= repos_len + 1;
738 /* Parse the actual rule. */
741 SVN_ERR(parse_rule_path(&acl.acl.rule, cb, glob, rule, rule_len,
743 SVN_ERR(check_unique_rule(cb, &acl.acl.rule, section->data));
745 else if (0 == strcmp(section->data, aliases_section))
747 cb->in_aliases = TRUE;
752 /* This must be the [groups] section. */
753 return groups_open_section(cb, section);
756 acl.acl.sequence_number = cb->parsed_acls->nelts;
757 acl.acl.anon_access = authz_access_none;
758 acl.acl.has_anon_access = FALSE;
759 acl.acl.authn_access = authz_access_none;
760 acl.acl.has_authn_access = FALSE;
761 acl.acl.user_access = NULL;
763 acl.aces = svn_hash__make(cb->parser_pool);
764 acl.alias_aces = svn_hash__make(cb->parser_pool);
766 cb->current_acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t);
767 *cb->current_acl = acl;
772 /* Parses an alias declaration. The definition (username) of the
773 alias will always be interned. */
775 add_alias_definition(ctor_baton_t *cb,
776 svn_stringbuf_t *option, svn_stringbuf_t *value)
779 apr_size_t alias_len;
782 if (strchr("@$&*~", *option->data))
783 return svn_error_createf(
784 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
785 _("Alias name '%s' may not begin with '%c'"),
786 option->data, *option->data);
788 /* Decorate the name to make lookups consistent. */
789 alias = apr_pstrcat(cb->parser_pool, "&", option->data, SVN_VA_NULL);
790 alias_len = option->len + 1;
791 if (apr_hash_get(cb->parsed_aliases, alias, alias_len))
792 return svn_error_createf(
793 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
794 _("Can't override definition of alias '%s'"),
797 user = intern_string(cb, value->data, value->len);
798 apr_hash_set(cb->parsed_aliases, alias, alias_len, user);
800 /* Prepare the global rights struct for this user. */
801 prepare_global_rights(cb, user);
805 /* Parses an access entry. Groups and users in access entry names will
806 always be interned, aliases will never be. */
808 add_access_entry(ctor_baton_t *cb, svn_stringbuf_t *section,
809 svn_stringbuf_t *option, svn_stringbuf_t *value)
811 parsed_acl_t *const acl = cb->current_acl;
812 const char *name = option->data;
813 apr_size_t name_len = option->len;
814 const svn_boolean_t inverted = (*name == '~');
815 svn_boolean_t anonymous = FALSE;
816 svn_boolean_t authenticated = FALSE;
817 authz_access_t access = authz_access_none;
821 SVN_ERR_ASSERT(acl != NULL);
829 /* Determine the access entry type. */
833 return svn_error_createf(
834 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
835 _("Access entry '%s' has more than one inversion;"
836 " double negatives are not permitted"),
842 return svn_error_createf(
843 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
844 _("Access entry '%s' is not valid;"
845 " it must be a single '*'"),
849 return svn_error_createf(
850 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
851 _("Access entry '~*' will never match"));
854 authenticated = TRUE;
858 if (0 == strcmp(name, anon_access_token))
861 authenticated = TRUE;
865 else if (0 == strcmp(name, authn_access_token))
870 authenticated = TRUE;
873 return svn_error_createf(
874 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
875 _("Access entry token '%s' is not valid;"
876 " should be '%s' or '%s'"),
877 option->data, anon_access_token, authn_access_token);
881 /* A username, group name or alias. */;
884 /* Parse the access rights. */
885 for (i = 0; i < value->len; ++i)
887 const char access_code = value->data[i];
891 access |= authz_access_read_flag;
895 access |= authz_access_write_flag;
899 if (!svn_ctype_isspace(access_code))
900 return svn_error_createf(
901 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
902 _("The access mode '%c' in access entry '%s'"
903 " of rule [%s] is not valid"),
904 access_code, option->data, section->data);
908 /* We do not support write-only access. */
909 if ((access & authz_access_write_flag) && !(access & authz_access_read_flag))
910 return svn_error_createf(
911 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
912 _("Write-only access entry '%s' of rule [%s] is not valid"),
913 option->data, section->data);
915 /* Update the parsed ACL with this access entry. */
916 if (anonymous || authenticated)
920 acl->acl.has_anon_access = TRUE;
921 acl->acl.anon_access |= access;
925 acl->acl.has_authn_access = TRUE;
926 acl->acl.authn_access |= access;
931 /* The inversion tag must be part of the key in the hash
932 table, otherwise we can't tell regular and inverted
934 const char *key = (inverted ? name - 1 : name);
935 const apr_size_t key_len = (inverted ? name_len + 1 : name_len);
936 const svn_boolean_t aliased = (*name == '&');
937 apr_hash_t *aces = (aliased ? acl->alias_aces : acl->aces);
939 ace = apr_hash_get(aces, key, key_len);
941 ace->access |= access;
944 ace = apr_palloc(cb->parser_pool, sizeof(*ace));
946 ? apr_pstrmemdup(cb->parser_pool, name, name_len)
947 : intern_string(cb, name, name_len));
949 ace->inverted = inverted;
950 ace->access = access;
953 ? apr_pstrmemdup(cb->parser_pool, key, key_len)
955 apr_hash_set(aces, key, key_len, ace);
957 /* Prepare the global rights struct for this user. */
958 if (!aliased && *ace->name != '@')
959 prepare_global_rights(cb, ace->name);
966 /* Constructor callback: Parse a rule, alias or group delcaration. */
968 rules_add_value(void *baton, svn_stringbuf_t *section,
969 svn_stringbuf_t *option, svn_stringbuf_t *value)
971 ctor_baton_t *const cb = baton;
974 return groups_add_value(baton, section, option, value);
977 return add_alias_definition(cb, option, value);
979 return add_access_entry(cb, section, option, value);
983 /* Constructor callback: Close a section. */
985 close_section(void *baton, svn_stringbuf_t *section)
987 ctor_baton_t *const cb = baton;
989 SVN_ERR_ASSERT(0 == strcmp(cb->section, section->data));
991 cb->current_acl = NULL;
992 cb->in_groups = FALSE;
993 cb->in_aliases = FALSE;
998 /* Add a user to GROUP.
999 GROUP is never internalized, but USER always is. */
1001 add_to_group(ctor_baton_t *cb, const char *group, const char *user)
1003 apr_hash_t *members = svn_hash_gets(cb->expanded_groups, group);
1006 group = intern_string(cb, group, -1);
1007 members = svn_hash__make(cb->authz->pool);
1008 svn_hash_sets(cb->expanded_groups, group, members);
1010 svn_hash_sets(members, user, interned_empty_string);
1014 /* Hash iterator for expanding group definitions.
1015 WARNING: This function is recursive! */
1016 static svn_error_t *
1017 expand_group_callback(void *baton,
1021 apr_pool_t *scratch_pool)
1023 ctor_baton_t *const cb = baton;
1024 const char *const group = key;
1025 apr_array_header_t *members = value;
1028 for (i = 0; i < members->nelts; ++i)
1030 const char *member = APR_ARRAY_IDX(members, i, const char*);
1031 if (0 == strcmp(member, group))
1032 return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1033 _("Recursive definition of group '%s'"),
1038 /* Add expanded alias to the group.
1039 N.B.: the user name is already internalized. */
1040 const char *user = svn_hash_gets(cb->parsed_aliases, member);
1042 return svn_error_createf(
1043 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1044 _("Alias '%s' was never defined"),
1047 add_to_group(cb, group, user);
1049 else if (*member != '@')
1051 /* Add the member to the group. */
1052 const char *user = intern_string(cb, member, -1);
1053 add_to_group(cb, group, user);
1055 /* Prepare the global rights struct for this user. */
1056 prepare_global_rights(cb, user);
1060 /* Recursively expand the group membership */
1061 members = svn_hash_gets(cb->parsed_groups, member);
1063 return svn_error_createf(
1064 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1065 _("Undefined group '%s'"),
1067 SVN_ERR(expand_group_callback(cb, key, klen,
1068 members, scratch_pool));
1071 return SVN_NO_ERROR;
1075 /* Hash iteration baton for merge_alias_ace. */
1076 typedef struct merge_alias_baton_t
1080 } merge_alias_baton_t;
1082 /* Hash iterator for expanding and mergina alias-based ACEs
1083 into the user/group-based ACEs. */
1084 static svn_error_t *
1085 merge_alias_ace(void *baton,
1089 apr_pool_t *scratch_pool)
1091 merge_alias_baton_t *const mab = baton;
1092 authz_ace_t *aliased_ace = value;
1093 const char *alias = aliased_ace->name;
1094 const char *unaliased_key;
1098 user = svn_hash_gets(mab->cb->parsed_aliases, alias);
1100 return svn_error_createf(
1101 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1102 _("Alias '%s' was never defined"),
1105 /* N.B.: The user name is always internalized,
1106 but the inverted key may not be. */
1107 if (!aliased_ace->inverted)
1108 unaliased_key = user;
1111 unaliased_key = apr_pstrcat(mab->cb->parser_pool,
1112 "~", user, SVN_VA_NULL);
1113 unaliased_key = intern_string(mab->cb, unaliased_key, -1);
1116 ace = svn_hash_gets(mab->aces, unaliased_key);
1119 aliased_ace->name = user;
1120 svn_hash_sets(mab->aces, unaliased_key, aliased_ace);
1124 SVN_ERR_ASSERT(!ace->inverted == !aliased_ace->inverted);
1125 ace->access |= aliased_ace->access;
1128 return SVN_NO_ERROR;
1132 /* Hash iteration baton for array_insert_ace. */
1133 typedef struct insert_ace_baton_t
1135 apr_array_header_t *ace_array;
1137 } insert_ace_baton_t;
1139 /* Hash iterator, inserts an ACE into the ACLs array. */
1140 static svn_error_t *
1141 array_insert_ace(void *baton,
1145 apr_pool_t *scratch_pool)
1147 insert_ace_baton_t *iab = baton;
1148 authz_ace_t *ace = value;
1150 /* Add group membership info to the ACE. */
1151 if (*ace->name == '@')
1153 SVN_ERR_ASSERT(ace->members == NULL);
1154 ace->members = svn_hash_gets(iab->cb->expanded_groups, ace->name);
1156 return svn_error_createf(
1157 SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1158 _("Access entry refers to undefined group '%s'"),
1162 APR_ARRAY_PUSH(iab->ace_array, authz_ace_t) = *ace;
1163 return SVN_NO_ERROR;
1167 /* Update accumulated RIGHTS from ACCESS. */
1169 update_rights(authz_rights_t *rights,
1170 authz_access_t access)
1172 rights->min_access &= access;
1173 rights->max_access |= access;
1177 /* Update a global RIGHTS based on REPOS and ACCESS. */
1179 update_global_rights(authz_global_rights_t *gr,
1181 authz_access_t access)
1183 update_rights(&gr->all_repos_rights, access);
1184 if (0 == strcmp(repos, AUTHZ_ANY_REPOSITORY))
1185 update_rights(&gr->any_repos_rights, access);
1188 authz_rights_t *rights = svn_hash_gets(gr->per_repos_rights, repos);
1190 update_rights(rights, access);
1193 rights = apr_palloc(apr_hash_pool_get(gr->per_repos_rights),
1195 init_rights(rights);
1196 update_rights(rights, access);
1197 svn_hash_sets(gr->per_repos_rights, repos, rights);
1203 /* Hash iterator to update global per-user rights from an ACL. */
1204 static svn_error_t *
1205 update_user_rights(void *baton,
1209 apr_pool_t *scratch_pool)
1211 const authz_acl_t *const acl = baton;
1212 const char *const user = key;
1213 authz_global_rights_t *const gr = value;
1214 authz_access_t access;
1215 svn_boolean_t has_access =
1216 svn_authz__get_acl_access(&access, acl, user, acl->rule.repos);
1219 update_global_rights(gr, acl->rule.repos, access);
1220 return SVN_NO_ERROR;
1224 /* List iterator, expands/merges a parsed ACL into its final form and
1225 appends it to the authz info's ACL array. */
1226 static svn_error_t *
1227 expand_acl_callback(void *baton,
1229 apr_pool_t *scratch_pool)
1231 ctor_baton_t *const cb = baton;
1232 parsed_acl_t *const pacl = item;
1233 authz_acl_t *const acl = &pacl->acl;
1235 /* Expand and merge the aliased ACEs. */
1236 if (apr_hash_count(pacl->alias_aces))
1238 merge_alias_baton_t mab;
1239 mab.aces = pacl->aces;
1241 SVN_ERR(svn_iter_apr_hash(NULL, pacl->alias_aces,
1242 merge_alias_ace, &mab, scratch_pool));
1245 /* Make an array from the merged hashes. */
1247 apr_array_make(cb->authz->pool, apr_hash_count(pacl->aces),
1248 sizeof(authz_ace_t));
1250 insert_ace_baton_t iab;
1251 iab.ace_array = acl->user_access;
1253 SVN_ERR(svn_iter_apr_hash(NULL, pacl->aces,
1254 array_insert_ace, &iab, scratch_pool));
1257 /* Store the completed ACL into authz. */
1258 APR_ARRAY_PUSH(cb->authz->acls, authz_acl_t) = *acl;
1260 /* Update global access rights for this ACL. */
1261 if (acl->has_anon_access)
1263 cb->authz->has_anon_rights = TRUE;
1264 update_global_rights(&cb->authz->anon_rights,
1265 acl->rule.repos, acl->anon_access);
1267 if (acl->has_authn_access)
1269 cb->authz->has_authn_rights = TRUE;
1270 update_global_rights(&cb->authz->authn_rights,
1271 acl->rule.repos, acl->authn_access);
1273 SVN_ERR(svn_iter_apr_hash(NULL, cb->authz->user_rights,
1274 update_user_rights, acl, scratch_pool));
1275 return SVN_NO_ERROR;
1279 /* Compare two ACLs in rule lexical order, then repository order, then
1280 order of definition. This ensures that our default ACL is always
1281 first in the sorted array. */
1283 compare_parsed_acls(const void *va, const void *vb)
1285 const parsed_acl_t *const a = va;
1286 const parsed_acl_t *const b = vb;
1288 int cmp = svn_authz__compare_rules(&a->acl.rule, &b->acl.rule);
1290 cmp = a->acl.sequence_number - b->acl.sequence_number;
1296 svn_authz__parse(authz_full_t **authz,
1297 svn_stream_t *rules,
1298 svn_stream_t *groups,
1299 apr_pool_t *result_pool,
1300 apr_pool_t *scratch_pool)
1302 ctor_baton_t *const cb = create_ctor_baton(result_pool, scratch_pool);
1305 * Pass 1: Parse the authz file.
1307 SVN_ERR(svn_config__parse_stream(rules,
1308 svn_config__constructor_create(
1313 cb, cb->parser_pool));
1316 * Pass 1.6487: Parse the global groups file.
1320 /* Check that the authz file did not contain any groups. */
1321 if (0 != apr_hash_count(cb->parsed_groups))
1322 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
1323 ("Authz file cannot contain any groups"
1324 " when global groups are being used."));
1326 apr_hash_clear(cb->sections);
1327 cb->parsing_groups = TRUE;
1328 SVN_ERR(svn_config__parse_stream(groups,
1329 svn_config__constructor_create(
1330 groups_open_section,
1334 cb, cb->parser_pool));
1338 * Pass 2: Expand groups and construct the final svn_authz_t.
1340 cb->expanded_groups = svn_hash__make(cb->parser_pool);
1341 SVN_ERR(svn_iter_apr_hash(NULL, cb->parsed_groups,
1342 expand_group_callback, cb, cb->parser_pool));
1345 /* Sort the parsed ACLs in rule lexical order and pop off the
1346 default global ACL iff an equivalent ACL was defined in the authz
1348 if (cb->parsed_acls->nelts > 1)
1350 parsed_acl_t *defacl;
1351 parsed_acl_t *nxtacl;
1353 svn_sort__array(cb->parsed_acls, compare_parsed_acls);
1354 defacl = &APR_ARRAY_IDX(cb->parsed_acls, 0, parsed_acl_t);
1355 nxtacl = &APR_ARRAY_IDX(cb->parsed_acls, 1, parsed_acl_t);
1357 /* If the first ACL is not our default thingamajig, there's a
1358 bug in our comparator. */
1360 defacl->acl.sequence_number == 0 && defacl->acl.rule.len == 0
1361 && 0 == strcmp(defacl->acl.rule.repos, AUTHZ_ANY_REPOSITORY));
1363 /* Pop the default ACL off the array if another equivalent
1364 exists, after merging the default rights. */
1365 if (0 == svn_authz__compare_rules(&defacl->acl.rule, &nxtacl->acl.rule))
1367 nxtacl->acl.has_anon_access = TRUE;
1368 nxtacl->acl.has_authn_access = TRUE;
1369 cb->parsed_acls->elts = (char*)(nxtacl);
1370 --cb->parsed_acls->nelts;
1374 cb->authz->acls = apr_array_make(cb->authz->pool, cb->parsed_acls->nelts,
1375 sizeof(authz_acl_t));
1376 SVN_ERR(svn_iter_apr_array(NULL, cb->parsed_acls,
1377 expand_acl_callback, cb, cb->parser_pool));
1380 apr_pool_destroy(cb->parser_pool);
1381 return SVN_NO_ERROR;
1386 svn_authz__reverse_string(char *string, apr_size_t len)
1388 char *left = string;
1389 char *right = string + len - 1;
1390 for (; left < right; ++left, --right)
1400 svn_authz__compare_paths(const authz_rule_t *a, const authz_rule_t *b)
1402 const int min_len = (a->len > b->len ? b->len : a->len);
1405 for (i = 0; i < min_len; ++i)
1407 int cmp = a->path[i].kind - b->path[i].kind;
1410 const char *const aseg = a->path[i].pattern.data;
1411 const char *const bseg = b->path[i].pattern.data;
1413 /* Exploit the fact that segment patterns are interned. */
1415 cmp = strcmp(aseg, bseg);
1423 /* Sort shorter rules first. */
1424 if (a->len != b->len)
1425 return a->len - b->len;
1431 svn_authz__compare_rules(const authz_rule_t *a, const authz_rule_t *b)
1433 int diff = svn_authz__compare_paths(a, b);
1437 /* Repository names are interned, too. */
1438 if (a->repos != b->repos)
1439 return strcmp(a->repos, b->repos);