Git commit 6eef4101131a197be7fd478f3852fdb08501697d by Montel Laurent. Committed on 05/04/2016 at 05:02. Pushed by mlaurent into branch 'master'.
Move here M +10 -1 CMakeLists.txt A +22 -0 cmake/COPYING-CMAKE-SCRIPTS A +53 -0 cmake/FindSasl2.cmake A +1 -0 kioslave/.krazy A +5 -0 kioslave/.reviewboardrc A +5 -0 kioslave/CMakeLists.txt A +1 -0 kioslave/docs/CMakeLists.txt A +3 -0 kioslave/docs/sieve/CMakeLists.txt A +30 -0 kioslave/docs/sieve/index.docbook A +4 -0 kioslave/src/CMakeLists.txt A +50 -0 kioslave/src/common.h [License: LGPL (v2+)] A +20 -0 kioslave/src/sieve/CMakeLists.txt A +2 -0 kioslave/src/sieve/Messages.sh A +4 -0 kioslave/src/sieve/RFCs A +2 -0 kioslave/src/sieve/sieve-config.h.cmake A +1313 -0 kioslave/src/sieve/sieve.cpp [License: GPL (v2)] A +145 -0 kioslave/src/sieve/sieve.h [License: GPL (v2)] A +65 -0 kioslave/src/sieve/sieve.protocol M +1 -0 libksieve.categories http://commits.kde.org/libksieve/6eef4101131a197be7fd478f3852fdb08501697d diff --git a/CMakeLists.txt b/CMakeLists.txt index ae70009..084a326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8.12) project(libksieve) find_package(ECM 5.19.0 CONFIG REQUIRED) -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) +set(CMAKE_MODULE_PATH ${libksieve_SOURCE_DIR}/cmake ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) include(GenerateExportHeader) include(ECMSetupVersion) @@ -45,6 +45,14 @@ find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQU find_package(KF5MailTransport ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) +find_package(Sasl2) +set_package_properties(Sasl2 PROPERTIES + DESCRIPTION "The Cyrus-sasl library" + URL "http://www.cyrussasl.org" + TYPE OPTIONAL +) + + option(QTWEBENGINE_SUPPORT_OPTION "Enable support for QtWebEngine. False by default." FALSE) if (QTWEBENGINE_SUPPORT_OPTION) @@ -89,6 +97,7 @@ install(FILES add_subdirectory(src) +add_subdirectory(kioslave) if(BUILD_TESTING) add_subdirectory(autotests) endif() diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS new file mode 100644 index 0000000..4b41776 --- /dev/null +++ b/cmake/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/FindSasl2.cmake b/cmake/FindSasl2.cmake new file mode 100644 index 0000000..ec3bb81 --- /dev/null +++ b/cmake/FindSasl2.cmake @@ -0,0 +1,53 @@ +# +# - Try to find the sasl2 directory library +# Once done this will define +# +# Sasl2_FOUND - system has SASL2 +# Sasl2_INCLUDE_DIRS - the SASL2 include directory +# Sasl2_LIBRARIES - The libraries needed to use SASL2 + +# Copyright (c) 2006, 2007 Laurent Montel, <[email protected]> +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Note: libsasl2.pc doesn't export the include dir. +find_package(PkgConfig QUIET) +pkg_check_modules(PC_Sasl2 libsasl2) + +find_path(Sasl2_INCLUDE_DIRS sasl/sasl.h +) + +# libsasl2 add for windows, because the windows package of cyrus-sasl2 +# contains a libsasl2 also for msvc which is not standard conform +find_library(Sasl2_LIBRARIES + NAMES sasl2 libsasl2 + HINTS ${PC_Sasl2_LIBRARY_DIRS} +) + +set(Sasl2_VERSION ${PC_Sasl2_VERSION}) + +if(NOT Sasl2_VERSION) + if(EXISTS ${Sasl2_INCLUDE_DIRS}/sasl/sasl.h) + file(READ ${Sasl2_INCLUDE_DIRS}/sasl/sasl.h SASL2_H_CONTENT) + string(REGEX MATCH "#define SASL_VERSION_MAJOR[ ]+[0-9]+" SASL2_VERSION_MAJOR_MATCH ${SASL2_H_CONTENT}) + string(REGEX MATCH "#define SASL_VERSION_MINOR[ ]+[0-9]+" SASL2_VERSION_MINOR_MATCH ${SASL2_H_CONTENT}) + string(REGEX MATCH "#define SASL_VERSION_STEP[ ]+[0-9]+" SASL2_VERSION_STEP_MATCH ${SASL2_H_CONTENT}) + + string(REGEX REPLACE ".*_MAJOR[ ]+(.*)" "\\1" SASL2_VERSION_MAJOR ${SASL2_VERSION_MAJOR_MATCH}) + string(REGEX REPLACE ".*_MINOR[ ]+(.*)" "\\1" SASL2_VERSION_MINOR ${SASL2_VERSION_MINOR_MATCH}) + string(REGEX REPLACE ".*_STEP[ ]+(.*)" "\\1" SASL2_VERSION_STEP ${SASL2_VERSION_STEP_MATCH}) + + set(Sasl2_VERSION "${SASL2_VERSION_MAJOR}.${SASL2_VERSION_MINOR}.${SASL2_VERSION_STEP}") + endif() +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Sasl2 + FOUND_VAR Sasl2_FOUND + REQUIRED_VARS Sasl2_LIBRARIES Sasl2_INCLUDE_DIRS + VERSION_VAR Sasl2_VERSION +) + +mark_as_advanced(Sasl2_LIBRARIES Sasl2_INCLUDE_DIRS Sasl2_VERSION) diff --git a/kioslave/.krazy b/kioslave/.krazy new file mode 100644 index 0000000..0b16e7f --- /dev/null +++ b/kioslave/.krazy @@ -0,0 +1 @@ +SKIP /tests/ diff --git a/kioslave/.reviewboardrc b/kioslave/.reviewboardrc new file mode 100644 index 0000000..5c1c502 --- /dev/null +++ b/kioslave/.reviewboardrc @@ -0,0 +1,5 @@ +REVIEWBOARD_URL = "https://git.reviewboard.kde.org" +#REPOSITORY = "git://anongit.kde.org/kioslave" +BRANCH = "master" +TARGET_GROUPS = "kdepimlibs" +TARGET_PEOPLE = "mlaurent" diff --git a/kioslave/CMakeLists.txt b/kioslave/CMakeLists.txt new file mode 100644 index 0000000..a29ebc0 --- /dev/null +++ b/kioslave/CMakeLists.txt @@ -0,0 +1,5 @@ +add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") + +add_subdirectory(src) +add_subdirectory(docs) + diff --git a/kioslave/docs/CMakeLists.txt b/kioslave/docs/CMakeLists.txt new file mode 100644 index 0000000..44813ac --- /dev/null +++ b/kioslave/docs/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(sieve) diff --git a/kioslave/docs/sieve/CMakeLists.txt b/kioslave/docs/sieve/CMakeLists.txt new file mode 100644 index 0000000..98fdda4 --- /dev/null +++ b/kioslave/docs/sieve/CMakeLists.txt @@ -0,0 +1,3 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/sieve) + diff --git a/kioslave/docs/sieve/index.docbook b/kioslave/docs/sieve/index.docbook new file mode 100644 index 0000000..d269a68 --- /dev/null +++ b/kioslave/docs/sieve/index.docbook @@ -0,0 +1,30 @@ +<?xml version="1.0" ?> +<!DOCTYPE article PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" +"dtd/kdedbx45.dtd" [ +<!ENTITY % addindex "IGNORE"> +<!ENTITY % English "INCLUDE" > <!-- change language only here --> +]> + +<article lang="&language;" id="sieve"> +<title>sieve</title> +<articleinfo> + +<authorgroup> +<author><personname><firstname>Daniel</firstname><surname>Black</surname></personname> +<email>[email protected]</email></author> +<!-- TRANS:ROLES_OF_TRANSLATORS --> +</authorgroup> + +<date>2009-11-05</date> + + +</articleinfo> + +<para>Sieve is a protocol that is used to manage filters for email.</para> + +<para>The filters are stored and run on the email server.</para> + +<para><ulink url="http://www.ietf.org/rfc/rfc5228.txt">IETF +RF5228</ulink> provides more information.</para> + +</article> diff --git a/kioslave/src/CMakeLists.txt b/kioslave/src/CMakeLists.txt new file mode 100644 index 0000000..0ee108d --- /dev/null +++ b/kioslave/src/CMakeLists.txt @@ -0,0 +1,4 @@ +#remove it +remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) +add_subdirectory(sieve) + diff --git a/kioslave/src/common.h b/kioslave/src/common.h new file mode 100644 index 0000000..acdeb17 --- /dev/null +++ b/kioslave/src/common.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2008 Jarosław Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _KIOSLAVE_COMMON_H +#define _KIOSLAVE_COMMON_H + +#include <stdio.h> +#include <QFile> +#include <QDir> + +extern "C" { +#include <sasl/sasl.h> +} + +inline bool initSASL() +{ +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + QByteArray libInstallPath(QFile::encodeName(QDir::toNativeSeparators(KGlobal::dirs()->installPath("lib") + QLatin1String("sasl2")))); + QByteArray configPath(QFile::encodeName(QDir::toNativeSeparators(KGlobal::dirs()->installPath("config") + QLatin1String("sasl2")))); + if (sasl_set_path(SASL_PATH_TYPE_PLUGIN, libInstallPath.data()) != SASL_OK || + sasl_set_path(SASL_PATH_TYPE_CONFIG, configPath.data()) != SASL_OK) { + fprintf(stderr, "SASL path initialization failed!\n"); + return false; + } +#endif + + if (sasl_client_init(NULL) != SASL_OK) { + fprintf(stderr, "SASL library initialization failed!\n"); + return false; + } + return true; +} + +#endif diff --git a/kioslave/src/sieve/CMakeLists.txt b/kioslave/src/sieve/CMakeLists.txt new file mode 100644 index 0000000..5a6efeb --- /dev/null +++ b/kioslave/src/sieve/CMakeLists.txt @@ -0,0 +1,20 @@ +configure_file (sieve-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/sieve-config.h ) +########### next target ############### + +set(kio_sieve_PART_SRCS sieve.cpp ) + +ecm_qt_declare_logging_category(kio_sieve_PART_SRCS HEADER sieve_debug.h IDENTIFIER SIEVE_LOG CATEGORY_NAME log_sieve) + +add_library(kio_sieve MODULE ${kio_sieve_PART_SRCS}) + + + +target_link_libraries(kio_sieve KF5::KIOCore KF5::I18n Qt5::Network KF5::WidgetsAddons ${Sasl2_LIBRARIES}) +set_target_properties(kio_sieve PROPERTIES OUTPUT_NAME "sieve") + +install(TARGETS kio_sieve DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio/) + + +########### install files ############### + +install( FILES sieve.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/kioslave/src/sieve/Messages.sh b/kioslave/src/sieve/Messages.sh new file mode 100644 index 0000000..d5b8d00 --- /dev/null +++ b/kioslave/src/sieve/Messages.sh @@ -0,0 +1,2 @@ +#! /bin/sh +$XGETTEXT *.cpp -o $podir/kio_sieve.pot diff --git a/kioslave/src/sieve/RFCs b/kioslave/src/sieve/RFCs new file mode 100644 index 0000000..9f87c4f --- /dev/null +++ b/kioslave/src/sieve/RFCs @@ -0,0 +1,4 @@ +3028: Sieve: A Mail Filtering Language +3431: Sieve Extension: Relational Tests + +Documents at http://ktown.kde.org/~dirk/sieve/ diff --git a/kioslave/src/sieve/sieve-config.h.cmake b/kioslave/src/sieve/sieve-config.h.cmake new file mode 100644 index 0000000..e11fa9f --- /dev/null +++ b/kioslave/src/sieve/sieve-config.h.cmake @@ -0,0 +1,2 @@ +/* Define if you have cyrus-sasl2 libraries */ +#cmakedefine HAVE_LIBSASL2 1 diff --git a/kioslave/src/sieve/sieve.cpp b/kioslave/src/sieve/sieve.cpp new file mode 100644 index 0000000..3b8760b --- /dev/null +++ b/kioslave/src/sieve/sieve.cpp @@ -0,0 +1,1313 @@ +/*************************************************************************** + sieve.cpp - description + ------------------- + begin : Thu Dec 20 18:47:08 EST 2001 + copyright : (C) 2001 by Hamish Rodda + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <[email protected]> + * Copyright (c) 2001 Michael Häckel <[email protected]> + * 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. + */ + +#include "sieve.h" +#include "../common.h" +#include "sieve_debug.h" + +extern "C" { +#include <sasl/sasl.h> +} + +#include <qregexp.h> +#include <QSslSocket> +#include <QUrlQuery> + +#include <KLocalizedString> +#include <QUrl> +#include <KMessageBox> +#include <QApplication> +#include <sys/stat.h> +#include <cassert> + +#define ksDebug qCDebug( SIEVE_LOG ) + +#define SIEVE_DEFAULT_PORT 2000 + +static const 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" +{ + Q_DECL_EXPORT int kdemain(int argc, char **argv) + { + QApplication app(argc, argv); + app.setApplicationName(QStringLiteral("kio_sieve")); + + ksDebug << "*** Starting kio_sieve " << endl; + + if (argc != 4) { + ksDebug << "Usage: kio_sieve protocol domain-socket1 domain-socket2" << endl; + return -1; + } + + if (!initSASL()) { + ::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; +} + +/* ---------------------------------------------------------------------------------- */ +uint kio_sieveResponse::getQuantity() const +{ + return quantity; +} + +/* ---------------------------------------------------------------------------------- */ +const QByteArray &kio_sieveResponse::getAction() const +{ + return key; +} + +/* ---------------------------------------------------------------------------------- */ +const QByteArray &kio_sieveResponse::getKey() const +{ + return key; +} + +/* ---------------------------------------------------------------------------------- */ +const QByteArray &kio_sieveResponse::getVal() const +{ + return val; +} + +/* ---------------------------------------------------------------------------------- */ +const QByteArray &kio_sieveResponse::getExtra() const +{ + return extra; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setQuantity(const uint &newQty) +{ + rType = QUANTITY; + quantity = newQty; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setAction(const QByteArray &newAction) +{ + rType = ACTION; + key = newAction; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setKey(const QByteArray &newKey) +{ + rType = KEY_VAL_PAIR; + key = newKey; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setVal(const QByteArray &newVal) +{ + val = newVal; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::setExtra(const QByteArray &newExtra) +{ + extra = newExtra; +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveResponse::clear() +{ + rType = NONE; + extra = key = val = QByteArray(); + quantity = 0; +} + +/* ---------------------------------------------------------------------------------- */ +kio_sieveProtocol::kio_sieveProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) + : TCPSlaveBase("sieve", pool_socket, app_socket, false) + , m_connMode(NORMAL) + , m_supportsTLS(false) + , m_shouldBeConnected(false) + , m_allowUnencrypted(false) + , m_port(SIEVE_DEFAULT_PORT) +{ +} + +/* ---------------------------------------------------------------------------------- */ +kio_sieveProtocol::~kio_sieveProtocol() +{ + if (isConnected()) { + disconnect(); + } +} + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::setHost(const QString &host, quint16 port, const QString &user, const QString &pass) +{ + if (isConnected() && + (m_sServer != host || + m_port != port || + m_sUser != user || + m_sPass != pass)) { + disconnect(); + } + m_sServer = host; + m_port = port ? port : SIEVE_DEFAULT_PORT; + 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 << endl; + + // Setup... + bool ret = false; + + if (requestCapabilities) { + sendData("CAPABILITY"); + } + + while (receiveData()) { + ksDebug << "Looping receive" << endl; + + if (r.getType() == kio_sieveResponse::ACTION) { + if (r.getAction().toLower().contains("ok")) { + ksDebug << "Sieve server ready & awaiting authentication." << endl; + break; + } else { + ksDebug << "Unknown action " << r.getAction() << "." << endl; + } + + } else if (r.getKey() == "IMPLEMENTATION") { + ksDebug << "Connected to Sieve server: " << r.getVal() << endl; + ret = true; + setMetaData(QStringLiteral("implementation"), QLatin1String(r.getVal())); + m_implementation = QLatin1String(r.getVal()); + + } else if (r.getKey() == "SASL") { + // Save list of available SASL methods + const QString val = QLatin1String(r.getVal()); + m_sasl_caps = val.split(QLatin1Char(' ')); + ksDebug << "Server SASL authentication methods: " << m_sasl_caps.join(QStringLiteral(", ")) << endl; + setMetaData(QStringLiteral("saslMethods"), QLatin1String(r.getVal())); + + } else if (r.getKey() == "SIEVE") { + // Save script capabilities; report back as meta data: + const QString val = QLatin1String(r.getVal()); + ksDebug << "Server script capabilities: " << val.split(QLatin1Char(' ')).join(QStringLiteral(", ")) << endl; + setMetaData(QStringLiteral("sieveExtensions"), QLatin1String(r.getVal())); + + } else if (r.getKey() == "STARTTLS") { + // The server supports TLS + ksDebug << "Server supports TLS" << endl; + m_supportsTLS = true; + setMetaData(QStringLiteral("tlsSupported"), QStringLiteral("true")); + + } else { + ksDebug << "Unrecognised key " << r.getKey() << endl; + } + } + + if (!m_supportsTLS) { + setMetaData(QStringLiteral("tlsSupported"), QStringLiteral("false")); + } + + return ret; +} + +/* ---------------------------------------------------------------------------------- */ +/** + * Checks if connection parameters have changed. + * If it it, close the current connection + */ +void kio_sieveProtocol::changeCheck(const QUrl &url) +{ + QString auth; + + // Check the SASL auth mechanism in the 'sasl' metadata... + if (!metaData(QStringLiteral("sasl")).isEmpty()) { + auth = metaData(QStringLiteral("sasl")).toUpper(); + } else { + // ... and if not found, check the x-mech=AUTH query part of the url. + QString query = url.query(); + if (query.startsWith(QLatin1Char('?'))) { + query.remove(0, 1); + } + QStringList q = query.split(QLatin1Char(',')); + QStringList::iterator it; + + for (it = q.begin(); it != q.end(); ++it) { + if (((*it).section(QLatin1Char('='), 0, 0)).toLower() == QLatin1String("x-mech")) { + auth = ((*it).section(QLatin1Char('='), 1)).toUpper(); + break; + } + } + } + ksDebug << "auth: " << auth << " m_sAuth: " << m_sAuth << endl; + if (m_sAuth != auth) { + m_sAuth = auth; + if (isConnected()) { + disconnect(); + } + } + // For TLS, only disconnect if we are unencrypted and are + // no longer allowed (otherwise, it's still fine): + const bool allowUnencryptedNow = QUrlQuery(url).queryItemValue(QStringLiteral("x-allow-unencrypted")) == QLatin1String("true"); + if (m_allowUnencrypted && !allowUnencryptedNow) { + if (isConnected()) { + disconnect(); + } + } + m_allowUnencrypted = allowUnencryptedNow; +} + +/* ---------------------------------------------------------------------------------- */ +/** + * Connects to the server. + * returns false and calls error() if an error occurred. + */ +bool kio_sieveProtocol::connect(bool useTLSIfAvailable) +{ + ksDebug << endl; + + if (isConnected()) { + return true; + } + + infoMessage(i18n("Connecting to %1...", m_sServer)); + + if (m_connMode == CONNECTION_ORIENTED && m_shouldBeConnected) { + error(ERR_CONNECTION_BROKEN, i18n("The connection to the server was lost.")); + return false; + } + + setBlocking(true); + + if (!connectToHost(QStringLiteral("sieve"), m_sServer, m_port)) { + return false; + } + + if (!parseCapabilities()) { + disconnectFromHost(); + error(ERR_UNSUPPORTED_PROTOCOL, i18n("Server identification failed.")); + return false; + } + + // Attempt to start TLS + if (!m_allowUnencrypted && !QSslSocket::supportsSsl()) { + error(ERR_SLAVE_DEFINED, i18n("Can not use TLS since the underlying Qt library does not support it.")); + disconnect(); + return false; + } + + if (!m_allowUnencrypted && useTLSIfAvailable && QSslSocket::supportsSsl() && !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 && m_supportsTLS && QSslSocket::supportsSsl()) { + sendData("STARTTLS"); + if (operationSuccessful()) { + ksDebug << "TLS has been accepted. Starting TLS..." << endl + << "WARNING this is untested and may fail."; + if (startSsl()) { + ksDebug << "TLS enabled successfully." << endl; + // reparse capabilities: + parseCapabilities(requestCapabilitiesAfterStartTLS()); + } else { + ksDebug << "TLS initiation failed."; + if (m_allowUnencrypted) { + disconnect(true); + return connect(false); + } + 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."; + 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 || isUsingSsl()); + + 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"); + + if (!operationSuccessful()) { + ksDebug << "Server did not logout cleanly." << endl; + } + } + + disconnectFromHost(); + m_shouldBeConnected = false; +} + +/* ---------------------------------------------------------------------------------- */ +/*void kio_sieveProtocol::slave_status() +{ + slaveStatus(isConnected() ? m_sServer : "", isConnected()); + + finished(); +}*/ + +/* ---------------------------------------------------------------------------------- */ +void kio_sieveProtocol::special(const QByteArray &data) +{ + int tmp; + QDataStream stream(data); + QUrl 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(i18nc("special command completed", "Done.")); + + finished(); +} + +/* ---------------------------------------------------------------------------------- */ +bool kio_sieveProtocol::activate(const QUrl &url) +{ + changeCheck(url); + if (!connect()) { + return false; + } + + infoMessage(i18n("Activating script...")); + + QString filename = url.fileName(); + + if (filename.isEmpty()) { + error(ERR_DOES_NOT_EXIST, url.toDisplayString()); + return false; + } + + if (!sendData("SETACTIVE \"" + filename.toUtf8() + "\"")) { + 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(QByteArray &out, const QByteArray &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 QUrl &url, int /*permissions*/, KIO::JobFlags) +{ + changeCheck(url); + if (!connect()) { + return; + } + + infoMessage(i18n("Sending data...")); + + QString filename = url.fileName(); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.toDisplayString()); + return; + } + + QByteArray data; + for (;;) { + dataReq(); + QByteArray 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) + + if (!sendData("PUTSCRIPT \"" + filename.toUtf8() + "\" {" + + QByteArray::number(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 + QByteArray 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(); + + QByteArray errmsg(len, 0); + + 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", QString::fromLatin1(errmsg.data(), errmsg.size()))); + + // 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", QString::fromUtf8(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(i18nc("data upload complete", "Done.")); + + finished(); +} + +static void inplace_crlf2lf(QByteArray &in) +{ + if (in.isEmpty()) { + return; + } + QByteArray &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 QUrl &url) +{ + changeCheck(url); + if (!connect()) { + return; + } + + infoMessage(i18n("Retrieving data...")); + + QString filename = url.fileName(); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.toDisplayString()); + return; + } + + //SlaveBase::mimetype( QString("text/plain") ); // "application/sieve"); + + if (!sendData("GETSCRIPT \"" + filename.toUtf8() + "\"")) { + return; + } + + if (receiveData() && r.getType() == kio_sieveResponse::QUANTITY) { + // determine script size + ssize_t total_len = r.getQuantity(); + totalSize(total_len); + + ssize_t 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. + QByteArray dat(qMin(total_len - recv_len, ssize_t(64 * 1024)), '\0'); + ssize_t this_recv_len = read(dat.data(), dat.size()); + + if (this_recv_len < 1 && !isConnected()) { + 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(QByteArray()); + + 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(i18nc("data retrival complete", "Done.")); + finished(); +} + +void kio_sieveProtocol::del(const QUrl &url, bool isfile) +{ + if (!isfile) { + error(ERR_INTERNAL, i18n("Folders are not supported.")); + return; + } + + changeCheck(url); + if (!connect()) { + return; + } + + infoMessage(i18n("Deleting file...")); + + QString filename = url.fileName(); + + if (filename.isEmpty()) { + error(ERR_MALFORMED_URL, url.toDisplayString()); + return; + } + + if (!sendData("DELETESCRIPT \"" + filename.toUtf8() + "\"")) { + return; + } + + if (operationSuccessful()) { + ksDebug << "Script deletion successful." << endl; + } else { + error(ERR_INTERNAL_SERVER, i18n("The server would not delete the file.")); + return; + } + + infoMessage(i18nc("file removal complete", "Done.")); + + finished(); +} + +void kio_sieveProtocol::chmod(const QUrl &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(); +} + +void kio_sieveProtocol::urlStat(const QUrl &url) +{ + changeCheck(url); + if (!connect()) { + return; + } + + UDSEntry entry; + + QString filename = url.fileName(); + + if (filename.isEmpty()) { + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/")); + + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + + statEntry(entry); + + } else { + if (!sendData("LISTSCRIPTS")) { + return; + } + + while (receiveData()) { + if (r.getType() == kio_sieveResponse::ACTION) { + if (r.getAction().toLower().count("ok") == 1) { + // Script list completed + break; + } + + } else { + if (filename == QString::fromUtf8(r.getKey())) { + entry.clear(); + + entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromUtf8(r.getKey())); + + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + + if (r.getExtra() == "ACTIVE") { + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + } else { + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0600); + } + + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/sieve")); + + //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 QUrl &url) +{ + changeCheck(url); + if (!connect()) { + return; + } + + if (!sendData("LISTSCRIPTS")) { + return; + } + + UDSEntry entry; + + while (receiveData()) { + if (r.getType() == kio_sieveResponse::ACTION) { + if (r.getAction().toLower().count("ok") == 1) { + // Script list completed. + break; + } + + } else { + entry.clear(); + entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromUtf8(r.getKey())); + + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + + if (r.getExtra() == "ACTIVE") { + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700);// mark exec'able + } else { + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0600); + } + + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/sieve")); + + //asetMetaData("active", (r.getExtra() == "ACTIVE") ? "true" : "false"); + + ksDebug << "Listing script " << r.getKey() << endl; + listEntry(entry); + } + } + + 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 (!openPasswordDialog(ai)) { + // calling error() below is wrong for two reasons: + // - ERR_ABORTED is too harsh + // - higher layers already call error() and that can't happen twice. + //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.toUtf8()); + interact->len = strlen((const char *) interact->result); + break; + case SASL_CB_PASS: + ksDebug << "SASL_CB_PASS: [hidden] " << endl; + interact->result = strdup(m_sPass.toUtf8()); + 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", \ + QString::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; + QByteArray challenge; + + /* 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.setScheme(QStringLiteral("sieve")); + ai.url.setHost(m_sServer); + ai.url.setPort(m_port); + 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.toLatin1(), 0, 0, callbacks, 0, &conn); + if (result != SASL_OK) { + ksDebug << "sasl_client_new failed with: " << result << endl; + SASLERROR + return false; + } + + QStringList 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(QStringLiteral(" ")).toLatin1(), + &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; + + QString firstCommand = QLatin1String("AUTHENTICATE \"") + QString::fromLatin1(mechusing) + QLatin1String("\""); + challenge = QByteArray::fromRawData(out, outlen).toBase64(); + if (!challenge.isEmpty()) { + firstCommand += QLatin1String(" \""); + firstCommand += QString::fromLatin1(challenge.data(), challenge.size()); + firstCommand += QLatin1Char('\"'); + } + + if (!sendData(firstCommand.toLatin1())) { + return false; + } + + do { + receiveData(); + + if (operationResult() != OTHER) { + break; + } + + ksDebug << "Challenge len " << r.getQuantity() << endl; + + if (r.getType() != kio_sieveResponse::QUANTITY) { + sasl_dispose(&conn); + error(ERR_UNSUPPORTED_PROTOCOL, QString::fromLatin1(mechusing)); + return false; + } + + int 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.", QLatin1String(mechusing))); + return false; + } + challenge = QByteArray::fromBase64(QByteArray::fromRawData(r.getAction().data(), qty)); +// ksDebug << "S: [" << r.getAction() << "]." << 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; + } + + sendData('\"' + QByteArray::fromRawData(out, outlen).toBase64() + '\"'); +// 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", + QString::fromLatin1(r.getAction()))); + return false; + } +} + +/* --------------------------------------------------------------------------- */ +void kio_sieveProtocol::mimetype(const QUrl &url) +{ + ksDebug << "Requesting mimetype for " << url.toDisplayString() << endl; + + if (url.fileName().isEmpty()) { + mimeType(QStringLiteral("inode/directory")); + } else { + mimeType(QStringLiteral("application/sieve")); + } + + finished(); +} + +/* --------------------------------------------------------------------------- */ +bool kio_sieveProtocol::sendData(const QByteArray &data) +{ + QByteArray 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, const QByteArray &reparse) +{ + QByteArray interpret; + int start, end; + + if (reparse.isEmpty()) { + if (!waitForData) { + // is there data waiting? + if (atEnd()) { + return false; + } + } + + // read data from the server + char buffer[SIEVE_DEFAULT_RECIEVE_BUFFER]; + const ssize_t numRead = readLine(buffer, SIEVE_DEFAULT_RECIEVE_BUFFER - 1); + if (numRead < 0) { + return false; + } + buffer[SIEVE_DEFAULT_RECIEVE_BUFFER - 1] = '\0'; + + // strip LF/CR + interpret = QByteArray(buffer, qstrlen(buffer) - 2); + + } else { + interpret = reparse; + } + + r.clear(); + + //ksDebug << "S: " << interpret << endl; + + switch (interpret[0]) { + case '{': { + // expecting {quantity} + start = 0; + end = interpret.indexOf("+}", start + 1); + // some older versions of Cyrus enclose the literal size just in { } instead of { +} + if (end == -1) { + end = interpret.indexOf('}', 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.indexOf('"', 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.indexOf('"', 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.indexOf('"', 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(true)) { + if (r.getType() == kio_sieveResponse::ACTION) { + QByteArray 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) { + QByteArray 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. + QRegExp regExp(QStringLiteral("Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)"), Qt::CaseInsensitive); + if (regExp.indexIn(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 QString vendor = regExp.cap(4); + if (major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == QLatin1String("-kolab-nocaps"))) { + ksDebug << " kio_sieveProtocol::requestCapabilitiesAfterStartTLS : Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"" << endl; + return true; + } + } + return false; +} diff --git a/kioslave/src/sieve/sieve.h b/kioslave/src/sieve/sieve.h new file mode 100644 index 0000000..6ed5d65 --- /dev/null +++ b/kioslave/src/sieve/sieve.h @@ -0,0 +1,145 @@ +/*************************************************************************** + sieve.h - description + ------------------- + begin : Thu Dec 20 18:47:08 EST 2001 + copyright : (C) 2001 by Hamish Rodda + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef __sieve_h__ +#define __sieve_h__ + +#include "sieve-config.h" + +#include <kio/tcpslavebase.h> +#include <kio/authinfo.h> + +#include <QString> +#include <QByteArray> +#include <QStringList> + +class kio_sieveResponse +{ +public: + enum responses { + NONE, + KEY_VAL_PAIR, + ACTION, + QUANTITY + }; + + kio_sieveResponse(); + + const uint &getType() const; + + const QByteArray &getAction() const; + uint getQuantity() const; + const QByteArray &getKey() const; + const QByteArray &getVal() const; + const QByteArray &getExtra() const; + + void setQuantity(const uint &quantity); + void setAction(const QByteArray &newAction); + void setKey(const QByteArray &newKey); + void setVal(const QByteArray &newVal); + void setExtra(const QByteArray &newExtra); + + void clear(); + +protected: + uint rType; + uint quantity; + QByteArray key; + QByteArray val; + QByteArray extra; +}; + +class kio_sieveProtocol : public KIO::TCPSlaveBase +{ + +public: + enum connectionModes { + NORMAL, + CONNECTION_ORIENTED + }; + enum Results { + OK, + NO, + BYE, + OTHER + }; + + kio_sieveProtocol(const QByteArray &pool_socket, const QByteArray &app_socket); + virtual ~kio_sieveProtocol(); + + void mimetype(const QUrl &url) Q_DECL_OVERRIDE; + void get(const QUrl &url) Q_DECL_OVERRIDE; + void put(const QUrl &url, int permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE; + void del(const QUrl &url, bool isfile) Q_DECL_OVERRIDE; + + void listDir(const QUrl &url) Q_DECL_OVERRIDE; + void chmod(const QUrl &url, int permissions) Q_DECL_OVERRIDE; + virtual void urlStat(const QUrl &url); + + void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) Q_DECL_OVERRIDE; + void openConnection() Q_DECL_OVERRIDE; + void closeConnection() Q_DECL_OVERRIDE; + //virtual void slave_status(); + + /** + * Special commands supported by this slave: + * 1 - activate script + * 2 - deactivate (all - only one active at any one time) scripts + * 3 - request capabilities, returned as metadata + */ + void special(const QByteArray &data) Q_DECL_OVERRIDE; + bool activate(const QUrl &url); + bool deactivate(); + +protected: + bool connect(bool useTLSIfAvailable = true); + bool authenticate(); + void disconnect(bool forcibly = false); + void changeCheck(const QUrl &url); + + bool sendData(const QByteArray &data); + bool receiveData(bool waitForData = true, const QByteArray &reparse = QByteArray()); + bool operationSuccessful(); + int operationResult(); + + bool parseCapabilities(bool requestCapabilities = false); + bool saslInteract(void *in, KIO::AuthInfo &ai); + + // IOSlave global data + uint m_connMode; + + // Host-specific data + QStringList m_sasl_caps; + bool m_supportsTLS; + + // Global server respose class + kio_sieveResponse r; + + // connection details + QString m_sServer; + QString m_sUser; + QString m_sPass; + QString m_sAuth; + bool m_shouldBeConnected; + bool m_allowUnencrypted; + quint16 m_port; + +private: + bool requestCapabilitiesAfterStartTLS() const; + + QString m_implementation; +}; + +#endif diff --git a/kioslave/src/sieve/sieve.protocol b/kioslave/src/sieve/sieve.protocol new file mode 100644 index 0000000..0bc4a7e --- /dev/null +++ b/kioslave/src/sieve/sieve.protocol @@ -0,0 +1,65 @@ +[Protocol] +exec=kf5/kio/sieve +protocol=sieve +input=none +output=filesystem +listing=Name,Access,Type,MimeType, +reading=true +writing=true +makedir=false +deleting=true +linking=false +moving=false +Icon=view-filter +X-DocPath=kioslave5/sieve/index.html +Description=An ioslave for the Sieve mail filtering protocol +Description[ast]=Un esclavu ES pal protocolu de peñera de corréu Sieve +Description[bs]=U/I program za Sieve protokol filtriranja e-pošte +Description[ca]=Un «ioslave» pel protocol de filtratge de correu «Sieve» +Description[ca@valencia]=Un «ioslave» pel protocol de filtratge de correu «Sieve» +Description[cs]=Protokol pro filtrování pošty Sieve +Description[da]=En ioslave til mail-filtreringsprotokollen Sieve +Description[de]=Ein Ein-/Ausgabemodul für das Mailfilter-Protokoll Sieve +Description[el]=Ένα ioslave για το πρωτόκολλο φιλτραρίσματος αλληλογραφίας Sieve +Description[en_GB]=An ioslave for the Sieve mail filtering protocol +Description[es]=Un esclavo de E/S para el protocolo de filtrado de correo Sieve +Description[et]=Sieve kirjade filtreerimise protokolli IO-moodul +Description[fi]=I/O-asiakas Sieve-postinsuodatusyhteyskäytännölle +Description[fr]=Un module d'entrées / sorties pour le protocole de filtrage de messagerie « Sieve » +Description[ga]=Sclábhaí I/A le haghaidh prótacail scagtha ríomhphoist Sieve +Description[gl]=Un ioslave para o protocolo de filtrado de correo Sieve. +Description[he]=תוסף ioslave עבור פרוטוקול מסנני דואר של Sieve +Description[hne]=सीव मेल फिल्टरिंग प्रोटोकाल बर आईओस्लेव +Description[hu]=KDE-protokoll a Sieve levélszűrő protokollhoz +Description[ia]=Un ioslave (sclavo de i/e) per le protocollo per filtrar posta Sieve +Description[it]=Un IOSlave per il protocollo di filtraggio della posta Sieve +Description[ja]=Sieve メールフィルタリングプロトコルのための ioslave +Description[kk]=Sieve пошта сүзгілеу протоколының ioslav-модулі +Description[km]=ioslave សម្រាប់ពិធីការតម្រងសំបុត្ររបស់ Sieve +Description[ko]=Sieve 메일 필터링 프로토콜을 위한 IO 슬레이브 +Description[lt]=Sieve pašto filtravimo protokolo palaikymas +Description[lv]=IO apstrādātājs Sieve pasta filtrēšanas protokolam +Description[mr]=सीव्ह मेल गाळणी शिष्टाचार करिता आयओस्लेव्ह +Description[nb]=En ioslave for Sieve e-postfiltrering +Description[nds]=In-/Utgaavmoduul för't Nettpostfilter-Protokoll Sieve +Description[nl]=Een KIO-slave voor het Sieve-mail filterprotocol +Description[nn]=Ein IO-slave for e-postfiltreringsprotokollen Sieve +Description[pl]=Moduł ioslave dla protokołu filtrowania poczty Sieve +Description[pt]=Um 'ioslave' para o protocolo de filtragem de correio Sieve +Description[pt_BR]=Um ioslave para o protocolo de filtragem de e-mail Sieve +Description[ro]=Un „ioslave” pentru protocolul de filtrare a poștei Sieve +Description[ru]=Поддержка протокола фильтрации почты Sieve +Description[se]=SO-šláva Sieve e-boastasillenprotokolla várás +Description[sk]=Ioslave pre Sieve mail filtrovací protokol +Description[sl]=Ioslave za protokol poštnega filtriranja Sieve +Description[sr]=У/И захват за сито, протокол филтрирања поште +Description[sr@ijekavian]=У/И захват за сито, протокол филтрирања поште +Description[sr@ijekavianlatin]=U/I zahvat za sito, protokol filtriranja pošte +Description[sr@latin]=U/I zahvat za sito, protokol filtriranja pošte +Description[sv]=En I/O-slav för brevfiltreringsprotokollet Sieve +Description[th]=ส่วนเสริม ioslave สำหรับโพรโทคอลการกรองจดหมาย Sieve +Description[tr]=Sieve e-posta filtrelemesi için bir kioslave +Description[uk]=Підлеглий В/В для протоколу фільтрування пошти Sieve +Description[x-test]=xxAn ioslave for the Sieve mail filtering protocolxx +Description[zh_CN]=Sieve 邮件过滤协议的 ioslave +Description[zh_TW]=Sieve 郵件過濾協定的 ioslave diff --git a/libksieve.categories b/libksieve.categories index e0874a2..8b3030b 100644 --- a/libksieve.categories +++ b/libksieve.categories @@ -1,2 +1,3 @@ log_libksieve kdepim (libksieve) log_kmanagersieve kdepim (libksieve manager) +log_sieve kioslave (sieve) _______________________________________________ kde-doc-english mailing list [email protected] https://mail.kde.org/mailman/listinfo/kde-doc-english
