]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/authz.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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
29 #include "svn_hash.h"
30 #include "svn_pools.h"
31 #include "svn_error.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_path.h"
34 #include "svn_repos.h"
35 #include "svn_config.h"
36 #include "svn_ctype.h"
37 #include "private/svn_fspath.h"
38 #include "private/svn_repos_private.h"
39 #include "repos.h"
40
41 \f
42 /*** Structures. ***/
43
44 /* Information for the config enumerators called during authz
45    lookup. */
46 struct authz_lookup_baton {
47   /* The authz configuration. */
48   svn_config_t *config;
49
50   /* The user to authorize. */
51   const char *user;
52
53   /* Explicitly granted rights. */
54   svn_repos_authz_access_t allow;
55   /* Explicitly denied rights. */
56   svn_repos_authz_access_t deny;
57
58   /* The rights required by the caller of the lookup. */
59   svn_repos_authz_access_t required_access;
60
61   /* The following are used exclusively in recursive lookups. */
62
63   /* The path in the repository (an fspath) to authorize. */
64   const char *repos_path;
65   /* repos_path prefixed by the repository name and a colon. */
66   const char *qualified_repos_path;
67
68   /* Whether, at the end of a recursive lookup, access is granted. */
69   svn_boolean_t access;
70 };
71
72 /* Information for the config enumeration functions called during the
73    validation process. */
74 struct authz_validate_baton {
75   svn_config_t *config; /* The configuration file being validated. */
76   svn_error_t *err;     /* The error being thrown out of the
77                            enumerator, if any. */
78 };
79
80 /* Currently this structure is just a wrapper around a svn_config_t.
81    Please update authz_pool if you modify this structure. */
82 struct svn_authz_t
83 {
84   svn_config_t *cfg;
85 };
86
87
88 \f
89 /*** Checking access. ***/
90
91 /* Determine whether the REQUIRED access is granted given what authz
92  * to ALLOW or DENY.  Return TRUE if the REQUIRED access is
93  * granted.
94  *
95  * Access is granted either when no required access is explicitly
96  * denied (implicit grant), or when the required access is explicitly
97  * granted, overriding any denials.
98  */
99 static svn_boolean_t
100 authz_access_is_granted(svn_repos_authz_access_t allow,
101                         svn_repos_authz_access_t deny,
102                         svn_repos_authz_access_t required)
103 {
104   svn_repos_authz_access_t stripped_req =
105     required & (svn_authz_read | svn_authz_write);
106
107   if ((deny & required) == svn_authz_none)
108     return TRUE;
109   else if ((allow & required) == stripped_req)
110     return TRUE;
111   else
112     return FALSE;
113 }
114
115
116 /* Decide whether the REQUIRED access has been conclusively
117  * determined.  Return TRUE if the given ALLOW/DENY authz are
118  * conclusive regarding the REQUIRED authz.
119  *
120  * Conclusive determination occurs when any of the REQUIRED authz are
121  * granted or denied by ALLOW/DENY.
122  */
123 static svn_boolean_t
124 authz_access_is_determined(svn_repos_authz_access_t allow,
125                            svn_repos_authz_access_t deny,
126                            svn_repos_authz_access_t required)
127 {
128   if ((deny & required) || (allow & required))
129     return TRUE;
130   else
131     return FALSE;
132 }
133
134 /* Return TRUE is USER equals ALIAS. The alias definitions are in the
135    "aliases" sections of CFG. Use POOL for temporary allocations during
136    the lookup. */
137 static svn_boolean_t
138 authz_alias_is_user(svn_config_t *cfg,
139                     const char *alias,
140                     const char *user,
141                     apr_pool_t *pool)
142 {
143   const char *value;
144
145   svn_config_get(cfg, &value, "aliases", alias, NULL);
146   if (!value)
147     return FALSE;
148
149   if (strcmp(value, user) == 0)
150     return TRUE;
151
152   return FALSE;
153 }
154
155
156 /* Return TRUE if USER is in GROUP.  The group definitions are in the
157    "groups" section of CFG.  Use POOL for temporary allocations during
158    the lookup. */
159 static svn_boolean_t
160 authz_group_contains_user(svn_config_t *cfg,
161                           const char *group,
162                           const char *user,
163                           apr_pool_t *pool)
164 {
165   const char *value;
166   apr_array_header_t *list;
167   int i;
168
169   svn_config_get(cfg, &value, "groups", group, NULL);
170
171   list = svn_cstring_split(value, ",", TRUE, pool);
172
173   for (i = 0; i < list->nelts; i++)
174     {
175       const char *group_user = APR_ARRAY_IDX(list, i, char *);
176
177       /* If the 'user' is a subgroup, recurse into it. */
178       if (*group_user == '@')
179         {
180           if (authz_group_contains_user(cfg, &group_user[1],
181                                         user, pool))
182             return TRUE;
183         }
184
185       /* If the 'user' is an alias, verify it. */
186       else if (*group_user == '&')
187         {
188           if (authz_alias_is_user(cfg, &group_user[1],
189                                   user, pool))
190             return TRUE;
191         }
192
193       /* If the user matches, stop. */
194       else if (strcmp(user, group_user) == 0)
195         return TRUE;
196     }
197
198   return FALSE;
199 }
200
201
202 /* Determines whether an authz rule applies to the current
203  * user, given the name part of the rule's name-value pair
204  * in RULE_MATCH_STRING and the authz_lookup_baton object
205  * B with the username in question.
206  */
207 static svn_boolean_t
208 authz_line_applies_to_user(const char *rule_match_string,
209                            struct authz_lookup_baton *b,
210                            apr_pool_t *pool)
211 {
212   /* If the rule has an inversion, recurse and invert the result. */
213   if (rule_match_string[0] == '~')
214     return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
215
216   /* Check for special tokens. */
217   if (strcmp(rule_match_string, "$anonymous") == 0)
218     return (b->user == NULL);
219   if (strcmp(rule_match_string, "$authenticated") == 0)
220     return (b->user != NULL);
221
222   /* Check for a wildcard rule. */
223   if (strcmp(rule_match_string, "*") == 0)
224     return TRUE;
225
226   /* If we get here, then the rule is:
227    *  - Not an inversion rule.
228    *  - Not an authz token rule.
229    *  - Not a wildcard rule.
230    *
231    * All that's left over is regular user or group specifications.
232    */
233
234   /* If the session is anonymous, then a user/group
235    * rule definitely won't match.
236    */
237   if (b->user == NULL)
238     return FALSE;
239
240   /* Process the rule depending on whether it is
241    * a user, alias or group rule.
242    */
243   if (rule_match_string[0] == '@')
244     return authz_group_contains_user(
245       b->config, &rule_match_string[1], b->user, pool);
246   else if (rule_match_string[0] == '&')
247     return authz_alias_is_user(
248       b->config, &rule_match_string[1], b->user, pool);
249   else
250     return (strcmp(b->user, rule_match_string) == 0);
251 }
252
253
254 /* Callback to parse one line of an authz file and update the
255  * authz_baton accordingly.
256  */
257 static svn_boolean_t
258 authz_parse_line(const char *name, const char *value,
259                  void *baton, apr_pool_t *pool)
260 {
261   struct authz_lookup_baton *b = baton;
262
263   /* Stop if the rule doesn't apply to this user. */
264   if (!authz_line_applies_to_user(name, b, pool))
265     return TRUE;
266
267   /* Set the access grants for the rule. */
268   if (strchr(value, 'r'))
269     b->allow |= svn_authz_read;
270   else
271     b->deny |= svn_authz_read;
272
273   if (strchr(value, 'w'))
274     b->allow |= svn_authz_write;
275   else
276     b->deny |= svn_authz_write;
277
278   return TRUE;
279 }
280
281
282 /* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
283  * (which is a repository name, colon, and repository fspath, such as
284  * "myrepos:/trunk/foo").
285  */
286 static svn_boolean_t
287 is_applicable_section(const char *path_spec,
288                       const char *section_name)
289 {
290   apr_size_t path_spec_len = strlen(path_spec);
291
292   return ((strncmp(path_spec, section_name, path_spec_len) == 0)
293           && (path_spec[path_spec_len - 1] == '/'
294               || section_name[path_spec_len] == '/'
295               || section_name[path_spec_len] == '\0'));
296 }
297
298
299 /* Callback to parse a section and update the authz_baton if the
300  * section denies access to the subtree the baton describes.
301  */
302 static svn_boolean_t
303 authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
304 {
305   struct authz_lookup_baton *b = baton;
306   svn_boolean_t conclusive;
307
308   /* Does the section apply to us? */
309   if (!is_applicable_section(b->qualified_repos_path, section_name)
310       && !is_applicable_section(b->repos_path, section_name))
311     return TRUE;
312
313   /* Work out what this section grants. */
314   b->allow = b->deny = 0;
315   svn_config_enumerate2(b->config, section_name,
316                         authz_parse_line, b, pool);
317
318   /* Has the section explicitly determined an access? */
319   conclusive = authz_access_is_determined(b->allow, b->deny,
320                                           b->required_access);
321
322   /* Is access granted OR inconclusive? */
323   b->access = authz_access_is_granted(b->allow, b->deny,
324                                       b->required_access)
325     || !conclusive;
326
327   /* As long as access isn't conclusively denied, carry on. */
328   return b->access;
329 }
330
331
332 /* Validate access to the given user for the given path.  This
333  * function checks rules for exactly the given path, and first tries
334  * to access a section specific to the given repository before falling
335  * back to pan-repository rules.
336  *
337  * Update *access_granted to inform the caller of the outcome of the
338  * lookup.  Return a boolean indicating whether the access rights were
339  * successfully determined.
340  */
341 static svn_boolean_t
342 authz_get_path_access(svn_config_t *cfg, const char *repos_name,
343                       const char *path, const char *user,
344                       svn_repos_authz_access_t required_access,
345                       svn_boolean_t *access_granted,
346                       apr_pool_t *pool)
347 {
348   const char *qualified_path;
349   struct authz_lookup_baton baton = { 0 };
350
351   baton.config = cfg;
352   baton.user = user;
353
354   /* Try to locate a repository-specific block first. */
355   qualified_path = apr_pstrcat(pool, repos_name, ":", path, SVN_VA_NULL);
356   svn_config_enumerate2(cfg, qualified_path,
357                         authz_parse_line, &baton, pool);
358
359   *access_granted = authz_access_is_granted(baton.allow, baton.deny,
360                                             required_access);
361
362   /* If the first test has determined access, stop now. */
363   if (authz_access_is_determined(baton.allow, baton.deny,
364                                  required_access))
365     return TRUE;
366
367   /* No repository specific rule, try pan-repository rules. */
368   svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
369
370   *access_granted = authz_access_is_granted(baton.allow, baton.deny,
371                                             required_access);
372   return authz_access_is_determined(baton.allow, baton.deny,
373                                     required_access);
374 }
375
376
377 /* Validate access to the given user for the subtree starting at the
378  * given path.  This function walks the whole authz file in search of
379  * rules applying to paths in the requested subtree which deny the
380  * requested access.
381  *
382  * As soon as one is found, or else when the whole ACL file has been
383  * searched, return the updated authorization status.
384  */
385 static svn_boolean_t
386 authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
387                       const char *path, const char *user,
388                       svn_repos_authz_access_t required_access,
389                       apr_pool_t *pool)
390 {
391   struct authz_lookup_baton baton = { 0 };
392
393   baton.config = cfg;
394   baton.user = user;
395   baton.required_access = required_access;
396   baton.repos_path = path;
397   baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
398                                            ":", path, SVN_VA_NULL);
399   /* Default to access granted if no rules say otherwise. */
400   baton.access = TRUE;
401
402   svn_config_enumerate_sections2(cfg, authz_parse_section,
403                                  &baton, pool);
404
405   return baton.access;
406 }
407
408
409 /* Callback to parse sections of the configuration file, looking for
410    any kind of granted access.  Implements the
411    svn_config_section_enumerator2_t interface. */
412 static svn_boolean_t
413 authz_get_any_access_parser_cb(const char *section_name, void *baton,
414                                apr_pool_t *pool)
415 {
416   struct authz_lookup_baton *b = baton;
417
418   /* Does the section apply to the query? */
419   if (section_name[0] == '/'
420       || strncmp(section_name, b->qualified_repos_path,
421                  strlen(b->qualified_repos_path)) == 0)
422     {
423       b->allow = b->deny = svn_authz_none;
424
425       svn_config_enumerate2(b->config, section_name,
426                             authz_parse_line, baton, pool);
427       b->access = authz_access_is_granted(b->allow, b->deny,
428                                           b->required_access);
429
430       /* Continue as long as we don't find a determined, granted access. */
431       return !(b->access
432                && authz_access_is_determined(b->allow, b->deny,
433                                              b->required_access));
434     }
435
436   return TRUE;
437 }
438
439
440 /* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
441  * to any path within the REPOSITORY.  Return TRUE if so.  Use POOL
442  * for temporary allocations. */
443 static svn_boolean_t
444 authz_get_any_access(svn_config_t *cfg, const char *repos_name,
445                      const char *user,
446                      svn_repos_authz_access_t required_access,
447                      apr_pool_t *pool)
448 {
449   struct authz_lookup_baton baton = { 0 };
450
451   baton.config = cfg;
452   baton.user = user;
453   baton.required_access = required_access;
454   baton.access = FALSE; /* Deny access by default. */
455   baton.repos_path = "/";
456   baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
457                                            ":/", SVN_VA_NULL);
458
459   /* We could have used svn_config_enumerate2 for "repos_name:/".
460    * However, this requires access for root explicitly (which the user
461    * may not always have). So we end up enumerating the sections in
462    * the authz CFG and stop on the first match with some access for
463    * this user. */
464   svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
465                                  &baton, pool);
466
467   /* If walking the configuration was inconclusive, deny access. */
468   if (!authz_access_is_determined(baton.allow,
469                                   baton.deny, baton.required_access))
470     return FALSE;
471
472   return baton.access;
473 }
474
475
476 \f
477 /*** Validating the authz file. ***/
478
479 /* Check for errors in GROUP's definition of CFG.  The errors
480  * detected are references to non-existent groups and circular
481  * dependencies between groups.  If an error is found, return
482  * SVN_ERR_AUTHZ_INVALID_CONFIG.  Use POOL for temporary
483  * allocations only.
484  *
485  * CHECKED_GROUPS should be an empty (it is used for recursive calls).
486  */
487 static svn_error_t *
488 authz_group_walk(svn_config_t *cfg,
489                  const char *group,
490                  apr_hash_t *checked_groups,
491                  apr_pool_t *pool)
492 {
493   const char *value;
494   apr_array_header_t *list;
495   int i;
496
497   svn_config_get(cfg, &value, "groups", group, NULL);
498   /* Having a non-existent group in the ACL configuration might be the
499      sign of a typo.  Refuse to perform authz on uncertain rules. */
500   if (!value)
501     return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
502                              "An authz rule refers to group '%s', "
503                              "which is undefined",
504                              group);
505
506   list = svn_cstring_split(value, ",", TRUE, pool);
507
508   for (i = 0; i < list->nelts; i++)
509     {
510       const char *group_user = APR_ARRAY_IDX(list, i, char *);
511
512       /* If the 'user' is a subgroup, recurse into it. */
513       if (*group_user == '@')
514         {
515           /* A circular dependency between groups is a Bad Thing.  We
516              don't do authz with invalid ACL files. */
517           if (svn_hash_gets(checked_groups, &group_user[1]))
518             return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
519                                      NULL,
520                                      "Circular dependency between "
521                                      "groups '%s' and '%s'",
522                                      &group_user[1], group);
523
524           /* Add group to hash of checked groups. */
525           svn_hash_sets(checked_groups, &group_user[1], "");
526
527           /* Recurse on that group. */
528           SVN_ERR(authz_group_walk(cfg, &group_user[1],
529                                    checked_groups, pool));
530
531           /* Remove group from hash of checked groups, so that we don't
532              incorrectly report an error if we see it again as part of
533              another group. */
534           svn_hash_sets(checked_groups, &group_user[1], NULL);
535         }
536       else if (*group_user == '&')
537         {
538           const char *alias;
539
540           svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
541           /* Having a non-existent alias in the ACL configuration might be the
542              sign of a typo.  Refuse to perform authz on uncertain rules. */
543           if (!alias)
544             return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
545                                      "An authz rule refers to alias '%s', "
546                                      "which is undefined",
547                                      &group_user[1]);
548         }
549     }
550
551   return SVN_NO_ERROR;
552 }
553
554
555 /* Callback to perform some simple sanity checks on an authz rule.
556  *
557  * - If RULE_MATCH_STRING references a group or an alias, verify that
558  *   the group or alias definition exists.
559  * - If RULE_MATCH_STRING specifies a token (starts with $), verify
560  *   that the token name is valid.
561  * - If RULE_MATCH_STRING is using inversion, verify that it isn't
562  *   doing it more than once within the one rule, and that it isn't
563  *   "~*", as that would never match.
564  * - Check that VALUE part of the rule specifies only allowed rule
565  *   flag characters ('r' and 'w').
566  *
567  * Return TRUE if the rule has no errors. Use BATON for context and
568  * error reporting.
569  */
570 static svn_boolean_t authz_validate_rule(const char *rule_match_string,
571                                          const char *value,
572                                          void *baton,
573                                          apr_pool_t *pool)
574 {
575   const char *val;
576   const char *match = rule_match_string;
577   struct authz_validate_baton *b = baton;
578
579   /* Make sure the user isn't using double-negatives. */
580   if (match[0] == '~')
581     {
582       /* Bump the pointer past the inversion for the other checks. */
583       match++;
584
585       /* Another inversion is a double negative; we can't not stop. */
586       if (match[0] == '~')
587         {
588           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
589                                      "Rule '%s' has more than one "
590                                      "inversion; double negatives are "
591                                      "not permitted.",
592                                      rule_match_string);
593           return FALSE;
594         }
595
596       /* Make sure that the rule isn't "~*", which won't ever match. */
597       if (strcmp(match, "*") == 0)
598         {
599           b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
600                                     "Authz rules with match string '~*' "
601                                     "are not allowed, because they never "
602                                     "match anyone.");
603           return FALSE;
604         }
605     }
606
607   /* If the rule applies to a group, check its existence. */
608   if (match[0] == '@')
609     {
610       const char *group = &match[1];
611
612       svn_config_get(b->config, &val, "groups", group, NULL);
613
614       /* Having a non-existent group in the ACL configuration might be
615          the sign of a typo.  Refuse to perform authz on uncertain
616          rules. */
617       if (!val)
618         {
619           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
620                                      "An authz rule refers to group "
621                                      "'%s', which is undefined",
622                                      rule_match_string);
623           return FALSE;
624         }
625     }
626
627   /* If the rule applies to an alias, check its existence. */
628   if (match[0] == '&')
629     {
630       const char *alias = &match[1];
631
632       svn_config_get(b->config, &val, "aliases", alias, NULL);
633
634       if (!val)
635         {
636           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
637                                      "An authz rule refers to alias "
638                                      "'%s', which is undefined",
639                                      rule_match_string);
640           return FALSE;
641         }
642      }
643
644   /* If the rule specifies a token, check its validity. */
645   if (match[0] == '$')
646     {
647       const char *token_name = &match[1];
648
649       if ((strcmp(token_name, "anonymous") != 0)
650        && (strcmp(token_name, "authenticated") != 0))
651         {
652           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
653                                      "Unrecognized authz token '%s'.",
654                                      rule_match_string);
655           return FALSE;
656         }
657     }
658
659   val = value;
660
661   while (*val)
662     {
663       if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
664         {
665           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
666                                      "The character '%c' in rule '%s' is not "
667                                      "allowed in authz rules", *val,
668                                      rule_match_string);
669           return FALSE;
670         }
671
672       ++val;
673     }
674
675   return TRUE;
676 }
677
678 /* Callback to check ALIAS's definition for validity.  Use
679    BATON for context and error reporting. */
680 static svn_boolean_t authz_validate_alias(const char *alias,
681                                           const char *value,
682                                           void *baton,
683                                           apr_pool_t *pool)
684 {
685   /* No checking at the moment, every alias is valid */
686   return TRUE;
687 }
688
689
690 /* Callback to check GROUP's definition for cyclic dependancies.  Use
691    BATON for context and error reporting. */
692 static svn_boolean_t authz_validate_group(const char *group,
693                                           const char *value,
694                                           void *baton,
695                                           apr_pool_t *pool)
696 {
697   struct authz_validate_baton *b = baton;
698
699   b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
700   if (b->err)
701     return FALSE;
702
703   return TRUE;
704 }
705
706
707 /* Callback to check the contents of the configuration section given
708    by NAME.  Use BATON for context and error reporting. */
709 static svn_boolean_t authz_validate_section(const char *name,
710                                             void *baton,
711                                             apr_pool_t *pool)
712 {
713   struct authz_validate_baton *b = baton;
714
715   /* Use the group checking callback for the "groups" section... */
716   if (strcmp(name, "groups") == 0)
717     svn_config_enumerate2(b->config, name, authz_validate_group,
718                           baton, pool);
719   /* ...and the alias checking callback for "aliases"... */
720   else if (strcmp(name, "aliases") == 0)
721     svn_config_enumerate2(b->config, name, authz_validate_alias,
722                           baton, pool);
723   /* ...but for everything else use the rule checking callback. */
724   else
725     {
726       /* Validate the section's name. Skip the optional REPOS_NAME. */
727       const char *fspath = strchr(name, ':');
728       if (fspath)
729         fspath++;
730       else
731         fspath = name;
732       if (! svn_fspath__is_canonical(fspath))
733         {
734           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
735                                      "Section name '%s' contains non-canonical "
736                                      "fspath '%s'",
737                                      name, fspath);
738           return FALSE;
739         }
740
741       svn_config_enumerate2(b->config, name, authz_validate_rule,
742                             baton, pool);
743     }
744
745   if (b->err)
746     return FALSE;
747
748   return TRUE;
749 }
750
751
752 svn_error_t *
753 svn_repos__authz_validate(svn_authz_t *authz, apr_pool_t *pool)
754 {
755   struct authz_validate_baton baton = { 0 };
756
757   baton.err = SVN_NO_ERROR;
758   baton.config = authz->cfg;
759
760   /* Step through the entire rule file stopping on error. */
761   svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
762                                  &baton, pool);
763   SVN_ERR(baton.err);
764
765   return SVN_NO_ERROR;
766 }
767
768
769 /* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
770  * file placing the result into CFG_P allocated in POOL.
771  *
772  * If DIRENT cannot be parsed as a config file then an error is returned.  The
773  * contents of CFG_P is then undefined.  If MUST_EXIST is TRUE, a missing
774  * authz file is also an error.  The CASE_SENSITIVE controls the lookup
775  * behavior for section and option names alike.
776  *
777  * SCRATCH_POOL will be used for temporary allocations. */
778 static svn_error_t *
779 authz_retrieve_config_repo(svn_config_t **cfg_p,
780                            const char *dirent,
781                            svn_boolean_t must_exist,
782                            svn_boolean_t case_sensitive,
783                            apr_pool_t *result_pool,
784                            apr_pool_t *scratch_pool)
785 {
786   svn_error_t *err;
787   svn_repos_t *repos;
788   const char *repos_root_dirent;
789   const char *fs_path;
790   svn_fs_t *fs;
791   svn_fs_root_t *root;
792   svn_revnum_t youngest_rev;
793   svn_node_kind_t node_kind;
794   svn_stream_t *contents;
795
796   /* Search for a repository in the full path. */
797   repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
798   if (!repos_root_dirent)
799     return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
800                              "Unable to find repository at '%s'", dirent);
801
802   /* Attempt to open a repository at repos_root_dirent. */
803   SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL, scratch_pool,
804                           scratch_pool));
805
806   fs_path = &dirent[strlen(repos_root_dirent)];
807
808   /* Root path is always a directory so no reason to go any further */
809   if (*fs_path == '\0')
810     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
811                              "'/' is not a file in repo '%s'",
812                              repos_root_dirent);
813
814   /* We skip some things that are non-important for how we're going to use
815    * this repo connection.  We do not set any capabilities since none of
816    * the current ones are important for what we're doing.  We also do not
817    * setup the environment that repos hooks would run under since we won't
818    * be triggering any. */
819
820   /* Get the filesystem. */
821   fs = svn_repos_fs(repos);
822
823   /* Find HEAD and the revision root */
824   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
825   SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
826
827   SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
828   if (node_kind == svn_node_none)
829     {
830       if (!must_exist)
831         {
832           SVN_ERR(svn_config_create2(cfg_p, case_sensitive, case_sensitive,
833                                      result_pool));
834           return SVN_NO_ERROR;
835         }
836       else
837         {
838           return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
839                                    "'%s' path not found in repo '%s'", fs_path,
840                                    repos_root_dirent);
841         }
842     }
843   else if (node_kind != svn_node_file)
844     {
845       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
846                                "'%s' is not a file in repo '%s'", fs_path,
847                                repos_root_dirent);
848     }
849
850   SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
851   err = svn_config_parse(cfg_p, contents, case_sensitive, case_sensitive,
852                          result_pool);
853
854   /* Add the URL to the error stack since the parser doesn't have it. */
855   if (err != SVN_NO_ERROR)
856     return svn_error_createf(err->apr_err, err,
857                              "Error while parsing config file: '%s' in repo '%s':",
858                              fs_path, repos_root_dirent);
859
860   return SVN_NO_ERROR;
861 }
862
863 svn_error_t *
864 svn_repos__retrieve_config(svn_config_t **cfg_p,
865                            const char *path,
866                            svn_boolean_t must_exist,
867                            svn_boolean_t case_sensitive,
868                            apr_pool_t *pool)
869 {
870   if (svn_path_is_url(path))
871     {
872       const char *dirent;
873       svn_error_t *err;
874       apr_pool_t *scratch_pool = svn_pool_create(pool);
875
876       err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
877
878       if (err == SVN_NO_ERROR)
879         err = authz_retrieve_config_repo(cfg_p, dirent, must_exist,
880                                          case_sensitive, pool, scratch_pool);
881
882       /* Close the repos and streams we opened. */
883       svn_pool_destroy(scratch_pool);
884
885       return err;
886     }
887   else
888     {
889       /* Outside of repo file or Windows registry*/
890       SVN_ERR(svn_config_read3(cfg_p, path, must_exist, case_sensitive,
891                                case_sensitive, pool));
892     }
893
894   return SVN_NO_ERROR;
895 }
896
897
898 /* Callback to copy (name, value) group into the "groups" section
899    of another configuration. */
900 static svn_boolean_t
901 authz_copy_group(const char *name, const char *value,
902                  void *baton, apr_pool_t *pool)
903 {
904   svn_config_t *authz_cfg = baton;
905
906   svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
907
908   return TRUE;
909 }
910
911 /* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
912  * If AUTHZ already contains any group definition, report an error.
913  * Use POOL for temporary allocations. */
914 static svn_error_t *
915 authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
916                   apr_pool_t *pool)
917 {
918   /* Easy out: we prohibit local groups in the authz file when global
919      groups are being used. */
920   if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
921     {
922       return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
923                               "Authz file cannot contain any groups "
924                               "when global groups are being used.");
925     }
926
927   svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
928                         authz_copy_group, authz->cfg, pool);
929
930   return SVN_NO_ERROR;
931 }
932
933 svn_error_t *
934 svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
935                       const char *groups_path, svn_boolean_t must_exist,
936                       svn_boolean_t accept_urls, apr_pool_t *pool)
937 {
938   svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
939
940   /* Load the authz file */
941   if (accept_urls)
942     SVN_ERR(svn_repos__retrieve_config(&authz->cfg, path, must_exist, TRUE,
943                                        pool));
944   else
945     SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE,
946                              pool));
947
948   if (groups_path)
949     {
950       svn_config_t *groups_cfg;
951       svn_error_t *err;
952
953       /* Load the groups file */
954       if (accept_urls)
955         SVN_ERR(svn_repos__retrieve_config(&groups_cfg, groups_path,
956                                            must_exist, TRUE, pool));
957       else
958         SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
959                                  TRUE, TRUE, pool));
960
961       /* Copy the groups from groups_cfg into authz. */
962       err = authz_copy_groups(authz, groups_cfg, pool);
963
964       /* Add the paths to the error stack since the authz_copy_groups
965          routine knows nothing about them. */
966       if (err != SVN_NO_ERROR)
967         return svn_error_createf(err->apr_err, err,
968                                  "Error reading authz file '%s' with "
969                                  "groups file '%s':", path, groups_path);
970     }
971
972   /* Make sure there are no errors in the configuration. */
973   SVN_ERR(svn_repos__authz_validate(authz, pool));
974
975   *authz_p = authz;
976   return SVN_NO_ERROR;
977 }
978
979
980 \f
981 /*** Public functions. ***/
982
983 svn_error_t *
984 svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
985                       const char *groups_path, svn_boolean_t must_exist,
986                       apr_pool_t *pool)
987 {
988   return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
989                                TRUE, pool);
990 }
991
992
993 svn_error_t *
994 svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
995                       svn_stream_t *groups_stream, apr_pool_t *pool)
996 {
997   svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
998
999   /* Parse the authz stream */
1000   SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
1001
1002   if (groups_stream)
1003     {
1004       svn_config_t *groups_cfg;
1005
1006       /* Parse the groups stream */
1007       SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
1008
1009       SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
1010     }
1011
1012   /* Make sure there are no errors in the configuration. */
1013   SVN_ERR(svn_repos__authz_validate(authz, pool));
1014
1015   *authz_p = authz;
1016   return SVN_NO_ERROR;
1017 }
1018
1019
1020 svn_error_t *
1021 svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
1022                              const char *path, const char *user,
1023                              svn_repos_authz_access_t required_access,
1024                              svn_boolean_t *access_granted,
1025                              apr_pool_t *pool)
1026 {
1027   const char *current_path;
1028
1029   if (!repos_name)
1030     repos_name = "";
1031
1032   /* If PATH is NULL, check if the user has *any* access. */
1033   if (!path)
1034     {
1035       *access_granted = authz_get_any_access(authz->cfg, repos_name,
1036                                              user, required_access, pool);
1037       return SVN_NO_ERROR;
1038     }
1039
1040   /* Sanity check. */
1041   SVN_ERR_ASSERT(path[0] == '/');
1042
1043   /* Determine the granted access for the requested path. */
1044   path = svn_fspath__canonicalize(path, pool);
1045   current_path = path;
1046
1047   while (!authz_get_path_access(authz->cfg, repos_name,
1048                                 current_path, user,
1049                                 required_access,
1050                                 access_granted,
1051                                 pool))
1052     {
1053       /* Stop if the loop hits the repository root with no
1054          results. */
1055       if (current_path[0] == '/' && current_path[1] == '\0')
1056         {
1057           /* Deny access by default. */
1058           *access_granted = FALSE;
1059           return SVN_NO_ERROR;
1060         }
1061
1062       /* Work back to the parent path. */
1063       current_path = svn_fspath__dirname(current_path, pool);
1064     }
1065
1066   /* If the caller requested recursive access, we need to walk through
1067      the entire authz config to see whether any child paths are denied
1068      to the requested user. */
1069   if (*access_granted && (required_access & svn_authz_recursive))
1070     *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
1071                                             user, required_access, pool);
1072
1073   return SVN_NO_ERROR;
1074 }