]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/authz.c
Update Subversion and dependencies to 1.14.0 LTS.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / authz.c
1 /* authz.c : 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 \f
24 /*** Includes. ***/
25
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
28 #include <apr_fnmatch.h>
29
30 #include "svn_hash.h"
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_repos.h"
36 #include "svn_config.h"
37 #include "svn_ctype.h"
38 #include "private/svn_atomic.h"
39 #include "private/svn_fspath.h"
40 #include "private/svn_repos_private.h"
41 #include "private/svn_sorts_private.h"
42 #include "private/svn_subr_private.h"
43 #include "repos.h"
44 #include "authz.h"
45 #include "config_file.h"
46
47 \f
48 /*** Access rights. ***/
49
50 /* This structure describes the access rights given to a specific user by
51  * a path rule (actually the rule set specified for a path).  I.e. there is
52  * one instance of this per path rule.
53  */
54 typedef struct path_access_t
55 {
56   /* Sequence number of the path rule that this struct was derived from.
57    * If multiple rules apply to the same path (only possible with wildcard
58    * matching), the one with the highest SEQUENCE_NUMBER wins, i.e. the latest
59    * one defined in the authz file.
60    *
61    * A value of 0 denotes the default rule at the repository root denying
62    * access to everybody.  User-defined path rules start with ID 1.
63    */
64   int sequence_number;
65
66   /* Access rights of the respective user as defined by the rule set. */
67   authz_access_t rights;
68 } path_access_t;
69
70 /* Use this to indicate that no sequence ID has been assigned.
71  * It will automatically be inferior to (less than) any other sequence ID. */
72 #define NO_SEQUENCE_NUMBER (-1)
73
74 /* Convenience structure combining the node-local access rights with the
75  * min and max rights granted within the sub-tree. */
76 typedef struct limited_rights_t
77 {
78   /* Access granted to the current user.  If the SEQUENCE_NUMBER member is
79    * NO_SEQUENCE_NUMBER, there has been no specific path rule for this PATH
80    * but only for some sub-path(s).  There is always a rule at the root node.
81    */
82   path_access_t access;
83
84   /* Minimal access rights that the user has on this or any other node in
85    * the sub-tree.  This does not take inherited rights into account. */
86   authz_access_t min_rights;
87
88   /* Maximal access rights that the user has on this or any other node in
89    * the sub-tree.  This does not take inherited rights into account. */
90   authz_access_t max_rights;
91
92 } limited_rights_t;
93
94 /* Return TRUE, if RIGHTS has local rights defined in the ACCESS member. */
95 static svn_boolean_t
96 has_local_rule(const limited_rights_t *rights)
97 {
98   return rights->access.sequence_number != NO_SEQUENCE_NUMBER;
99 }
100
101 /* Aggregate the ACCESS spec of TARGET and RIGHTS into TARGET.  I.e. if both
102  * are specified, pick one in accordance to the precedence rules. */
103 static void
104 combine_access(limited_rights_t *target,
105                const limited_rights_t *rights)
106 {
107   /* This implies the check for NO_SEQUENCE_NUMBER, i.e no rights being
108    * specified. */
109   if (target->access.sequence_number < rights->access.sequence_number)
110     target->access = rights->access;
111 }
112
113 /* Aggregate the min / max access rights of TARGET and RIGHTS into TARGET. */
114 static void
115 combine_right_limits(limited_rights_t *target,
116                      const limited_rights_t *rights)
117 {
118   target->max_rights |= rights->max_rights;
119   target->min_rights &= rights->min_rights;
120 }
121
122 \f
123
124 /*** Authz cache access. ***/
125
126 /* All authz instances currently in use as well as all filtered authz
127  * instances in use will be cached here.
128  * Both caches will be instantiated at most once. */
129 static svn_object_pool__t *authz_pool = NULL;
130 static svn_object_pool__t *filtered_pool = NULL;
131 static svn_atomic_t authz_pool_initialized = FALSE;
132
133 /* Implements svn_atomic__err_init_func_t. */
134 static svn_error_t *
135 synchronized_authz_initialize(void *baton, apr_pool_t *pool)
136 {
137 #if APR_HAS_THREADS
138   svn_boolean_t multi_threaded = TRUE;
139 #else
140   svn_boolean_t multi_threaded = FALSE;
141 #endif
142
143   SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool));
144   SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool));
145
146   return SVN_NO_ERROR;
147 }
148
149 svn_error_t *
150 svn_repos_authz_initialize(apr_pool_t *pool)
151 {
152   /* Protect against multiple calls. */
153   return svn_error_trace(svn_atomic__init_once(&authz_pool_initialized,
154                                                synchronized_authz_initialize,
155                                                NULL, pool));
156 }
157
158 /* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in RESULT_POOL.
159  * GROUPS_KEY may be NULL.  This is the key for the AUTHZ_POOL.
160  */
161 static svn_membuf_t *
162 construct_authz_key(const svn_checksum_t *authz_key,
163                     const svn_checksum_t *groups_key,
164                     apr_pool_t *result_pool)
165 {
166   svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
167   if (groups_key)
168     {
169       apr_size_t authz_size = svn_checksum_size(authz_key);
170       apr_size_t groups_size = svn_checksum_size(groups_key);
171
172       svn_membuf__create(result, authz_size + groups_size, result_pool);
173       result->size = authz_size + groups_size; /* exact length is required! */
174
175       memcpy(result->data, authz_key->digest, authz_size);
176       memcpy((char *)result->data + authz_size,
177              groups_key->digest, groups_size);
178     }
179   else
180     {
181       apr_size_t size = svn_checksum_size(authz_key);
182       svn_membuf__create(result, size, result_pool);
183       result->size = size; /* exact length is required! */
184       memcpy(result->data, authz_key->digest, size);
185     }
186
187   return result;
188 }
189
190 /* Return a combination of REPOS_NAME, USER and AUTHZ_ID, allocated in
191  * RESULT_POOL.  USER may be NULL.  This is the key for the FILTERED_POOL.
192  */
193 static svn_membuf_t *
194 construct_filtered_key(const char *repos_name,
195                        const char *user,
196                        const svn_membuf_t *authz_id,
197                        apr_pool_t *result_pool)
198 {
199   svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
200   size_t repos_len = strlen(repos_name);
201   size_t user_len = user ? strlen(user) : 1;
202   const char *nullable_user = user ? user : "\0";
203   size_t size = authz_id->size + repos_len + 1 + user_len + 1;
204
205   svn_membuf__create(result, size, result_pool);
206   result->size = size;
207
208   memcpy(result->data, repos_name, repos_len + 1);
209   size = repos_len + 1;
210   memcpy((char *)result->data + size, nullable_user, user_len + 1);
211   size += user_len + 1;
212   memcpy((char *)result->data + size, authz_id->data, authz_id->size);
213
214   return result;
215 }
216
217 \f
218 /*** Constructing the prefix tree. ***/
219
220 /* Since prefix arrays may have more than one hit, we need to link them
221  * for fast lookup. */
222 typedef struct sorted_pattern_t
223 {
224   /* The filtered tree node carrying the prefix. */
225   struct node_t *node;
226
227   /* Entry that is a prefix to this one or NULL. */
228   struct sorted_pattern_t *next;
229 } sorted_pattern_t;
230
231 /* Substructure of node_t.  It contains all sub-node that use patterns
232  * in the next segment level. We keep it separate to save a bit of memory
233  * and to be able to check for pattern presence in a single operation.
234  */
235 typedef struct node_pattern_t
236 {
237   /* If not NULL, this represents the "*" follow-segment. */
238   struct node_t *any;
239
240   /* If not NULL, this represents the "**" follow-segment. */
241   struct node_t *any_var;
242
243   /* If not NULL, the segments of all sorted_pattern_t in this array are the
244    * prefix part of "prefix*" patterns.  Sorted by segment prefix. */
245   apr_array_header_t *prefixes;
246
247   /* If not NULL, the segments of all sorted_pattern_t in this array are the
248    * reversed suffix part of "*suffix" patterns.  Sorted by reversed
249    * segment suffix. */
250   apr_array_header_t *suffixes;
251
252   /* If not NULL, the segments of all sorted_pattern_t in this array contain
253    * wildcards and don't fit into any of the above categories.
254    * The NEXT members of the elements will not be used. */
255   apr_array_header_t *complex;
256
257   /* This node itself is a "**" segment and must therefore itself be added
258    * to the matching node list for the next level. */
259   svn_boolean_t repeat;
260 } node_pattern_t;
261
262 /* The pattern tree.  All relevant path rules are being folded into this
263  * prefix tree, with a single, whole segment stored at each node.  The whole
264  * tree applies to a single user only.
265  */
266 typedef struct node_t
267 {
268   /* The segment as specified in the path rule.  During the lookup tree walk,
269    * this will compared to the respective segment of the path to check. */
270   svn_string_t segment;
271
272   /* Immediate access rights granted by rules on this node and the min /
273    * max rights on any path in this sub-tree. */
274   limited_rights_t rights;
275
276   /* Map of sub-segment(const char *) to respective node (node_t) for all
277    * sub-segments that have rules on themselves or their respective subtrees.
278    * NULL, if there are no rules for sub-paths relevant to the user. */
279   apr_hash_t *sub_nodes;
280
281   /* If not NULL, this contains the pattern-based segment sub-nodes. */
282   node_pattern_t *pattern_sub_nodes;
283 } node_t;
284
285 /* Create a new tree node for SEGMENT.
286    Note: SEGMENT->pattern is always interned and therefore does not
287    have to be copied into the result pool. */
288 static node_t *
289 create_node(authz_rule_segment_t *segment,
290             apr_pool_t *result_pool)
291 {
292   node_t *result = apr_pcalloc(result_pool, sizeof(*result));
293   if (segment)
294     result->segment = segment->pattern;
295   else
296     {
297       result->segment.data = "";
298       result->segment.len = 0;
299     }
300   result->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
301   return result;
302 }
303
304 /* Auto-create a node in *NODE, make it apply to SEGMENT and return it. */
305 static node_t *
306 ensure_node(node_t **node,
307             authz_rule_segment_t *segment,
308             apr_pool_t *result_pool)
309 {
310   if (!*node)
311     *node = create_node(segment, result_pool);
312
313   return *node;
314 }
315
316 /* compare_func comparing segment names. It takes a sorted_pattern_t* as
317  * VOID_LHS and a const authz_rule_segment_t * as VOID_RHS.
318  */
319 static int
320 compare_node_rule_segment(const void *void_lhs,
321                           const void *void_rhs)
322 {
323   const sorted_pattern_t *element = void_lhs;
324   const authz_rule_segment_t *segment = void_rhs;
325
326   return strcmp(element->node->segment.data, segment->pattern.data);
327 }
328
329 /* compare_func comparing segment names. It takes a sorted_pattern_t* as
330  * VOID_LHS and a const char * as VOID_RHS.
331  */
332 static int
333 compare_node_path_segment(const void *void_lhs,
334                           const void *void_rhs)
335 {
336   const sorted_pattern_t *element = void_lhs;
337   const char *segment = void_rhs;
338
339   return strcmp(element->node->segment.data, segment);
340 }
341
342 /* Make sure a node_t* for SEGMENT exists in *ARRAY and return it.
343  * Auto-create either if they don't exist.  Entries in *ARRAY are
344  * sorted by their segment strings.
345  */
346 static node_t *
347 ensure_node_in_array(apr_array_header_t **array,
348                      authz_rule_segment_t *segment,
349                      apr_pool_t *result_pool)
350 {
351   int idx;
352   sorted_pattern_t entry;
353   sorted_pattern_t *entry_ptr;
354
355   /* Auto-create the array. */
356   if (!*array)
357     *array = apr_array_make(result_pool, 4, sizeof(sorted_pattern_t));
358
359   /* Find the node in ARRAY and the IDX at which it were to be inserted.
360    * Initialize IDX such that we won't attempt a hinted lookup (likely
361    * to fail and therefore pure overhead). */
362   idx = (*array)->nelts;
363   entry_ptr = svn_sort__array_lookup(*array, segment, &idx,
364                                      compare_node_rule_segment);
365   if (entry_ptr)
366     return entry_ptr->node;
367
368   /* There is no such node, yet.
369    * Create one and insert it into the sorted array. */
370   entry.node = create_node(segment, result_pool);
371   entry.next = NULL;
372   svn_error_clear(svn_sort__array_insert2(*array, &entry, idx));
373
374   return entry.node;
375 }
376
377 /* Auto-create the PATTERN_SUB_NODES sub-structure in *NODE and return it. */
378 static node_pattern_t *
379 ensure_pattern_sub_nodes(node_t *node,
380                          apr_pool_t *result_pool)
381 {
382   if (node->pattern_sub_nodes == NULL)
383     node->pattern_sub_nodes = apr_pcalloc(result_pool,
384                                           sizeof(*node->pattern_sub_nodes));
385
386   return node->pattern_sub_nodes;
387 }
388
389 /* Combine an ACL rule segment with the corresponding node in our filtered
390  * data model. */
391 typedef struct node_segment_pair_t
392 {
393   authz_rule_segment_t *segment;
394   node_t *node;
395 } node_segment_pair_t;
396
397 /* Context object to be used with process_acl. It allows us to re-use
398  * information from previous insertions. */
399 typedef struct construction_context_t
400 {
401   /* Array of node_segment_pair_t.  It contains all segments already
402    * processed of the current insertion together with the respective
403    * nodes in our filtered tree.  Before the next lookup, the tree
404    * walk for the common prefix can be skipped. */
405   apr_array_header_t *path;
406 } construction_context_t;
407
408 /* Return a new context object allocated in RESULT_POOL. */
409 static construction_context_t *
410 create_construction_context(apr_pool_t *result_pool)
411 {
412   construction_context_t *result = apr_pcalloc(result_pool, sizeof(*result));
413
414   /* Array will be auto-extended but this initial size will make it rarely
415    * ever necessary. */
416   result->path = apr_array_make(result_pool, 32, sizeof(node_segment_pair_t));
417
418   return result;
419 }
420
421 /* Constructor utility:  Below NODE, recursively insert sub-nodes for the
422  * path given as *SEGMENTS of length SEGMENT_COUNT. If matching nodes
423  * already exist, use those instead of creating new ones.  Set the leave
424  * node's access rights spec to PATH_ACCESS.  Update the context info in CTX.
425  */
426 static void
427 insert_path(construction_context_t *ctx,
428             node_t *node,
429             path_access_t *path_access,
430             int segment_count,
431             authz_rule_segment_t *segment,
432             apr_pool_t *result_pool,
433             apr_pool_t *scratch_pool)
434 {
435   node_t *sub_node;
436   node_segment_pair_t *node_segment;
437
438   /* End of path? */
439   if (segment_count == 0)
440     {
441       /* Set access rights.  Note that there might be multiple rules for
442        * the same path due to non-repo-specific rules vs. repo-specific
443        * ones.  Whichever gets defined last wins.
444        */
445       limited_rights_t rights;
446       rights.access = *path_access;
447       rights.max_rights = path_access->rights;
448       rights.min_rights = path_access->rights;
449       combine_access(&node->rights, &rights);
450       return;
451     }
452
453   /* Any wildcards?  They will go into a separate sub-structure. */
454   if (segment->kind != authz_rule_literal)
455     ensure_pattern_sub_nodes(node, result_pool);
456
457   switch (segment->kind)
458     {
459       /* A full wildcard segment? */
460     case authz_rule_any_segment:
461       sub_node = ensure_node(&node->pattern_sub_nodes->any,
462                              segment, result_pool);
463       break;
464
465       /* One or more full wildcard segments? */
466     case authz_rule_any_recursive:
467       sub_node = ensure_node(&node->pattern_sub_nodes->any_var,
468                              segment, result_pool);
469       ensure_pattern_sub_nodes(sub_node, result_pool)->repeat = TRUE;
470       break;
471
472       /* A single wildcard at the end of the segment? */
473     case authz_rule_prefix:
474       sub_node = ensure_node_in_array(&node->pattern_sub_nodes->prefixes,
475                                       segment, result_pool);
476       break;
477
478       /* A single wildcard at the start of segments? */
479     case authz_rule_suffix:
480       sub_node = ensure_node_in_array(&node->pattern_sub_nodes->suffixes,
481                                       segment, result_pool);
482       break;
483
484       /* General pattern? */
485     case authz_rule_fnmatch:
486       sub_node = ensure_node_in_array(&node->pattern_sub_nodes->complex,
487                                       segment, result_pool);
488       break;
489
490       /* Then it must be a literal. */
491     default:
492       SVN_ERR_ASSERT_NO_RETURN(segment->kind == authz_rule_literal);
493
494       if (!node->sub_nodes)
495         {
496           node->sub_nodes = svn_hash__make(result_pool);
497           sub_node = NULL;
498         }
499       else
500         {
501           sub_node = svn_hash_gets(node->sub_nodes, segment->pattern.data);
502         }
503
504       /* Auto-insert a sub-node for the current segment. */
505       if (!sub_node)
506         {
507           sub_node = create_node(segment, result_pool);
508           apr_hash_set(node->sub_nodes,
509                        sub_node->segment.data,
510                        sub_node->segment.len,
511                        sub_node);
512         }
513     }
514
515   /* Update context. */
516   node_segment = apr_array_push(ctx->path);
517   node_segment->segment = segment;
518   node_segment->node = sub_node;
519
520   /* Continue at the sub-node with the next segment. */
521   insert_path(ctx, sub_node, path_access, segment_count - 1, segment + 1,
522               result_pool, scratch_pool);
523 }
524
525
526 /* If the ACL is relevant to the REPOSITORY and user (given as MEMBERSHIPS
527  * plus ANONYMOUS flag), insert the respective nodes into tree starting
528  * at ROOT.  Use the context info of the previous call in CTX to eliminate
529  * repeated lookups.  Allocate new nodes in RESULT_POOL and use SCRATCH_POOL
530  * for temporary allocations.
531  */
532 static void
533 process_acl(construction_context_t *ctx,
534             const authz_acl_t *acl,
535             node_t *root,
536             const char *repository,
537             const char *user,
538             apr_pool_t *result_pool,
539             apr_pool_t *scratch_pool)
540 {
541   path_access_t path_access;
542   int i;
543   node_t *node;
544
545   /* Skip ACLs that don't say anything about the current user
546      and/or repository. */
547   if (!svn_authz__get_acl_access(&path_access.rights, acl, user, repository))
548     return;
549
550   /* Insert the rule into the filtered tree. */
551   path_access.sequence_number = acl->sequence_number;
552
553   /* Try to reuse results from previous runs.
554    * Basically, skip the common prefix. */
555   node = root;
556   for (i = 0; i < ctx->path->nelts; ++i)
557     {
558       const node_segment_pair_t *step
559         = &APR_ARRAY_IDX(ctx->path, i, const node_segment_pair_t);
560
561       /* Exploit the fact that all strings in the authz model are unique /
562        * internized and can be identified by address alone. */
563       if (   !step->node
564           || i >= acl->rule.len
565           || step->segment->kind != acl->rule.path[i].kind
566           || step->segment->pattern.data != acl->rule.path[i].pattern.data)
567         {
568           ctx->path->nelts = i;
569           break;
570         }
571       else
572         {
573           node = step->node;
574         }
575     }
576
577   /* Insert the path rule into the filtered tree. */
578   insert_path(ctx, node, &path_access,
579               acl->rule.len - i, acl->rule.path + i,
580               result_pool, scratch_pool);
581 }
582
583 /* Forward declaration ... */
584 static svn_boolean_t
585 trim_tree(node_t *node,
586           int latest_any_var,
587           apr_pool_t *scratch_pool);
588
589 /* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
590  * node_t * and remove empty nodes from.  *HASH may be NULL.  If all nodes
591  * could be removed, set *HASH to NULL and return TRUE.  Allocate temporary
592  * data in SCRATCH_POOL.
593  */
594 static svn_boolean_t
595 trim_subnode_hash(apr_hash_t **hash,
596                   int latest_any_var,
597                   apr_pool_t *scratch_pool)
598 {
599   if (*hash)
600     {
601       apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
602                                                      sizeof(node_t *));
603
604       apr_hash_index_t *hi;
605       for (hi = apr_hash_first(scratch_pool, *hash);
606            hi;
607            hi = apr_hash_next(hi))
608         {
609           node_t *node = apr_hash_this_val(hi);
610           if (trim_tree(node, latest_any_var, scratch_pool))
611             APR_ARRAY_PUSH(to_remove, node_t *) = node;
612         }
613
614       /* Are some nodes left? */
615       if (to_remove->nelts < apr_hash_count(*hash))
616         {
617           /* Remove empty nodes (if any). */
618           int i;
619           for (i = 0; i < to_remove->nelts; ++i)
620             {
621               node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
622               apr_hash_set(*hash, node->segment.data, node->segment.len,
623                            NULL);
624             }
625
626           return FALSE;
627         }
628
629       /* No nodes left.  A NULL hash is more efficient than an empty one. */
630       *hash = NULL;
631     }
632
633   return TRUE;
634 }
635
636 /* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
637  * node_t * and remove empty nodes from.  *ARRAY may be NULL.  If all nodes
638  * could be removed, set *ARRAY to NULL and return TRUE.  Allocate
639  * temporary data in SCRATCH_POOL.
640  */
641 static svn_boolean_t
642 trim_subnode_array(apr_array_header_t **array,
643                    int latest_any_var,
644                    apr_pool_t *scratch_pool)
645 {
646   if (*array)
647     {
648       int i, dest;
649       for (i = 0, dest = 0; i < (*array)->nelts; ++i)
650         {
651           node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
652           if (!trim_tree(node, latest_any_var, scratch_pool))
653             {
654               if (i != dest)
655                 APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
656                   = APR_ARRAY_IDX(*array, i, sorted_pattern_t);
657               ++dest;
658             }
659         }
660
661       /* Are some nodes left? */
662       if (dest)
663         {
664           /* Trim it to the number of valid entries. */
665           (*array)->nelts = dest;
666           return FALSE;
667         }
668
669       /* No nodes left.  A NULL array is more efficient than an empty one. */
670       *array = NULL;
671     }
672
673   return TRUE;
674 }
675
676 /* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
677  * "any-var" rule with sequence number LATEST_ANY_VAR.  Return TRUE, if
678  * there are no rules left in the sub-tree, including NODE.
679  * Allocate temporary data in SCRATCH_POOL.
680  */
681 static svn_boolean_t
682 trim_tree(node_t *node,
683           int latest_any_var,
684           apr_pool_t *scratch_pool)
685 {
686   svn_boolean_t removed_all = TRUE;
687
688   /* For convenience, we allow NODE to be NULL: */
689   if (!node)
690     return TRUE;
691
692   /* Do we have a later "any_var" rule at this node. */
693   if (   node->pattern_sub_nodes
694       && node->pattern_sub_nodes->any_var
695       &&   node->pattern_sub_nodes->any_var->rights.access.sequence_number
696          > latest_any_var)
697     {
698       latest_any_var
699         = node->pattern_sub_nodes->any_var->rights.access.sequence_number;
700     }
701
702   /* Is there a local rule at this node that is not eclipsed by any_var? */
703   if (has_local_rule(&node->rights))
704     {
705       /* Remove the local rule, if it got eclipsed.
706        * Note that for the latest any_var node, the sequence number is equal. */
707       if (node->rights.access.sequence_number >= latest_any_var)
708         removed_all = FALSE;
709       else
710          node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
711     }
712
713   /* Process all sub-nodes. */
714   removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
715                                    scratch_pool);
716
717   if (node->pattern_sub_nodes)
718     {
719       if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
720                     scratch_pool))
721         node->pattern_sub_nodes->any = NULL;
722       else
723         removed_all = FALSE;
724
725       if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
726                     scratch_pool))
727         node->pattern_sub_nodes->any_var = NULL;
728       else
729         removed_all = FALSE;
730
731       removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
732                                         latest_any_var, scratch_pool);
733       removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
734                                         latest_any_var, scratch_pool);
735       removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
736                                         latest_any_var, scratch_pool);
737
738       /* Trim the tree as much as possible to speed up lookup(). */
739       if (removed_all)
740         node->pattern_sub_nodes = NULL;
741     }
742
743   return removed_all;
744 }
745
746 /* Forward declaration ... */
747 static void
748 finalize_tree(node_t *node,
749               limited_rights_t *sum,
750               apr_pool_t *scratch_pool);
751
752 /* Call finalize_tree() on all elements in the HASH of node_t *, passing
753  * SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
754  */
755 static void
756 finalize_subnode_hash(apr_hash_t *hash,
757                       limited_rights_t *sum,
758                       apr_pool_t *scratch_pool)
759 {
760   if (hash)
761     {
762       apr_hash_index_t *hi;
763       for (hi = apr_hash_first(scratch_pool, hash);
764            hi;
765            hi = apr_hash_next(hi))
766         finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
767     }
768 }
769
770 /* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
771  * passing SUM along.  ARRAY may be NULL.  Use SCRATCH_POOL for temporary
772  * allocations.
773  */
774 static void
775 finalize_subnode_array(apr_array_header_t *array,
776                        limited_rights_t *sum,
777                        apr_pool_t *scratch_pool)
778 {
779   if (array)
780     {
781       int i;
782       for (i = 0; i < array->nelts; ++i)
783         finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
784                       scratch_pool);
785     }
786 }
787
788 /* Link prefixes within the sorted ARRAY. */
789 static void
790 link_prefix_patterns(apr_array_header_t *array)
791 {
792   int i;
793   if (!array)
794     return;
795
796   for (i = 1; i < array->nelts; ++i)
797     {
798       sorted_pattern_t *prev
799         = &APR_ARRAY_IDX(array, i - 1, sorted_pattern_t);
800       sorted_pattern_t *pattern
801         = &APR_ARRAY_IDX(array, i, sorted_pattern_t);
802
803       /* Does PATTERN potentially have a prefix in ARRAY?
804        * If so, at least the first char must match with the predecessor's
805        * because the array is sorted by that string. */
806       if (prev->node->segment.data[0] != pattern->node->segment.data[0])
807         continue;
808
809       /* Only the predecessor or any of its prefixes can be the closest
810        * prefix to PATTERN. */
811       for ( ; prev; prev = prev->next)
812         if (   prev->node->segment.len < pattern->node->segment.len
813             && !memcmp(prev->node->segment.data,
814                        pattern->node->segment.data,
815                        prev->node->segment.len))
816           {
817             pattern->next = prev;
818             break;
819           }
820     }
821 }
822
823 /* Recursively finalization the tree node properties for NODE.  Update SUM
824  * (of NODE's parent) by combining it with the recursive access rights info
825  * on NODE.  Use SCRATCH_POOL for temporary allocations.
826  */
827 static void
828 finalize_tree(node_t *node,
829               limited_rights_t *sum,
830               apr_pool_t *scratch_pool)
831 {
832   limited_rights_t *local_sum = &node->rights;
833
834   /* For convenience, we allow NODE to be NULL: */
835   if (!node)
836     return;
837
838   /* Sum of rights at NODE - so far. */
839   if (has_local_rule(local_sum))
840     {
841       local_sum->max_rights = local_sum->access.rights;
842       local_sum->min_rights = local_sum->access.rights;
843     }
844   else
845     {
846       local_sum->min_rights = authz_access_write;
847       local_sum->max_rights = authz_access_none;
848     }
849
850   /* Process all sub-nodes. */
851   finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
852
853   if (node->pattern_sub_nodes)
854     {
855       finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
856       finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
857
858       finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
859                              scratch_pool);
860       finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
861                              scratch_pool);
862       finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
863                              scratch_pool);
864
865       /* Link up the prefixes / suffixes. */
866       link_prefix_patterns(node->pattern_sub_nodes->prefixes);
867       link_prefix_patterns(node->pattern_sub_nodes->suffixes);
868     }
869
870   /* Add our min / max info to the parent's info.
871    * Idempotent for parent == node (happens at root). */
872   combine_right_limits(sum, local_sum);
873 }
874
875 /* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
876  * Return the filtered rule tree.
877  */
878 static node_t *
879 create_user_authz(authz_full_t *authz,
880                   const char *repository,
881                   const char *user,
882                   apr_pool_t *result_pool,
883                   apr_pool_t *scratch_pool)
884 {
885   int i;
886   node_t *root = create_node(NULL, result_pool);
887   construction_context_t *ctx = create_construction_context(scratch_pool);
888
889   /* Use a separate sub-pool to keep memory usage tight. */
890   apr_pool_t *subpool = svn_pool_create(scratch_pool);
891
892   /* Find all ACLs for REPOSITORY.
893    * Note that repo-specific rules replace global rules,
894    * even if they don't apply to the current user. */
895   apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts,
896                                             sizeof(authz_acl_t *));
897   for (i = 0; i < authz->acls->nelts; ++i)
898     {
899       const authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
900       if (svn_authz__acl_applies_to_repo(acl, repository))
901         {
902           /* ACLs in the AUTHZ are sorted by path and repository.
903            * So, if there is a rule for the repo and a global rule for the
904            * same path, we will detect them here. */
905           if (acls->nelts)
906             {
907               const authz_acl_t *prev_acl
908                 = APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *);
909               if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0)
910                 {
911                   SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos,
912                                                    AUTHZ_ANY_REPOSITORY));
913                   SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos,
914                                                   AUTHZ_ANY_REPOSITORY));
915                   apr_array_pop(acls);
916                 }
917             }
918
919           APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
920         }
921     }
922
923   /* Filtering and tree construction. */
924   for (i = 0; i < acls->nelts; ++i)
925     process_acl(ctx, APR_ARRAY_IDX(acls, i, const authz_acl_t *),
926                 root, repository, user, result_pool, subpool);
927
928   /* If there is no relevant rule at the root node, the "no access" default
929    * applies. Give it a SEQUENCE_NUMBER that will never overrule others. */
930   if (!has_local_rule(&root->rights))
931     {
932       root->rights.access.sequence_number = 0;
933       root->rights.access.rights = authz_access_none;
934     }
935
936   /* Trim the tree.
937    *
938    * We can't do pattern comparison, so for most pattern rules we cannot
939    * say that a set of rules "eclipses" / overrides a given other set of
940    * rules for all possible paths.  That limits the accuracy of our check
941    * for recursive access in similar ways than for non-pattern rules.
942    *
943    * However, the user expects a rule ending with "**" to eclipse any older
944    * rule in that sub-tree recursively.  So, this trim function removes
945    * eclipsed nodes from the tree.
946    */
947   svn_pool_clear(subpool);
948   trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
949
950   /* Calculate recursive rights.
951    *
952    * This is a bottom-up calculation of the range of access rights
953    * specified anywhere in  the respective sub-tree, including the base
954    * node itself.
955    *
956    * To prevent additional finalization passes, we piggy-back the addition
957    * of the ordering links of the prefix and suffix sub-node rules.
958    */
959   svn_pool_clear(subpool);
960   finalize_tree(root, &root->rights, subpool);
961
962   /* Done. */
963   svn_pool_destroy(subpool);
964   return root;
965 }
966
967 \f
968 /*** Lookup. ***/
969
970 /* Reusable lookup state object. It is easy to pass to functions and
971  * recycling it between lookups saves significant setup costs. */
972 typedef struct lookup_state_t
973 {
974   /* Rights immediately applying to this node and limits to the rights to
975    * any sub-path. */
976   limited_rights_t rights;
977
978   /* Nodes applying to the path followed so far. */
979   apr_array_header_t *current;
980
981   /* Temporary array containing the nodes applying to the next path
982    * segment (used to build up the next contents of CURRENT). */
983   apr_array_header_t *next;
984
985   /* Scratch pad for path operations. */
986   svn_stringbuf_t *scratch_pad;
987
988   /* After each lookup iteration, CURRENT and PARENT_RIGHTS will
989    * apply to this path. */
990   svn_stringbuf_t *parent_path;
991
992   /* Rights that apply at PARENT_PATH, if PARENT_PATH is not empty. */
993   limited_rights_t parent_rights;
994
995 } lookup_state_t;
996
997 /* Constructor for lookup_state_t. */
998 static lookup_state_t *
999 create_lookup_state(apr_pool_t *result_pool)
1000 {
1001   lookup_state_t *state = apr_pcalloc(result_pool, sizeof(*state));
1002
1003   state->next = apr_array_make(result_pool, 4, sizeof(node_t *));
1004   state->current = apr_array_make(result_pool, 4, sizeof(node_t *));
1005
1006   /* Virtually all path segments should fit into this buffer.  If they
1007    * don't, the buffer gets automatically reallocated.
1008    *
1009    * Using a smaller initial size would be fine as well but does not
1010    * buy us much for the increased risk of being expanded anyway - at
1011    * some extra cost. */
1012   state->scratch_pad = svn_stringbuf_create_ensure(200, result_pool);
1013
1014   /* Most paths should fit into this buffer.  The same rationale as
1015    * above applies. */
1016   state->parent_path = svn_stringbuf_create_ensure(200, result_pool);
1017
1018   return state;
1019 }
1020
1021 /* Clear the current contents of STATE and re-initialize it for ROOT.
1022  * Check whether we can reuse a previous parent path lookup to shorten
1023  * the current PATH walk.  Return the full or remaining portion of
1024  * PATH, respectively.  PATH must not be NULL. */
1025 static const char *
1026 init_lockup_state(lookup_state_t *state,
1027                   node_t *root,
1028                   const char *path)
1029 {
1030   apr_size_t len = strlen(path);
1031   if (   (len > state->parent_path->len)
1032       && state->parent_path->len
1033       && (path[state->parent_path->len] == '/')
1034       && !memcmp(path, state->parent_path->data, state->parent_path->len))
1035     {
1036       /* The PARENT_PATH of the previous lookup is actually a parent path
1037        * of PATH.  The CURRENT node list already matches the parent path
1038        * and we only have to set the correct rights info. */
1039       state->rights = state->parent_rights;
1040
1041       /* Tell the caller where to proceed. */
1042       return path + state->parent_path->len;
1043     }
1044
1045   /* Start lookup at ROOT for the full PATH. */
1046   state->rights = root->rights;
1047   state->parent_rights = root->rights;
1048
1049   apr_array_clear(state->next);
1050   apr_array_clear(state->current);
1051   APR_ARRAY_PUSH(state->current, node_t *) = root;
1052
1053   /* Var-segment rules match empty segments as well */
1054   if (root->pattern_sub_nodes && root->pattern_sub_nodes->any_var)
1055    {
1056       node_t *node = root->pattern_sub_nodes->any_var;
1057
1058       /* This is non-recursive due to ACL normalization. */
1059       combine_access(&state->rights, &node->rights);
1060       combine_right_limits(&state->rights, &node->rights);
1061       APR_ARRAY_PUSH(state->current, node_t *) = node;
1062    }
1063
1064   svn_stringbuf_setempty(state->parent_path);
1065   svn_stringbuf_setempty(state->scratch_pad);
1066
1067   return path;
1068 }
1069
1070 /* Add NODE to the list of NEXT nodes in STATE.  NODE may be NULL in which
1071  * case this is a no-op.  Also update and aggregate the access rights data
1072  * for the next path segment.
1073  */
1074 static void
1075 add_next_node(lookup_state_t *state,
1076               node_t *node)
1077 {
1078   /* Allowing NULL nodes simplifies the caller. */
1079   if (node)
1080     {
1081       /* The rule with the highest sequence number is the one that applies.
1082        * Not all nodes that we are following have rules that apply directly
1083        * to this path but are mere intermediates that may only have some
1084        * matching deep sub-node. */
1085       combine_access(&state->rights, &node->rights);
1086
1087       /* The rule tree node can be seen as an overlay of all the nodes that
1088        * we are following.  Any of them _may_ match eventually, so the min/
1089        * max possible access rights are a combination of all these sub-trees.
1090        */
1091       combine_right_limits(&state->rights, &node->rights);
1092
1093       /* NODE is now enlisted as a (potential) match for the next segment. */
1094       APR_ARRAY_PUSH(state->next, node_t *) = node;
1095
1096       /* Variable length sub-segment sequences apply to the same node as
1097        * they match empty sequences as well. */
1098       if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
1099         {
1100           node = node->pattern_sub_nodes->any_var;
1101
1102           /* This is non-recursive due to ACL normalization. */
1103           combine_access(&state->rights, &node->rights);
1104           combine_right_limits(&state->rights, &node->rights);
1105           APR_ARRAY_PUSH(state->next, node_t *) = node;
1106         }
1107     }
1108 }
1109
1110 /* If PREFIX is indeed a prefix (or exact match) or SEGMENT, add the
1111  * node in PREFIX to STATE. */
1112 static void
1113 add_if_prefix_matches(lookup_state_t *state,
1114                       const sorted_pattern_t *prefix,
1115                       const svn_stringbuf_t *segment)
1116 {
1117   node_t *node = prefix->node;
1118   if (   node->segment.len <= segment->len
1119       && !memcmp(node->segment.data, segment->data, node->segment.len))
1120     add_next_node(state, node);
1121 }
1122
1123 /* Scan the PREFIXES array of node_t* for all entries whose SEGMENT members
1124  * are prefixes of SEGMENT.  Add these to STATE for the next tree level. */
1125 static void
1126 add_prefix_matches(lookup_state_t *state,
1127                    const svn_stringbuf_t *segment,
1128                    apr_array_header_t *prefixes)
1129 {
1130   /* Index of the first node that might be a match.  All matches will
1131    * be at this and the immediately following indexes. */
1132   int i = svn_sort__bsearch_lower_bound(prefixes, segment->data,
1133                                         compare_node_path_segment);
1134
1135   /* The entry we found may be an exact match (but not a true prefix).
1136    * The prefix matching test will still work. */
1137   if (i < prefixes->nelts)
1138     add_if_prefix_matches(state,
1139                           &APR_ARRAY_IDX(prefixes, i, sorted_pattern_t),
1140                           segment);
1141
1142   /* The immediate predecessor may be a true prefix and all potential
1143    * prefixes can be found following the NEXT links between the array
1144    * indexes. */
1145   if (i > 0)
1146     {
1147       sorted_pattern_t *pattern;
1148       for (pattern = &APR_ARRAY_IDX(prefixes, i - 1, sorted_pattern_t);
1149            pattern;
1150            pattern = pattern->next)
1151         {
1152           add_if_prefix_matches(state, pattern, segment);
1153         }
1154     }
1155 }
1156
1157 /* Scan the PATTERNS array of node_t* for all entries whose SEGMENT members
1158  * (usually containing wildcards) match SEGMENT.  Add these to STATE for the
1159  * next tree level. */
1160 static void
1161 add_complex_matches(lookup_state_t *state,
1162                     const svn_stringbuf_t *segment,
1163                     apr_array_header_t *patterns)
1164 {
1165   int i;
1166   for (i = 0; i < patterns->nelts; ++i)
1167     {
1168       node_t *node = APR_ARRAY_IDX(patterns, i, sorted_pattern_t).node;
1169       if (0 == apr_fnmatch(node->segment.data, segment->data, 0))
1170         add_next_node(state, node);
1171     }
1172 }
1173
1174 /* Extract the next segment from PATH and copy it into SEGMENT, whose current
1175  * contents get overwritten.  Empty paths ("") are supported and leading '/'
1176  * segment separators will be interpreted as an empty segment ("").  Non-
1177  * normalizes parts, i.e. sequences of '/', will be treated as a single '/'.
1178  *
1179  * Return the start of the next segment within PATH, skipping the '/'
1180  * separator(s).  Return NULL, if there are no further segments.
1181  *
1182  * The caller (only called by lookup(), ATM) must ensure that SEGMENT has
1183  * enough room to store all of PATH.
1184  */
1185 static const char *
1186 next_segment(svn_stringbuf_t *segment,
1187              const char *path)
1188 {
1189   apr_size_t len;
1190   char c;
1191
1192   /* Read and scan PATH for NUL and '/' -- whichever comes first. */
1193   for (len = 0, c = *path; c; c = path[++len])
1194     if (c == '/')
1195       {
1196         /* End of segment. */
1197         segment->data[len] = 0;
1198         segment->len = len;
1199
1200         /* If PATH is not normalized, this is where we skip whole sequences
1201          * of separators. */
1202         while (path[++len] == '/')
1203           ;
1204
1205         /* Continue behind the last separator in the sequence.  We will
1206          * treat trailing '/' as indicating an empty trailing segment.
1207          * Therefore, we never have to return NULL here. */
1208         return path + len;
1209       }
1210     else
1211       {
1212         /* Copy segment contents directly into the result buffer.
1213          * On many architectures, this is almost or entirely for free. */
1214         segment->data[len] = c;
1215       }
1216
1217   /* No separator found, so all of PATH has been the last segment. */
1218   segment->data[len] = 0;
1219   segment->len = len;
1220
1221   /* Tell the caller that this has been the last segment. */
1222   return NULL;
1223 }
1224
1225 /* Starting at the respective user's authz root node provided with STATE,
1226  * follow PATH and return TRUE, iff the REQUIRED access has been granted to
1227  * that user for this PATH.  REQUIRED must not contain svn_authz_recursive.
1228  * If RECURSIVE is set, all paths in the sub-tree at and below PATH must
1229  * have REQUIRED access.  PATH does not need to be normalized, may be empty
1230  * but must not be NULL.
1231  */
1232 static svn_boolean_t
1233 lookup(lookup_state_t *state,
1234        const char *path,
1235        authz_access_t required,
1236        svn_boolean_t recursive,
1237        apr_pool_t *scratch_pool)
1238 {
1239   /* Create a scratch pad large enough to hold any of PATH's segments. */
1240   apr_size_t path_len = strlen(path);
1241   svn_stringbuf_ensure(state->scratch_pad, path_len);
1242
1243   /* Normalize start and end of PATH.  Most paths will be fully normalized,
1244    * so keep the overhead as low as possible. */
1245   if (path_len && path[path_len-1] == '/')
1246     {
1247       do
1248       {
1249         --path_len;
1250       }
1251       while (path_len && path[path_len-1] == '/');
1252       path = apr_pstrmemdup(scratch_pool, path, path_len);
1253     }
1254
1255   while (path[0] == '/')
1256     ++path;     /* Don't update PATH_LEN as we won't need it anymore. */
1257
1258   /* Actually walk the path rule tree following PATH until we run out of
1259    * either tree or PATH. */
1260   while (state->current->nelts && path)
1261     {
1262       apr_array_header_t *temp;
1263       int i;
1264       svn_stringbuf_t *segment = state->scratch_pad;
1265
1266       /* Shortcut 1: We could nowhere find enough rights in this sub-tree. */
1267       if ((state->rights.max_rights & required) != required)
1268         return FALSE;
1269
1270       /* Shortcut 2: We will find enough rights everywhere in this sub-tree. */
1271       if ((state->rights.min_rights & required) == required)
1272         return TRUE;
1273
1274       /* Extract the next segment. */
1275       path = next_segment(segment, path);
1276
1277       /* Initial state for this segment. */
1278       apr_array_clear(state->next);
1279       state->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
1280       state->rights.access.rights = authz_access_none;
1281
1282       /* These init values ensure that the first node's value will be used
1283        * when combined with them.  If there is no first node,
1284        * state->access.sequence_number remains unchanged and we will use
1285        * the parent's (i.e. inherited) access rights. */
1286       state->rights.min_rights = authz_access_write;
1287       state->rights.max_rights = authz_access_none;
1288
1289       /* Update the PARENT_PATH member in STATE to match the nodes in
1290        * CURRENT at the end of this iteration, i.e. if and when NEXT
1291        * has become CURRENT. */
1292       if (path)
1293         {
1294           svn_stringbuf_appendbyte(state->parent_path, '/');
1295           svn_stringbuf_appendbytes(state->parent_path, segment->data,
1296                                     segment->len);
1297         }
1298
1299       /* Scan follow all alternative routes to the next level. */
1300       for (i = 0; i < state->current->nelts; ++i)
1301         {
1302           node_t *node = APR_ARRAY_IDX(state->current, i, node_t *);
1303           if (node->sub_nodes)
1304             add_next_node(state, apr_hash_get(node->sub_nodes, segment->data,
1305                                               segment->len));
1306
1307           /* Process alternative, wildcard-based sub-nodes. */
1308           if (node->pattern_sub_nodes)
1309             {
1310               add_next_node(state, node->pattern_sub_nodes->any);
1311
1312               /* If the current node represents a "**" pattern, it matches
1313                * to all levels. So, add it to the list for the NEXT level. */
1314               if (node->pattern_sub_nodes->repeat)
1315                 add_next_node(state, node);
1316
1317               /* Find all prefix pattern matches. */
1318               if (node->pattern_sub_nodes->prefixes)
1319                 add_prefix_matches(state, segment,
1320                                    node->pattern_sub_nodes->prefixes);
1321
1322               if (node->pattern_sub_nodes->complex)
1323                 add_complex_matches(state, segment,
1324                                     node->pattern_sub_nodes->complex);
1325
1326               /* Find all suffux pattern matches.
1327                * This must be the last check as it destroys SEGMENT. */
1328               if (node->pattern_sub_nodes->suffixes)
1329                 {
1330                   /* Suffixes behave like reversed prefixes. */
1331                   svn_authz__reverse_string(segment->data, segment->len);
1332                   add_prefix_matches(state, segment,
1333                                      node->pattern_sub_nodes->suffixes);
1334                 }
1335             }
1336         }
1337
1338       /* If no rule applied to this SEGMENT directly, the parent rights
1339        * will apply to at least the SEGMENT node itself and possibly
1340        * other parts deeper in it's subtree. */
1341       if (!has_local_rule(&state->rights))
1342         {
1343           state->rights.access = state->parent_rights.access;
1344           state->rights.min_rights &= state->parent_rights.access.rights;
1345           state->rights.max_rights |= state->parent_rights.access.rights;
1346         }
1347
1348       /* The list of nodes for SEGMENT is now complete.  If we need to
1349        * continue, make it the current and put the old one into the recycler.
1350        *
1351        * If this is the end of the path, keep the parent path and rights in
1352        * STATE as are such that sibling lookups will benefit from it.
1353        */
1354       if (path)
1355         {
1356           temp = state->current;
1357           state->current = state->next;
1358           state->next = temp;
1359
1360           /* In STATE, PARENT_PATH, PARENT_RIGHTS and CURRENT are now in sync. */
1361           state->parent_rights = state->rights;
1362         }
1363     }
1364
1365   /* If we check recursively, none of the (potential) sub-paths must have
1366    * less than the REQUIRED access rights.  "Potential" because we don't
1367    * verify that the respective paths actually exist in the repository.
1368    */
1369   if (recursive)
1370     return (state->rights.min_rights & required) == required;
1371
1372   /* Return whether the access rights on PATH fully include REQUIRED. */
1373   return (state->rights.access.rights & required) == required;
1374 }
1375
1376 \f
1377
1378 /*** The authz data structure. ***/
1379
1380 /* An entry in svn_authz_t's USER_RULES cache.  All members must be
1381  * allocated in the POOL and the latter has to be cleared / destroyed
1382  * before overwriting the entries' contents.
1383  */
1384 struct authz_user_rules_t
1385 {
1386   /* User name for which we filtered the rules.
1387    * User NULL for the anonymous user. */
1388   const char *user;
1389
1390   /* Repository name for which we filtered the rules.
1391    * May be empty but never NULL for used entries. */
1392   const char *repository;
1393
1394   /* The combined min/max rights USER has on REPOSITORY. */
1395   authz_rights_t global_rights;
1396
1397   /* Root of the filtered path rule tree.
1398    * Will remain NULL until the first usage. */
1399   node_t *root;
1400
1401   /* Reusable lookup state instance. */
1402   lookup_state_t *lookup_state;
1403
1404   /* Pool from which all data within this struct got allocated.
1405    * Can be destroyed or cleaned up with no further side-effects. */
1406   apr_pool_t *pool;
1407 };
1408
1409 /* Return TRUE, iff AUTHZ matches the pair of REPOS_NAME and USER.
1410  * Note that USER may be NULL.
1411  */
1412 static svn_boolean_t
1413 matches_filtered_tree(const authz_user_rules_t *authz,
1414                       const char *repos_name,
1415                       const char *user)
1416 {
1417   /* Does the user match? */
1418   if (user)
1419     {
1420       if (authz->user == NULL || strcmp(user, authz->user))
1421         return FALSE;
1422     }
1423   else if (authz->user != NULL)
1424     return FALSE;
1425
1426   /* Does the repository match as well? */
1427   return strcmp(repos_name, authz->repository) == 0;
1428 }
1429
1430 /* Check if AUTHZ's already contains a path rule tree filtered for this
1431  * USER, REPOS_NAME combination.  If that does not exist, yet, create one
1432  * but don't construct the actual filtered tree, yet.
1433  */
1434 static authz_user_rules_t *
1435 get_user_rules(svn_authz_t *authz,
1436                const char *repos_name,
1437                const char *user)
1438 {
1439   apr_pool_t *pool;
1440
1441   /* Search our cache for a suitable previously filtered tree. */
1442   if (authz->filtered)
1443     {
1444       /* Is this a suitable filtered tree? */
1445       if (matches_filtered_tree(authz->filtered, repos_name, user))
1446         return authz->filtered;
1447
1448       /* Drop the old filtered tree before creating a new one. */
1449       svn_pool_destroy(authz->filtered->pool);
1450       authz->filtered = NULL;
1451     }
1452
1453   /* Global cache lookup.  Filter the full model only if necessary. */
1454   pool = svn_pool_create(authz->pool);
1455
1456   /* Write a new entry. */
1457   authz->filtered = apr_palloc(pool, sizeof(*authz->filtered));
1458   authz->filtered->pool = pool;
1459   authz->filtered->repository = apr_pstrdup(pool, repos_name);
1460   authz->filtered->user = user ? apr_pstrdup(pool, user) : NULL;
1461   authz->filtered->lookup_state = create_lookup_state(pool);
1462   authz->filtered->root = NULL;
1463
1464   svn_authz__get_global_rights(&authz->filtered->global_rights,
1465                                authz->full, user, repos_name);
1466
1467   return authz->filtered;
1468 }
1469
1470 /* In AUTHZ's user rules, construct the actual filtered tree.
1471  * Use SCRATCH_POOL for temporary allocations.
1472  */
1473 static svn_error_t *
1474 filter_tree(svn_authz_t *authz,
1475             apr_pool_t *scratch_pool)
1476 {
1477   apr_pool_t *pool = authz->filtered->pool;
1478   const char *repos_name = authz->filtered->repository;
1479   const char *user = authz->filtered->user;
1480   node_t *root;
1481
1482   if (filtered_pool)
1483     {
1484       svn_membuf_t *key = construct_filtered_key(repos_name, user,
1485                                                  authz->authz_id,
1486                                                  scratch_pool);
1487
1488       /* Cache lookup. */
1489       SVN_ERR(svn_object_pool__lookup((void **)&root, filtered_pool, key,
1490                                       pool));
1491
1492       if (!root)
1493         {
1494           apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
1495           authz_full_t *add_ref = NULL;
1496
1497           /* Make sure the underlying full authz object lives as long as the
1498            * filtered one that we are about to create.  We do this by adding
1499            * a reference to it in ITEM_POOL (which may live longer than AUTHZ).
1500            *
1501            * Note that we already have a reference to that full authz in
1502            * AUTHZ->FULL. Assert that we actually don't created multiple
1503            * instances of the same full model.
1504            */
1505           svn_error_clear(svn_object_pool__lookup((void **)&add_ref,
1506                                                   authz_pool, authz->authz_id,
1507                                                   item_pool));
1508           SVN_ERR_ASSERT(add_ref == authz->full);
1509
1510           /* Now construct the new filtered tree and cache it. */
1511           root = create_user_authz(authz->full, repos_name, user, item_pool,
1512                                    scratch_pool);
1513           svn_error_clear(svn_object_pool__insert((void **)&root,
1514                                                   filtered_pool, key, root,
1515                                                   item_pool, pool));
1516         }
1517      }
1518   else
1519     {
1520       root = create_user_authz(authz->full, repos_name, user, pool,
1521                                scratch_pool);
1522     }
1523
1524   /* Write a new entry. */
1525   authz->filtered->root = root;
1526
1527   return SVN_NO_ERROR;
1528 }
1529
1530 \f
1531
1532 /* Read authz configuration data from PATH into *AUTHZ_P, allocated in
1533    RESULT_POOL.  Return the cache key in *AUTHZ_ID.  If GROUPS_PATH is set,
1534    use the global groups parsed from it.  Use SCRATCH_POOL for temporary
1535    allocations.
1536
1537    PATH and GROUPS_PATH may be a dirent or an absolute file url.  REPOS_HINT
1538    may be specified to speed up access to in-repo authz files.
1539
1540    If PATH or GROUPS_PATH is not a valid authz rule file, then return
1541    SVN_AUTHZ_INVALID_CONFIG.  The contents of *AUTHZ_P is then
1542    undefined.  If MUST_EXIST is TRUE, a missing authz or global groups file
1543    is also an error. */
1544 static svn_error_t *
1545 authz_read(authz_full_t **authz_p,
1546            svn_membuf_t **authz_id,
1547            const char *path,
1548            const char *groups_path,
1549            svn_boolean_t must_exist,
1550            svn_repos_t *repos_hint,
1551            svn_repos_authz_warning_func_t warning_func,
1552            void *warning_baton,
1553            apr_pool_t *result_pool,
1554            apr_pool_t *scratch_pool)
1555 {
1556   svn_error_t* err = NULL;
1557   svn_stream_t *rules_stream = NULL;
1558   svn_stream_t *groups_stream = NULL;
1559   svn_checksum_t *rules_checksum = NULL;
1560   svn_checksum_t *groups_checksum = NULL;
1561
1562   config_access_t *config_access =
1563     svn_repos__create_config_access(repos_hint, scratch_pool);
1564
1565   /* Open the main authz file */
1566   SVN_ERR(svn_repos__get_config(&rules_stream, &rules_checksum, config_access,
1567                                 path, must_exist, scratch_pool));
1568
1569   /* Open the optional groups file */
1570   if (groups_path)
1571     SVN_ERR(svn_repos__get_config(&groups_stream, &groups_checksum,
1572                                   config_access, groups_path, must_exist,
1573                                   scratch_pool));
1574
1575   /* The authz cache is optional. */
1576   *authz_id = construct_authz_key(rules_checksum, groups_checksum,
1577                                   result_pool);
1578   if (authz_pool)
1579     {
1580       /* Cache lookup. */
1581       SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool,
1582                                       *authz_id, result_pool));
1583
1584       /* If not found, parse and add to cache. */
1585       if (!*authz_p)
1586         {
1587           apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
1588
1589           /* Parse the configuration(s) and construct the full authz model
1590            * from it. */
1591           err = svn_authz__parse(authz_p, rules_stream, groups_stream,
1592                                  warning_func, warning_baton,
1593                                  item_pool, scratch_pool);
1594           if (err != SVN_NO_ERROR)
1595             {
1596               /* That pool would otherwise never get destroyed. */
1597               svn_pool_destroy(item_pool);
1598
1599               /* Add the URL / file name to the error stack since the parser
1600                * doesn't have it. */
1601               err = svn_error_quick_wrapf(err,
1602                                    "Error while parsing config file: '%s':",
1603                                    path);
1604             }
1605           else
1606             {
1607               SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool,
1608                                               *authz_id, *authz_p,
1609                                               item_pool, result_pool));
1610             }
1611         }
1612     }
1613   else
1614     {
1615       /* Parse the configuration(s) and construct the full authz model from
1616        * it. */
1617       err = svn_error_quick_wrapf(
1618           svn_authz__parse(authz_p, rules_stream, groups_stream,
1619                            warning_func, warning_baton,
1620                            result_pool, scratch_pool),
1621           "Error while parsing authz file: '%s':", path);
1622     }
1623
1624   svn_repos__destroy_config_access(config_access);
1625
1626   return err;
1627 }
1628
1629
1630 \f
1631 /*** Public functions. ***/
1632
1633 svn_error_t *
1634 svn_repos_authz_read4(svn_authz_t **authz_p,
1635                       const char *path,
1636                       const char *groups_path,
1637                       svn_boolean_t must_exist,
1638                       svn_repos_t *repos_hint,
1639                       svn_repos_authz_warning_func_t warning_func,
1640                       void *warning_baton,
1641                       apr_pool_t *result_pool,
1642                       apr_pool_t *scratch_pool)
1643 {
1644   svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
1645   authz->pool = result_pool;
1646
1647   SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path,
1648                      must_exist, repos_hint, warning_func, warning_baton,
1649                      result_pool, scratch_pool));
1650
1651   *authz_p = authz;
1652   return SVN_NO_ERROR;
1653 }
1654
1655
1656 svn_error_t *
1657 svn_repos_authz_parse2(svn_authz_t **authz_p,
1658                        svn_stream_t *stream,
1659                        svn_stream_t *groups_stream,
1660                        svn_repos_authz_warning_func_t warning_func,
1661                        void *warning_baton,
1662                        apr_pool_t *result_pool,
1663                        apr_pool_t *scratch_pool)
1664 {
1665   svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
1666   authz->pool = result_pool;
1667
1668   /* Parse the configuration and construct the full authz model from it. */
1669   SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream,
1670                            warning_func, warning_baton,
1671                            result_pool, scratch_pool));
1672
1673   *authz_p = authz;
1674   return SVN_NO_ERROR;
1675 }
1676
1677 svn_error_t *
1678 svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
1679                              const char *path, const char *user,
1680                              svn_repos_authz_access_t required_access,
1681                              svn_boolean_t *access_granted,
1682                              apr_pool_t *pool)
1683 {
1684   const authz_access_t required =
1685     ((required_access & svn_authz_read ? authz_access_read_flag : 0)
1686      | (required_access & svn_authz_write ? authz_access_write_flag : 0));
1687
1688   /* Pick or create the suitable pre-filtered path rule tree. */
1689   authz_user_rules_t *rules = get_user_rules(
1690       authz,
1691       (repos_name ? repos_name : AUTHZ_ANY_REPOSITORY),
1692       user);
1693
1694   /* In many scenarios, users have uniform access to a repository
1695    * (blanket access or no access at all).
1696    *
1697    * In these cases, don't bother creating or consulting the filtered tree.
1698    */
1699   if ((rules->global_rights.min_access & required) == required)
1700     {
1701       *access_granted = TRUE;
1702       return SVN_NO_ERROR;
1703     }
1704
1705   if ((rules->global_rights.max_access & required) != required)
1706     {
1707       *access_granted = FALSE;
1708       return SVN_NO_ERROR;
1709     }
1710
1711   /* No specific path given, i.e. looking for anywhere in the tree? */
1712   if (!path)
1713     {
1714       *access_granted =
1715         ((rules->global_rights.max_access & required) == required);
1716       return SVN_NO_ERROR;
1717     }
1718
1719   /* Rules tree lookup */
1720
1721   /* Did we already filter the data model? */
1722   if (!rules->root)
1723     SVN_ERR(filter_tree(authz, pool));
1724
1725   /* Re-use previous lookup results, if possible. */
1726   path = init_lockup_state(authz->filtered->lookup_state,
1727                            authz->filtered->root, path);
1728
1729   /* Sanity check. */
1730   SVN_ERR_ASSERT(path[0] == '/');
1731
1732   /* Determine the granted access for the requested path.
1733    * PATH does not need to be normalized for lockup(). */
1734   *access_granted = lookup(rules->lookup_state, path, required,
1735                            !!(required_access & svn_authz_recursive), pool);
1736
1737   return SVN_NO_ERROR;
1738 }