WebDAV: first version which can log into Google

The Neon::Session wraps most of the relevant calls. It is
parameterized by Neon::Settings. It is uncertain where all of these
are meant to come from, because there is no peer configuration in many
cases. Perhaps we can enforce that a WebDAV source may only be created
in a context which has one and exactly one peer config?

The current intermediate solution in WebDAVSourceRegister.cpp grabs
all settings from WEBDAV_* env variables.

Disabling SSL verification and Neon debug logging are implemented.
Opening the source runs a few checks on the URL. Disabling SSL
certificate checking turned out to be necessary, probably because of
the known issue of gnutls not trusting the weak Google certificate
chain.
This commit is contained in:
Patrick Ohly 2010-10-05 11:02:33 +02:00
parent 544456364b
commit a4dabc3685
8 changed files with 379 additions and 12 deletions

View file

@ -9,8 +9,9 @@
#include <syncevo/declarations.h>
SE_BEGIN_CXX
CalDAVSource::CalDAVSource(const SyncSourceParams &params) :
WebDAVSource(params)
CalDAVSource::CalDAVSource(const SyncSourceParams &params,
const boost::shared_ptr<Neon::Settings> &settings) :
WebDAVSource(params, settings)
{
}

View file

@ -17,7 +17,7 @@ SE_BEGIN_CXX
class CalDAVSource : public WebDAVSource
{
public:
CalDAVSource(const SyncSourceParams &params);
CalDAVSource(const SyncSourceParams &params, const boost::shared_ptr<SyncEvo::Neon::Settings> &settings);
/* implementation of SyncSource interface */
virtual const char *getMimeType() const { return "text/calendar"; }

View file

@ -16,9 +16,11 @@ SYNCDAV_SOURCES = \
CalDAVSource.h \
CalDAVSource.cpp \
WebDAVSource.h \
WebDAVSource.cpp
WebDAVSource.cpp \
NeonCXX.h \
NeonCXX.cpp
syncdav_la_SOURCES = $(SYNCDAV_SOURCES)
syncdav_la_LIBADD = $(FILE_LIBS)
syncdav_la_LIBADD = $(NEON_LIBS)
syncdav_la_LDFLAGS = -module -avoid-version
syncdav_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS)
syncdav_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(NEON_CFLAGS)

View file

@ -0,0 +1,177 @@
/*
* Copyright (C) 2010 Patrick Ohly <patrick.ohly@gmx.de>
*/
#include "NeonCXX.h"
#include <ne_socket.h>
#include <ne_auth.h>
#include <list>
#include <boost/algorithm/string/join.hpp>
#include <syncevo/TransportAgent.h>
#include <syncevo/util.h>
#include <syncevo/Logging.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
namespace Neon {
#if 0
}
#endif
std::string features()
{
std::list<std::string> res;
if (ne_has_support(NE_FEATURE_SSL)) { res.push_back("SSL"); }
if (ne_has_support(NE_FEATURE_ZLIB)) { res.push_back("ZLIB"); }
if (ne_has_support(NE_FEATURE_IPV6)) { res.push_back("IPV6"); }
if (ne_has_support(NE_FEATURE_LFS)) { res.push_back("LFS"); }
if (ne_has_support(NE_FEATURE_SOCKS)) { res.push_back("SOCKS"); }
if (ne_has_support(NE_FEATURE_TS_SSL)) { res.push_back("TS_SSL"); }
if (ne_has_support(NE_FEATURE_I18N)) { res.push_back("I18N"); }
return boost::join(res, ", ");
}
URI URI::parse(const std::string &url)
{
URI res;
ne_uri uri;
int error = ne_uri_parse(url.c_str(), &uri);
if (uri.scheme) { res.m_scheme = uri.scheme; }
if (uri.host) { res.m_host = uri.host; }
if (uri.userinfo) { res.m_userinfo = uri.userinfo; }
if (uri.path) { res.m_path = uri.path; }
if (uri.query) { res.m_query = uri.query; }
if (uri.fragment) { res.m_fragment = uri.fragment; }
res.m_port = uri.port ?
uri.port : ne_uri_defaultport(res.m_scheme.c_str());
ne_uri_free(&uri);
if (error) {
SE_THROW_EXCEPTION(TransportException,
StringPrintf("invalid URL '%s' (parsed as '%s')",
url.c_str(),
res.toURL().c_str()));
}
return res;
}
std::string URI::toURL() const
{
return StringPrintf("%s://%s@%s:%u/%s#%s",
m_scheme.c_str(),
m_userinfo.c_str(),
m_host.c_str(),
m_port,
m_path.c_str(),
m_fragment.c_str());
}
Session::Session(const boost::shared_ptr<Settings> &settings) :
m_settings(settings),
m_session(NULL)
{
int logLevel = m_settings->logLevel();
if (logLevel >= 3) {
ne_debug_init(stderr,
NE_DBG_FLUSH|NE_DBG_HTTP|NE_DBG_HTTPAUTH|
(logLevel >= 4 ? NE_DBG_HTTPBODY : 0) |
(logLevel >= 5 ? (NE_DBG_XML|NE_DBG_LOCKS|NE_DBG_SSL) : 0)|
(logLevel >= 6 ? (NE_DBG_XMLPARSE) : 0)|
(logLevel >= 11 ? (NE_DBG_HTTPPLAIN) : 0));
} else {
ne_debug_init(NULL, 0);
}
ne_sock_init();
m_uri = URI::parse(settings->getURL());
m_session = ne_session_create(m_uri.m_scheme.c_str(),
m_uri.m_host.c_str(),
m_uri.m_port);
ne_set_server_auth(m_session, getCredentials, this);
ne_ssl_set_verify(m_session, sslVerify, this);
}
Session::~Session()
{
if (m_session) {
ne_session_destroy(m_session);
}
ne_sock_exit();
}
int Session::getCredentials(void *userdata, const char *realm, int attempt, char *username, char *password) throw()
{
try {
Session *session = static_cast<Session *>(userdata);
std::string user, pw;
session->m_settings->getCredentials(realm, user, pw);
SyncEvo::Strncpy(username, user.c_str(), NE_ABUFSIZ);
SyncEvo::Strncpy(password, pw.c_str(), NE_ABUFSIZ);
// allow only one attempt, credentials are not expected to change in most cases
return attempt;
} catch (...) {
Exception::handle();
SE_LOG_ERROR(NULL, NULL, "no credentials for %s", realm);
return 1;
}
}
int Session::sslVerify(void *userdata, int failures, const ne_ssl_certificate *cert) throw()
{
try {
Session *session = static_cast<Session *>(userdata);
static const Flag descr[] = {
{ NE_SSL_NOTYETVALID, "certificate not yet valid" },
{ NE_SSL_EXPIRED, "certificate has expired" },
{ NE_SSL_IDMISMATCH, "hostname mismatch" },
{ NE_SSL_UNTRUSTED, "untrusted certificate" },
{ 0, NULL }
};
SE_LOG_DEBUG(NULL, NULL,
"%s: SSL verification problem: %s",
session->getURL().c_str(),
Flags2String(failures, descr).c_str());
if (!session->m_settings->verifySSLCertificate()) {
SE_LOG_DEBUG(NULL, NULL, "ignoring bad certificate");
return 0;
}
if (failures == NE_SSL_IDMISMATCH &&
!session->m_settings->verifySSLHost()) {
SE_LOG_DEBUG(NULL, NULL, "ignoring hostname mismatch");
return 0;
}
return 1;
} catch (...) {
Exception::handle();
return 1;
}
}
unsigned int Session::options()
{
unsigned int caps;
check(ne_options2(m_session, m_uri.m_path.c_str(), &caps));
return caps;
}
void Session::check(int error)
{
if (error) {
SE_THROW_EXCEPTION(TransportException,
StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session)));
}
}
}
SE_END_CXX

View file

@ -11,10 +11,119 @@
#define INCL_NEONCXX
#include <ne_session.h>
#include <ne_utils.h>
#include <ne_basic.h>
#include <string>
#include <boost/shared_ptr.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
namespace Neon {
#if 0
}
#endif
/** comma separated list of features supported by libneon in use */
std::string features();
class Settings {
public:
/**
* base URL for WebDAV service
*/
virtual std::string getURL() = 0;
/**
* host name must match for SSL?
*/
virtual bool verifySSLHost() = 0;
/**
* SSL certificate must be valid?
*/
virtual bool verifySSLCertificate() = 0;
/**
* fill in username and password for specified realm (URL?),
* throw error if not available
*/
virtual void getCredentials(const std::string &realm,
std::string &username,
std::string &password) = 0;
/**
* standard SyncEvolution log level, see
* Session::Session() how that is mapped to neon debugging
*/
virtual int logLevel() = 0;
/**
* use this to create a boost_shared pointer for a
* Settings instance which needs to be freed differently
*/
struct NullDeleter {
void operator()(Settings *) const {}
};
};
struct URI {
std::string m_scheme;
std::string m_host;
std::string m_userinfo;
unsigned int m_port;
std::string m_path;
std::string m_query;
std::string m_fragment;
/**
* Split URL into parts. Throws TransportAgentException on
* invalid url. Port will be set to default for scheme if not set
* in URL.
*/
static URI parse(const std::string &url);
/** compose URL from parts */
std::string toURL() const;
};
/**
* Wraps all session related activities.
* Throws transport errors for fatal problems.
*/
class Session {
public:
/**
* @param settings must provide information about settings on demand
*/
Session(const boost::shared_ptr<Settings> &settings);
~Session();
/** ne_options2() */
unsigned int options();
/** URL which is in use */
std::string getURL() const { return m_uri.toURL(); }
private:
boost::shared_ptr<Settings> m_settings;
ne_session *m_session;
URI m_uri;
/** ne_set_server_auth() callback */
static int getCredentials(void *userdata, const char *realm, int attempt, char *username, char *password) throw();
/** ne_ssl_set_verify() callback */
static int sslVerify(void *userdata, int failures, const ne_ssl_certificate *cert) throw();
/** throw error if error code indicates failure */
void check(int error);
};
}
SE_END_CXX
#endif // INCL_NEONCXX

View file

@ -8,14 +8,49 @@
SE_BEGIN_CXX
WebDAVSource::WebDAVSource(const SyncSourceParams &params) :
TrackingSyncSource(params)
WebDAVSource::WebDAVSource(const SyncSourceParams &params,
const boost::shared_ptr<Neon::Settings> &settings) :
TrackingSyncSource(params),
m_settings(settings)
{
}
void WebDAVSource::open()
{
// TODO
SE_LOG_DEBUG(NULL, NULL, "using libneon %s with %s",
ne_version_string(), Neon::features().c_str());
m_session.reset(new Neon::Session(m_settings));
// Start by checking server capabilities.
// Verifies URL.
int caps = m_session->options();
static const Flag descr[] = {
{ NE_CAP_DAV_CLASS1, "Class 1 WebDAV (RFC 2518)" },
{ NE_CAP_DAV_CLASS2, "Class 2 WebDAV (RFC 2518)" },
{ NE_CAP_DAV_CLASS3, "Class 3 WebDAV (RFC 4918)" },
{ NE_CAP_MODDAV_EXEC, "mod_dav 'executable' property" },
{ NE_CAP_DAV_ACL, "WebDAV ACL (RFC 3744)" },
{ NE_CAP_VER_CONTROL, "DeltaV version-control" },
{ NE_CAP_CO_IN_PLACE, "DeltaV checkout-in-place" },
{ NE_CAP_VER_HISTORY, "DeltaV version-history" },
{ NE_CAP_WORKSPACE, "DeltaV workspace" },
{ NE_CAP_UPDATE, "DeltaV update" },
{ NE_CAP_LABEL, "DeltaV label" },
{ NE_CAP_WORK_RESOURCE, "DeltaV working-resouce" },
{ NE_CAP_MERGE, "DeltaV merge" },
{ NE_CAP_BASELINE, "DeltaV baseline" },
{ NE_CAP_ACTIVITY, "DeltaV activity" },
{ NE_CAP_VC_COLLECTION, "DeltaV version-controlled-collection" },
{ 0, NULL }
};
SE_LOG_DEBUG(NULL, NULL, "%s WebDAV capabilities: %s",
m_session->getURL().c_str(),
Flags2String(caps, descr).c_str());
// Check that base URL really is a calendar collection.
// This also checks credentials.
}
bool WebDAVSource::isEmpty()
@ -26,6 +61,7 @@ bool WebDAVSource::isEmpty()
void WebDAVSource::close()
{
m_session.reset();
}
WebDAVSource::Databases WebDAVSource::getDatabases()
@ -62,6 +98,7 @@ void WebDAVSource::removeItem(const string &uid)
// TODO
}
SE_END_CXX
#endif /* ENABLE_DAV */

View file

@ -26,7 +26,11 @@ SE_BEGIN_CXX
class WebDAVSource : public TrackingSyncSource, private boost::noncopyable
{
public:
WebDAVSource(const SyncSourceParams &params);
/**
* @param settings instance which provides necessary settings callbacks for Neon
*/
WebDAVSource(const SyncSourceParams &params,
const boost::shared_ptr<Neon::Settings> &settings);
protected:
/* implementation of SyncSource interface */
@ -42,7 +46,8 @@ class WebDAVSource : public TrackingSyncSource, private boost::noncopyable
virtual void removeItem(const string &uid);
private:
boost::shared_ptr<Neon::Settings> m_settings;
boost::shared_ptr<Neon::Session> m_session;
};
SE_END_CXX

View file

@ -20,7 +20,43 @@ static SyncSource *createSource(const SyncSourceParams &params)
sourceType.m_format == "text/x-calendar" ||
sourceType.m_format == "text/x-vcalendar") {
#ifdef ENABLE_DAV
return new CalDAVSource(params);
class EnvSettings : public Neon::Settings {
virtual std::string getURL()
{
const char *var = getenv("WEBDAV_URL");
if (!var) {
SE_THROW("no WEBDAV_URL");
}
return var;
}
virtual bool verifySSLHost() { return !getenv("WEBDAV_NO_SSL_HOST"); }
virtual bool verifySSLCertificate() { return !getenv("WEBDAV_NO_SSL_CERT"); }
virtual void getCredentials(const std::string &realm,
std::string &username,
std::string &password)
{
const char *var = getenv("WEBDAV_USERNAME");
if (!var) {
SE_THROW("no WEBDAV_USERNAME");
}
username = var;
var = getenv("WEBDAV_PASSWORD");
if (var) {
password = var;
}
}
virtual int logLevel()
{
const char *var = getenv("WEBDAV_LOGLEVEL");
return var ? atoi(var) : 0;
}
};
boost::shared_ptr<Neon::Settings> settings(static_cast<Neon::Settings *>(new EnvSettings));
return new CalDAVSource(params, settings);
#else
return RegisterSyncSource::InactiveSource;
#endif