authz.c revision 299742
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
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
42/*** Structures. ***/
43
44/* Information for the config enumerators called during authz
45   lookup. */
46struct 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. */
74struct 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. */
82struct svn_authz_t
83{
84  svn_config_t *cfg;
85};
86
87
88
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 */
99static svn_boolean_t
100authz_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 */
123static svn_boolean_t
124authz_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. */
137static svn_boolean_t
138authz_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. */
159static svn_boolean_t
160authz_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 */
207static svn_boolean_t
208authz_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 */
257static svn_boolean_t
258authz_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 */
286static svn_boolean_t
287is_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 */
302static svn_boolean_t
303authz_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 */
341static svn_boolean_t
342authz_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 */
385static svn_boolean_t
386authz_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. */
412static svn_boolean_t
413authz_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. */
443static svn_boolean_t
444authz_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
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 */
487static svn_error_t *
488authz_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 */
570static 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. */
680static 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. */
692static 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. */
709static 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
752svn_error_t *
753svn_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. */
778static svn_error_t *
779authz_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
863svn_error_t *
864svn_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. */
900static svn_boolean_t
901authz_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. */
914static svn_error_t *
915authz_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
933svn_error_t *
934svn_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
981/*** Public functions. ***/
982
983svn_error_t *
984svn_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
993svn_error_t *
994svn_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
1020svn_error_t *
1021svn_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}
1075