You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1344 lines
36 KiB
1344 lines
36 KiB
/***************************************************************************
|
|
sieve.cpp - description
|
|
-------------------
|
|
begin : Thu Dec 20 18:47:08 EST 2001
|
|
copyright : (C) 2001 by Hamish Rodda
|
|
email : meddie@yoyo.cc.monash.edu.au
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License version 2 as *
|
|
* published by the Free Software Foundation. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
/**
|
|
* Portions adapted from the SMTP ioslave.
|
|
* Copyright (c) 2000, 2001 Alex Zepeda <jazepeda@pacbell.net>
|
|
* Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net>
|
|
* All rights reserved.
|
|
*
|
|
* Policy: the function where the error occurs calls error(). A result of
|
|
* false, where it signifies an error, thus doesn't need to call error() itself.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
extern "C" {
|
|
#include <sasl/sasl.h>
|
|
}
|
|
#include "sieve.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <kinstance.h>
|
|
#include <klocale.h>
|
|
#include <kurl.h>
|
|
#include <kmdcodec.h>
|
|
#include <kglobal.h>
|
|
#include <kmessagebox.h>
|
|
|
|
#include <tqcstring.h>
|
|
#include <tqregexp.h>
|
|
|
|
#include <cstdlib>
|
|
using std::exit;
|
|
#include <sys/stat.h>
|
|
#include <cassert>
|
|
|
|
#include <tdepimmacros.h>
|
|
|
|
static const int debugArea = 7122;
|
|
|
|
static inline
|
|
#ifdef NDEBUG
|
|
kndbgstream ksDebug() { return kdDebug( debugArea ); }
|
|
kndbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); }
|
|
#else
|
|
kdbgstream ksDebug() { return kdDebug( debugArea ); }
|
|
kdbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); }
|
|
#endif
|
|
|
|
#define SIEVE_DEFAULT_PORT 2000
|
|
|
|
static sasl_callback_t callbacks[] = {
|
|
{ SASL_CB_ECHOPROMPT, NULL, NULL },
|
|
{ SASL_CB_NOECHOPROMPT, NULL, NULL },
|
|
{ SASL_CB_GETREALM, NULL, NULL },
|
|
{ SASL_CB_USER, NULL, NULL },
|
|
{ SASL_CB_AUTHNAME, NULL, NULL },
|
|
{ SASL_CB_PASS, NULL, NULL },
|
|
{ SASL_CB_CANON_USER, NULL, NULL },
|
|
{ SASL_CB_LIST_END, NULL, NULL }
|
|
};
|
|
|
|
static const unsigned int SIEVE_DEFAULT_RECIEVE_BUFFER = 512;
|
|
|
|
using namespace KIO;
|
|
extern "C"
|
|
{
|
|
KDE_EXPORT int kdemain(int argc, char **argv)
|
|
{
|
|
KInstance instance("kio_sieve" );
|
|
|
|
ksDebug() << "*** Starting kio_sieve " << endl;
|
|
|
|
if (argc != 4) {
|
|
ksDebug() << "Usage: kio_sieve protocol domain-socket1 domain-socket2" << endl;
|
|
exit(-1);
|
|
}
|
|
|
|
if ( sasl_client_init( NULL ) != SASL_OK ) {
|
|
fprintf(stderr, "SASL library initialization failed!\n");
|
|
::exit (-1);
|
|
}
|
|
|
|
kio_sieveProtocol slave(argv[2], argv[3]);
|
|
slave.dispatchLoop();
|
|
|
|
sasl_done();
|
|
|
|
ksDebug() << "*** kio_sieve Done" << endl;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
kio_sieveResponse::kio_sieveResponse()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const uint& kio_sieveResponse::getType() const
|
|
{
|
|
return rType;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const uint kio_sieveResponse::getQuantity() const
|
|
{
|
|
return quantity;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const TQCString& kio_sieveResponse::getAction() const
|
|
{
|
|
return key;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const TQCString& kio_sieveResponse::getKey() const
|
|
{
|
|
return key;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const TQCString& kio_sieveResponse::getVal() const
|
|
{
|
|
return val;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
const TQCString& kio_sieveResponse::getExtra() const
|
|
{
|
|
return extra;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::setQuantity(const uint& newTQty)
|
|
{
|
|
rType = QUANTITY;
|
|
quantity = newTQty;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::setAction(const TQCString& newAction)
|
|
{
|
|
rType = ACTION;
|
|
key = newAction.copy();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::setKey(const TQCString& newKey)
|
|
{
|
|
rType = KEY_VAL_PAIR;
|
|
key = newKey.copy();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::setVal(const TQCString& newVal)
|
|
{
|
|
val = newVal.copy();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::setExtra(const TQCString& newExtra)
|
|
{
|
|
extra = newExtra.copy();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveResponse::clear()
|
|
{
|
|
rType = NONE;
|
|
extra = key = val = TQCString("");
|
|
quantity = 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
kio_sieveProtocol::kio_sieveProtocol(const TQCString &pool_socket, const TQCString &app_socket)
|
|
: TCPSlaveBase( SIEVE_DEFAULT_PORT, "sieve", pool_socket, app_socket, false)
|
|
, m_connMode(NORMAL)
|
|
, m_supportsTLS(false)
|
|
, m_shouldBeConnected(false)
|
|
, m_allowUnencrypted(false)
|
|
{
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
kio_sieveProtocol::~kio_sieveProtocol()
|
|
{
|
|
if ( isConnectionValid() )
|
|
disconnect();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::setHost (const TQString &host, int port, const TQString &user, const TQString &pass)
|
|
{
|
|
if ( isConnectionValid() &&
|
|
( m_sServer != host ||
|
|
m_iPort != port ||
|
|
m_sUser != user ||
|
|
m_sPass != pass ) ) {
|
|
disconnect();
|
|
}
|
|
m_sServer = host;
|
|
m_iPort = port ? port : m_iDefaultPort;
|
|
m_sUser = user;
|
|
m_sPass = pass;
|
|
m_supportsTLS = false;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::openConnection()
|
|
{
|
|
m_connMode = CONNECTION_ORIENTED;
|
|
connect();
|
|
}
|
|
|
|
bool kio_sieveProtocol::parseCapabilities(bool requestCapabilities/* = false*/)
|
|
{
|
|
ksDebug() << k_funcinfo << endl;
|
|
|
|
// Setup...
|
|
bool ret = false;
|
|
|
|
if (requestCapabilities) {
|
|
sendData("CAPABILITY");
|
|
}
|
|
|
|
while (receiveData()) {
|
|
ksDebug() << "Looping receive" << endl;
|
|
|
|
if (r.getType() == kio_sieveResponse::ACTION) {
|
|
if ( r.getAction().contains("ok", false) != -1 ) {
|
|
ksDebug() << "Sieve server ready & awaiting authentication." << endl;
|
|
break;
|
|
} else
|
|
ksDebug() << "Unknown action " << r.getAction() << "." << endl;
|
|
|
|
} else if (r.getKey() == "IMPLEMENTATION") {
|
|
if (r.getVal().contains("sieve", false) != -1) {
|
|
ksDebug() << "Connected to Sieve server: " << r.getVal() << endl;
|
|
ret = true;
|
|
setMetaData("implementation", r.getVal());
|
|
m_implementation = r.getVal();
|
|
}
|
|
|
|
} else if (r.getKey() == "SASL") {
|
|
// Save list of available SASL methods
|
|
m_sasl_caps = TQStringList::split(' ', r.getVal());
|
|
ksDebug() << "Server SASL authentication methods: " << m_sasl_caps.join(", ") << endl;
|
|
setMetaData("saslMethods", r.getVal());
|
|
|
|
} else if (r.getKey() == "SIEVE") {
|
|
// Save script capabilities; report back as meta data:
|
|
ksDebug() << "Server script capabilities: " << TQStringList::split(' ', r.getVal()).join(", ") << endl;
|
|
setMetaData("sieveExtensions", r.getVal());
|
|
|
|
} else if (r.getKey() == "STARTTLS") {
|
|
// The server supports TLS
|
|
ksDebug() << "Server supports TLS" << endl;
|
|
m_supportsTLS = true;
|
|
setMetaData("tlsSupported", "true");
|
|
|
|
} else {
|
|
ksDebug() << "Unrecognised key." << endl;
|
|
}
|
|
}
|
|
|
|
if (!m_supportsTLS) {
|
|
setMetaData("tlsSupported", "false");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
/**
|
|
* Checks if connection parameters have changed.
|
|
* If it it, close the current connection
|
|
*/
|
|
void kio_sieveProtocol::changeCheck( const KURL &url )
|
|
{
|
|
TQString auth;
|
|
|
|
if (!metaData("sasl").isEmpty())
|
|
auth = metaData("sasl").upper();
|
|
else {
|
|
TQString query = url.query();
|
|
if ( query.startsWith("?") ) query.remove( 0, 1 );
|
|
TQStringList q = TQStringList::split( ",", query );
|
|
TQStringList::iterator it;
|
|
|
|
for ( it = q.begin(); it != q.end(); ++it ) {
|
|
if ( TQString( (*it).section('=',0,0) ).lower() == "x-mech" ) {
|
|
auth = TQString( (*it).section('=',1) ).upper();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ksDebug() << "auth: " << auth << " m_sAuth: " << m_sAuth << endl;
|
|
if ( m_sAuth != auth ) {
|
|
m_sAuth = auth;
|
|
if ( isConnectionValid() )
|
|
disconnect();
|
|
}
|
|
|
|
// For TLS, only disconnect if we are unencrypted and are
|
|
// no longer allowed (otherwise, it's still fine):
|
|
const bool allowUnencryptedNow = url.queryItem("x-allow-unencrypted") == "true" ;
|
|
if ( m_allowUnencrypted && !allowUnencryptedNow )
|
|
if ( isConnectionValid() )
|
|
disconnect();
|
|
m_allowUnencrypted = allowUnencryptedNow;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
/**
|
|
* Connects to the server.
|
|
* returns false and calls error() if an error occurred.
|
|
*/
|
|
bool kio_sieveProtocol::connect(bool useTLSIfAvailable)
|
|
{
|
|
ksDebug() << k_funcinfo << endl;
|
|
|
|
if (isConnectionValid()) return true;
|
|
|
|
infoMessage(i18n("Connecting to %1...").arg( m_sServer));
|
|
|
|
if (m_connMode == CONNECTION_ORIENTED && m_shouldBeConnected) {
|
|
error(ERR_CONNECTION_BROKEN, i18n("The connection to the server was lost."));
|
|
return false;
|
|
}
|
|
|
|
setBlockConnection(true);
|
|
|
|
if (!connectToHost(m_sServer, m_iPort, true)) {
|
|
return false;
|
|
}
|
|
|
|
if (!parseCapabilities()) {
|
|
closeDescriptor();
|
|
error(ERR_UNSUPPORTED_PROTOCOL, i18n("Server identification failed."));
|
|
return false;
|
|
}
|
|
|
|
// Attempt to start TLS
|
|
if ( !m_allowUnencrypted && !canUseTLS() ) {
|
|
error( ERR_SLAVE_DEFINED, i18n("Can not use TLS. Please enable TLS in the TDE cryptography setting.") );
|
|
disconnect();
|
|
return false;
|
|
}
|
|
|
|
if ( !m_allowUnencrypted && useTLSIfAvailable && canUseTLS() && !m_supportsTLS &&
|
|
messageBox( WarningContinueCancel,
|
|
i18n("TLS encryption was requested, but your Sieve server does not advertise TLS in its capabilities.\n"
|
|
"You can choose to try to initiate TLS negotiations nonetheless, or cancel the operation."),
|
|
i18n("Server Does Not Advertise TLS"), i18n("&Start TLS nonetheless"), i18n("&Cancel") ) != KMessageBox::Continue )
|
|
{
|
|
error( ERR_USER_CANCELED, i18n("TLS encryption requested, but not supported by server.") );
|
|
disconnect();
|
|
return false;
|
|
}
|
|
|
|
// FIXME find a test server and test that this works
|
|
if (useTLSIfAvailable && canUseTLS()) {
|
|
sendData("STARTTLS");
|
|
if (operationSuccessful()) {
|
|
ksDebug() << "TLS has been accepted. Starting TLS..." << endl
|
|
<< "WARNING this is untested and may fail." << endl;
|
|
int retval = startTLS();
|
|
if (retval == 1) {
|
|
ksDebug() << "TLS enabled successfully." << endl;
|
|
// reparse capabilities:
|
|
parseCapabilities( requestCapabilitiesAfterStartTLS() );
|
|
} else {
|
|
ksDebug() << "TLS initiation failed, code " << retval << endl;
|
|
if ( m_allowUnencrypted ) {
|
|
disconnect(true);
|
|
return connect(false);
|
|
}
|
|
if ( retval != -3 )
|
|
messageBox( Information,
|
|
i18n("Your Sieve server claims to support TLS, "
|
|
"but negotiation was unsuccessful."),
|
|
i18n("Connection Failed") );
|
|
disconnect(true);
|
|
return false;
|
|
}
|
|
} else if ( !m_allowUnencrypted ) {
|
|
ksDebug() << "Server incapable of TLS." << endl;
|
|
disconnect();
|
|
error( ERR_SLAVE_DEFINED, i18n("The server does not seem to support TLS. "
|
|
"Disable TLS if you want to connect without encryption.") );
|
|
return false;
|
|
} else
|
|
ksDebug() << "Server incapable of TLS. Transmitted documents will be unencrypted." << endl;
|
|
} else
|
|
ksDebug() << "We are incapable of TLS. Transmitted documents will be unencrypted." << endl;
|
|
|
|
assert( m_allowUnencrypted || usingTLS() );
|
|
|
|
infoMessage(i18n("Authenticating user..."));
|
|
if (!authenticate()) {
|
|
disconnect();
|
|
error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed."));
|
|
return false;
|
|
}
|
|
|
|
m_shouldBeConnected = true;
|
|
return true;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::closeConnection()
|
|
{
|
|
m_connMode = CONNECTION_ORIENTED;
|
|
disconnect();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::disconnect(bool forcibly)
|
|
{
|
|
if (!forcibly) {
|
|
sendData("LOGOUT");
|
|
|
|
// This crashes under certain conditions as described in
|
|
// http://intevation.de/roundup/kolab/issue2442
|
|
// Fixing KIO::TCPSlaveBase::atEnd() for !fd would also work but 3.x is on life support.
|
|
//if (!operationSuccessful())
|
|
// ksDebug() << "Server did not logout cleanly." << endl;
|
|
}
|
|
|
|
closeDescriptor();
|
|
m_shouldBeConnected = false;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
/*void kio_sieveProtocol::slave_status()
|
|
{
|
|
slaveStatus(isConnectionValid() ? m_sServer : "", isConnectionValid());
|
|
|
|
finished();
|
|
}*/
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::special(const TQByteArray &data)
|
|
{
|
|
int tmp;
|
|
TQDataStream stream(data, IO_ReadOnly);
|
|
KURL url;
|
|
|
|
stream >> tmp;
|
|
|
|
switch (tmp) {
|
|
case 1:
|
|
stream >> url;
|
|
if (!activate(url))
|
|
return;
|
|
break;
|
|
case 2:
|
|
if (!deactivate())
|
|
return;
|
|
break;
|
|
case 3:
|
|
parseCapabilities(true);
|
|
break;
|
|
}
|
|
|
|
infoMessage(i18n("Done."));
|
|
|
|
finished();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
bool kio_sieveProtocol::activate(const KURL& url)
|
|
{
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return false;
|
|
|
|
infoMessage(i18n("Activating script..."));
|
|
|
|
TQString filename = url.fileName(false);
|
|
|
|
if (filename.isEmpty()) {
|
|
error(ERR_DOES_NOT_EXIST, url.prettyURL());
|
|
return false;
|
|
}
|
|
|
|
if (!sendData("SETACTIVE \"" + filename.utf8() + "\""))
|
|
return false;
|
|
|
|
if (operationSuccessful()) {
|
|
ksDebug() << "Script activation complete." << endl;
|
|
return true;
|
|
} else {
|
|
error(ERR_INTERNAL_SERVER, i18n("There was an error activating the script."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
bool kio_sieveProtocol::deactivate()
|
|
{
|
|
if (!connect())
|
|
return false;
|
|
|
|
if (!sendData("SETACTIVE \"\""))
|
|
return false;
|
|
|
|
if (operationSuccessful()) {
|
|
ksDebug() << "Script deactivation complete." << endl;
|
|
return true;
|
|
} else {
|
|
error(ERR_INTERNAL_SERVER, i18n("There was an error deactivating the script."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void append_lf2crlf( TQByteArray & out, const TQByteArray & in ) {
|
|
if ( in.isEmpty() )
|
|
return;
|
|
const unsigned int oldOutSize = out.size();
|
|
out.resize( oldOutSize + 2 * in.size() );
|
|
const char * s = in.begin();
|
|
const char * const end = in.end();
|
|
char * d = out.begin() + oldOutSize;
|
|
char last = '\0';
|
|
while ( s < end ) {
|
|
if ( *s == '\n' && last != '\r' )
|
|
*d++ = '\r';
|
|
*d++ = last = *s++;
|
|
}
|
|
out.resize( d - out.begin() );
|
|
}
|
|
|
|
void kio_sieveProtocol::put(const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/)
|
|
{
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return;
|
|
|
|
infoMessage(i18n("Sending data..."));
|
|
|
|
TQString filename = url.fileName(false);
|
|
|
|
if (filename.isEmpty()) {
|
|
error(ERR_MALFORMED_URL, url.prettyURL());
|
|
return;
|
|
}
|
|
|
|
TQByteArray data;
|
|
for (;;) {
|
|
dataReq();
|
|
TQByteArray buffer;
|
|
const int newSize = readData(buffer);
|
|
append_lf2crlf( data, buffer );
|
|
if ( newSize < 0 ) {
|
|
// read error: network in unknown state so disconnect
|
|
error(ERR_COULD_NOT_READ, i18n("KIO data supply error."));
|
|
return;
|
|
}
|
|
if ( newSize == 0 )
|
|
break;
|
|
}
|
|
|
|
// script size
|
|
int bufLen = (int)data.size();
|
|
totalSize(bufLen);
|
|
|
|
// timsieved 1.1.0:
|
|
// C: HAVESPACE "rejected" 74
|
|
// S: NO "Number expected"
|
|
// C: HAVESPACE 74
|
|
// S: NO "Missing script name"
|
|
// S: HAVESPACE "rejected" "74"
|
|
// C: NO "Number expected"
|
|
// => broken, we can't use it :-(
|
|
// (will be fixed in Cyrus 2.1.10)
|
|
#ifndef HAVE_BROKEN_TIMSIEVED
|
|
// first, check quota (it's a SHOULD in draft std)
|
|
if (!sendData("HAVESPACE \"" + filename.utf8() + "\" "
|
|
+ TQCString().setNum( bufLen )))
|
|
return;
|
|
|
|
if (!operationSuccessful()) {
|
|
error(ERR_DISK_FULL, i18n("Quota exceeded"));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!sendData("PUTSCRIPT \"" + filename.utf8() + "\" {"
|
|
+ TQCString().setNum( bufLen ) + "+}"))
|
|
return;
|
|
|
|
// atEnd() lies so the code below doesn't work.
|
|
/*if (!atEnd()) {
|
|
// We are not expecting any data here, so if the server has responded
|
|
// with anything but OK we treat it as an error.
|
|
char * buf = new char[2];
|
|
while (!atEnd()) {
|
|
ksDebug() << "Reading..." << endl;
|
|
read(buf, 1);
|
|
ksDebug() << "Trailing [" << buf[0] << "]" << endl;
|
|
}
|
|
ksDebug() << "End of data." << endl;
|
|
delete[] buf;
|
|
|
|
if (!operationSuccessful()) {
|
|
error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred "
|
|
"while trying to negotiate script uploading.\n"
|
|
"The server responded:\n%1")
|
|
.arg(r.getAction().right(r.getAction().length() - 3)));
|
|
return;
|
|
}
|
|
}*/
|
|
|
|
// upload data to the server
|
|
if (write(data, bufLen) != bufLen) {
|
|
error(ERR_COULD_NOT_WRITE, i18n("Network error."));
|
|
disconnect(true);
|
|
return;
|
|
}
|
|
|
|
// finishing CR/LF
|
|
if (!sendData(""))
|
|
return;
|
|
|
|
processedSize(bufLen);
|
|
|
|
infoMessage(i18n("Verifying upload completion..."));
|
|
|
|
if (operationSuccessful())
|
|
ksDebug() << "Script upload complete." << endl;
|
|
|
|
else {
|
|
/* The managesieve server parses received scripts and rejects
|
|
* scripts which are not syntactically correct. Here we expect
|
|
* to receive a message detailing the error (only the first
|
|
* error is reported. */
|
|
if (r.getAction().length() > 3) {
|
|
// make a copy of the extra info
|
|
TQCString extra = r.getAction().right(r.getAction().length() - 3);
|
|
|
|
// send the extra message off for re-processing
|
|
receiveData(false, &extra);
|
|
|
|
if (r.getType() == kio_sieveResponse::QUANTITY) {
|
|
// length of the error message
|
|
uint len = r.getQuantity();
|
|
|
|
TQCString errmsg(len + 1);
|
|
|
|
read(errmsg.data(), len);
|
|
|
|
error(ERR_INTERNAL_SERVER,
|
|
i18n("The script did not upload successfully.\n"
|
|
"This is probably due to errors in the script.\n"
|
|
"The server responded:\n%1").arg(TQString(errmsg)));
|
|
|
|
// clear the rest of the incoming data
|
|
receiveData();
|
|
} else if (r.getType() == kio_sieveResponse::KEY_VAL_PAIR) {
|
|
error(ERR_INTERNAL_SERVER,
|
|
i18n("The script did not upload successfully.\n"
|
|
"This is probably due to errors in the script.\n"
|
|
"The server responded:\n%1").arg(TQString(r.getKey())));
|
|
} else
|
|
error(ERR_INTERNAL_SERVER,
|
|
i18n("The script did not upload successfully.\n"
|
|
"The script may contain errors."));
|
|
} else
|
|
error(ERR_INTERNAL_SERVER,
|
|
i18n("The script did not upload successfully.\n"
|
|
"The script may contain errors."));
|
|
}
|
|
|
|
//if ( permissions != -1 )
|
|
// chmod( url, permissions );
|
|
|
|
infoMessage(i18n("Done."));
|
|
|
|
finished();
|
|
}
|
|
|
|
static void inplace_crlf2lf( TQByteArray & in ) {
|
|
if ( in.isEmpty() )
|
|
return;
|
|
TQByteArray & out = in; // inplace
|
|
const char * s = in.begin();
|
|
const char * const end = in.end();
|
|
char * d = out.begin();
|
|
char last = '\0';
|
|
while ( s < end ) {
|
|
if ( *s == '\n' && last == '\r' )
|
|
--d;
|
|
*d++ = last = *s++;
|
|
}
|
|
out.resize( d - out.begin() );
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::get(const KURL& url)
|
|
{
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return;
|
|
|
|
infoMessage(i18n("Retrieving data..."));
|
|
|
|
TQString filename = url.fileName(false);
|
|
|
|
if (filename.isEmpty()) {
|
|
error(ERR_MALFORMED_URL, url.prettyURL());
|
|
return;
|
|
}
|
|
|
|
//SlaveBase::mimetype( TQString("text/plain") ); // "application/sieve");
|
|
|
|
if (!sendData("GETSCRIPT \"" + filename.utf8() + "\""))
|
|
return;
|
|
|
|
if (receiveData() && r.getType() == kio_sieveResponse::QUANTITY) {
|
|
// determine script size
|
|
ssize_t total_len = r.getQuantity();
|
|
totalSize( total_len );
|
|
|
|
int recv_len = 0;
|
|
do {
|
|
// wait for data...
|
|
if ( !waitForResponse( 600 ) ) {
|
|
error( KIO::ERR_SERVER_TIMEOUT, m_sServer );
|
|
disconnect( true );
|
|
return;
|
|
}
|
|
|
|
// ...read data...
|
|
// Only read as much as we need, otherwise we slurp in the OK that
|
|
// operationSuccessful() is expecting below.
|
|
TQByteArray dat( kMin( total_len - recv_len, ssize_t(64 * 1024 )) );
|
|
ssize_t this_recv_len = read( dat.data(), dat.size() );
|
|
|
|
if ( this_recv_len < 1 && !isConnectionValid() ) {
|
|
error( KIO::ERR_CONNECTION_BROKEN, m_sServer );
|
|
disconnect( true );
|
|
return;
|
|
}
|
|
|
|
dat.resize( this_recv_len );
|
|
inplace_crlf2lf( dat );
|
|
// send data to slaveinterface
|
|
data( dat );
|
|
|
|
recv_len += this_recv_len;
|
|
processedSize( recv_len );
|
|
} while ( recv_len < total_len );
|
|
|
|
infoMessage(i18n("Finishing up...") );
|
|
data(TQByteArray());
|
|
|
|
if (operationSuccessful())
|
|
ksDebug() << "Script retrieval complete." << endl;
|
|
else
|
|
ksDebug() << "Script retrieval failed." << endl;
|
|
} else {
|
|
error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred "
|
|
"while trying to negotiate script downloading."));
|
|
return;
|
|
}
|
|
|
|
infoMessage(i18n("Done."));
|
|
finished();
|
|
}
|
|
|
|
void kio_sieveProtocol::del(const KURL &url, bool isfile)
|
|
{
|
|
if (!isfile) {
|
|
error(ERR_INTERNAL, i18n("Folders are not supported."));
|
|
return;
|
|
}
|
|
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return;
|
|
|
|
infoMessage(i18n("Deleting file..."));
|
|
|
|
TQString filename = url.fileName(false);
|
|
|
|
if (filename.isEmpty()) {
|
|
error(ERR_MALFORMED_URL, url.prettyURL());
|
|
return;
|
|
}
|
|
|
|
if (!sendData("DELETESCRIPT \"" + filename.utf8() + "\""))
|
|
return;
|
|
|
|
if (operationSuccessful())
|
|
ksDebug() << "Script deletion successful." << endl;
|
|
else {
|
|
error(ERR_INTERNAL_SERVER, i18n("The server would not delete the file."));
|
|
return;
|
|
}
|
|
|
|
infoMessage(i18n("Done."));
|
|
|
|
finished();
|
|
}
|
|
|
|
void kio_sieveProtocol::chmod(const KURL& url, int permissions)
|
|
{
|
|
switch ( permissions ) {
|
|
case 0700: // activate
|
|
activate(url);
|
|
break;
|
|
case 0600: // deactivate
|
|
deactivate();
|
|
break;
|
|
default: // unsupported
|
|
error(ERR_CANNOT_CHMOD, i18n("Cannot chmod to anything but 0700 (active) or 0600 (inactive script)."));
|
|
return;
|
|
}
|
|
|
|
finished();
|
|
}
|
|
|
|
#if defined(_AIX) && defined(stat)
|
|
#undef stat
|
|
#endif
|
|
|
|
void kio_sieveProtocol::stat(const KURL& url)
|
|
{
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return;
|
|
|
|
UDSEntry entry;
|
|
|
|
TQString filename = url.fileName(false);
|
|
|
|
if (filename.isEmpty()) {
|
|
UDSAtom atom;
|
|
atom.m_uds = KIO::UDS_NAME;
|
|
atom.m_str = "/";
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_FILE_TYPE;
|
|
atom.m_long = S_IFDIR;
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_ACCESS;
|
|
atom.m_long = 0700;
|
|
entry.append(atom);
|
|
|
|
statEntry(entry);
|
|
|
|
} else {
|
|
if (!sendData("LISTSCRIPTS"))
|
|
return;
|
|
|
|
while(receiveData()) {
|
|
if (r.getType() == kio_sieveResponse::ACTION) {
|
|
if (r.getAction().contains("OK", false) == 1)
|
|
// Script list completed
|
|
break;
|
|
|
|
} else
|
|
if (filename == TQString::fromUtf8(r.getKey())) {
|
|
entry.clear();
|
|
|
|
UDSAtom atom;
|
|
atom.m_uds = KIO::UDS_NAME;
|
|
atom.m_str = TQString::fromUtf8(r.getKey());
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_FILE_TYPE;
|
|
atom.m_long = S_IFREG;
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_ACCESS;
|
|
if ( r.getExtra() == "ACTIVE" )
|
|
atom.m_long = 0700; // mark exec'able
|
|
else
|
|
atom.m_long = 0600;
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_MIME_TYPE;
|
|
atom.m_str = "application/sieve";
|
|
entry.append(atom);
|
|
|
|
//setMetaData("active", (r.getExtra() == "ACTIVE") ? "yes" : "no");
|
|
|
|
statEntry(entry);
|
|
// cannot break here because we need to clear
|
|
// the rest of the incoming data.
|
|
}
|
|
}
|
|
}
|
|
|
|
finished();
|
|
}
|
|
|
|
void kio_sieveProtocol::listDir(const KURL& url)
|
|
{
|
|
changeCheck( url );
|
|
if (!connect())
|
|
return;
|
|
|
|
if (!sendData("LISTSCRIPTS"))
|
|
return;
|
|
|
|
UDSEntry entry;
|
|
|
|
while(receiveData()) {
|
|
if (r.getType() == kio_sieveResponse::ACTION) {
|
|
if (r.getAction().contains("OK", false) == 1)
|
|
// Script list completed.
|
|
break;
|
|
|
|
} else {
|
|
entry.clear();
|
|
|
|
UDSAtom atom;
|
|
atom.m_uds = KIO::UDS_NAME;
|
|
atom.m_str = TQString::fromUtf8(r.getKey());
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_FILE_TYPE;
|
|
atom.m_long = S_IFREG;
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_ACCESS;
|
|
if ( r.getExtra() == "ACTIVE" )
|
|
atom.m_long = 0700; // mark exec'able
|
|
else
|
|
atom.m_long = 0600;
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = KIO::UDS_MIME_TYPE;
|
|
atom.m_str = "application/sieve";
|
|
entry.append(atom);
|
|
|
|
//asetMetaData("active", (r.getExtra() == "ACTIVE") ? "true" : "false");
|
|
|
|
ksDebug() << "Listing script " << r.getKey() << endl;
|
|
listEntry(entry , false);
|
|
}
|
|
}
|
|
|
|
listEntry(entry, true);
|
|
|
|
finished();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------- */
|
|
bool kio_sieveProtocol::saslInteract( void *in, AuthInfo &ai )
|
|
{
|
|
ksDebug() << "sasl_interact" << endl;
|
|
sasl_interact_t *interact = ( sasl_interact_t * ) in;
|
|
|
|
//some mechanisms do not require username && pass, so it doesn't need a popup
|
|
//window for getting this info
|
|
for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
|
|
if ( interact->id == SASL_CB_AUTHNAME ||
|
|
interact->id == SASL_CB_PASS ) {
|
|
|
|
if (m_sUser.isEmpty() || m_sPass.isEmpty()) {
|
|
if (!openPassDlg(ai)) {
|
|
error(ERR_ABORTED, i18n("No authentication details supplied."));
|
|
return false;
|
|
}
|
|
m_sUser = ai.username;
|
|
m_sPass = ai.password;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
interact = ( sasl_interact_t * ) in;
|
|
while( interact->id != SASL_CB_LIST_END ) {
|
|
ksDebug() << "SASL_INTERACT id: " << interact->id << endl;
|
|
switch( interact->id ) {
|
|
case SASL_CB_USER:
|
|
case SASL_CB_AUTHNAME:
|
|
ksDebug() << "SASL_CB_[AUTHNAME|USER]: '" << m_sUser << "'" << endl;
|
|
interact->result = strdup( m_sUser.utf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
case SASL_CB_PASS:
|
|
ksDebug() << "SASL_CB_PASS: [hidden] " << endl;
|
|
interact->result = strdup( m_sPass.utf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
default:
|
|
interact->result = NULL; interact->len = 0;
|
|
break;
|
|
}
|
|
interact++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define SASLERROR error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1").arg( \
|
|
TQString::fromUtf8( sasl_errdetail( conn ) )));
|
|
|
|
bool kio_sieveProtocol::authenticate()
|
|
{
|
|
int result;
|
|
sasl_conn_t *conn = NULL;
|
|
sasl_interact_t *client_interact = NULL;
|
|
const char *out = NULL;
|
|
uint outlen;
|
|
const char *mechusing = NULL;
|
|
TQByteArray challenge, tmp;
|
|
|
|
/* Retrieve authentication details from user.
|
|
* Note: should this require realm as well as user & pass details
|
|
* before it automatically skips the prompt?
|
|
* Note2: encoding issues with PLAIN login? */
|
|
AuthInfo ai;
|
|
ai.url.setProtocol("sieve");
|
|
ai.url.setHost(m_sServer);
|
|
ai.url.setPort(m_iPort);
|
|
ai.username = m_sUser;
|
|
ai.password = m_sPass;
|
|
ai.keepPassword = true;
|
|
ai.caption = i18n("Sieve Authentication Details");
|
|
ai.comment = i18n("Please enter your authentication details for your sieve account "
|
|
"(usually the same as your email password):");
|
|
|
|
result = sasl_client_new( "sieve",
|
|
m_sServer.latin1(),
|
|
0, 0, callbacks, 0, &conn );
|
|
|
|
if ( result != SASL_OK ) {
|
|
ksDebug() << "sasl_client_new failed with: " << result << endl;
|
|
SASLERROR
|
|
return false;
|
|
}
|
|
|
|
TQStringList strList;
|
|
// strList.append("NTLM");
|
|
|
|
if ( !m_sAuth.isEmpty() )
|
|
strList.append( m_sAuth );
|
|
else
|
|
strList = m_sasl_caps;
|
|
|
|
do {
|
|
result = sasl_client_start(conn, strList.join(" ").latin1(), &client_interact,
|
|
&out, &outlen, &mechusing);
|
|
|
|
if (result == SASL_INTERACT)
|
|
if ( !saslInteract( client_interact, ai ) ) {
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
};
|
|
} while ( result == SASL_INTERACT );
|
|
|
|
if ( result != SASL_CONTINUE && result != SASL_OK ) {
|
|
ksDebug() << "sasl_client_start failed with: " << result << endl;
|
|
SASLERROR
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
|
|
ksDebug() << "Preferred authentication method is " << mechusing << "." << endl;
|
|
|
|
TQString firstCommand = "AUTHENTICATE \"" + TQString::fromLatin1( mechusing ) + "\"";
|
|
tmp.setRawData( out, outlen );
|
|
KCodecs::base64Encode( tmp, challenge );
|
|
tmp.resetRawData( out, outlen );
|
|
if ( !challenge.isEmpty() ) {
|
|
firstCommand += " \"";
|
|
firstCommand += TQString::fromLatin1( challenge.data(), challenge.size() );
|
|
firstCommand += "\"";
|
|
}
|
|
|
|
if (!sendData( firstCommand.latin1() ))
|
|
return false;
|
|
|
|
TQCString command;
|
|
|
|
do {
|
|
receiveData();
|
|
|
|
if (operationResult() != OTHER)
|
|
break;
|
|
|
|
ksDebug() << "Challenge len " << r.getQuantity() << endl;
|
|
|
|
if (r.getType() != kio_sieveResponse::QUANTITY) {
|
|
sasl_dispose( &conn );
|
|
error(ERR_SLAVE_DEFINED,
|
|
i18n("A protocol error occurred during authentication.\n"
|
|
"Choose a different authentication method to %1.").arg(mechusing));
|
|
return false;
|
|
}
|
|
|
|
uint qty = r.getQuantity();
|
|
|
|
receiveData();
|
|
|
|
if (r.getType() != kio_sieveResponse::ACTION && r.getAction().length() != qty) {
|
|
sasl_dispose( &conn );
|
|
error(ERR_UNSUPPORTED_PROTOCOL,
|
|
i18n("A protocol error occurred during authentication.\n"
|
|
"Choose a different authentication method to %1.").arg(mechusing));
|
|
return false;
|
|
}
|
|
|
|
tmp.setRawData( r.getAction().data(), qty );
|
|
KCodecs::base64Decode( tmp, challenge );
|
|
tmp.resetRawData( r.getAction().data(), qty );
|
|
// ksDebug() << "S: [" << r.getAction() << "]." << endl;
|
|
// ksDebug() << "S-1: [" << TQCString(challenge.data(), challenge.size()+1) << "]." << endl;
|
|
|
|
do {
|
|
result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
|
|
challenge.size(),
|
|
&client_interact,
|
|
&out, &outlen);
|
|
|
|
if (result == SASL_INTERACT)
|
|
if ( !saslInteract( client_interact, ai ) ) {
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
};
|
|
} while ( result == SASL_INTERACT );
|
|
|
|
ksDebug() << "sasl_client_step: " << result << endl;
|
|
if ( result != SASL_CONTINUE && result != SASL_OK ) {
|
|
ksDebug() << "sasl_client_step failed with: " << result << endl;
|
|
SASLERROR
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
|
|
tmp.setRawData( out, outlen );
|
|
KCodecs::base64Encode( tmp, challenge );
|
|
tmp.resetRawData( out, outlen );
|
|
sendData("\"" + TQCString( challenge.data(), challenge.size()+1 ) + "\"");
|
|
// ksDebug() << "C: [" << TQCString(challenge.data(), challenge.size()+1) << "]." << endl;
|
|
// ksDebug() << "C-1: [" << out << "]." << endl;
|
|
} while ( true );
|
|
|
|
ksDebug() << "Challenges finished." << endl;
|
|
sasl_dispose( &conn );
|
|
|
|
if (operationResult() == OK) {
|
|
// Authentication succeeded.
|
|
return true;
|
|
} else {
|
|
// Authentication failed.
|
|
error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.\nMost likely the password is wrong.\nThe server responded:\n%1").arg( TQString(r.getAction()) ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------- */
|
|
void kio_sieveProtocol::mimetype(const KURL & url)
|
|
{
|
|
ksDebug() << "Requesting mimetype for " << url.prettyURL() << endl;
|
|
|
|
if (url.fileName(false).isEmpty())
|
|
mimeType( "inode/directory" );
|
|
else
|
|
mimeType( "application/sieve" );
|
|
|
|
finished();
|
|
}
|
|
|
|
|
|
/* --------------------------------------------------------------------------- */
|
|
bool kio_sieveProtocol::sendData(const TQCString &data)
|
|
{
|
|
TQCString write_buf = data + "\r\n";
|
|
|
|
//ksDebug() << "C: " << data << endl;
|
|
|
|
// Write the command
|
|
ssize_t write_buf_len = write_buf.length();
|
|
if (write(write_buf.data(), write_buf_len) != write_buf_len) {
|
|
error(ERR_COULD_NOT_WRITE, i18n("Network error."));
|
|
disconnect(true);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------- */
|
|
bool kio_sieveProtocol::receiveData(bool waitForData, TQCString *reparse)
|
|
{
|
|
TQCString interpret;
|
|
int start, end;
|
|
|
|
if (!reparse) {
|
|
if (!waitForData)
|
|
// is there data waiting?
|
|
if (atEnd()) return false;
|
|
|
|
// read data from the server
|
|
char buffer[SIEVE_DEFAULT_RECIEVE_BUFFER];
|
|
readLine(buffer, SIEVE_DEFAULT_RECIEVE_BUFFER - 1);
|
|
buffer[SIEVE_DEFAULT_RECIEVE_BUFFER-1] = '\0';
|
|
|
|
// strip LF/CR
|
|
interpret = TQCString(buffer).left(tqstrlen(buffer) - 2);
|
|
|
|
} else {
|
|
interpret = reparse->copy();
|
|
}
|
|
|
|
r.clear();
|
|
|
|
//ksDebug() << "S: " << interpret << endl;
|
|
|
|
switch(interpret[0]) {
|
|
case '{':
|
|
{
|
|
// expecting {quantity}
|
|
start = 0;
|
|
end = interpret.find("+}", start + 1);
|
|
// some older versions of Cyrus enclose the literal size just in { } instead of { +}
|
|
if ( end == -1 )
|
|
end = interpret.find('}', start + 1);
|
|
|
|
bool ok = false;
|
|
r.setQuantity(interpret.mid(start + 1, end - start - 1).toUInt( &ok ));
|
|
if (!ok) {
|
|
disconnect();
|
|
error(ERR_INTERNAL_SERVER, i18n("A protocol error occurred."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
case '"':
|
|
// expecting "key" "value" pairs
|
|
break;
|
|
default:
|
|
// expecting single string
|
|
r.setAction(interpret);
|
|
return true;
|
|
}
|
|
|
|
start = 0;
|
|
|
|
end = interpret.find(34, start + 1);
|
|
if (end == -1) {
|
|
ksDebug() << "Possible insufficient buffer size." << endl;
|
|
r.setKey(interpret.right(interpret.length() - start));
|
|
return true;
|
|
}
|
|
|
|
r.setKey(interpret.mid(start + 1, end - start - 1));
|
|
|
|
start = interpret.find(34, end + 1);
|
|
if (start == -1) {
|
|
if ((int)interpret.length() > end)
|
|
// skip " and space
|
|
r.setExtra(interpret.right(interpret.length() - end - 2));
|
|
|
|
return true;
|
|
}
|
|
|
|
end = interpret.find(34, start + 1);
|
|
if (end == -1) {
|
|
ksDebug() << "Possible insufficient buffer size." << endl;
|
|
r.setVal(interpret.right(interpret.length() - start));
|
|
return true;
|
|
}
|
|
|
|
r.setVal(interpret.mid(start + 1, end - start - 1));
|
|
return true;
|
|
}
|
|
|
|
bool kio_sieveProtocol::operationSuccessful()
|
|
{
|
|
while (receiveData(false)) {
|
|
if (r.getType() == kio_sieveResponse::ACTION) {
|
|
TQCString response = r.getAction().left(2);
|
|
if (response == "OK") {
|
|
return true;
|
|
} else if (response == "NO") {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int kio_sieveProtocol::operationResult()
|
|
{
|
|
if (r.getType() == kio_sieveResponse::ACTION) {
|
|
TQCString response = r.getAction().left(2);
|
|
if (response == "OK") {
|
|
return OK;
|
|
} else if (response == "NO") {
|
|
return NO;
|
|
} else if (response == "BY"/*E*/) {
|
|
return BYE;
|
|
}
|
|
}
|
|
|
|
return OTHER;
|
|
}
|
|
|
|
bool kio_sieveProtocol::requestCapabilitiesAfterStartTLS() const
|
|
{
|
|
// Cyrus didn't send CAPABILITIES after STARTTLS until 2.3.11, which is
|
|
// not standard conform, but we need to support that anyway.
|
|
// m_implementation looks like this 'Cyrus timsieved v2.2.12' for Cyrus btw.
|
|
TQRegExp regExp( "Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)", false );
|
|
if ( regExp.search( m_implementation ) >= 0 ) {
|
|
const int major = regExp.cap( 1 ).toInt();
|
|
const int minor = regExp.cap( 2 ).toInt();
|
|
const int patch = regExp.cap( 3 ).toInt();
|
|
const TQString vendor = regExp.cap( 4 );
|
|
if ( major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == "-kolab-nocaps") ) {
|
|
ksDebug() << k_funcinfo << "Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"" << endl;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|