1251881Speter/* authz.c : path-based access control
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter
24251881Speter/*** Includes. ***/
25251881Speter
26251881Speter#include <apr_pools.h>
27251881Speter#include <apr_file_io.h>
28251881Speter
29251881Speter#include "svn_hash.h"
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_dirent_uri.h"
33251881Speter#include "svn_path.h"
34251881Speter#include "svn_repos.h"
35251881Speter#include "svn_config.h"
36251881Speter#include "svn_ctype.h"
37251881Speter#include "private/svn_fspath.h"
38251881Speter#include "repos.h"
39251881Speter
40251881Speter
41251881Speter/*** Structures. ***/
42251881Speter
43251881Speter/* Information for the config enumerators called during authz
44251881Speter   lookup. */
45251881Speterstruct authz_lookup_baton {
46251881Speter  /* The authz configuration. */
47251881Speter  svn_config_t *config;
48251881Speter
49251881Speter  /* The user to authorize. */
50251881Speter  const char *user;
51251881Speter
52251881Speter  /* Explicitly granted rights. */
53251881Speter  svn_repos_authz_access_t allow;
54251881Speter  /* Explicitly denied rights. */
55251881Speter  svn_repos_authz_access_t deny;
56251881Speter
57251881Speter  /* The rights required by the caller of the lookup. */
58251881Speter  svn_repos_authz_access_t required_access;
59251881Speter
60251881Speter  /* The following are used exclusively in recursive lookups. */
61251881Speter
62251881Speter  /* The path in the repository (an fspath) to authorize. */
63251881Speter  const char *repos_path;
64251881Speter  /* repos_path prefixed by the repository name and a colon. */
65251881Speter  const char *qualified_repos_path;
66251881Speter
67251881Speter  /* Whether, at the end of a recursive lookup, access is granted. */
68251881Speter  svn_boolean_t access;
69251881Speter};
70251881Speter
71251881Speter/* Information for the config enumeration functions called during the
72251881Speter   validation process. */
73251881Speterstruct authz_validate_baton {
74251881Speter  svn_config_t *config; /* The configuration file being validated. */
75251881Speter  svn_error_t *err;     /* The error being thrown out of the
76251881Speter                           enumerator, if any. */
77251881Speter};
78251881Speter
79251881Speter/* Currently this structure is just a wrapper around a
80251881Speter   svn_config_t. */
81251881Speterstruct svn_authz_t
82251881Speter{
83251881Speter  svn_config_t *cfg;
84251881Speter};
85251881Speter
86251881Speter
87251881Speter
88251881Speter/*** Checking access. ***/
89251881Speter
90251881Speter/* Determine whether the REQUIRED access is granted given what authz
91251881Speter * to ALLOW or DENY.  Return TRUE if the REQUIRED access is
92251881Speter * granted.
93251881Speter *
94251881Speter * Access is granted either when no required access is explicitly
95251881Speter * denied (implicit grant), or when the required access is explicitly
96251881Speter * granted, overriding any denials.
97251881Speter */
98251881Speterstatic svn_boolean_t
99251881Speterauthz_access_is_granted(svn_repos_authz_access_t allow,
100251881Speter                        svn_repos_authz_access_t deny,
101251881Speter                        svn_repos_authz_access_t required)
102251881Speter{
103251881Speter  svn_repos_authz_access_t stripped_req =
104251881Speter    required & (svn_authz_read | svn_authz_write);
105251881Speter
106251881Speter  if ((deny & required) == svn_authz_none)
107251881Speter    return TRUE;
108251881Speter  else if ((allow & required) == stripped_req)
109251881Speter    return TRUE;
110251881Speter  else
111251881Speter    return FALSE;
112251881Speter}
113251881Speter
114251881Speter
115251881Speter/* Decide whether the REQUIRED access has been conclusively
116251881Speter * determined.  Return TRUE if the given ALLOW/DENY authz are
117251881Speter * conclusive regarding the REQUIRED authz.
118251881Speter *
119251881Speter * Conclusive determination occurs when any of the REQUIRED authz are
120251881Speter * granted or denied by ALLOW/DENY.
121251881Speter */
122251881Speterstatic svn_boolean_t
123251881Speterauthz_access_is_determined(svn_repos_authz_access_t allow,
124251881Speter                           svn_repos_authz_access_t deny,
125251881Speter                           svn_repos_authz_access_t required)
126251881Speter{
127251881Speter  if ((deny & required) || (allow & required))
128251881Speter    return TRUE;
129251881Speter  else
130251881Speter    return FALSE;
131251881Speter}
132251881Speter
133251881Speter/* Return TRUE is USER equals ALIAS. The alias definitions are in the
134251881Speter   "aliases" sections of CFG. Use POOL for temporary allocations during
135251881Speter   the lookup. */
136251881Speterstatic svn_boolean_t
137251881Speterauthz_alias_is_user(svn_config_t *cfg,
138251881Speter                    const char *alias,
139251881Speter                    const char *user,
140251881Speter                    apr_pool_t *pool)
141251881Speter{
142251881Speter  const char *value;
143251881Speter
144251881Speter  svn_config_get(cfg, &value, "aliases", alias, NULL);
145251881Speter  if (!value)
146251881Speter    return FALSE;
147251881Speter
148251881Speter  if (strcmp(value, user) == 0)
149251881Speter    return TRUE;
150251881Speter
151251881Speter  return FALSE;
152251881Speter}
153251881Speter
154251881Speter
155251881Speter/* Return TRUE if USER is in GROUP.  The group definitions are in the
156251881Speter   "groups" section of CFG.  Use POOL for temporary allocations during
157251881Speter   the lookup. */
158251881Speterstatic svn_boolean_t
159251881Speterauthz_group_contains_user(svn_config_t *cfg,
160251881Speter                          const char *group,
161251881Speter                          const char *user,
162251881Speter                          apr_pool_t *pool)
163251881Speter{
164251881Speter  const char *value;
165251881Speter  apr_array_header_t *list;
166251881Speter  int i;
167251881Speter
168251881Speter  svn_config_get(cfg, &value, "groups", group, NULL);
169251881Speter
170251881Speter  list = svn_cstring_split(value, ",", TRUE, pool);
171251881Speter
172251881Speter  for (i = 0; i < list->nelts; i++)
173251881Speter    {
174251881Speter      const char *group_user = APR_ARRAY_IDX(list, i, char *);
175251881Speter
176251881Speter      /* If the 'user' is a subgroup, recurse into it. */
177251881Speter      if (*group_user == '@')
178251881Speter        {
179251881Speter          if (authz_group_contains_user(cfg, &group_user[1],
180251881Speter                                        user, pool))
181251881Speter            return TRUE;
182251881Speter        }
183251881Speter
184251881Speter      /* If the 'user' is an alias, verify it. */
185251881Speter      else if (*group_user == '&')
186251881Speter        {
187251881Speter          if (authz_alias_is_user(cfg, &group_user[1],
188251881Speter                                  user, pool))
189251881Speter            return TRUE;
190251881Speter        }
191251881Speter
192251881Speter      /* If the user matches, stop. */
193251881Speter      else if (strcmp(user, group_user) == 0)
194251881Speter        return TRUE;
195251881Speter    }
196251881Speter
197251881Speter  return FALSE;
198251881Speter}
199251881Speter
200251881Speter
201251881Speter/* Determines whether an authz rule applies to the current
202251881Speter * user, given the name part of the rule's name-value pair
203251881Speter * in RULE_MATCH_STRING and the authz_lookup_baton object
204251881Speter * B with the username in question.
205251881Speter */
206251881Speterstatic svn_boolean_t
207251881Speterauthz_line_applies_to_user(const char *rule_match_string,
208251881Speter                           struct authz_lookup_baton *b,
209251881Speter                           apr_pool_t *pool)
210251881Speter{
211251881Speter  /* If the rule has an inversion, recurse and invert the result. */
212251881Speter  if (rule_match_string[0] == '~')
213251881Speter    return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
214251881Speter
215251881Speter  /* Check for special tokens. */
216251881Speter  if (strcmp(rule_match_string, "$anonymous") == 0)
217251881Speter    return (b->user == NULL);
218251881Speter  if (strcmp(rule_match_string, "$authenticated") == 0)
219251881Speter    return (b->user != NULL);
220251881Speter
221251881Speter  /* Check for a wildcard rule. */
222251881Speter  if (strcmp(rule_match_string, "*") == 0)
223251881Speter    return TRUE;
224251881Speter
225251881Speter  /* If we get here, then the rule is:
226251881Speter   *  - Not an inversion rule.
227251881Speter   *  - Not an authz token rule.
228251881Speter   *  - Not a wildcard rule.
229251881Speter   *
230251881Speter   * All that's left over is regular user or group specifications.
231251881Speter   */
232251881Speter
233251881Speter  /* If the session is anonymous, then a user/group
234251881Speter   * rule definitely won't match.
235251881Speter   */
236251881Speter  if (b->user == NULL)
237251881Speter    return FALSE;
238251881Speter
239251881Speter  /* Process the rule depending on whether it is
240251881Speter   * a user, alias or group rule.
241251881Speter   */
242251881Speter  if (rule_match_string[0] == '@')
243251881Speter    return authz_group_contains_user(
244251881Speter      b->config, &rule_match_string[1], b->user, pool);
245251881Speter  else if (rule_match_string[0] == '&')
246251881Speter    return authz_alias_is_user(
247251881Speter      b->config, &rule_match_string[1], b->user, pool);
248251881Speter  else
249251881Speter    return (strcmp(b->user, rule_match_string) == 0);
250251881Speter}
251251881Speter
252251881Speter
253251881Speter/* Callback to parse one line of an authz file and update the
254251881Speter * authz_baton accordingly.
255251881Speter */
256251881Speterstatic svn_boolean_t
257251881Speterauthz_parse_line(const char *name, const char *value,
258251881Speter                 void *baton, apr_pool_t *pool)
259251881Speter{
260251881Speter  struct authz_lookup_baton *b = baton;
261251881Speter
262251881Speter  /* Stop if the rule doesn't apply to this user. */
263251881Speter  if (!authz_line_applies_to_user(name, b, pool))
264251881Speter    return TRUE;
265251881Speter
266251881Speter  /* Set the access grants for the rule. */
267251881Speter  if (strchr(value, 'r'))
268251881Speter    b->allow |= svn_authz_read;
269251881Speter  else
270251881Speter    b->deny |= svn_authz_read;
271251881Speter
272251881Speter  if (strchr(value, 'w'))
273251881Speter    b->allow |= svn_authz_write;
274251881Speter  else
275251881Speter    b->deny |= svn_authz_write;
276251881Speter
277251881Speter  return TRUE;
278251881Speter}
279251881Speter
280251881Speter
281251881Speter/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
282251881Speter * (which is a repository name, colon, and repository fspath, such as
283251881Speter * "myrepos:/trunk/foo").
284251881Speter */
285251881Speterstatic svn_boolean_t
286251881Speteris_applicable_section(const char *path_spec,
287251881Speter                      const char *section_name)
288251881Speter{
289251881Speter  apr_size_t path_spec_len = strlen(path_spec);
290251881Speter
291251881Speter  return ((strncmp(path_spec, section_name, path_spec_len) == 0)
292251881Speter          && (path_spec[path_spec_len - 1] == '/'
293251881Speter              || section_name[path_spec_len] == '/'
294251881Speter              || section_name[path_spec_len] == '\0'));
295251881Speter}
296251881Speter
297251881Speter
298251881Speter/* Callback to parse a section and update the authz_baton if the
299251881Speter * section denies access to the subtree the baton describes.
300251881Speter */
301251881Speterstatic svn_boolean_t
302251881Speterauthz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
303251881Speter{
304251881Speter  struct authz_lookup_baton *b = baton;
305251881Speter  svn_boolean_t conclusive;
306251881Speter
307251881Speter  /* Does the section apply to us? */
308251881Speter  if (!is_applicable_section(b->qualified_repos_path, section_name)
309251881Speter      && !is_applicable_section(b->repos_path, section_name))
310251881Speter    return TRUE;
311251881Speter
312251881Speter  /* Work out what this section grants. */
313251881Speter  b->allow = b->deny = 0;
314251881Speter  svn_config_enumerate2(b->config, section_name,
315251881Speter                        authz_parse_line, b, pool);
316251881Speter
317251881Speter  /* Has the section explicitly determined an access? */
318251881Speter  conclusive = authz_access_is_determined(b->allow, b->deny,
319251881Speter                                          b->required_access);
320251881Speter
321251881Speter  /* Is access granted OR inconclusive? */
322251881Speter  b->access = authz_access_is_granted(b->allow, b->deny,
323251881Speter                                      b->required_access)
324251881Speter    || !conclusive;
325251881Speter
326251881Speter  /* As long as access isn't conclusively denied, carry on. */
327251881Speter  return b->access;
328251881Speter}
329251881Speter
330251881Speter
331251881Speter/* Validate access to the given user for the given path.  This
332251881Speter * function checks rules for exactly the given path, and first tries
333251881Speter * to access a section specific to the given repository before falling
334251881Speter * back to pan-repository rules.
335251881Speter *
336251881Speter * Update *access_granted to inform the caller of the outcome of the
337251881Speter * lookup.  Return a boolean indicating whether the access rights were
338251881Speter * successfully determined.
339251881Speter */
340251881Speterstatic svn_boolean_t
341251881Speterauthz_get_path_access(svn_config_t *cfg, const char *repos_name,
342251881Speter                      const char *path, const char *user,
343251881Speter                      svn_repos_authz_access_t required_access,
344251881Speter                      svn_boolean_t *access_granted,
345251881Speter                      apr_pool_t *pool)
346251881Speter{
347251881Speter  const char *qualified_path;
348251881Speter  struct authz_lookup_baton baton = { 0 };
349251881Speter
350251881Speter  baton.config = cfg;
351251881Speter  baton.user = user;
352251881Speter
353251881Speter  /* Try to locate a repository-specific block first. */
354251881Speter  qualified_path = apr_pstrcat(pool, repos_name, ":", path, (char *)NULL);
355251881Speter  svn_config_enumerate2(cfg, qualified_path,
356251881Speter                        authz_parse_line, &baton, pool);
357251881Speter
358251881Speter  *access_granted = authz_access_is_granted(baton.allow, baton.deny,
359251881Speter                                            required_access);
360251881Speter
361251881Speter  /* If the first test has determined access, stop now. */
362251881Speter  if (authz_access_is_determined(baton.allow, baton.deny,
363251881Speter                                 required_access))
364251881Speter    return TRUE;
365251881Speter
366251881Speter  /* No repository specific rule, try pan-repository rules. */
367251881Speter  svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
368251881Speter
369251881Speter  *access_granted = authz_access_is_granted(baton.allow, baton.deny,
370251881Speter                                            required_access);
371251881Speter  return authz_access_is_determined(baton.allow, baton.deny,
372251881Speter                                    required_access);
373251881Speter}
374251881Speter
375251881Speter
376251881Speter/* Validate access to the given user for the subtree starting at the
377251881Speter * given path.  This function walks the whole authz file in search of
378251881Speter * rules applying to paths in the requested subtree which deny the
379251881Speter * requested access.
380251881Speter *
381251881Speter * As soon as one is found, or else when the whole ACL file has been
382251881Speter * searched, return the updated authorization status.
383251881Speter */
384251881Speterstatic svn_boolean_t
385251881Speterauthz_get_tree_access(svn_config_t *cfg, const char *repos_name,
386251881Speter                      const char *path, const char *user,
387251881Speter                      svn_repos_authz_access_t required_access,
388251881Speter                      apr_pool_t *pool)
389251881Speter{
390251881Speter  struct authz_lookup_baton baton = { 0 };
391251881Speter
392251881Speter  baton.config = cfg;
393251881Speter  baton.user = user;
394251881Speter  baton.required_access = required_access;
395251881Speter  baton.repos_path = path;
396251881Speter  baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
397251881Speter                                           ":", path, (char *)NULL);
398251881Speter  /* Default to access granted if no rules say otherwise. */
399251881Speter  baton.access = TRUE;
400251881Speter
401251881Speter  svn_config_enumerate_sections2(cfg, authz_parse_section,
402251881Speter                                 &baton, pool);
403251881Speter
404251881Speter  return baton.access;
405251881Speter}
406251881Speter
407251881Speter
408251881Speter/* Callback to parse sections of the configuration file, looking for
409251881Speter   any kind of granted access.  Implements the
410251881Speter   svn_config_section_enumerator2_t interface. */
411251881Speterstatic svn_boolean_t
412251881Speterauthz_get_any_access_parser_cb(const char *section_name, void *baton,
413251881Speter                               apr_pool_t *pool)
414251881Speter{
415251881Speter  struct authz_lookup_baton *b = baton;
416251881Speter
417251881Speter  /* Does the section apply to the query? */
418251881Speter  if (section_name[0] == '/'
419251881Speter      || strncmp(section_name, b->qualified_repos_path,
420251881Speter                 strlen(b->qualified_repos_path)) == 0)
421251881Speter    {
422251881Speter      b->allow = b->deny = svn_authz_none;
423251881Speter
424251881Speter      svn_config_enumerate2(b->config, section_name,
425251881Speter                            authz_parse_line, baton, pool);
426251881Speter      b->access = authz_access_is_granted(b->allow, b->deny,
427251881Speter                                          b->required_access);
428251881Speter
429251881Speter      /* Continue as long as we don't find a determined, granted access. */
430251881Speter      return !(b->access
431251881Speter               && authz_access_is_determined(b->allow, b->deny,
432251881Speter                                             b->required_access));
433251881Speter    }
434251881Speter
435251881Speter  return TRUE;
436251881Speter}
437251881Speter
438251881Speter
439251881Speter/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
440251881Speter * to any path within the REPOSITORY.  Return TRUE if so.  Use POOL
441251881Speter * for temporary allocations. */
442251881Speterstatic svn_boolean_t
443251881Speterauthz_get_any_access(svn_config_t *cfg, const char *repos_name,
444251881Speter                     const char *user,
445251881Speter                     svn_repos_authz_access_t required_access,
446251881Speter                     apr_pool_t *pool)
447251881Speter{
448251881Speter  struct authz_lookup_baton baton = { 0 };
449251881Speter
450251881Speter  baton.config = cfg;
451251881Speter  baton.user = user;
452251881Speter  baton.required_access = required_access;
453251881Speter  baton.access = FALSE; /* Deny access by default. */
454251881Speter  baton.repos_path = "/";
455251881Speter  baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
456251881Speter                                           ":/", (char *)NULL);
457251881Speter
458251881Speter  /* We could have used svn_config_enumerate2 for "repos_name:/".
459251881Speter   * However, this requires access for root explicitly (which the user
460251881Speter   * may not always have). So we end up enumerating the sections in
461251881Speter   * the authz CFG and stop on the first match with some access for
462251881Speter   * this user. */
463251881Speter  svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
464251881Speter                                 &baton, pool);
465251881Speter
466251881Speter  /* If walking the configuration was inconclusive, deny access. */
467251881Speter  if (!authz_access_is_determined(baton.allow,
468251881Speter                                  baton.deny, baton.required_access))
469251881Speter    return FALSE;
470251881Speter
471251881Speter  return baton.access;
472251881Speter}
473251881Speter
474251881Speter
475251881Speter
476251881Speter/*** Validating the authz file. ***/
477251881Speter
478251881Speter/* Check for errors in GROUP's definition of CFG.  The errors
479251881Speter * detected are references to non-existent groups and circular
480251881Speter * dependencies between groups.  If an error is found, return
481251881Speter * SVN_ERR_AUTHZ_INVALID_CONFIG.  Use POOL for temporary
482251881Speter * allocations only.
483251881Speter *
484251881Speter * CHECKED_GROUPS should be an empty (it is used for recursive calls).
485251881Speter */
486251881Speterstatic svn_error_t *
487251881Speterauthz_group_walk(svn_config_t *cfg,
488251881Speter                 const char *group,
489251881Speter                 apr_hash_t *checked_groups,
490251881Speter                 apr_pool_t *pool)
491251881Speter{
492251881Speter  const char *value;
493251881Speter  apr_array_header_t *list;
494251881Speter  int i;
495251881Speter
496251881Speter  svn_config_get(cfg, &value, "groups", group, NULL);
497251881Speter  /* Having a non-existent group in the ACL configuration might be the
498251881Speter     sign of a typo.  Refuse to perform authz on uncertain rules. */
499251881Speter  if (!value)
500251881Speter    return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
501251881Speter                             "An authz rule refers to group '%s', "
502251881Speter                             "which is undefined",
503251881Speter                             group);
504251881Speter
505251881Speter  list = svn_cstring_split(value, ",", TRUE, pool);
506251881Speter
507251881Speter  for (i = 0; i < list->nelts; i++)
508251881Speter    {
509251881Speter      const char *group_user = APR_ARRAY_IDX(list, i, char *);
510251881Speter
511251881Speter      /* If the 'user' is a subgroup, recurse into it. */
512251881Speter      if (*group_user == '@')
513251881Speter        {
514251881Speter          /* A circular dependency between groups is a Bad Thing.  We
515251881Speter             don't do authz with invalid ACL files. */
516251881Speter          if (svn_hash_gets(checked_groups, &group_user[1]))
517251881Speter            return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
518251881Speter                                     NULL,
519251881Speter                                     "Circular dependency between "
520251881Speter                                     "groups '%s' and '%s'",
521251881Speter                                     &group_user[1], group);
522251881Speter
523251881Speter          /* Add group to hash of checked groups. */
524251881Speter          svn_hash_sets(checked_groups, &group_user[1], "");
525251881Speter
526251881Speter          /* Recurse on that group. */
527251881Speter          SVN_ERR(authz_group_walk(cfg, &group_user[1],
528251881Speter                                   checked_groups, pool));
529251881Speter
530251881Speter          /* Remove group from hash of checked groups, so that we don't
531251881Speter             incorrectly report an error if we see it again as part of
532251881Speter             another group. */
533251881Speter          svn_hash_sets(checked_groups, &group_user[1], NULL);
534251881Speter        }
535251881Speter      else if (*group_user == '&')
536251881Speter        {
537251881Speter          const char *alias;
538251881Speter
539251881Speter          svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
540251881Speter          /* Having a non-existent alias in the ACL configuration might be the
541251881Speter             sign of a typo.  Refuse to perform authz on uncertain rules. */
542251881Speter          if (!alias)
543251881Speter            return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
544251881Speter                                     "An authz rule refers to alias '%s', "
545251881Speter                                     "which is undefined",
546251881Speter                                     &group_user[1]);
547251881Speter        }
548251881Speter    }
549251881Speter
550251881Speter  return SVN_NO_ERROR;
551251881Speter}
552251881Speter
553251881Speter
554251881Speter/* Callback to perform some simple sanity checks on an authz rule.
555251881Speter *
556251881Speter * - If RULE_MATCH_STRING references a group or an alias, verify that
557251881Speter *   the group or alias definition exists.
558251881Speter * - If RULE_MATCH_STRING specifies a token (starts with $), verify
559251881Speter *   that the token name is valid.
560251881Speter * - If RULE_MATCH_STRING is using inversion, verify that it isn't
561251881Speter *   doing it more than once within the one rule, and that it isn't
562251881Speter *   "~*", as that would never match.
563251881Speter * - Check that VALUE part of the rule specifies only allowed rule
564251881Speter *   flag characters ('r' and 'w').
565251881Speter *
566251881Speter * Return TRUE if the rule has no errors. Use BATON for context and
567251881Speter * error reporting.
568251881Speter */
569251881Speterstatic svn_boolean_t authz_validate_rule(const char *rule_match_string,
570251881Speter                                         const char *value,
571251881Speter                                         void *baton,
572251881Speter                                         apr_pool_t *pool)
573251881Speter{
574251881Speter  const char *val;
575251881Speter  const char *match = rule_match_string;
576251881Speter  struct authz_validate_baton *b = baton;
577251881Speter
578251881Speter  /* Make sure the user isn't using double-negatives. */
579251881Speter  if (match[0] == '~')
580251881Speter    {
581251881Speter      /* Bump the pointer past the inversion for the other checks. */
582251881Speter      match++;
583251881Speter
584251881Speter      /* Another inversion is a double negative; we can't not stop. */
585251881Speter      if (match[0] == '~')
586251881Speter        {
587251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
588251881Speter                                     "Rule '%s' has more than one "
589251881Speter                                     "inversion; double negatives are "
590251881Speter                                     "not permitted.",
591251881Speter                                     rule_match_string);
592251881Speter          return FALSE;
593251881Speter        }
594251881Speter
595251881Speter      /* Make sure that the rule isn't "~*", which won't ever match. */
596251881Speter      if (strcmp(match, "*") == 0)
597251881Speter        {
598251881Speter          b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
599251881Speter                                    "Authz rules with match string '~*' "
600251881Speter                                    "are not allowed, because they never "
601251881Speter                                    "match anyone.");
602251881Speter          return FALSE;
603251881Speter        }
604251881Speter    }
605251881Speter
606251881Speter  /* If the rule applies to a group, check its existence. */
607251881Speter  if (match[0] == '@')
608251881Speter    {
609251881Speter      const char *group = &match[1];
610251881Speter
611251881Speter      svn_config_get(b->config, &val, "groups", group, NULL);
612251881Speter
613251881Speter      /* Having a non-existent group in the ACL configuration might be
614251881Speter         the sign of a typo.  Refuse to perform authz on uncertain
615251881Speter         rules. */
616251881Speter      if (!val)
617251881Speter        {
618251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
619251881Speter                                     "An authz rule refers to group "
620251881Speter                                     "'%s', which is undefined",
621251881Speter                                     rule_match_string);
622251881Speter          return FALSE;
623251881Speter        }
624251881Speter    }
625251881Speter
626251881Speter  /* If the rule applies to an alias, check its existence. */
627251881Speter  if (match[0] == '&')
628251881Speter    {
629251881Speter      const char *alias = &match[1];
630251881Speter
631251881Speter      svn_config_get(b->config, &val, "aliases", alias, NULL);
632251881Speter
633251881Speter      if (!val)
634251881Speter        {
635251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
636251881Speter                                     "An authz rule refers to alias "
637251881Speter                                     "'%s', which is undefined",
638251881Speter                                     rule_match_string);
639251881Speter          return FALSE;
640251881Speter        }
641251881Speter     }
642251881Speter
643251881Speter  /* If the rule specifies a token, check its validity. */
644251881Speter  if (match[0] == '$')
645251881Speter    {
646251881Speter      const char *token_name = &match[1];
647251881Speter
648251881Speter      if ((strcmp(token_name, "anonymous") != 0)
649251881Speter       && (strcmp(token_name, "authenticated") != 0))
650251881Speter        {
651251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
652251881Speter                                     "Unrecognized authz token '%s'.",
653251881Speter                                     rule_match_string);
654251881Speter          return FALSE;
655251881Speter        }
656251881Speter    }
657251881Speter
658251881Speter  val = value;
659251881Speter
660251881Speter  while (*val)
661251881Speter    {
662251881Speter      if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
663251881Speter        {
664251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
665251881Speter                                     "The character '%c' in rule '%s' is not "
666251881Speter                                     "allowed in authz rules", *val,
667251881Speter                                     rule_match_string);
668251881Speter          return FALSE;
669251881Speter        }
670251881Speter
671251881Speter      ++val;
672251881Speter    }
673251881Speter
674251881Speter  return TRUE;
675251881Speter}
676251881Speter
677251881Speter/* Callback to check ALIAS's definition for validity.  Use
678251881Speter   BATON for context and error reporting. */
679251881Speterstatic svn_boolean_t authz_validate_alias(const char *alias,
680251881Speter                                          const char *value,
681251881Speter                                          void *baton,
682251881Speter                                          apr_pool_t *pool)
683251881Speter{
684251881Speter  /* No checking at the moment, every alias is valid */
685251881Speter  return TRUE;
686251881Speter}
687251881Speter
688251881Speter
689251881Speter/* Callback to check GROUP's definition for cyclic dependancies.  Use
690251881Speter   BATON for context and error reporting. */
691251881Speterstatic svn_boolean_t authz_validate_group(const char *group,
692251881Speter                                          const char *value,
693251881Speter                                          void *baton,
694251881Speter                                          apr_pool_t *pool)
695251881Speter{
696251881Speter  struct authz_validate_baton *b = baton;
697251881Speter
698251881Speter  b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
699251881Speter  if (b->err)
700251881Speter    return FALSE;
701251881Speter
702251881Speter  return TRUE;
703251881Speter}
704251881Speter
705251881Speter
706251881Speter/* Callback to check the contents of the configuration section given
707251881Speter   by NAME.  Use BATON for context and error reporting. */
708251881Speterstatic svn_boolean_t authz_validate_section(const char *name,
709251881Speter                                            void *baton,
710251881Speter                                            apr_pool_t *pool)
711251881Speter{
712251881Speter  struct authz_validate_baton *b = baton;
713251881Speter
714251881Speter  /* Use the group checking callback for the "groups" section... */
715251881Speter  if (strcmp(name, "groups") == 0)
716251881Speter    svn_config_enumerate2(b->config, name, authz_validate_group,
717251881Speter                          baton, pool);
718251881Speter  /* ...and the alias checking callback for "aliases"... */
719251881Speter  else if (strcmp(name, "aliases") == 0)
720251881Speter    svn_config_enumerate2(b->config, name, authz_validate_alias,
721251881Speter                          baton, pool);
722251881Speter  /* ...but for everything else use the rule checking callback. */
723251881Speter  else
724251881Speter    {
725251881Speter      /* Validate the section's name. Skip the optional REPOS_NAME. */
726251881Speter      const char *fspath = strchr(name, ':');
727251881Speter      if (fspath)
728251881Speter        fspath++;
729251881Speter      else
730251881Speter        fspath = name;
731251881Speter      if (! svn_fspath__is_canonical(fspath))
732251881Speter        {
733251881Speter          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
734251881Speter                                     "Section name '%s' contains non-canonical "
735251881Speter                                     "fspath '%s'",
736251881Speter                                     name, fspath);
737251881Speter          return FALSE;
738251881Speter        }
739251881Speter
740251881Speter      svn_config_enumerate2(b->config, name, authz_validate_rule,
741251881Speter                            baton, pool);
742251881Speter    }
743251881Speter
744251881Speter  if (b->err)
745251881Speter    return FALSE;
746251881Speter
747251881Speter  return TRUE;
748251881Speter}
749251881Speter
750251881Speter
751251881Speter/* Walk the configuration in AUTHZ looking for any errors. */
752251881Speterstatic svn_error_t *
753251881Speterauthz_validate(svn_authz_t *authz, apr_pool_t *pool)
754251881Speter{
755251881Speter  struct authz_validate_baton baton = { 0 };
756251881Speter
757251881Speter  baton.err = SVN_NO_ERROR;
758251881Speter  baton.config = authz->cfg;
759251881Speter
760251881Speter  /* Step through the entire rule file stopping on error. */
761251881Speter  svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
762251881Speter                                 &baton, pool);
763251881Speter  SVN_ERR(baton.err);
764251881Speter
765251881Speter  return SVN_NO_ERROR;
766251881Speter}
767251881Speter
768251881Speter
769251881Speter/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
770251881Speter * file placing the result into CFG_P allocated in POOL.
771251881Speter *
772251881Speter * If DIRENT cannot be parsed as a config file then an error is returned.  The
773251881Speter * contents of CFG_P is then undefined.  If MUST_EXIST is TRUE, a missing
774251881Speter * authz file is also an error.
775251881Speter *
776251881Speter * SCRATCH_POOL will be used for temporary allocations. */
777251881Speterstatic svn_error_t *
778251881Speterauthz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
779251881Speter                          svn_boolean_t must_exist,
780251881Speter                          apr_pool_t *result_pool, apr_pool_t *scratch_pool)
781251881Speter{
782251881Speter  svn_error_t *err;
783251881Speter  svn_repos_t *repos;
784251881Speter  const char *repos_root_dirent;
785251881Speter  const char *fs_path;
786251881Speter  svn_fs_t *fs;
787251881Speter  svn_fs_root_t *root;
788251881Speter  svn_revnum_t youngest_rev;
789251881Speter  svn_node_kind_t node_kind;
790251881Speter  svn_stream_t *contents;
791251881Speter
792251881Speter  /* Search for a repository in the full path. */
793251881Speter  repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
794251881Speter  if (!repos_root_dirent)
795251881Speter    return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
796251881Speter                             "Unable to find repository at '%s'", dirent);
797251881Speter
798251881Speter  /* Attempt to open a repository at repos_root_dirent. */
799251881Speter  SVN_ERR(svn_repos_open2(&repos, repos_root_dirent, NULL, scratch_pool));
800251881Speter
801251881Speter  fs_path = &dirent[strlen(repos_root_dirent)];
802251881Speter
803251881Speter  /* Root path is always a directory so no reason to go any further */
804251881Speter  if (*fs_path == '\0')
805251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
806251881Speter                             "'/' is not a file in repo '%s'",
807251881Speter                             repos_root_dirent);
808251881Speter
809251881Speter  /* We skip some things that are non-important for how we're going to use
810251881Speter   * this repo connection.  We do not set any capabilities since none of
811251881Speter   * the current ones are important for what we're doing.  We also do not
812251881Speter   * setup the environment that repos hooks would run under since we won't
813251881Speter   * be triggering any. */
814251881Speter
815251881Speter  /* Get the filesystem. */
816251881Speter  fs = svn_repos_fs(repos);
817251881Speter
818251881Speter  /* Find HEAD and the revision root */
819251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
820251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
821251881Speter
822251881Speter  SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
823251881Speter  if (node_kind == svn_node_none)
824251881Speter    {
825251881Speter      if (!must_exist)
826251881Speter        {
827251881Speter          SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool));
828251881Speter          return SVN_NO_ERROR;
829251881Speter        }
830251881Speter      else
831251881Speter        {
832251881Speter          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
833251881Speter                                   "'%s' path not found in repo '%s'", fs_path,
834251881Speter                                   repos_root_dirent);
835251881Speter        }
836251881Speter    }
837251881Speter  else if (node_kind != svn_node_file)
838251881Speter    {
839251881Speter      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
840251881Speter                               "'%s' is not a file in repo '%s'", fs_path,
841251881Speter                               repos_root_dirent);
842251881Speter    }
843251881Speter
844251881Speter  SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
845251881Speter  err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool);
846251881Speter
847251881Speter  /* Add the URL to the error stack since the parser doesn't have it. */
848251881Speter  if (err != SVN_NO_ERROR)
849251881Speter    return svn_error_createf(err->apr_err, err,
850251881Speter                             "Error while parsing config file: '%s' in repo '%s':",
851251881Speter                             fs_path, repos_root_dirent);
852251881Speter
853251881Speter  return SVN_NO_ERROR;
854251881Speter}
855251881Speter
856251881Speter/* Given a PATH which might be a relative repo URL (^/), an absolute
857251881Speter * local repo URL (file://), an absolute path outside of the repo
858251881Speter * or a location in the Windows registry.
859251881Speter *
860251881Speter * Retrieve the configuration data that PATH points at and parse it into
861251881Speter * CFG_P allocated in POOL.
862251881Speter *
863251881Speter * If PATH cannot be parsed as a config file then an error is returned.  The
864251881Speter * contents of CFG_P is then undefined.  If MUST_EXIST is TRUE, a missing
865251881Speter * authz file is also an error.
866251881Speter *
867251881Speter * REPOS_ROOT points at the root of the repos you are
868251881Speter * going to apply the authz against, can be NULL if you are sure that you
869251881Speter * don't have a repos relative URL in PATH. */
870251881Speterstatic svn_error_t *
871251881Speterauthz_retrieve_config(svn_config_t **cfg_p, const char *path,
872251881Speter                      svn_boolean_t must_exist, apr_pool_t *pool)
873251881Speter{
874251881Speter  if (svn_path_is_url(path))
875251881Speter    {
876251881Speter      const char *dirent;
877251881Speter      svn_error_t *err;
878251881Speter      apr_pool_t *scratch_pool = svn_pool_create(pool);
879251881Speter
880251881Speter      err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
881251881Speter
882251881Speter      if (err == SVN_NO_ERROR)
883251881Speter        err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, pool,
884251881Speter                                         scratch_pool);
885251881Speter
886251881Speter      /* Close the repos and streams we opened. */
887251881Speter      svn_pool_destroy(scratch_pool);
888251881Speter
889251881Speter      return err;
890251881Speter    }
891251881Speter  else
892251881Speter    {
893251881Speter      /* Outside of repo file or Windows registry*/
894251881Speter      SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool));
895251881Speter    }
896251881Speter
897251881Speter  return SVN_NO_ERROR;
898251881Speter}
899251881Speter
900251881Speter
901251881Speter/* Callback to copy (name, value) group into the "groups" section
902251881Speter   of another configuration. */
903251881Speterstatic svn_boolean_t
904251881Speterauthz_copy_group(const char *name, const char *value,
905251881Speter                 void *baton, apr_pool_t *pool)
906251881Speter{
907251881Speter  svn_config_t *authz_cfg = baton;
908251881Speter
909251881Speter  svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
910251881Speter
911251881Speter  return TRUE;
912251881Speter}
913251881Speter
914251881Speter/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
915251881Speter * If AUTHZ already contains any group definition, report an error.
916251881Speter * Use POOL for temporary allocations. */
917251881Speterstatic svn_error_t *
918251881Speterauthz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
919251881Speter                  apr_pool_t *pool)
920251881Speter{
921251881Speter  /* Easy out: we prohibit local groups in the authz file when global
922251881Speter     groups are being used. */
923251881Speter  if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
924251881Speter    {
925251881Speter      return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
926251881Speter                              "Authz file cannot contain any groups "
927251881Speter                              "when global groups are being used.");
928251881Speter    }
929251881Speter
930251881Speter  svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
931251881Speter                        authz_copy_group, authz->cfg, pool);
932251881Speter
933251881Speter  return SVN_NO_ERROR;
934251881Speter}
935251881Speter
936251881Spetersvn_error_t *
937251881Spetersvn_repos__authz_read(svn_authz_t **authz_p, const char *path,
938251881Speter                      const char *groups_path, svn_boolean_t must_exist,
939251881Speter                      svn_boolean_t accept_urls, apr_pool_t *pool)
940251881Speter{
941251881Speter  svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
942251881Speter
943251881Speter  /* Load the authz file */
944251881Speter  if (accept_urls)
945251881Speter    SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool));
946251881Speter  else
947251881Speter    SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool));
948251881Speter
949251881Speter  if (groups_path)
950251881Speter    {
951251881Speter      svn_config_t *groups_cfg;
952251881Speter      svn_error_t *err;
953251881Speter
954251881Speter      /* Load the groups file */
955251881Speter      if (accept_urls)
956251881Speter        SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
957251881Speter                                      pool));
958251881Speter      else
959251881Speter        SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
960251881Speter                                 TRUE, TRUE, pool));
961251881Speter
962251881Speter      /* Copy the groups from groups_cfg into authz. */
963251881Speter      err = authz_copy_groups(authz, groups_cfg, pool);
964251881Speter
965251881Speter      /* Add the paths to the error stack since the authz_copy_groups
966251881Speter         routine knows nothing about them. */
967251881Speter      if (err != SVN_NO_ERROR)
968251881Speter        return svn_error_createf(err->apr_err, err,
969251881Speter                                 "Error reading authz file '%s' with "
970251881Speter                                 "groups file '%s':", path, groups_path);
971251881Speter    }
972251881Speter
973251881Speter  /* Make sure there are no errors in the configuration. */
974251881Speter  SVN_ERR(authz_validate(authz, pool));
975251881Speter
976251881Speter  *authz_p = authz;
977251881Speter  return SVN_NO_ERROR;
978251881Speter}
979251881Speter
980251881Speter
981251881Speter
982251881Speter/*** Public functions. ***/
983251881Speter
984251881Spetersvn_error_t *
985251881Spetersvn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
986251881Speter                      const char *groups_path, svn_boolean_t must_exist,
987251881Speter                      apr_pool_t *pool)
988251881Speter{
989251881Speter  return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
990251881Speter                               TRUE, pool);
991251881Speter}
992251881Speter
993251881Speter
994251881Spetersvn_error_t *
995251881Spetersvn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
996251881Speter                      svn_stream_t *groups_stream, apr_pool_t *pool)
997251881Speter{
998251881Speter  svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
999251881Speter
1000251881Speter  /* Parse the authz stream */
1001251881Speter  SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
1002251881Speter
1003251881Speter  if (groups_stream)
1004251881Speter    {
1005251881Speter      svn_config_t *groups_cfg;
1006251881Speter
1007251881Speter      /* Parse the groups stream */
1008251881Speter      SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
1009251881Speter
1010251881Speter      SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
1011251881Speter    }
1012251881Speter
1013251881Speter  /* Make sure there are no errors in the configuration. */
1014251881Speter  SVN_ERR(authz_validate(authz, pool));
1015251881Speter
1016251881Speter  *authz_p = authz;
1017251881Speter  return SVN_NO_ERROR;
1018251881Speter}
1019251881Speter
1020251881Speter
1021251881Spetersvn_error_t *
1022251881Spetersvn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
1023251881Speter                             const char *path, const char *user,
1024251881Speter                             svn_repos_authz_access_t required_access,
1025251881Speter                             svn_boolean_t *access_granted,
1026251881Speter                             apr_pool_t *pool)
1027251881Speter{
1028251881Speter  const char *current_path;
1029251881Speter
1030251881Speter  if (!repos_name)
1031251881Speter    repos_name = "";
1032251881Speter
1033251881Speter  /* If PATH is NULL, check if the user has *any* access. */
1034251881Speter  if (!path)
1035251881Speter    {
1036251881Speter      *access_granted = authz_get_any_access(authz->cfg, repos_name,
1037251881Speter                                             user, required_access, pool);
1038251881Speter      return SVN_NO_ERROR;
1039251881Speter    }
1040251881Speter
1041251881Speter  /* Sanity check. */
1042251881Speter  SVN_ERR_ASSERT(path[0] == '/');
1043251881Speter
1044251881Speter  /* Determine the granted access for the requested path. */
1045251881Speter  path = svn_fspath__canonicalize(path, pool);
1046251881Speter  current_path = path;
1047251881Speter
1048251881Speter  while (!authz_get_path_access(authz->cfg, repos_name,
1049251881Speter                                current_path, user,
1050251881Speter                                required_access,
1051251881Speter                                access_granted,
1052251881Speter                                pool))
1053251881Speter    {
1054251881Speter      /* Stop if the loop hits the repository root with no
1055251881Speter         results. */
1056251881Speter      if (current_path[0] == '/' && current_path[1] == '\0')
1057251881Speter        {
1058251881Speter          /* Deny access by default. */
1059251881Speter          *access_granted = FALSE;
1060251881Speter          return SVN_NO_ERROR;
1061251881Speter        }
1062251881Speter
1063251881Speter      /* Work back to the parent path. */
1064251881Speter      current_path = svn_fspath__dirname(current_path, pool);
1065251881Speter    }
1066251881Speter
1067251881Speter  /* If the caller requested recursive access, we need to walk through
1068251881Speter     the entire authz config to see whether any child paths are denied
1069251881Speter     to the requested user. */
1070251881Speter  if (*access_granted && (required_access & svn_authz_recursive))
1071251881Speter    *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
1072251881Speter                                            user, required_access, pool);
1073251881Speter
1074251881Speter  return SVN_NO_ERROR;
1075251881Speter}
1076