blob: 0514a8eea318f3ea1d13afb17826892bf0759b1b [file] [log] [blame]
/*
* session.c : routines for maintaining sessions state (to the DAV server)
*
* ====================================================================
* Copyright (c) 2000-2002 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/.
* ====================================================================
*/
#include <apr_pools.h>
#include <apr_fnmatch.h>
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include <apr_general.h>
#include <ne_socket.h>
#include <ne_request.h>
#include <ne_uri.h>
#include <ne_auth.h>
#include "svn_error.h"
#include "svn_ra.h"
#include "svn_config.h"
#include "svn_version.h"
#include "ra_dav.h"
/* a cleanup routine attached to the pool that contains the RA session
baton. */
static apr_status_t cleanup_session(void *sess)
{
ne_session_destroy(sess);
return APR_SUCCESS;
}
/* A neon-session callback to 'pull' authentication data when
challenged. In turn, this routine 'pulls' the data from the client
callbacks if needed. */
static int request_auth(void *userdata, const char *realm, int attempt,
char *username, char *password)
{
void *a, *auth_baton;
char *uname, *pword;
svn_ra_simple_password_authenticator_t *authenticator = NULL;
svn_ra_session_t *ras = userdata;
if (attempt > 1)
{
/* Only use two retries. */
return -1;
}
/* ### my only worry is that we're not catching any svn_errors from
get_authenticator, get_username, get_password... */
/* pull the username and password from the client */
ras->callbacks->get_authenticator (&a, &auth_baton,
svn_ra_auth_simple_password,
ras->callback_baton, ras->pool);
authenticator = (svn_ra_simple_password_authenticator_t *) a;
authenticator->get_user_and_pass (&uname, &pword,
auth_baton,
/* possibly force a user-prompt: */
attempt ? TRUE : FALSE,
ras->pool);
/* ### silently truncates username/password to 256 chars. */
apr_cpystrn(username, uname, NE_ABUFSIZ);
apr_cpystrn(password, pword, NE_ABUFSIZ);
return 0;
}
/* A neon-session callback to validate the SSL certificate when the CA
is unknown or there are other SSL certificate problems. */
static int ssl_set_verify_callback(void *userdata, int failures,
const ne_ssl_certificate *cert)
{
/* XXX Right now this accepts any SSL server certificates.
Subversion should perform checks of the SSL certificates and keep
any information related to the certificates in $HOME/.subversion
and not in the .svn directories so that the same information can
be used for multiple working copies.
Upon connecting to an SSL svn server, this is was subversion
should do:
1) Check if a copy of the SSL certificate exists for the given
svn server hostname in $HOME/.subversion. If it is there, then
just continue processing the svn request. Otherwise, print all
the information about the svn server's SSL certificate and ask if
the user wants to:
a) Cancel the request.
b) Continue this request but do the store the SSL certificate so
that the next request will require the same revalidation.
c) Accept the SSL certificate forever. Store a copy of the
certificate in $HOME/.subversion.
Also, when checking the certificate, warn if the certificate is
not properly signed by a CA.
*/
return 0;
}
/* Baton for search_groups(). */
struct search_groups_baton
{
const char *requested_host; /* the host in the original uri */
const char *proxy_group; /* NULL unless/until we find a host
match, in which case this is set to
the name of the config file section
where we can find proxy information
for this host. */
apr_pool_t *pool;
};
/* Return true iff STR matches any of the elements of LIST, a
* comma-separated list of one or more hostname-style strings,
* possibly using wildcards; else return false.
*
* Use POOL for temporary allocation.
*
* The following are all valid LISTs
*
* "svn.collab.net"
* "svn.collab.net, *.tigris.org"
* "*.tigris.org, *.collab.net, sp.red-bean.com"
*
* and the STR "svn.collab.net" would match all of them.
*/
static svn_boolean_t match_in_list(const char *str,
const char *list,
apr_pool_t *pool)
{
apr_array_header_t *subvals = svn_cstring_split(list, ',', TRUE, pool);
int i;
for (i = 0; i < subvals->nelts; i++)
{
const char *this_pattern = APR_ARRAY_IDX(subvals, i, char *);
if (APR_STATUS_IS_SUCCESS(apr_fnmatch(this_pattern, str, 0)))
return TRUE;
}
return FALSE;
}
/* This is an `svn_config_enumerator_t' function, and BATON is a
* `struct search_groups_baton *'.
*
* VALUE is in the same format as the LIST param of match_in_list().
* If an element of VALUE matches BATON->requested_host, then set
* BATON->proxy_group to a copy of NAME allocated in BATON->pool, and
* return false, to end the enumeration.
*
* If no match, return true, to continue enumerating.
*/
static svn_boolean_t search_groups(const char *name,
const char *value,
void *baton)
{
struct search_groups_baton *b = baton;
if (match_in_list(b->requested_host, value, b->pool))
{
b->proxy_group = apr_pstrdup(b->pool, name);
return FALSE;
}
else
return TRUE;
}
/* Set *PROXY_HOST, *PROXY_PORT, *PROXY_USERNAME, and *PROXY_PASSWORD
* to the proxy information for REQUESTED_HOST, allocated in POOL, if
* there is any applicable information. Else set *PROXY_PORT to -1
* and the rest to NULL.
*
* If return error, the effect on the return parameters is undefined.
*/
static svn_error_t *get_proxy(const char **proxy_host,
int *proxy_port,
const char **proxy_username,
const char **proxy_password,
const char *requested_host,
apr_pool_t *pool)
{
struct search_groups_baton gb;
svn_config_t *cfg;
const char *exceptions;
const char *port_str;
/* If we find nothing, default to nulls. */
*proxy_host = NULL;
*proxy_port = -1;
*proxy_username = NULL;
*proxy_password = NULL;
SVN_ERR( svn_config_read_proxies(&cfg, pool) );
/* If there are defaults, use them, but only if the requested host
is not one of the exceptions to the defaults. */
svn_config_get(cfg, &exceptions, "default", "no_proxy", NULL);
if ((! exceptions) || (! match_in_list(requested_host, exceptions, pool)))
{
svn_config_get(cfg, proxy_host, "default", "host", NULL);
svn_config_get(cfg, &port_str, "default", "port", NULL);
svn_config_get(cfg, proxy_username, "default", "username", NULL);
svn_config_get(cfg, proxy_password, "default", "password", NULL);
}
/* Search for a proxy pattern specific to this host. */
gb.requested_host = requested_host;
gb.proxy_group = NULL;
gb.pool = pool;
(void) svn_config_enumerate(cfg, "groups", search_groups, &gb);
if (gb.proxy_group)
{
svn_config_get(cfg, proxy_host, gb.proxy_group, "host", *proxy_host);
svn_config_get(cfg, &port_str, gb.proxy_group, "port", port_str);
svn_config_get(cfg, proxy_username, gb.proxy_group, "username",
*proxy_username);
svn_config_get(cfg, proxy_password, gb.proxy_group, "password",
*proxy_password);
}
/* Special case: convert the port value, if any. */
if (port_str)
*proxy_port = atoi(port_str);
else
*proxy_port = -1;
return SVN_NO_ERROR;
}
/* Userdata for the `proxy_auth' function. */
struct proxy_auth_baton
{
const char *username; /* Cannot be NULL, but "" is okay. */
const char *password; /* Cannot be NULL, but "" is okay. */
};
/* An `ne_request_auth' callback, see ne_auth.h. USERDATA is a
* `struct proxy_auth_baton *'.
*
* If ATTEMPT < 10, copy USERDATA->username and USERDATA->password
* into USERNAME and PASSWORD respectively (but do not copy more than
* NE_ABUFSIZ bytes of either), and return zero to indicate to Neon
* that authentication should be attempted.
*
* If ATTEMPT >= 10, copy nothing into USERNAME and PASSWORD and
* return 1, to cancel further authentication attempts.
*
* Ignore REALM.
*
* ### Note: There is no particularly good reason for the 10-attempt
* limit. Perhaps there should only be one attempt, and if it fails,
* we just cancel any further attempts. I used 10 just in case the
* proxy tries various times with various realms, since we ignore
* REALM. And why do we ignore REALM? Because we currently don't
* have any way to specify different auth information for different
* realms. (I'm assuming that REALM would be a realm on the proxy
* server, not on the Subversion repository server that is the real
* destination.) Do we have any need to support proxy realms?
*/
static int proxy_auth(void *userdata,
const char *realm,
int attempt,
char *username,
char *password)
{
struct proxy_auth_baton *pab = userdata;
if (attempt >= 10)
return 1;
/* Else. */
apr_cpystrn(username, pab->username, NE_ABUFSIZ);
apr_cpystrn(password, pab->password, NE_ABUFSIZ);
return 0;
}
/* ### need an ne_session_dup to avoid the second gethostbyname
* call and make this halfway sane. */
static svn_error_t *
svn_ra_dav__open (void **session_baton,
svn_stringbuf_t *repos_URL,
const svn_ra_callbacks_t *callbacks,
void *callback_baton,
apr_pool_t *pool)
{
const char *repository = repos_URL->data;
apr_size_t len;
ne_session *sess, *sess2;
struct uri uri = { 0 };
svn_ra_session_t *ras;
int is_ssl_session;
/* Sanity check the URI */
if (uri_parse(repository, &uri, NULL)
|| uri.host == NULL || uri.path == NULL)
{
uri_free(&uri);
return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, 0, NULL, pool,
"illegal URL for repository");
}
/* Can we initialize network? */
if (sock_init() != 0) {
uri_free(&uri);
return svn_error_create(SVN_ERR_RA_SOCK_INIT, 0, NULL, pool,
"network socket initialization failed");
}
#if 0
/* #### enable this block for debugging output on stderr. */
ne_debug_init(stderr, NE_DBG_HTTP|NE_DBG_HTTPBODY);
#endif
/* we want to know if the repository is actually somewhere else */
/* ### not yet: http_redirect_register(sess, ... ); */
is_ssl_session = (strcasecmp(uri.scheme, "https") == 0);
if (is_ssl_session)
{
if (uri.port == -1)
{
uri.port = 443;
}
if (ne_supports_ssl() == 0)
{
uri_free(&uri);
return svn_error_create(SVN_ERR_RA_SOCK_INIT, 0, NULL, pool,
"SSL is not supported");
}
}
#if 0
else
{
/* accept server-requested TLS upgrades... useless feature
* currently since there is no server support yet. */
(void) ne_set_accept_secure_upgrade(sess, 1);
}
#endif
if (uri.port == -1)
{
uri.port = 80;
}
/* Create two neon session objects, and set their properties... */
sess = ne_session_create(uri.scheme, uri.host, uri.port);
sess2 = ne_session_create(uri.scheme, uri.host, uri.port);
/* If there's a proxy for this URL, use it. */
{
const char *proxy_host;
int proxy_port;
const char *proxy_username;
const char *proxy_password;
SVN_ERR( get_proxy(&proxy_host,
&proxy_port,
&proxy_username,
&proxy_password,
uri.host,
pool) );
if (proxy_port == -1)
proxy_port = 80;
if (proxy_host)
{
ne_session_proxy(sess, proxy_host, proxy_port);
ne_session_proxy(sess2, proxy_host, proxy_port);
if (proxy_username)
{
/* Allocate the baton in pool, not on stack, so it will
last till whenever Neon needs it. */
struct proxy_auth_baton *pab = apr_palloc(pool, sizeof (*pab));
pab->username = proxy_username;
pab->password = proxy_password ? proxy_password : "";
ne_set_proxy_auth(sess, proxy_auth, pab);
ne_set_proxy_auth(sess2, proxy_auth, pab);
}
}
}
/* For SSL connections, when the CA certificate is not known for the
server certificate or the server cert has other verification
problems, neon will fail the connection unless we add a callback
to tell it to ignore the problem. */
if (is_ssl_session)
{
ne_ssl_set_verify(sess, ssl_set_verify_callback, NULL);
ne_ssl_set_verify(sess2, ssl_set_verify_callback, NULL);
}
#if 0
/* Turn off persistent connections. */
ne_set_persist(sess, 0);
ne_set_persist(sess2, 0);
#endif
/* make sure we will eventually destroy the session */
apr_pool_cleanup_register(pool, sess, cleanup_session, apr_pool_cleanup_null);
apr_pool_cleanup_register(pool, sess2, cleanup_session, apr_pool_cleanup_null);
ne_set_useragent(sess, "SVN/" SVN_VERSION);
ne_set_useragent(sess2, "SVN/" SVN_VERSION);
/* clean up trailing slashes from the URL */
len = strlen(uri.path);
if (len > 1 && uri.path[len - 1] == '/')
uri.path[len - 1] = '\0';
/* Create and fill a session_baton. */
ras = apr_pcalloc(pool, sizeof(*ras));
ras->pool = pool;
ras->url = apr_pstrdup (pool, repos_URL->data);
ras->root = uri;
ras->sess = sess;
ras->sess2 = sess2;
ras->callbacks = callbacks;
ras->callback_baton = callback_baton;
/* note that ras->username and ras->password are still NULL at this
point. */
/* Register an authentication 'pull' callback with the neon sessions */
ne_set_server_auth(sess, request_auth, ras);
ne_set_server_auth(sess2, request_auth, ras);
*session_baton = ras;
return SVN_NO_ERROR;
}
static svn_error_t *svn_ra_dav__close (void *session_baton)
{
svn_ra_session_t *ras = session_baton;
(void) apr_pool_cleanup_run(ras->pool, ras->sess, cleanup_session);
(void) apr_pool_cleanup_run(ras->pool, ras->sess2, cleanup_session);
return NULL;
}
static const svn_ra_plugin_t dav_plugin = {
"ra_dav",
"Module for accessing a repository via WebDAV (DeltaV) protocol.",
svn_ra_dav__open,
svn_ra_dav__close,
svn_ra_dav__get_latest_revnum,
svn_ra_dav__get_dated_revision,
svn_ra_dav__get_commit_editor,
svn_ra_dav__get_file,
svn_ra_dav__do_checkout,
svn_ra_dav__do_update,
svn_ra_dav__do_switch,
svn_ra_dav__do_status,
NULL,
svn_ra_dav__get_log,
svn_ra_dav__do_check_path
};
svn_error_t *svn_ra_dav_init(int abi_version,
apr_pool_t *pconf,
apr_hash_t *hash)
{
/* ### need a version number to check here... */
if (abi_version != 0)
;
apr_hash_set (hash, "http", APR_HASH_KEY_STRING, &dav_plugin);
if (ne_supports_ssl())
{
/* Only add this if neon is compiled with SSL support. */
apr_hash_set (hash, "https", APR_HASH_KEY_STRING, &dav_plugin);
}
return NULL;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/