📄 serve.c
字号:
/*
* serve.c : Functions for serving the Subversion protocol
*
* ====================================================================
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include <apr_general.h>
#include <apr_lib.h>
#include <apr_strings.h>
#include <apr_network_io.h>
#include <apr_user.h>
#include <apr_file_info.h>
#include <apr_md5.h>
#include <svn_types.h>
#include <svn_string.h>
#include <svn_pools.h>
#include <svn_error.h>
#include <svn_ra.h>
#include <svn_ra_svn.h>
#include <svn_repos.h>
#include <svn_path.h>
#include <svn_time.h>
#include <svn_utf.h>
#include <svn_md5.h>
#include <svn_config.h>
#include "server.h"
typedef struct {
svn_repos_t *repos;
svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */
svn_config_t *cfg; /* Parsed repository svnserve.conf */
svn_config_t *pwdb; /* Parsed password database */
const char *realm; /* Authentication realm */
const char *repos_url; /* URL to base of repository */
const char *fs_path; /* Decoded base path inside repository */
const char *user;
svn_boolean_t tunnel; /* Tunneled through login agent */
const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */
svn_boolean_t read_only; /* Disallow write access (global flag) */
int protocol_version;
} server_baton_t;
typedef struct {
svn_revnum_t *new_rev;
const char **date;
const char **author;
} commit_callback_baton_t;
typedef struct {
server_baton_t *sb;
const char *repos_url; /* Decoded repository URL. */
void *report_baton;
svn_error_t *err;
} report_driver_baton_t;
typedef struct {
const char *fs_path;
svn_ra_svn_conn_t *conn;
} log_baton_t;
typedef struct {
svn_ra_svn_conn_t *conn;
apr_pool_t *pool; /* Pool provided in the handler call. */
} file_revs_baton_t;
enum authn_type { UNAUTHENTICATED, AUTHENTICATED };
enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS };
/* Verify that URL is inside REPOS_URL and get its fs path. Assume that
REPOS_URL and URL are already URI-decoded. */
static svn_error_t *get_fs_path(const char *repos_url, const char *url,
const char **fs_path, apr_pool_t *pool)
{
apr_size_t len;
len = strlen(repos_url);
if (strncmp(url, repos_url, len) != 0)
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
"'%s' is not the same repository as '%s'",
url, repos_url);
*fs_path = url + len;
return SVN_NO_ERROR;
}
/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
static enum access_type get_access(server_baton_t *b, enum authn_type auth)
{
const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
SVN_CONFIG_OPTION_ANON_ACCESS;
const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
enum access_type result;
svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
}
static enum access_type current_access(server_baton_t *b)
{
return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
}
static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
server_baton_t *b, enum access_type required)
{
if (get_access(b, UNAUTHENTICATED) >= required)
SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
return SVN_NO_ERROR;
}
/* Authenticate, once the client has chosen a mechanism and possibly
* sent an initial mechanism token. On success, set *success to true
* and b->user to the authenticated username (or NULL for anonymous).
* On authentication failure, report failure to the client and set
* *success to FALSE. On communications failure, return an error. */
static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
const char *mech, const char *mecharg,
server_baton_t *b, enum access_type required,
svn_boolean_t *success)
{
*success = FALSE;
if (get_access(b, AUTHENTICATED) >= required
&& b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
{
b->user = b->tunnel_user;
if (*mecharg && strcmp(mecharg, b->user) != 0)
return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
"Requested username does not match");
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
*success = TRUE;
return SVN_NO_ERROR;
}
if (get_access(b, UNAUTHENTICATED) >= required
&& strcmp(mech, "ANONYMOUS") == 0)
{
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
*success = TRUE;
return SVN_NO_ERROR;
}
if (get_access(b, AUTHENTICATED) >= required
&& b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
return svn_ra_svn_cram_server(conn, pool, b->pwdb, &b->user, success);
return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
"Must authenticate with listed mechanism");
}
/* Perform an authentication request in order to get an access level of
* REQUIRED or higher. Since the client may escape the authentication
* exchange, the caller should check current_access(b) to see if
* authentication succeeded. */
static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
server_baton_t *b, enum access_type required)
{
svn_boolean_t success;
const char *mech, *mecharg;
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
SVN_ERR(send_mechs(conn, pool, b, required));
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
do
{
SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
if (!*mech)
break;
SVN_ERR(auth(conn, pool, mech, mecharg, b, required, &success));
}
while (!success);
return SVN_NO_ERROR;
}
/* Send a trivial auth request, listing no mechanisms. */
static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
apr_pool_t *pool, server_baton_t *b)
{
if (b->protocol_version < 2)
return SVN_NO_ERROR;
return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
}
static svn_error_t *must_have_write_access(svn_ra_svn_conn_t *conn,
apr_pool_t *pool, server_baton_t *b)
{
if (current_access(b) == WRITE_ACCESS)
return trivial_auth_request(conn, pool, b);
/* If we can get write access by authenticating, try that. */
if (b->user == NULL && get_access(b, AUTHENTICATED) == WRITE_ACCESS
&& (b->tunnel_user || b->pwdb) && b->protocol_version >= 2)
SVN_ERR(auth_request(conn, pool, b, WRITE_ACCESS));
if (current_access(b) != WRITE_ACCESS)
return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
"Connection is read-only"), NULL);
return SVN_NO_ERROR;
}
/* --- REPORTER COMMAND SET --- */
/* To allow for pipelining, reporter commands have no reponses. If we
* get an error, we ignore all subsequent reporter commands and return
* the error finish_report, to be handled by the calling command.
*/
static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
report_driver_baton_t *b = baton;
const char *path;
svn_revnum_t rev;
svn_boolean_t start_empty;
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb",
&path, &rev, &start_empty));
path = svn_path_canonicalize(path, pool);
if (!b->err)
b->err = svn_repos_set_path(b->report_baton, path, rev, start_empty, pool);
return SVN_NO_ERROR;
}
static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
report_driver_baton_t *b = baton;
const char *path;
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
path = svn_path_canonicalize(path, pool);
if (!b->err)
b->err = svn_repos_delete_path(b->report_baton, path, pool);
return SVN_NO_ERROR;
}
static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
report_driver_baton_t *b = baton;
const char *path, *url, *fs_path;
svn_revnum_t rev;
svn_boolean_t start_empty;
SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb",
&path, &url, &rev, &start_empty));
path = svn_path_canonicalize(path, pool);
url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
if (!b->err)
b->err = get_fs_path(b->repos_url, url, &fs_path, pool);
if (!b->err)
b->err = svn_repos_link_path(b->report_baton, path, fs_path, rev,
start_empty, pool);
return SVN_NO_ERROR;
}
static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
report_driver_baton_t *b = baton;
/* No arguments to parse. */
SVN_ERR(trivial_auth_request(conn, pool, b->sb));
if (!b->err)
b->err = svn_repos_finish_report(b->report_baton, pool);
return SVN_NO_ERROR;
}
static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
apr_array_header_t *params, void *baton)
{
report_driver_baton_t *b = baton;
/* No arguments to parse. */
svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
return SVN_NO_ERROR;
}
static const svn_ra_svn_cmd_entry_t report_commands[] = {
{ "set-path", set_path },
{ "delete-path", delete_path },
{ "link-path", link_path },
{ "finish-report", finish_report, TRUE },
{ "abort-report", abort_report, TRUE },
{ NULL }
};
/* Accept a report from the client, drive the network editor with the
* result, and then write an empty command response. If there is a
* non-protocol failure, accept_report will abort the edit and return
* a command error to be reported by handle_commands(). */
static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
server_baton_t *b, svn_revnum_t rev,
const char *target, const char *tgt_path,
svn_boolean_t text_deltas,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry)
{
const svn_delta_editor_t *editor;
void *edit_baton, *report_baton;
report_driver_baton_t rb;
svn_error_t *err;
/* Make an svn_repos report baton. Tell it to drive the network editor
* when the report is complete. */
svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
SVN_CMD_ERR(svn_repos_begin_report(&report_baton, rev, b->user, b->repos,
b->fs_path, target, tgt_path, text_deltas,
recurse, ignore_ancestry, editor,
edit_baton, NULL, NULL, pool));
rb.sb = b;
rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
rb.report_baton = report_baton;
rb.err = NULL;
err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb);
if (err)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -