/*
* ====================================================================
* Copyright (c) 2002-2005 The RapidSvn Group. All rights reserved.
*
* This software is licensed as described in the file LICENSE.txt,
* which you should have received as part of this distribution.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://rapidsvn.tigris.org/.
* ====================================================================
*/
// Apache Portable Runtime
#include "apr_xlate.h"
// Subversion api
#include "svn_auth.h"
#include "svn_config.h"
#include "svn_subst.h"
#include "svn_version.h"
#include "svn_wc.h"
//#include "svn_utf.h"
// subcpp
#include "apr.hpp"
#include "context.hpp"
#include "context_listener.hpp"
#include <assert.h>
#if defined( _MSC_VER) && _MSC_VER
#pragma warning( disable: 4127 )// conditional expression is constant
#endif
namespace svn
{
struct Context::Data
{
public:
/** The usage of Apr makes sure Apr is initialized
* before any use of apr functions.
*/
Apr* apr;
ContextListener * listener;
bool logIsSet;
int promptCounter;
Pool pool;
svn_client_ctx_t* ctx;
std::string username;
std::string password;
std::string logMessage;
std::string configDir;
/**
* translate native c-string to utf8
*/
static svn_error_t * TranslateString( const char * str, const char ** newStr,
apr_pool_t * )
{
// due to problems with apr_xlate we dont perform
// any conversion at this place. YOU will have to make
// sure any strings passed are UTF 8 strings
// svn_string_t *string = svn_string_create ("", pool);
//
// string->data = str;
// string->len = strlen (str);
//
// const char * encoding = APR_LOCALE_CHARSET;
//
// SVN_ERR (svn_subst_translate_string (&string, string,
// encoding, pool));
//
// *newStr = string->data;
*newStr = str;
return SVN_NO_ERROR;
}
/**
* the @a baton is interpreted as Data *
* Several checks are performed on the baton:
* - baton == 0?
* - baton->Data
* - listener set?
*
* @param baton
* @param data returned data if everything is OK
* @retval SVN_NO_ERROR if everything is fine
* @retval SVN_ERR_CANCELLED on invalid values
*/
static svn_error_t * GetData( void * baton, Data ** data )
{
if ( baton == NULL )
return svn_error_create( SVN_ERR_CANCELLED, NULL,
"invalid baton" );
Data * data_ = static_cast <Data *>( baton );
if ( data_->listener == 0 )
return svn_error_create( SVN_ERR_CANCELLED, NULL,
"invalid listener" );
*data = data_;
return SVN_NO_ERROR;
}
Data( const std::string & configDir_ )
: apr( Apr::EnsureAprIsInitialized() ), listener( 0 ), logIsSet( false ),
promptCounter( 0 ), configDir( configDir_ )
{
const char * c_configDir = 0;
if ( configDir.length() > 0 )
c_configDir = configDir.c_str();
// make sure the configuration directory exists
svn_config_ensure( c_configDir, pool );
// intialize authentication providers
// * simple
// * username
// * simple prompt
// * ssl server trust file
// * ssl server trust prompt
// * ssl client cert pw file
// * ssl client cert pw prompt
// * ssl client cert file
// ===================
// 8 providers
svn_auth_provider_object_t *provider;
apr_array_header_t* providers = NULL;
// support encryption in saved passwords
svn_auth_get_platform_specific_client_providers( &providers, NULL, pool );
svn_auth_get_simple_provider2( &provider, OnPlainTextPrompt, this, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) = provider;
svn_auth_get_username_provider( &provider, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) = provider;
svn_auth_get_simple_prompt_provider( &provider, OnSimplePrompt, this, -1, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) = provider;
// add ssl providers
// file first then prompt providers
svn_client_get_ssl_server_trust_file_provider( &provider, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) =
provider;
svn_client_get_ssl_client_cert_file_provider( &provider, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) =
provider;
svn_client_get_ssl_client_cert_pw_file_provider( &provider, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) =
provider;
svn_client_get_ssl_server_trust_prompt_provider(
&provider, OnSslServerTrustPrompt, this, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) =
provider;
// plugged in 3 as the retry limit - what is a good limit?
svn_client_get_ssl_client_cert_pw_prompt_provider(
&provider, OnSslClientCertPwPrompt, this, 3, pool );
*( svn_auth_provider_object_t ** )apr_array_push( providers ) =
provider;
svn_auth_baton_t *ab;
svn_auth_open( &ab, providers, pool );
// initialize ctx structure
svn_client_create_context( &ctx, pool );
// get the config based on the configDir passed in
svn_config_get_config( &ctx->config, c_configDir, pool );
// tell the auth functions where the config is
svn_auth_set_parameter( ab, SVN_AUTH_PARAM_CONFIG_DIR,
c_configDir );
ctx->auth_baton = ab;
ctx->log_msg_func = OnLogMsg;
ctx->log_msg_baton = this;
ctx->notify_func2 = OnNotify2;
ctx->notify_baton2 = this;
ctx->cancel_func = OnCancel;
ctx->cancel_baton = this;
}
svn_error_t* ReleaseWorkingCopyLock()
{
#if ( (SVN_VER_MAJOR > 1) || ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 7)) )
// create a new context first
// if we deleted the old one first, things would be broken if we were unable to create a replacement
svn_wc_context_t* newContext = NULL;
SVN_ERR( svn_wc_context_create( &newContext, NULL, pool, pool ) );
// update the wc_ctx in the ctx
svn_wc_context_t* oldContext = ctx->wc_ctx;
ctx->wc_ctx = newContext;
// destroy the old context last
// if destroy fails, hopefully the lock is released
// but, because the wc_ctx in ctx is already updated, this can't hurt any subsequent operations with this client, even if it fails
SVN_ERR( svn_wc_context_destroy( oldContext ) );
#endif
return SVN_NO_ERROR;
}
~Data()
{
delete listener;
}
void SetAuthCache( bool value )
{
void *param = 0;
if ( !value )
param = ( void * )"1";
svn_auth_set_parameter( ctx->auth_baton,
SVN_AUTH_PARAM_NO_AUTH_CACHE,
param );
}
/** @see Context::SetLogin */
void SetLogin( const char * usr, const char * pwd )
{
username = usr;
password = pwd;
svn_auth_baton_t * ab = ctx->auth_baton;
svn_auth_set_parameter( ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
username.c_str() );
svn_auth_set_parameter( ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
password.c_str() );
}
/** @see Context::SetLogMessage */
void SetLogMessage( const char * msg )
{
logMessage = msg;
logIsSet = true;
}
/**
* this function gets called by the subversion api function
* when a log message is needed. This is the case on a commit
* for example
*/
static svn_error_t * OnLogMsg( const char **log_msg,
const char **tmp_file,
apr_array_header_t * /*commit_items*/,
void *baton,
apr_pool_t *pool )
{
Data * data = 0;
SVN_ERR( GetData( baton, &data ) );
std::string msg;
if ( data->logIsSet )
{
msg = data->GetLogMessage();
}
else
{
try
{
if ( !data->RetrieveLogMessage( msg ) )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "" );
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
}
*log_msg = apr_pstrdup( pool, msg.c_str() );
*tmp_file = NULL;
return SVN_NO_ERROR;
}
/**
* this is the callback function for the subversion
* api functions to signal the progress of an action
*/
static void OnNotify2( void* baton,
const svn_wc_notify_t* notify,
apr_pool_t* /*pool*/ )
{
if ( 0 == baton )
{
return;
}
Data * data = static_cast <Data *>( baton );
try
{
data->Notify2( notify );
}
catch ( ... )
{
assert( !"Cannot throw from ContextNotify, because an exception cannot go anywhere from here" );
}
}
/**
* this is the callback function for the subversion
* api functions to signal the progress of an action
*/
static svn_error_t * OnCancel( void * baton )
{
if ( 0 == baton )
{
return SVN_NO_ERROR;
}
Data * data = static_cast <Data *>( baton );
try
{
if ( data->Cancel() )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "cancelled by user" );
}
else
{
return SVN_NO_ERROR;
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
}
/**
* @see svn_auth_plaintext_prompt_func_t
*/
static svn_error_t * OnPlainTextPrompt(svn_boolean_t* _may_save_plaintext, const char* realm, void* baton, apr_pool_t* /*pool*/ )
{
Data * data = 0;
SVN_ERR( GetData( baton, &data ) );
bool may_save_plaintext = _may_save_plaintext != 0;
try
{
if ( !data->RetrieveMaySavePlainText( realm, may_save_plaintext ) )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "" );
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
*_may_save_plaintext = may_save_plaintext;
return SVN_NO_ERROR;
}
/**
* @see svn_auth_simple_prompt_func_t
*/
static svn_error_t * OnSimplePrompt( svn_auth_cred_simple_t **cred,
void *baton,
const char *realm,
const char *username,
svn_boolean_t _may_save,
apr_pool_t *pool )
{
Data * data = 0;
SVN_ERR( GetData( baton, &data ) );
bool may_save = _may_save != 0;
try
{
if ( !data->RetrieveLogin( username, realm, may_save ) )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "" );
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
svn_auth_cred_simple_t* lcred = ( svn_auth_cred_simple_t* )apr_palloc( pool, sizeof( svn_auth_cred_simple_t ) );
/* SVN_ERR (svn_utf_cstring_to_utf8 (
&lcred->password,
data->GetPassword (), pool));
SVN_ERR (svn_utf_cstring_to_utf8 (
&lcred->username,
data->GetUsername (), pool)); */
lcred->password = data->GetPassword();
lcred->username = data->GetUsername();
// tell svn if the credentials need to be saved
lcred->may_save = may_save;
*cred = lcred;
return SVN_NO_ERROR;
}
/**
* @see svn_auth_ssl_server_trust_prompt_func_t
*/
static svn_error_t * OnSslServerTrustPrompt( svn_auth_cred_ssl_server_trust_t **cred,
void *baton,
const char *realm,
apr_uint32_t failures,
const svn_auth_ssl_server_cert_info_t *info,
svn_boolean_t may_save,
apr_pool_t *pool )
{
Data * data = 0;
SVN_ERR( GetData( baton, &data ) );
ContextListener::SslServerTrustData trustData( failures );
if ( realm != NULL )
{
trustData.realm = realm;
}
trustData.hostname = info->hostname;
trustData.fingerprint = info->fingerprint;
trustData.validFrom = info->valid_from;
trustData.validUntil = info->valid_until;
trustData.issuerDName = info->issuer_dname;
trustData.maySave = may_save != 0;
apr_uint32_t acceptedFailures;
ContextListener::SslServerTrustAnswer answer = ContextListener::DONT_ACCEPT;
try
{
answer = data->RetrieveSslServerTrustPrompt( trustData, acceptedFailures );
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
if ( answer == ContextListener::DONT_ACCEPT )
{
*cred = NULL;
}
else
{
svn_auth_cred_ssl_server_trust_t *cred_ =
( svn_auth_cred_ssl_server_trust_t* )
apr_palloc( pool, sizeof( svn_auth_cred_ssl_server_trust_t ) );
if ( answer == ContextListener::ACCEPT_PERMANENTLY )
{
cred_->may_save = 1;
cred_->accepted_failures = acceptedFailures;
}
*cred = cred_;
}
return SVN_NO_ERROR;
}
/**
* @see svn_auth_ssl_client_cert_prompt_func_t
*/
static svn_error_t * OnSslClientCertPrompt( svn_auth_cred_ssl_client_cert_t **cred,
void *baton,
apr_pool_t *pool )
{
Data * data;
SVN_ERR( GetData( baton, &data ) );
std::string certFile;
try
{
if ( !data->RetrieveSslClientCertPrompt( certFile ) )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "" );
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
svn_auth_cred_ssl_client_cert_t *cred_ =
( svn_auth_cred_ssl_client_cert_t* )
apr_palloc( pool, sizeof( svn_auth_cred_ssl_client_cert_t ) );
/* SVN_ERR (svn_utf_cstring_to_utf8 (
&cred_->cert_file,
certFile.c_str (),
pool)); */
cred_->cert_file = certFile.c_str();
*cred = cred_;
return SVN_NO_ERROR;
}
/**
* @see svn_auth_ssl_client_cert_pw_prompt_func_t
*/
static svn_error_t * OnSslClientCertPwPrompt(
svn_auth_cred_ssl_client_cert_pw_t **cred,
void *baton,
const char *realm,
svn_boolean_t maySave,
apr_pool_t *pool )
{
Data * data = 0;
SVN_ERR( GetData( baton, &data ) );
std::string password;
bool may_save = maySave != 0;
try
{
if ( !data->RetrieveSslClientCertPwPrompt( password, realm, may_save ) )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, "" );
}
}
catch ( std::exception& ex )
{
return svn_error_create( SVN_ERR_CANCELLED, NULL, ex.what() );
}
svn_auth_cred_ssl_client_cert_pw_t *cred_ =
( svn_auth_cred_ssl_client_cert_pw_t * )
apr_palloc( pool, sizeof( svn_auth_cred_ssl_client_cert_pw_t ) );
/* SVN_ERR (svn_utf_cstring_to_utf8 (
&cred_->password,
password.c_str (),
pool)); */
cred_->password = password.c_str();
cred_->may_save = may_save;
*cred = cred_;
return SVN_NO_ERROR;
}
const char * GetUsername() const
{
return username.c_str();
}
const char * GetPassword() const
{
return password.c_str();
}
const char * GetLogMessage() const
{
return logMessage.c_str();
}
/**
* if the @a listener is set, use it to retrieve the log
* message using ContextListener::contextGetLogMessage.
* This return values is given back, then.
*
* if the @a listener is not set the its checked whether
* the log message has been set using @a SetLogMessage
* yet. If not, return false otherwise true
*
* @param msg log message
* @retval false cancel
*/
bool RetrieveLogMessage( std::string & msg )
{
bool ok;
if ( listener == 0 )
{
return false;
}
ok = listener->ContextGetLogMessage( logMessage );
if ( ok )
{
msg = logMessage;
}
else
{
logIsSet = false;
}
return ok;
}
/**
* if the @a listener is set and no password has been
* set yet, use it to retrieve login and password using
* ContextListener::ContextGetLogin.
*
* if the @a listener is not set, check if SetLogin
* has been called yet.
*
* @return continue?
* @retval false cancel
*/
bool RetrieveLogin( const char * username_,
const char * realm,
bool &may_save )
{
bool ok;
if ( listener == 0 )
{
return false;
}
if ( username_ == NULL )
{
username = "";
}
else
{
username = username_;
}
ok = listener->ContextGetLogin( realm, username, password, may_save );
return ok;
}
/**
* if the @a listener is set, use it to retrieve plaintext password saving preferences
*
* if the @a listener is not set, may_save_plaintext = true
* @return continue?
* @retval false cancel
*/
bool RetrieveMaySavePlainText( const char* realm, bool& may_save_plaintext )
{
if ( listener == 0 )
{
may_save_plaintext = true;
return true;
}
return listener->ContextMaySavePlainText( realm, may_save_plaintext );
}
/**
* if the @a listener is set, use it to retrieve client side information
*
* if the @a listener is not set, cancel
* @return continue?
* @retval false cancel
*/
bool RetrieveSslClientCertPrompt( std::string & certFile )
{
if ( listener == 0 )
{
return false;
}
return listener->ContextSslClientCertPrompt( certFile );
}
/**
* if the @a listener is set, use it to retrieve the password for the client certificate
*
* if the @a listener is not set, cancel
* @return continue?
* @retval false cancel
*/
bool RetrieveSslClientCertPwPrompt( std::string& password, const std::string& realm, bool& maySave )
{
if ( listener == 0 )
{
return false;
}
return listener->ContextSslClientCertPwPrompt( password, realm, maySave );
}
/**
* if the @a listener is set, use it to retrieve user confirmation of ssl server information
*
* if the @a listener is not set, cancel
* @return continue?
* @retval false cancel
*/
ContextListener::SslServerTrustAnswer RetrieveSslServerTrustPrompt( const ContextListener::SslServerTrustData& data, apr_uint32_t& acceptedFailures )
{
if ( listener == 0 )
{
return ContextListener::DONT_ACCEPT;
}
return listener->ContextSslServerTrustPrompt( data, acceptedFailures );
}
/**
* if the @a listener is set call the method
* @a ContextNotify
*/
void Notify2( const svn_wc_notify_t* notify )
{
if ( listener != 0 )
{
listener->ContextNotify( notify );
}
}
/**
* if the @a listener is set call the method
* @a ContextCancel
*/
bool Cancel()
{
if ( listener != 0 )
{
return listener->ContextCancel();
}
else
{
// don't cancel if no listener
return false;
}
}
};
Context::Context( const std::string &configDir )
{
m = new Data( configDir );
}
Context::Context( const Context & src )
{
m = new Data( src.m->configDir );
SetLogin( src.GetUsername(), src.GetPassword() );
}
Context::~Context()
{
delete m;
}
void Context::SetAuthCache( bool value )
{
m->SetAuthCache( value );
}
void Context::SetLogin( const char * username, const char * password )
{
m->SetLogin( username, password );
}
Context::operator svn_client_ctx_t * ()
{
return m->ctx;
}
svn_client_ctx_t * Context::ctx()
{
return m->ctx;
}
void Context::SetLogMessage( const char * msg )
{
m->SetLogMessage( msg );
}
const char * Context::GetUsername() const
{
return m->GetUsername();
}
const char * Context::GetPassword() const
{
return m->GetPassword();
}
const char * Context::GetLogMessage() const
{
return m->GetLogMessage();
}
void Context::SetListener( ContextListener * listener )
{
ContextListener* oldListener = m->listener;
m->listener = listener;
delete oldListener;
}
ContextListener * Context::GetListener() const
{
return m->listener;
}
void Context::Reset()
{
m->promptCounter = 0;
m->logIsSet = false;
}
svn_error_t* Context::ReleaseWorkingCopyLock()
{
return m->ReleaseWorkingCopyLock();
}
ContextListener::~ContextListener()
{
}
}