blob: 62b6df387f44c460803f2ea69974af915354e5e7 [file] [log] [blame]
/*
* server_fs.c : wrappers around filesystem calls, and other things
*
* ================================================================
* Copyright (c) 2000 Collab.Net. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement: "This product includes
* software developed by Collab.Net (http://www.Collab.Net/)."
* Alternately, this acknowlegement may appear in the software itself, if
* and wherever such third-party acknowlegements normally appear.
*
* 4. The hosted project names must not be used to endorse or promote
* products derived from this software without prior written
* permission. For written permission, please contact info@collab.net.
*
* 5. Products derived from this software may not use the "Tigris" name
* nor may "Tigris" appear in their names without prior written
* permission of Collab.Net.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of Collab.Net.
*/
/* **************************************************************
The main idea here is that filesystem calls are "wrappered", giving
the server library the chance to check for authorization and
execute any policies that may supercede the request.
NOTE: The "repos" argument in exported routines can be either a
nickname (specified in the svn.conf file) or the full pathname of a
repository.
****************************************************************/
#include <svn_svr.h> /* declarations for this library */
#include <svn_fs.h> /* the Subversion filesystem API */
#include <svn_string.h> /* Subversion bytestring routines */
/* svr__expand_repos_name : NOT EXPORTED.
Input: a policy and a repository name. Repository name *might* be
an abbreviated nickname (listed in `svn.conf' and in the policy
structure)
Returns: the full (proper) repository pathname.
*/
svn_string_t *
svr__expand_repos_name (svn_svr_policy_t *policy,
svn_string_t *repos)
{
/* Loop through policy->repos_aliases hash.
If there's a match, return new bytestring containing hash value.
If there's no match, return original string pointer.
*/
return repos;
}
/*
svr_plugin_authorize()
Loops through all authorization plugins, checking for success.
Input: policy + {repos, user, action, path} group
Returns: ptr to error structure (if not authorized)
or 0 if authorized!
*/
svn_error_t *
svn_svr_plugin_authorize (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_svr_action_t *action,
unsigned long ver,
svn_string_t *path)
{
int i;
svn_error_t *err;
svn_svr_plugin_t *current_plugin;
(svn_error_t *) (* current_auth_hook) (svn_string_t *r, svn_user_t *u,
svn_svr_action_t *a, unsigned long v,
svr_string_t *p);
/* Next: loop through our policy's array of plugins... */
for (i = 0; i < (policy->plugins->nelts); i++)
{
/* grab a plugin from the list of plugins */
current_plugin = AP_ARRAY_GET_ITEM (policy->plugins, i,
(svn_svr_plugin_t *));
/* grab the authorization routine from this plugin */
current_auth_hook = current_plugin->authorization_hook;
if (current_auth_hook != NULL)
{
/* Call the authorization routine, giving it a chance to
kill our authorization assumption */
err = (*my_hook) (repos, user, action, ver, path);
}
if (err) /* bail out if we fail at any point in the loop */
{
return err;
}
}
/* If all auth_hooks are successful, double-check that
user->svn_username is actually filled in!
(A good auth_hook should fill it in automatically, though.)
*/
if (svn_string_isempty (user->svn_username))
{
/* Using the policy's memory pool, duplicate the auth_username
string and assign it to svn_username */
user->svn_username = svn_string_dup (user->auth_username,
policy->pool);
}
return 0; /* successfully authorized to perform the action! */
}
/* svr__policy_authorize()
See if general server `policy' allows an action.
Input: policy + {repos, user, action, ver, path} group
Returns: error structure (if authorization fails)
0 (if authorization succeeds)
*/
svn_error_t *
svn_svr_policy_authorize (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_svr_action_t *action,
unsigned long ver,
svn_string_t *path)
{
/* BIG TODO: loop through policy->global_restrictions array,
interpreting each restriction and checking authorization */
return 0;
}
/*
Convenience routine -- calls the other two authorization routines.
This routine is called by each "wrappered" filesystem call in this
library.
Input: policy + {repos, user, action, ver, path} group
Returns: error structure (if authorization fails)
0 (if authorization succeeds)
*/
svn_error_t *
svn_svr_authorize (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_svr_action_t *action,
unsigned long ver,
svn_string_t *path)
{
svn_error_t *err;
err = svn_svr_policy_authorize (policy, repos, user, action, ver, path);
if (err)
{
return err;
}
err = svn_svr_plugin_authorize (policy, repos, user, action, ver, path);
if (err)
{
return err;
}
return 0; /* successfully authorized! */
}
/*========================================================================
READING HISTORY ARRAY.
These routines retrieve info from a repository's history array.
They return FALSE if authorization fails.
*/
/* Returns latest version of the repository */
svn_ver_t *
svn_svr_latest (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = latest;
authorized = svr__authorize (policy, repository, user,
my_action, NULL, NULL);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_latest (repository,
user->svn_username));
}
}
/* Given a version, return a certain property value */
svn_string_t *
svn_svr_get_ver_prop (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_string_t *user,
unsigned long ver,
svn_string_t *propname)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_ver_prop;
authorized = svr__authorize (policy, repository, user,
my_action, ver, NULL);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_ver_prop (repository,
user->svn_username,
ver,
propname));
}
}
/* Retrieve entire proplist of a version */
svn_proplist_t *
svn_svr_get_ver_proplist (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_string_t *user,
unsigned long ver)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_ver_proplist;
authorized = svr__authorize (policy, repository, user,
my_action, ver, NULL);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_ver_proplist (repository,
user->svn_username,
ver));
}
}
/* Return the property names of a version.
TODO: Should this return something other than a proplist?
If not, how is it any different than get_ver_proplist()?
*/
svn_proplist_t *
svn_svr_get_ver_propnames (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_string_t *user,
unsigned long ver)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_ver_propnames;
authorized = svr__authorize (policy, repository, user,
my_action, ver, NULL);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_ver_propnames (repository,
user->svn_username,
ver));
}
}
/*========================================================================
READING NODES.
These routines retrieve info from a node in the filesystem.
They return FALSE if authorization fails.
*/
/* Return the entire contents of a node */
svn_node_t *
svn_svr_read (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = read;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_read (repository,
user->svn_username,
ver,
path));
}
}
/* Return the value of a node's propery */
svn_string_t *
svn_svr_get_node_prop (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path,
svn_string_t *propname)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_node_prop;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_node_prop (repository,
user->svn_username,
ver,
path,
propname));
}
}
/* Get the value of a dirent's property */
svn_string_t *
svn_svr_get_dirent_prop (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path,
svn_string_t *propname)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_dirent_prop;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_dirent_prop (repository,
user->svn_username,
ver,
path,
propname));
}
}
/* Get a node's entire proplist */
svn_proplist_t *
svn_svr_get_node_proplist (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_node_proplist;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_node_proplist (repository,
user->svn_username,
ver,
path));
}
}
/* Get a dirent's entire proplist */
svn_proplist_t *
svn_svr_get_dirent_proplist (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_dirent_proplist;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_dirent_proplist (repository,
user->svn_username,
ver,
path));
}
}
/* Get a list of a node's property names */
svn_proplist_t *
svn_svr_get_node_propnames (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_node_propnames;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_node_propnames (repository,
user->svn_username,
ver,
path));
}
}
/* Get a list of a dirent's property names */
svn_proplist_t *
svn_svr_get_dirent_propnames (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver,
svn_string_t *path)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_dirent_propnames;
authorized = svr__authorize (policy, repository, user,
my_action, ver, path);
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_dirent_propnames (repository,
user->svn_username,
ver,
path));
}
}
/*========================================================================
WRITING.
These routines for writing deltas into the filesystem.
They return FALSE if authorization fails.
*/
/* Submit a skelta for approval; on success, returns a transaction
token. */
svn_token_t
svn_svr_submit (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_skelta_t *skelta)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = submit;
authorized = svr__authorize (policy, repository, user,
my_action, NULL, NULL);
/* TODO: perhaps the "path" argument to svr__authorize should be
somehow read out of the skelta? */
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_submit (repository,
user->svn_username,
skelta));
}
}
/* Use the token to apply the delta to the filesystem.
On success, returns the new version number of the repository. */
unsigned long
svn_svr_write (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_delta_t *delta,
svn_token_t token)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = write;
authorized = svr__authorize (policy, repository, user,
my_action, NULL, NULL);
/* TODO: perhaps the "path" argument to svr__authorize should be
somehow read out of the delta?
Actually, nobody can call this routine without a token, which
means they've already been authorized to submit(). Is there any
point in differentiating authorization between submit() and
write()? */
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_write (repository,
user->svn_username,
delta,
token));
}
}
/* Abandon an approved, pending token */
svn_boolean_t
svn_svr_abandon (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_token_t token)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = abandon;
authorized = svr__authorize (policy, repository, user,
my_action, NULL, NULL);
/* TODO:
What does it mean to have (or *not* have) permission to abandon an
approved token? :)
*/
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_abandon (repository,
user->svn_username,
token));
}
}
/*========================================================================
DIFFERENCE QUERIES.
Report information on differences between objects in the repository.
They return FALSE if authorization fails.
*/
/* Return a delta that describes the difference between two trees in
the repository. */
svn_delta_t *
svn_svr_get_delta (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver1,
svn_string_t *path1,
unsigned long ver2,
svn_string_t *path2)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_delta;
authorized = svr__authorize (policy, repository, user,
my_action, ver1, path1);
authorized = svr__authorize (policy, repository, user,
my_action, ver2, path2);
/*
TODO: notice I'm calling the authorize routine twice, checking
*both* paths and versions. Is this right?
*/
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_delta (repository,
user->svn_username,
ver1, path1, ver2, path2));
}
}
/* Return a GNU diff describing the difference between two files */
svn_diff_t *
svn_svr_get_diff (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
unsigned long ver1,
svn_string_t *path1,
unsigned long ver2,
svn_string_t *path2)
{
/* Convert "repos" into real pathname */
svn_string_t *repository = svr__expand_repos_name (policy, repos);
/* Check authorization, both server policy & auth hooks */
svn_boolean_t authorized = FALSE;
svn_svr_action_t my_action = get_diff;
authorized = svr__authorize (policy, repository, user,
my_action, ver1, path1);
authorized = svr__authorize (policy, repository, user,
my_action, ver2, path2);
/*
TODO: notice I'm calling the authorize routine twice, checking
*both* paths and versions. Is this right?
*/
if (! authorized)
{
/* Generate CUSTOM Subversion errno: */
svn_handle_error (svn_create_error (SVN_ERR_NOT_AUTHORIZED,
SVN_NON_FATAL,
policy->pool));
return FALSE;
}
else
{
/* Do filesystem call with "canonical" username */
return (svn_fs_get_diff (repository,
user->svn_username,
ver1, path1, ver2, path2));
}
}
/*========================================================================
STATUS / UPDATE
The status() and update() routines are the only ones which aren't
simple wrappers for the filesystem API. They make repeated small
calls to svn_fs_cmp() and svn_fs_get_delta() respectively (see
<svn_fs.h>)
They return FALSE if authorization fails.
*/
/* Input: a skelta describing working copy's current tree
Output: a skelta describing exactly how the tree is out of date
*/
svn_skelta_t *
svn_svr_get_status (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_skelta_t *skelta)
{
/* Can't do anything here till we have a working delta/skelta library.
We would iterate over the skelta and call svn_fs_cmp() on each
file to check for up-to-date-ness. Then we'd built a new skelta
to send back the results. */
}
/* Input: a skelta describing working copy's current tree.
Output: a delta which, when applied, will actually update working
copy's tree to latest version.
*/
svn_delta_t *
svn_svr_get_update (svn_svr_policies_t *policy,
svn_string_t *repos,
svn_user_t *user,
svn_skelta_t *skelta)
{
/* Can't do anything here till we have a working delta/skelta library.
We would iterate over the skelta and call svn_fs_get_delta() on
each file. Then we'd built a new composite delta to send back.
*/
}
/* --------------------------------------------------------------
* local variables:
* eval: (load-file "../svn-dev.el")
* end: */