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