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