xref: /freebsd-13-stable/contrib/subversion/subversion/libsvn_repos/authz_parse.c (revision b7ec5dea64b6513b41316a38cc72efa9139bc4ae)
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. */
45 typedef 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. */
67 typedef 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. */
75 typedef 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. */
144 static const char interned_empty_string[] = "";
145 
146 /* The name of the aliases section. */
147 static const char aliases_section[] = "aliases";
148 
149 /* The name of the groups section. */
150 static const char groups_section[] = "groups";
151 
152 /* The token indicating that an authz rule contains wildcards. */
153 static const char glob_rule_token[] = "glob";
154 
155 /* The anonymous access token. */
156 static const char anon_access_token[] = "$anonymous";
157 
158 /* The authenticated access token. */
159 static const char authn_access_token[] = "$authenticated";
160 
161 /* Fake token for inverted rights. */
162 static 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. */
init_rights(authz_rights_t * rights)168 static 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. */
176 static void
init_global_rights(authz_global_rights_t * gr,const char * user,apr_pool_t * result_pool)177 init_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. */
188 static void
insert_default_acl(ctor_baton_t * cb)189 insert_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. */
209 static ctor_baton_t *
create_ctor_baton(svn_repos_authz_warning_func_t warning_func,void * warning_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)210 create_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 */
255 static void
emit_parser_warning(const ctor_baton_t * cb,svn_error_t * error,apr_pool_t * scratch_pool)256 emit_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. */
275 static void
prepare_global_rights(ctor_baton_t * cb,const char * user)276 prepare_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. */
290 static const char *
intern_string(ctor_baton_t * cb,const char * str,apr_size_t len)291 intern_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. */
309 static svn_error_t *
check_open_section(ctor_baton_t * cb,svn_stringbuf_t * section)310 check_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. */
336 static svn_error_t *
groups_open_section(void * baton,svn_stringbuf_t * section)337 groups_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. */
360 static svn_error_t *
groups_add_value(void * baton,svn_stringbuf_t * section,svn_stringbuf_t * option,svn_stringbuf_t * value)361 groups_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. */
414 static void
unescape_in_place(svn_stringbuf_t * buf)415 unescape_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. */
460 static void
intern_pattern(ctor_baton_t * cb,svn_string_t * pattern,const char * string,apr_size_t len)461 intern_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. */
475 static svn_error_t *
parse_rule_path(authz_rule_t * rule,ctor_baton_t * cb,svn_boolean_t glob,const char * path,apr_size_t path_len,const char * section)476 parse_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. */
667 static svn_error_t *
check_unique_rule(ctor_baton_t * cb,const authz_rule_t * rule,const char * section)668 check_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. */
708 static svn_error_t *
rules_open_section(void * baton,svn_stringbuf_t * section)709 rules_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. */
809 static svn_error_t *
add_alias_definition(ctor_baton_t * cb,svn_stringbuf_t * option,svn_stringbuf_t * value)810 add_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. */
842 static svn_error_t *
add_access_entry(ctor_baton_t * cb,svn_stringbuf_t * section,svn_stringbuf_t * option,svn_stringbuf_t * value)843 add_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. */
1010 static svn_error_t *
rules_add_value(void * baton,svn_stringbuf_t * section,svn_stringbuf_t * option,svn_stringbuf_t * value)1011 rules_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. */
1027 static svn_error_t *
close_section(void * baton,svn_stringbuf_t * section)1028 close_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. */
1044 static void
add_to_group(ctor_baton_t * cb,const char * group,const char * user)1045 add_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! */
1061 static svn_error_t *
expand_group_callback(void * baton,const void * key,apr_ssize_t klen,void * value,apr_pool_t * scratch_pool)1062 expand_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. */
1129 typedef 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. */
1137 static svn_error_t *
merge_alias_ace(void * baton,const void * key,apr_ssize_t klen,void * value,apr_pool_t * scratch_pool)1138 merge_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. */
1186 typedef 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. */
1193 static svn_error_t *
array_insert_ace(void * baton,const void * key,apr_ssize_t klen,void * value,apr_pool_t * scratch_pool)1194 array_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. */
1235 static void
update_rights(authz_rights_t * rights,authz_access_t access)1236 update_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. */
1245 static void
update_global_rights(authz_global_rights_t * gr,const char * repos,authz_access_t access)1246 update_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. */
1271 static svn_error_t *
update_user_rights(void * baton,const void * key,apr_ssize_t klen,void * value,apr_pool_t * scratch_pool)1272 update_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. */
1293 static svn_error_t *
expand_acl_callback(void * baton,void * item,apr_pool_t * scratch_pool)1294 expand_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. */
1355 static int
compare_parsed_acls(const void * va,const void * vb)1356 compare_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 
1368 svn_error_t *
svn_authz__parse(authz_full_t ** authz,svn_stream_t * rules,svn_stream_t * groups,svn_repos_authz_warning_func_t warning_func,void * warning_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1369 svn_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 
1461 void
svn_authz__reverse_string(char * string,apr_size_t len)1462 svn_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 
1475 int
svn_authz__compare_paths(const authz_rule_t * a,const authz_rule_t * b)1476 svn_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 
1506 int
svn_authz__compare_rules(const authz_rule_t * a,const authz_rule_t * b)1507 svn_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