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