From 145abc15d57fb29701a12e8a14dcb9c1fd72e9be Mon Sep 17 00:00:00 2001 From: Michele Calgaro Date: Mon, 7 Dec 2020 22:58:44 +0900 Subject: Renaming of files in preparation for code style tools. Signed-off-by: Michele Calgaro --- tdeioslave/filter/CMakeLists.txt | 2 +- tdeioslave/filter/Makefile.am | 2 +- tdeioslave/filter/filter.cc | 167 ---- tdeioslave/filter/filter.cpp | 167 ++++ tdeioslave/floppy/tdeio_floppy.cpp | 2 +- tdeioslave/home/Makefile.am | 2 +- tdeioslave/info/CMakeLists.txt | 2 +- tdeioslave/info/Makefile.am | 2 +- tdeioslave/info/info.cc | 261 ------ tdeioslave/info/info.cpp | 261 ++++++ tdeioslave/nfs/tdeio_nfs.cpp | 4 +- tdeioslave/pop3/CMakeLists.txt | 2 +- tdeioslave/pop3/Makefile.am | 4 +- tdeioslave/pop3/pop3.cc | 1263 ----------------------------- tdeioslave/pop3/pop3.cpp | 1263 +++++++++++++++++++++++++++++ tdeioslave/remote/Makefile.am | 2 +- tdeioslave/settings/CMakeLists.txt | 2 +- tdeioslave/settings/Makefile.am | 4 +- tdeioslave/settings/tdeio_settings.cc | 296 ------- tdeioslave/settings/tdeio_settings.cpp | 296 +++++++ tdeioslave/smtp/CMakeLists.txt | 2 +- tdeioslave/smtp/Makefile.am | 12 +- tdeioslave/smtp/capabilities.cc | 143 ---- tdeioslave/smtp/capabilities.cpp | 143 ++++ tdeioslave/smtp/command.cc | 606 -------------- tdeioslave/smtp/command.cpp | 606 ++++++++++++++ tdeioslave/smtp/interactivesmtpserver.cc | 127 --- tdeioslave/smtp/interactivesmtpserver.cpp | 127 +++ tdeioslave/smtp/request.cc | 189 ----- tdeioslave/smtp/request.cpp | 189 +++++ tdeioslave/smtp/response.cc | 160 ---- tdeioslave/smtp/response.cpp | 160 ++++ tdeioslave/smtp/smtp.cc | 648 --------------- tdeioslave/smtp/smtp.cpp | 648 +++++++++++++++ tdeioslave/smtp/test_commands.cc | 728 ----------------- tdeioslave/smtp/test_commands.cpp | 728 +++++++++++++++++ tdeioslave/smtp/test_headergeneration.cc | 86 -- tdeioslave/smtp/test_headergeneration.cpp | 86 ++ tdeioslave/smtp/test_responseparser.cc | 107 --- tdeioslave/smtp/test_responseparser.cpp | 107 +++ tdeioslave/smtp/transactionstate.cc | 114 --- tdeioslave/smtp/transactionstate.cpp | 114 +++ tdeioslave/system/Makefile.am | 2 +- tdeioslave/tar/CMakeLists.txt | 2 +- tdeioslave/tar/Makefile.am | 4 +- tdeioslave/tar/tar.cc | 621 -------------- tdeioslave/tar/tar.cpp | 621 ++++++++++++++ tdeioslave/trash/DESIGN | 2 +- tdeioslave/trash/Makefile.am | 2 +- tdeioslave/trash/trashimpl.cpp | 2 +- 50 files changed, 5545 insertions(+), 5545 deletions(-) delete mode 100644 tdeioslave/filter/filter.cc create mode 100644 tdeioslave/filter/filter.cpp delete mode 100644 tdeioslave/info/info.cc create mode 100644 tdeioslave/info/info.cpp delete mode 100644 tdeioslave/pop3/pop3.cc create mode 100644 tdeioslave/pop3/pop3.cpp delete mode 100644 tdeioslave/settings/tdeio_settings.cc create mode 100644 tdeioslave/settings/tdeio_settings.cpp delete mode 100644 tdeioslave/smtp/capabilities.cc create mode 100644 tdeioslave/smtp/capabilities.cpp delete mode 100644 tdeioslave/smtp/command.cc create mode 100644 tdeioslave/smtp/command.cpp delete mode 100644 tdeioslave/smtp/interactivesmtpserver.cc create mode 100644 tdeioslave/smtp/interactivesmtpserver.cpp delete mode 100644 tdeioslave/smtp/request.cc create mode 100644 tdeioslave/smtp/request.cpp delete mode 100644 tdeioslave/smtp/response.cc create mode 100644 tdeioslave/smtp/response.cpp delete mode 100644 tdeioslave/smtp/smtp.cc create mode 100644 tdeioslave/smtp/smtp.cpp delete mode 100644 tdeioslave/smtp/test_commands.cc create mode 100644 tdeioslave/smtp/test_commands.cpp delete mode 100644 tdeioslave/smtp/test_headergeneration.cc create mode 100644 tdeioslave/smtp/test_headergeneration.cpp delete mode 100644 tdeioslave/smtp/test_responseparser.cc create mode 100644 tdeioslave/smtp/test_responseparser.cpp delete mode 100644 tdeioslave/smtp/transactionstate.cc create mode 100644 tdeioslave/smtp/transactionstate.cpp delete mode 100644 tdeioslave/tar/tar.cc create mode 100644 tdeioslave/tar/tar.cpp (limited to 'tdeioslave') diff --git a/tdeioslave/filter/CMakeLists.txt b/tdeioslave/filter/CMakeLists.txt index 446393d74..4387ab0f5 100644 --- a/tdeioslave/filter/CMakeLists.txt +++ b/tdeioslave/filter/CMakeLists.txt @@ -34,7 +34,7 @@ tde_create_translated_desktop( set( target tdeio_filter ) tde_add_kpart( ${target} AUTOMOC - SOURCES filter.cc + SOURCES filter.cpp LINK tdeio-shared DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/filter/Makefile.am b/tdeioslave/filter/Makefile.am index fcf2a9e9f..10a83993d 100644 --- a/tdeioslave/filter/Makefile.am +++ b/tdeioslave/filter/Makefile.am @@ -4,7 +4,7 @@ INCLUDES = $(all_includes) kde_module_LTLIBRARIES = tdeio_filter.la -tdeio_filter_la_SOURCES = filter.cc +tdeio_filter_la_SOURCES = filter.cpp tdeio_filter_la_LIBADD = $(LIB_TDESYCOCA) tdeio_filter_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) noinst_HEADERS = filter.h diff --git a/tdeioslave/filter/filter.cc b/tdeioslave/filter/filter.cc deleted file mode 100644 index a429103e1..000000000 --- a/tdeioslave/filter/filter.cc +++ /dev/null @@ -1,167 +0,0 @@ -/* -This file is part of KDE - - Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include -#include -#include - -#include -#include -#include -#include - -#include "filter.h" - -extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } - -int kdemain( int argc, char ** argv) -{ - TDEInstance instance( "tdeio_filter" ); - - kdDebug(7110) << "Starting " << getpid() << endl; - - if (argc != 4) - { - fprintf(stderr, "Usage: tdeio_filter protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - FilterProtocol slave(argv[1], argv[2], argv[3]); - slave.dispatchLoop(); - - kdDebug(7110) << "Done" << endl; - return 0; -} - -FilterProtocol::FilterProtocol( const TQCString & protocol, const TQCString &pool, const TQCString &app ) - : TDEIO::SlaveBase( protocol, pool, app ) -{ - TQString mimetype = TQString::fromLatin1("application/x-") + TQString::fromLatin1(protocol); - filter = KFilterBase::findFilterByMimeType( mimetype ); - Q_ASSERT(filter); -} - -void FilterProtocol::get( const KURL & ) -{ - if (subURL.isEmpty()) - { - error( TDEIO::ERR_NO_SOURCE_PROTOCOL, mProtocol ); - return; - } - if (!filter) - { - error( TDEIO::ERR_INTERNAL, mProtocol ); - return; - } - needSubURLData(); - - filter->init(IO_ReadOnly); - - bool bNeedHeader = true; - bool bNeedMimetype = true; - bool bError = true; - int result; - - TQByteArray inputBuffer; - TQByteArray outputBuffer(8*1024); // Start with a modest buffer - filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); - while(true) - { - if (filter->inBufferEmpty()) - { - dataReq(); // Request data - result = readData( inputBuffer); - kdDebug(7110) << "requestData: got " << result << endl; - if (result <= 0) - { - bError = true; - break; // Unexpected EOF. - } - filter->setInBuffer( inputBuffer.data(), inputBuffer.size() ); - } - if (bNeedHeader) - { - bError = !filter->readHeader(); - if (bError) - break; - bNeedHeader = false; - } - result = filter->uncompress(); - if ((filter->outBufferAvailable() == 0) || (result == KFilterBase::END)) - { - kdDebug(7110) << "avail_out = " << filter->outBufferAvailable() << endl; - if (filter->outBufferAvailable() != 0) - { - // Discard unused space :-) - outputBuffer.resize(outputBuffer.size() - filter->outBufferAvailable()); - } - if (bNeedMimetype) - { - KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( outputBuffer, subURL.fileName() ); - kdDebug(7110) << "Emitting mimetype " << result->mimeType() << endl; - mimeType( result->mimeType() ); - bNeedMimetype = false; - } - data( outputBuffer ); // Send data - filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); - if (result == KFilterBase::END) - break; // Finished. - } - if (result != KFilterBase::OK) - { - bError = true; - break; // Error - } - } - - if (!bError) - { - dataReq(); // Request data - result = readData( inputBuffer); - kdDebug(7110) << "requestData: got " << result << "(expecting 0)" << endl; - data( TQByteArray() ); // Send EOF - } - - filter->terminate(); - - if (bError) - { - error(TDEIO::ERR_COULD_NOT_READ, subURL.url()); - subURL = KURL(); // Clear subURL - return; - } - - subURL = KURL(); // Clear subURL - finished(); -} - -void FilterProtocol::put( const KURL &/*url*/, int, bool /*_overwrite*/, bool /*_resume*/ ) -{ - error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("put")); -} - -void FilterProtocol::setSubURL(const KURL &url) -{ - subURL = url; -} - diff --git a/tdeioslave/filter/filter.cpp b/tdeioslave/filter/filter.cpp new file mode 100644 index 000000000..a429103e1 --- /dev/null +++ b/tdeioslave/filter/filter.cpp @@ -0,0 +1,167 @@ +/* +This file is part of KDE + + Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "filter.h" + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char ** argv) +{ + TDEInstance instance( "tdeio_filter" ); + + kdDebug(7110) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: tdeio_filter protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FilterProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7110) << "Done" << endl; + return 0; +} + +FilterProtocol::FilterProtocol( const TQCString & protocol, const TQCString &pool, const TQCString &app ) + : TDEIO::SlaveBase( protocol, pool, app ) +{ + TQString mimetype = TQString::fromLatin1("application/x-") + TQString::fromLatin1(protocol); + filter = KFilterBase::findFilterByMimeType( mimetype ); + Q_ASSERT(filter); +} + +void FilterProtocol::get( const KURL & ) +{ + if (subURL.isEmpty()) + { + error( TDEIO::ERR_NO_SOURCE_PROTOCOL, mProtocol ); + return; + } + if (!filter) + { + error( TDEIO::ERR_INTERNAL, mProtocol ); + return; + } + needSubURLData(); + + filter->init(IO_ReadOnly); + + bool bNeedHeader = true; + bool bNeedMimetype = true; + bool bError = true; + int result; + + TQByteArray inputBuffer; + TQByteArray outputBuffer(8*1024); // Start with a modest buffer + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + while(true) + { + if (filter->inBufferEmpty()) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << endl; + if (result <= 0) + { + bError = true; + break; // Unexpected EOF. + } + filter->setInBuffer( inputBuffer.data(), inputBuffer.size() ); + } + if (bNeedHeader) + { + bError = !filter->readHeader(); + if (bError) + break; + bNeedHeader = false; + } + result = filter->uncompress(); + if ((filter->outBufferAvailable() == 0) || (result == KFilterBase::END)) + { + kdDebug(7110) << "avail_out = " << filter->outBufferAvailable() << endl; + if (filter->outBufferAvailable() != 0) + { + // Discard unused space :-) + outputBuffer.resize(outputBuffer.size() - filter->outBufferAvailable()); + } + if (bNeedMimetype) + { + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( outputBuffer, subURL.fileName() ); + kdDebug(7110) << "Emitting mimetype " << result->mimeType() << endl; + mimeType( result->mimeType() ); + bNeedMimetype = false; + } + data( outputBuffer ); // Send data + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + if (result == KFilterBase::END) + break; // Finished. + } + if (result != KFilterBase::OK) + { + bError = true; + break; // Error + } + } + + if (!bError) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << "(expecting 0)" << endl; + data( TQByteArray() ); // Send EOF + } + + filter->terminate(); + + if (bError) + { + error(TDEIO::ERR_COULD_NOT_READ, subURL.url()); + subURL = KURL(); // Clear subURL + return; + } + + subURL = KURL(); // Clear subURL + finished(); +} + +void FilterProtocol::put( const KURL &/*url*/, int, bool /*_overwrite*/, bool /*_resume*/ ) +{ + error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("put")); +} + +void FilterProtocol::setSubURL(const KURL &url) +{ + subURL = url; +} + diff --git a/tdeioslave/floppy/tdeio_floppy.cpp b/tdeioslave/floppy/tdeio_floppy.cpp index c67af0b39..50303f1b7 100644 --- a/tdeioslave/floppy/tdeio_floppy.cpp +++ b/tdeioslave/floppy/tdeio_floppy.cpp @@ -1105,7 +1105,7 @@ void FloppyProtocol::put( const KURL& url, int , bool overwrite, bool ) int bytesRead(0); TQByteArray array; - //from file.cc + //from file.cpp // Loop until we got 0 (end of data) do { diff --git a/tdeioslave/home/Makefile.am b/tdeioslave/home/Makefile.am index 3bd078852..4176a196a 100644 --- a/tdeioslave/home/Makefile.am +++ b/tdeioslave/home/Makefile.am @@ -28,5 +28,5 @@ check: testhome ./testhome messages: - $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_home.pot + $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_home.pot diff --git a/tdeioslave/info/CMakeLists.txt b/tdeioslave/info/CMakeLists.txt index 6f243ba3f..85937d486 100644 --- a/tdeioslave/info/CMakeLists.txt +++ b/tdeioslave/info/CMakeLists.txt @@ -37,7 +37,7 @@ install( PROGRAMS kde-info2html DESTINATION ${DATA_INSTALL_DIR}/tdeio_info ) set( target tdeio_info ) tde_add_kpart( ${target} AUTOMOC - SOURCES info.cc + SOURCES info.cpp LINK tdeio-shared DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/info/Makefile.am b/tdeioslave/info/Makefile.am index a682f4a77..8f0541c0f 100644 --- a/tdeioslave/info/Makefile.am +++ b/tdeioslave/info/Makefile.am @@ -8,7 +8,7 @@ METASOURCES = AUTO kde_module_LTLIBRARIES = tdeio_info.la -tdeio_info_la_SOURCES = info.cc +tdeio_info_la_SOURCES = info.cpp tdeio_info_la_LIBADD = $(LIB_TDEIO) tdeio_info_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) noinst_HEADERS = info.h diff --git a/tdeioslave/info/info.cc b/tdeioslave/info/info.cc deleted file mode 100644 index 83b41dae8..000000000 --- a/tdeioslave/info/info.cc +++ /dev/null @@ -1,261 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "info.h" - -using namespace TDEIO; - -InfoProtocol::InfoProtocol( const TQCString &pool, const TQCString &app ) - : SlaveBase( "info", pool, app ) - , m_page( "" ) - , m_node( "" ) -{ - kdDebug( 7108 ) << "InfoProtocol::InfoProtocol" << endl; - - m_perl = TDEGlobal::dirs()->findExe( "perl" ); - m_infoScript = locate( "data", "tdeio_info/kde-info2html" ); - m_infoConf = locate("data", "tdeio_info/kde-info2html.conf"); - - if( m_perl.isNull() || m_infoScript.isNull() || m_infoConf.isNull() ) { - kdError( 7108 ) << "Critical error: Cannot locate files for HTML-conversion" << endl; - TQString errorStr; - if ( m_perl.isNull() ) { - errorStr = "perl."; - } else { - TQString missing =m_infoScript.isNull() ? "tdeio_info/kde-info2html" : "tdeio_info/kde-info2html.conf"; - errorStr = "kde-info2html" + i18n( "\nUnable to locate file %1 which is necessary to run this service. " - "Please check your software installation" ).arg( missing ); - } - error( TDEIO::ERR_CANNOT_LAUNCH_PROCESS, errorStr ); - exit(); - } - - kdDebug( 7108 ) << "InfoProtocol::InfoProtocol - done" << endl; -} - -InfoProtocol::~InfoProtocol() -{ - kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol" << endl; - - kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol - done" << endl; -} - -void InfoProtocol::get( const KURL& url ) -{ - kdDebug( 7108 ) << "InfoProtocol::get" << endl; - kdDebug( 7108 ) << "URL: " << url.prettyURL() << " , Path :" << url.path() << endl; - - if (url.path()=="/") - { - KURL newUrl("info:/dir"); - redirection(newUrl); - finished(); - return; - }; - - // some people write info://autoconf instead of info:/autoconf - if (!url.host().isEmpty()) { - KURL newURl(url); - newURl.setPath(url.host()+url.path()); - newURl.setHost(TQString::null); - redirection(newURl); - finished(); - return; - } - - if ( url.path().right(1) == "/" ) - { - // Trailing / are not supported, so we need to remove them. - KURL newUrl( url ); - TQString newPath( url.path() ); - newPath.truncate( newPath.length()-1 ); - newUrl.setPath( newPath ); - redirection( newUrl ); - finished(); - return; - } - - mimeType("text/html"); - // extract the path and node from url - decodeURL( url ); - - TQString path = TDEGlobal::iconLoader()->iconPath("go-up", TDEIcon::Toolbar, true); - int revindex = path.findRev('/'); - path = path.left(revindex); - - TQString cmd = TDEProcess::quote(m_perl); - cmd += " "; - cmd += TDEProcess::quote(m_infoScript); - cmd += " "; - cmd += TDEProcess::quote(m_infoConf); - cmd += " "; - cmd += TDEProcess::quote(path); - cmd += " "; - cmd += TDEProcess::quote(m_page); - cmd += " "; - cmd += TDEProcess::quote(m_node); - - kdDebug( 7108 ) << "cmd: " << cmd << endl; - - FILE *file = popen( TQFile::encodeName(cmd), "r" ); - if ( !file ) { - kdDebug( 7108 ) << "InfoProtocol::get popen failed" << endl; - error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); - return; - } - - char buffer[ 4096 ]; - TQByteArray array; - - bool empty = true; - while ( !feof( file ) ) - { - int n = fread( buffer, 1, sizeof( buffer ), file ); - if ( !n && feof( file ) && empty ) { - error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); - return; - } - if ( n < 0 ) - { - // ERROR - kdDebug( 7108 ) << "InfoProtocol::get ERROR!" << endl; - pclose( file ); - return; - } - - empty = false; - array.setRawData( buffer, n ); - data( array ); - array.resetRawData( buffer, n ); - } - - pclose( file ); - - finished(); - - kdDebug( 7108 ) << "InfoProtocol::get - done" << endl; -} - -void InfoProtocol::mimetype( const KURL& /* url */ ) -{ - kdDebug( 7108 ) << "InfoProtocol::mimetype" << endl; - - // to get rid of those "Open with" dialogs... - mimeType( "text/html" ); - - // finish action - finished(); - - kdDebug( 7108 ) << "InfoProtocol::mimetype - done" << endl; -} - -void InfoProtocol::decodeURL( const KURL &url ) -{ - kdDebug( 7108 ) << "InfoProtocol::decodeURL" << endl; - - /* Notes: - * - * I cleaned up the URL decoding and chose not to support URLs in the - * form "info:/usr/local/share/info/libc.info.gz" or similar which the - * older code attempted (and failed, maybe it had worked once) to do. - * - * The reason is that an obvious use such as viewing a info file off your - * infopath would work for the first page, but then all the links would be - * wrong. Of course, one could change kde-info2html to make it work, but I don't - * think it worthy, others are free to disagree and write the necessary code ;) - * - * luis pedro - */ - - if ( url == KURL( "info:/browse_by_file?special=yes" ) ) { - m_page = "#special#"; - m_node = "browse_by_file"; - kdDebug( 7108 ) << "InfoProtocol::decodeURL - special - browse by file" << endl; - return; - } - - decodePath( url.path() ); - - kdDebug( 7108 ) << "InfoProtocol::decodeURL - done" << endl; -} - -void InfoProtocol::decodePath( TQString path ) -{ - kdDebug( 7108 ) << "InfoProtocol::decodePath(-" < +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "info.h" + +using namespace TDEIO; + +InfoProtocol::InfoProtocol( const TQCString &pool, const TQCString &app ) + : SlaveBase( "info", pool, app ) + , m_page( "" ) + , m_node( "" ) +{ + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol" << endl; + + m_perl = TDEGlobal::dirs()->findExe( "perl" ); + m_infoScript = locate( "data", "tdeio_info/kde-info2html" ); + m_infoConf = locate("data", "tdeio_info/kde-info2html.conf"); + + if( m_perl.isNull() || m_infoScript.isNull() || m_infoConf.isNull() ) { + kdError( 7108 ) << "Critical error: Cannot locate files for HTML-conversion" << endl; + TQString errorStr; + if ( m_perl.isNull() ) { + errorStr = "perl."; + } else { + TQString missing =m_infoScript.isNull() ? "tdeio_info/kde-info2html" : "tdeio_info/kde-info2html.conf"; + errorStr = "kde-info2html" + i18n( "\nUnable to locate file %1 which is necessary to run this service. " + "Please check your software installation" ).arg( missing ); + } + error( TDEIO::ERR_CANNOT_LAUNCH_PROCESS, errorStr ); + exit(); + } + + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol - done" << endl; +} + +InfoProtocol::~InfoProtocol() +{ + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol" << endl; + + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol - done" << endl; +} + +void InfoProtocol::get( const KURL& url ) +{ + kdDebug( 7108 ) << "InfoProtocol::get" << endl; + kdDebug( 7108 ) << "URL: " << url.prettyURL() << " , Path :" << url.path() << endl; + + if (url.path()=="/") + { + KURL newUrl("info:/dir"); + redirection(newUrl); + finished(); + return; + }; + + // some people write info://autoconf instead of info:/autoconf + if (!url.host().isEmpty()) { + KURL newURl(url); + newURl.setPath(url.host()+url.path()); + newURl.setHost(TQString::null); + redirection(newURl); + finished(); + return; + } + + if ( url.path().right(1) == "/" ) + { + // Trailing / are not supported, so we need to remove them. + KURL newUrl( url ); + TQString newPath( url.path() ); + newPath.truncate( newPath.length()-1 ); + newUrl.setPath( newPath ); + redirection( newUrl ); + finished(); + return; + } + + mimeType("text/html"); + // extract the path and node from url + decodeURL( url ); + + TQString path = TDEGlobal::iconLoader()->iconPath("go-up", TDEIcon::Toolbar, true); + int revindex = path.findRev('/'); + path = path.left(revindex); + + TQString cmd = TDEProcess::quote(m_perl); + cmd += " "; + cmd += TDEProcess::quote(m_infoScript); + cmd += " "; + cmd += TDEProcess::quote(m_infoConf); + cmd += " "; + cmd += TDEProcess::quote(path); + cmd += " "; + cmd += TDEProcess::quote(m_page); + cmd += " "; + cmd += TDEProcess::quote(m_node); + + kdDebug( 7108 ) << "cmd: " << cmd << endl; + + FILE *file = popen( TQFile::encodeName(cmd), "r" ); + if ( !file ) { + kdDebug( 7108 ) << "InfoProtocol::get popen failed" << endl; + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + + char buffer[ 4096 ]; + TQByteArray array; + + bool empty = true; + while ( !feof( file ) ) + { + int n = fread( buffer, 1, sizeof( buffer ), file ); + if ( !n && feof( file ) && empty ) { + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + if ( n < 0 ) + { + // ERROR + kdDebug( 7108 ) << "InfoProtocol::get ERROR!" << endl; + pclose( file ); + return; + } + + empty = false; + array.setRawData( buffer, n ); + data( array ); + array.resetRawData( buffer, n ); + } + + pclose( file ); + + finished(); + + kdDebug( 7108 ) << "InfoProtocol::get - done" << endl; +} + +void InfoProtocol::mimetype( const KURL& /* url */ ) +{ + kdDebug( 7108 ) << "InfoProtocol::mimetype" << endl; + + // to get rid of those "Open with" dialogs... + mimeType( "text/html" ); + + // finish action + finished(); + + kdDebug( 7108 ) << "InfoProtocol::mimetype - done" << endl; +} + +void InfoProtocol::decodeURL( const KURL &url ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodeURL" << endl; + + /* Notes: + * + * I cleaned up the URL decoding and chose not to support URLs in the + * form "info:/usr/local/share/info/libc.info.gz" or similar which the + * older code attempted (and failed, maybe it had worked once) to do. + * + * The reason is that an obvious use such as viewing a info file off your + * infopath would work for the first page, but then all the links would be + * wrong. Of course, one could change kde-info2html to make it work, but I don't + * think it worthy, others are free to disagree and write the necessary code ;) + * + * luis pedro + */ + + if ( url == KURL( "info:/browse_by_file?special=yes" ) ) { + m_page = "#special#"; + m_node = "browse_by_file"; + kdDebug( 7108 ) << "InfoProtocol::decodeURL - special - browse by file" << endl; + return; + } + + decodePath( url.path() ); + + kdDebug( 7108 ) << "InfoProtocol::decodeURL - done" << endl; +} + +void InfoProtocol::decodePath( TQString path ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodePath(-" < - * All rights reserved. - * - * 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 above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include -#include - -#ifdef HAVE_LIBSASL2 -extern "C" { -#include -} -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "pop3.h" - -#define GREETING_BUF_LEN 1024 -#define MAX_RESPONSE_LEN 512 -#define MAX_COMMANDS 10 - -#define POP3_DEBUG kdDebug(7105) - -extern "C" { - int KDE_EXPORT kdemain(int argc, char **argv); -} - -using namespace TDEIO; - -#ifdef HAVE_LIBSASL2 -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 } -}; -#endif - -int kdemain(int argc, char **argv) -{ - - if (argc != 4) { - POP3_DEBUG << "Usage: tdeio_pop3 protocol domain-socket1 domain-socket2" - << endl; - return -1; - } - -#ifdef HAVE_LIBSASL2 - if ( sasl_client_init( NULL ) != SASL_OK ) { - fprintf(stderr, "SASL library initialization failed!\n"); - return -1; - } -#endif - - TDEInstance instance("tdeio_pop3"); - POP3Protocol *slave; - - // Are we looking to use SSL? - if (strcasecmp(argv[1], "pop3s") == 0) { - slave = new POP3Protocol(argv[2], argv[3], true); - } else { - slave = new POP3Protocol(argv[2], argv[3], false); - } - - slave->dispatchLoop(); - delete slave; - -#ifdef HAVE_LIBSASL2 - sasl_done(); -#endif - - return 0; -} - -POP3Protocol::POP3Protocol(const TQCString & pool, const TQCString & app, - bool isSSL) -: TCPSlaveBase((isSSL ? 995 : 110), (isSSL ? "pop3s" : "pop3"), pool, app, - isSSL) -{ - POP3_DEBUG << "POP3Protocol::POP3Protocol()" << endl; - m_bIsSSL = isSSL; - m_cmd = CMD_NONE; - m_iOldPort = 0; - m_tTimeout.tv_sec = 10; - m_tTimeout.tv_usec = 0; - supports_apop = false; - m_try_apop = true; - m_try_sasl = true; - opened = false; - readBufferLen = 0; -} - -POP3Protocol::~POP3Protocol() -{ - POP3_DEBUG << "POP3Protocol::~POP3Protocol()" << endl; - closeConnection(); -} - -void POP3Protocol::setHost(const TQString & _host, int _port, - const TQString & _user, const TQString & _pass) -{ - m_sServer = _host; - m_iPort = _port; - m_sUser = _user; - m_sPass = _pass; -} - -ssize_t POP3Protocol::myRead(void *data, ssize_t len) -{ - if (readBufferLen) { - ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; - memcpy(data, readBuffer, copyLen); - readBufferLen -= copyLen; - if (readBufferLen) - memmove(readBuffer, &readBuffer[copyLen], readBufferLen); - return copyLen; - } - waitForResponse(600); - return read(data, len); -} - -ssize_t POP3Protocol::myReadLine(char *data, ssize_t len) -{ - ssize_t copyLen = 0, readLen = 0; - while (true) { - while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') - copyLen++; - if (copyLen < readBufferLen || copyLen == len) { - copyLen++; - memcpy(data, readBuffer, copyLen); - data[copyLen] = '\0'; - readBufferLen -= copyLen; - if (readBufferLen) - memmove(readBuffer, &readBuffer[copyLen], readBufferLen); - return copyLen; - } - waitForResponse(600); - readLen = read(&readBuffer[readBufferLen], len - readBufferLen); - readBufferLen += readLen; - if (readLen <= 0) { - data[0] = '\0'; - return 0; - } - } -} - -POP3Protocol::Resp POP3Protocol::getResponse(char *r_buf, unsigned int r_len, - const char *cmd) -{ - char *buf = 0; - unsigned int recv_len = 0; - // fd_set FDs; - - // Give the buffer the appropriate size - r_len = r_len ? r_len : MAX_RESPONSE_LEN; - - buf = new char[r_len]; - - // Clear out the buffer - memset(buf, 0, r_len); - myReadLine(buf, r_len - 1); - - // This is really a funky crash waiting to happen if something isn't - // null terminated. - recv_len = strlen(buf); - - /* - * From rfc1939: - * - * Responses in the POP3 consist of a status indicator and a keyword - * possibly followed by additional information. All responses are - * terminated by a CRLF pair. Responses may be up to 512 characters - * long, including the terminating CRLF. There are currently two status - * indicators: positive ("+OK") and negative ("-ERR"). Servers MUST - * send the "+OK" and "-ERR" in upper case. - */ - - if (strncmp(buf, "+OK", 3) == 0) { - if (r_buf && r_len) { - memcpy(r_buf, (buf[3] == ' ' ? buf + 4 : buf + 3), - TQMIN(r_len, (buf[3] == ' ' ? recv_len - 4 : recv_len - 3))); - } - - delete[]buf; - - return Ok; - } else if (strncmp(buf, "-ERR", 4) == 0) { - if (r_buf && r_len) { - memcpy(r_buf, (buf[4] == ' ' ? buf + 5 : buf + 4), - TQMIN(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); - } - - TQString command = TQString::fromLatin1(cmd); - TQString serverMsg = TQString::fromLatin1(buf).mid(5).stripWhiteSpace(); - - if (command.left(4) == "PASS") { - command = i18n("PASS "); - } - - m_sError = i18n("The server said: \"%1\"").arg(serverMsg); - - delete[]buf; - - return Err; - } else if (strncmp(buf, "+ ", 2) == 0) { - if (r_buf && r_len) { - memcpy(r_buf, buf + 2, TQMIN(r_len, recv_len - 4)); - r_buf[TQMIN(r_len - 1, recv_len - 4)] = '\0'; - } - - delete[]buf; - - return Cont; - } else { - POP3_DEBUG << "Invalid POP3 response received!" << endl; - - if (r_buf && r_len) { - memcpy(r_buf, buf, TQMIN(r_len, recv_len)); - } - - if (!buf || !*buf) { - m_sError = i18n("The server terminated the connection."); - } else { - m_sError = i18n("Invalid response from server:\n\"%1\"").arg(buf); - } - - delete[]buf; - - return Invalid; - } -} - -bool POP3Protocol::sendCommand(const char *cmd) -{ - /* - * From rfc1939: - * - * Commands in the POP3 consist of a case-insensitive keyword, possibly - * followed by one or more arguments. All commands are terminated by a - * CRLF pair. Keywords and arguments consist of printable ASCII - * characters. Keywords and arguments are each separated by a single - * SPACE character. Keywords are three or four characters long. Each - * argument may be up to 40 characters long. - */ - - if (!isConnectionValid()) return false; - - char *cmdrn = new char[strlen(cmd) + 3]; - sprintf(cmdrn, "%s\r\n", (cmd) ? cmd : ""); - - if (write(cmdrn, strlen(cmdrn)) != static_cast < ssize_t > - (strlen(cmdrn))) { - m_sError = i18n("Could not send to server.\n"); - delete[]cmdrn; - return false; - } - - delete[]cmdrn; - return true; -} - -POP3Protocol::Resp POP3Protocol::command(const char *cmd, char *recv_buf, - unsigned int len) -{ - sendCommand(cmd); - return getResponse(recv_buf, len, cmd); -} - -void POP3Protocol::openConnection() -{ - m_try_apop = !hasMetaData("auth") || metaData("auth") == "APOP"; - m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; - - if (!pop3_open()) { - POP3_DEBUG << "pop3_open failed" << endl; - } else { - connected(); - } -} - -void POP3Protocol::closeConnection() -{ - // If the file pointer exists, we can assume the socket is valid, - // and to make sure that the server doesn't magically undo any of - // our deletions and so-on, we should send a QUIT and wait for a - // response. We don't care if it's positive or negative. Also - // flush out any semblance of a persistant connection, i.e.: the - // old username and password are now invalid. - if (!opened) { - return; - } - - command("QUIT"); - closeDescriptor(); - readBufferLen = 0; - m_sOldUser = m_sOldPass = m_sOldServer = ""; - opened = false; -} - -int POP3Protocol::loginAPOP( char *challenge, TDEIO::AuthInfo &ai ) -{ - char buf[512]; - - TQString apop_string = TQString::fromLatin1("APOP "); - if (m_sUser.isEmpty() || m_sPass.isEmpty()) { - // Prompt for usernames - if (!openPassDlg(ai)) { - error(ERR_ABORTED, i18n("No authentication details supplied.")); - closeConnection(); - return -1; - } else { - m_sUser = ai.username; - m_sPass = ai.password; - } - } - m_sOldUser = m_sUser; - m_sOldPass = m_sPass; - - apop_string.append(m_sUser); - - memset(buf, 0, sizeof(buf)); - - KMD5 ctx; - - POP3_DEBUG << "APOP challenge: " << challenge << endl; - - // Generate digest - ctx.update(challenge, strlen(challenge)); - ctx.update(m_sPass.latin1() ); - - // Genenerate APOP command - apop_string.append(" "); - apop_string.append(ctx.hexDigest()); - - if (command(apop_string.local8Bit(), buf, sizeof(buf)) == Ok) { - return 0; - } - - POP3_DEBUG << "Couldn't login via APOP. Falling back to USER/PASS" << - endl; - closeConnection(); - if (metaData("auth") == "APOP") { - error(ERR_COULD_NOT_LOGIN, - i18n - ("Login via APOP failed. The server %1 may not support APOP, although it claims to support it, or the password may be wrong.\n\n%2"). - arg(m_sServer). - arg(m_sError)); - return -1; - } - return 1; -} - -bool POP3Protocol::saslInteract( void *in, AuthInfo &ai ) -{ -#ifdef HAVE_LIBSASL2 - POP3_DEBUG << "sasl_interact" << endl; - sasl_interact_t *interact = ( sasl_interact_t * ) in; - - //some mechanisms do not require username && pass, so don'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 ) { - POP3_DEBUG << "SASL_INTERACT id: " << interact->id << endl; - switch( interact->id ) { - case SASL_CB_USER: - case SASL_CB_AUTHNAME: - POP3_DEBUG << "SASL_CB_[USER|AUTHNAME]: " << m_sUser << endl; - interact->result = strdup( m_sUser.utf8() ); - interact->len = strlen( (const char *) interact->result ); - break; - case SASL_CB_PASS: - POP3_DEBUG << "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; -#else - return false; -#endif -} - -#define SASLERROR closeConnection(); \ -error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occured during authentication: %1").arg \ -( TQString::fromUtf8( sasl_errdetail( conn ) ))); \ - -int POP3Protocol::loginSASL( TDEIO::AuthInfo &ai ) -{ -#ifdef HAVE_LIBSASL2 - char buf[512]; - TQString sasl_buffer = TQString::fromLatin1("AUTH"); - - int result; - sasl_conn_t *conn = NULL; - sasl_interact_t *client_interact = NULL; - const char *out = NULL; - uint outlen; - const char *mechusing = NULL; - Resp resp; - - result = sasl_client_new( "pop", - m_sServer.latin1(), - 0, 0, callbacks, 0, &conn ); - - if ( result != SASL_OK ) { - POP3_DEBUG << "sasl_client_new failed with: " << result << endl; - SASLERROR - return false; - } - - // We need to check what methods the server supports... - // This is based on RFC 1734's wisdom - if ( hasMetaData("sasl") || command(sasl_buffer.local8Bit()) == Ok ) { - - TQStringList sasl_list; - if (hasMetaData("sasl")) { - sasl_list.append(metaData("sasl").latin1()); - } else - while (true /* !AtEOF() */ ) { - memset(buf, 0, sizeof(buf)); - myReadLine(buf, sizeof(buf) - 1); - - // HACK: This assumes fread stops at the first \n and not \r - if (strcmp(buf, ".\r\n") == 0) { - break; // End of data - } - // sanders, changed -2 to -1 below - buf[strlen(buf) - 2] = '\0'; - - sasl_list.append(buf); - } - - do { - result = sasl_client_start(conn, sasl_list.join(" ").latin1(), - &client_interact, &out, &outlen, &mechusing); - - if (result == SASL_INTERACT) - if ( !saslInteract( client_interact, ai ) ) { - closeConnection(); - sasl_dispose( &conn ); - return -1; - }; - } while ( result == SASL_INTERACT ); - if ( result != SASL_CONTINUE && result != SASL_OK ) { - POP3_DEBUG << "sasl_client_start failed with: " << result << endl; - SASLERROR - sasl_dispose( &conn ); - return -1; - } - - POP3_DEBUG << "Preferred authentication method is " << mechusing << "." << endl; - - TQByteArray challenge, tmp; - - TQString firstCommand = "AUTH " + TQString::fromLatin1( mechusing ); - challenge.setRawData( out, outlen ); - KCodecs::base64Encode( challenge, tmp ); - challenge.resetRawData( out, outlen ); - if ( !tmp.isEmpty() ) { - firstCommand += " "; - firstCommand += TQString::fromLatin1( tmp.data(), tmp.size() ); - } - - challenge.resize( 2049 ); - resp = command( firstCommand.latin1(), challenge.data(), 2049 ); - while( resp == Cont ) { - challenge.resize(challenge.find(0)); -// POP3_DEBUG << "S: " << TQCString(challenge.data(),challenge.size()+1) << endl; - KCodecs::base64Decode( challenge, tmp ); - do { - result = sasl_client_step(conn, tmp.isEmpty() ? 0 : tmp.data(), - tmp.size(), - &client_interact, - &out, &outlen); - - if (result == SASL_INTERACT) - if ( !saslInteract( client_interact, ai ) ) { - closeConnection(); - sasl_dispose( &conn ); - return -1; - }; - } while ( result == SASL_INTERACT ); - if ( result != SASL_CONTINUE && result != SASL_OK ) { - POP3_DEBUG << "sasl_client_step failed with: " << result << endl; - SASLERROR - sasl_dispose( &conn ); - return -1; - } - - challenge.setRawData( out, outlen ); - KCodecs::base64Encode( challenge, tmp ); - challenge.resetRawData( out, outlen ); -// POP3_DEBUG << "C: " << TQCString(tmp.data(),tmp.size()+1) << endl; - tmp.resize(tmp.size()+1); - tmp[tmp.size()-1] = '\0'; - challenge.resize(2049); - resp = command( tmp.data(), challenge.data(), 2049 ); - } - - sasl_dispose( &conn ); - if ( resp == Ok ) { - POP3_DEBUG << "SASL authenticated" << endl; - m_sOldUser = m_sUser; - m_sOldPass = m_sPass; - return 0; - } - - if (metaData("auth") == "SASL") { - closeConnection(); - error(ERR_COULD_NOT_LOGIN, - i18n - ("Login via SASL (%1) failed. The server may not support %2, or the password may be wrong.\n\n%3"). - arg(mechusing).arg(mechusing).arg(m_sError)); - return -1; - } - } - - if (metaData("auth") == "SASL") { - closeConnection(); - error(ERR_COULD_NOT_LOGIN, - i18n("Your POP3 server does not support SASL.\n" - "Choose a different authentication method.")); - return -1; - } - return 1; -#else - if (metaData("auth") == "SASL") { - closeConnection(); - error(ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into tdeio_pop3.")); - return -1; - } - return 1; //if SASL not explicitly required, try another method (USER/PASS) -#endif -} - -bool POP3Protocol::loginPASS( TDEIO::AuthInfo &ai ) -{ - char buf[512]; - - if (m_sUser.isEmpty() || m_sPass.isEmpty()) { - // Prompt for usernames - if (!openPassDlg(ai)) { - error(ERR_ABORTED, i18n("No authentication details supplied.")); - closeConnection(); - return false; - } else { - m_sUser = ai.username; - m_sPass = ai.password; - } - } - m_sOldUser = m_sUser; - m_sOldPass = m_sPass; - - TQString one_string = TQString::fromLatin1("USER "); - one_string.append( m_sUser ); - - if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { - POP3_DEBUG << "Couldn't login. Bad username Sorry" << endl; - - m_sError = - i18n("Could not login to %1.\n\n").arg(m_sServer) + m_sError; - error(ERR_COULD_NOT_LOGIN, m_sError); - closeConnection(); - - return false; - } - - one_string = TQString::fromLatin1("PASS "); - one_string.append(m_sPass); - - if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { - POP3_DEBUG << "Couldn't login. Bad password Sorry." << endl; - m_sError = - i18n - ("Could not login to %1. The password may be wrong.\n\n%2"). - arg(m_sServer).arg(m_sError); - error(ERR_COULD_NOT_LOGIN, m_sError); - closeConnection(); - return false; - } - POP3_DEBUG << "USER/PASS login succeeded" << endl; - return true; -} - -bool POP3Protocol::pop3_open() -{ - POP3_DEBUG << "pop3_open()" << endl; - char *greeting_buf; - if ((m_iOldPort == port(m_iPort)) && (m_sOldServer == m_sServer) && - (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { - POP3_DEBUG << "Reusing old connection" << endl; - return true; - } - do { - closeConnection(); - - if (!connectToHost(m_sServer.ascii(), m_iPort)) { - // error(ERR_COULD_NOT_CONNECT, m_sServer); - // ConnectToHost has already send an error message. - return false; - } - opened = true; - - greeting_buf = new char[GREETING_BUF_LEN]; - memset(greeting_buf, 0, GREETING_BUF_LEN); - - // If the server doesn't respond with a greeting - if (getResponse(greeting_buf, GREETING_BUF_LEN, "") != Ok) { - m_sError = - i18n("Could not login to %1.\n\n").arg(m_sServer) + - ((!greeting_buf - || !*greeting_buf) ? - i18n("The server terminated the connection immediately.") : - i18n("Server does not respond properly:\n%1\n"). - arg(greeting_buf)); - error(ERR_COULD_NOT_LOGIN, m_sError); - delete[]greeting_buf; - closeConnection(); - return false; // we've got major problems, and possibly the - // wrong port - } - TQCString greeting(greeting_buf); - delete[]greeting_buf; - - if (greeting.length() > 0) { - greeting.truncate(greeting.length() - 2); - } - - // Does the server support APOP? - TQString apop_cmd; - TQRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", false); - - POP3_DEBUG << "greeting: " << greeting << endl; - int apop_pos = greeting.find(re); - supports_apop = (bool) (apop_pos != -1); - - if (metaData("nologin") == "on") - return true; - - if (metaData("auth") == "APOP" && !supports_apop) { - error(ERR_COULD_NOT_LOGIN, - i18n("Your POP3 server does not support APOP.\n" - "Choose a different authentication method.")); - closeConnection(); - return false; - } - - m_iOldPort = m_iPort; - m_sOldServer = m_sServer; - - // Try to go into TLS mode - if ((metaData("tls") == "on" || (canUseTLS() && - metaData("tls") != "off")) - && command("STLS") == Ok ) { - int tlsrc = startTLS(); - if (tlsrc == 1) { - POP3_DEBUG << "TLS mode has been enabled." << endl; - } else { - if (tlsrc != -3) { - POP3_DEBUG << "TLS mode setup has failed. Aborting." << endl; - error(ERR_COULD_NOT_CONNECT, - i18n("Your POP3 server claims to " - "support TLS but negotiation " - "was unsuccessful. You can " - "disable TLS in TDE using the " - "crypto settings module.")); - } - closeConnection(); - return false; - } - } else if (metaData("tls") == "on") { - error(ERR_COULD_NOT_CONNECT, - i18n("Your POP3 server does not support TLS. Disable " - "TLS, if you want to connect without encryption.")); - closeConnection(); - return false; - } - - TDEIO::AuthInfo authInfo; - authInfo.username = m_sUser; - authInfo.password = m_sPass; - authInfo.prompt = i18n("Username and password for your POP3 account:"); - - if ( supports_apop && m_try_apop ) { - POP3_DEBUG << "Trying APOP" << endl; - int retval = loginAPOP( greeting.data() + apop_pos, authInfo ); - switch ( retval ) { - case 0: return true; - case -1: return false; - default: - m_try_apop = false; - } - } else if ( m_try_sasl ) { - POP3_DEBUG << "Trying SASL" << endl; - int retval = loginSASL( authInfo ); - switch ( retval ) { - case 0: return true; - case -1: return false; - default: - m_try_sasl = false; - } - } else { - // Fall back to conventional USER/PASS scheme - POP3_DEBUG << "Trying USER/PASS" << endl; - return loginPASS( authInfo ); - } - } while ( true ); -} - -size_t POP3Protocol::realGetSize(unsigned int msg_num) -{ - char *buf; - TQCString cmd; - size_t ret = 0; - - buf = new char[MAX_RESPONSE_LEN]; - memset(buf, 0, MAX_RESPONSE_LEN); - cmd.sprintf("LIST %u", msg_num); - if ( command(cmd.data(), buf, MAX_RESPONSE_LEN) != Ok ) { - delete[]buf; - return 0; - } else { - cmd = buf; - cmd.remove(0, cmd.find(" ")); - ret = cmd.toLong(); - } - delete[]buf; - return ret; -} - -void POP3Protocol::special(const TQByteArray & aData) -{ - TQString result; - char buf[MAX_PACKET_LEN]; - TQDataStream stream(aData, IO_ReadOnly); - int tmp; - stream >> tmp; - - if (tmp != 'c') - return; - - for (int i = 0; i < 2; i++) { - TQCString cmd = (i) ? "AUTH" : "CAPA"; - if ( command(cmd) != Ok ) - continue; - while (true) { - myReadLine(buf, MAX_PACKET_LEN - 1); - if (qstrcmp(buf, ".\r\n") == 0) - break; - result += " " + TQString(buf).left(strlen(buf) - 2) - .replace(" ", "-"); - } - } - if (supports_apop) - result += " APOP"; - result = result.mid(1); - infoMessage(result); - finished(); -} - -void POP3Protocol::get(const KURL & url) -{ -// List of supported commands -// -// URI Command Result -// pop3://user:pass@domain/index LIST List message sizes -// pop3://user:pass@domain/uidl UIDL List message UIDs -// pop3://user:pass@domain/remove/#1 DELE #1 Mark a message for deletion -// pop3://user:pass@domain/download/#1 RETR #1 Get message header and body -// pop3://user:pass@domain/list/#1 LIST #1 Get size of a message -// pop3://user:pass@domain/uid/#1 UIDL #1 Get UID of a message -// pop3://user:pass@domain/commit QUIT Delete marked messages -// pop3://user:pass@domain/headers/#1 TOP #1 Get header of message -// -// Notes: -// Sizes are in bytes. -// No support for the STAT command has been implemented. -// commit closes the connection to the server after issuing the QUIT command. - - bool ok = true; - char buf[MAX_PACKET_LEN]; - char destbuf[MAX_PACKET_LEN]; - TQByteArray array; - TQString cmd, path = url.path(); - int maxCommands = (metaData("pipelining") == "on") ? MAX_COMMANDS : 1; - - if (path.at(0) == '/') - path.remove(0, 1); - if (path.isEmpty()) { - POP3_DEBUG << "We should be a dir!!" << endl; - error(ERR_IS_DIRECTORY, url.url()); - m_cmd = CMD_NONE; - return; - } - - if (((path.find('/') == -1) && (path != "index") && (path != "uidl") - && (path != "commit"))) { - error(ERR_MALFORMED_URL, url.url()); - m_cmd = CMD_NONE; - return; - } - - cmd = path.left(path.find('/')); - path.remove(0, path.find('/') + 1); - - if (!pop3_open()) { - POP3_DEBUG << "pop3_open failed" << endl; - error(ERR_COULD_NOT_CONNECT, m_sServer); - return; - } - - if ((cmd == "index") || (cmd == "uidl")) { - unsigned long size = 0; - bool result; - - if (cmd == "index") { - result = ( command("LIST") == Ok ); - } else { - result = ( command("UIDL") == Ok ); - } - - /* - LIST - +OK Mailbox scan listing follows - 1 2979 - 2 1348 - . - */ - if (result) { - while (true /* !AtEOF() */ ) { - memset(buf, 0, sizeof(buf)); - myReadLine(buf, sizeof(buf) - 1); - - // HACK: This assumes fread stops at the first \n and not \r - if (strcmp(buf, ".\r\n") == 0) { - break; // End of data - } - // sanders, changed -2 to -1 below - int bufStrLen = strlen(buf); - buf[bufStrLen - 2] = '\0'; - size += bufStrLen; - array.setRawData(buf, bufStrLen); - data(array); - array.resetRawData(buf, bufStrLen); - totalSize(size); - } - } - POP3_DEBUG << "Finishing up list" << endl; - data(TQByteArray()); - finished(); - } else if (cmd == "remove") { - TQStringList waitingCommands = TQStringList::split(',', path); - int activeCommands = 0; - TQStringList::Iterator it = waitingCommands.begin(); - while (it != waitingCommands.end() || activeCommands > 0) { - while (activeCommands < maxCommands && it != waitingCommands.end()) { - sendCommand(("DELE " + *it).latin1()); - activeCommands++; - it++; - } - getResponse(buf, sizeof(buf) - 1, ""); - activeCommands--; - } - finished(); - m_cmd = CMD_NONE; - } else if (cmd == "download" || cmd == "headers") { - TQStringList waitingCommands = TQStringList::split(',', path); - bool noProgress = (metaData("progress") == "off" - || waitingCommands.count() > 1); - int p_size = 0; - unsigned int msg_len = 0; - TQString list_cmd("LIST "); - list_cmd += path; - memset(buf, 0, sizeof(buf)); - if ( !noProgress ) { - if ( command(list_cmd.ascii(), buf, sizeof(buf) - 1) == Ok ) { - list_cmd = buf; - // We need a space, otherwise we got an invalid reply - if (!list_cmd.find(" ")) { - POP3_DEBUG << "List command needs a space? " << list_cmd << endl; - closeConnection(); - error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); - return; - } - list_cmd.remove(0, list_cmd.find(" ") + 1); - msg_len = list_cmd.toUInt(&ok); - if (!ok) { - POP3_DEBUG << "LIST command needs to return a number? :" << - list_cmd << ":" << endl; - closeConnection(); - error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); - return; - } - } else { - closeConnection(); - error(ERR_COULD_NOT_READ, m_sError); - return; - } - } - - int activeCommands = 0; - TQStringList::Iterator it = waitingCommands.begin(); - while (it != waitingCommands.end() || activeCommands > 0) { - while (activeCommands < maxCommands && it != waitingCommands.end()) { - sendCommand(((cmd == - "headers") ? "TOP " + *it + " 0" : "RETR " + - *it).latin1()); - activeCommands++; - it++; - } - if ( getResponse(buf, sizeof(buf) - 1, "") == Ok ) { - activeCommands--; - mimeType("message/rfc822"); - totalSize(msg_len); - memset(buf, 0, sizeof(buf)); - char ending = '\n'; - bool endOfMail = false; - bool eat = false; - while (true /* !AtEOF() */ ) { - ssize_t readlen = myRead(buf, sizeof(buf) - 1); - if (readlen <= 0) { - if (isConnectionValid()) - error(ERR_SERVER_TIMEOUT, m_sServer); - else - error(ERR_CONNECTION_BROKEN, m_sServer); - closeConnection(); - return; - } - if (ending == '.' && readlen > 1 && buf[0] == '\r' - && buf[1] == '\n') { - readBufferLen = readlen - 2; - memcpy(readBuffer, &buf[2], readBufferLen); - break; - } - bool newline = (ending == '\n'); - - if (buf[readlen - 1] == '\n') - ending = '\n'; - else if (buf[readlen - 1] == '.' - && ((readlen > 1) ? buf[readlen - 2] == '\n' : ending == - '\n')) - ending = '.'; - else - ending = ' '; - - char *buf1 = buf, *buf2 = destbuf; - // ".." at start of a line means only "." - // "." means end of data - for (ssize_t i = 0; i < readlen; i++) { - if (*buf1 == '\r' && eat) { - endOfMail = true; - if (i == readlen - 1 /* && !AtEOF() */ ) - myRead(buf, 1); - else if (i < readlen - 2) { - readBufferLen = readlen - i - 2; - memcpy(readBuffer, &buf[i + 2], readBufferLen); - } - break; - } else if (*buf1 == '\n') { - newline = true; - eat = false; - } else if (*buf1 == '.' && newline) { - newline = false; - eat = true; - } else { - newline = false; - eat = false; - } - if (!eat) { - *buf2 = *buf1; - buf2++; - } - buf1++; - } - - if (buf2 > destbuf) { - array.setRawData(destbuf, buf2 - destbuf); - data(array); - array.resetRawData(destbuf, buf2 - destbuf); - } - - if (endOfMail) - break; - - if (!noProgress) { - p_size += readlen; - processedSize(p_size); - } - } - infoMessage("message complete"); - } else { - POP3_DEBUG << "Couldn't login. Bad RETR Sorry" << endl; - closeConnection(); - error(ERR_COULD_NOT_READ, m_sError); - return; - } - } - POP3_DEBUG << "Finishing up" << endl; - data(TQByteArray()); - finished(); - } else if ((cmd == "uid") || (cmd == "list")) { - TQString qbuf; - (void) path.toInt(&ok); - - if (!ok) { - return; // We fscking need a number! - } - - if (cmd == "uid") { - path.prepend("UIDL "); - } else { - path.prepend("LIST "); - } - - memset(buf, 0, sizeof(buf)); - if ( command(path.ascii(), buf, sizeof(buf) - 1) == Ok ) { - const int len = strlen(buf); - mimeType("text/plain"); - totalSize(len); - array.setRawData(buf, len); - data(array); - array.resetRawData(buf, len); - processedSize(len); - POP3_DEBUG << buf << endl; - POP3_DEBUG << "Finishing up uid" << endl; - data(TQByteArray()); - finished(); - } else { - closeConnection(); - error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); - return; - } - } else if (cmd == "commit") { - POP3_DEBUG << "Issued QUIT" << endl; - closeConnection(); - finished(); - m_cmd = CMD_NONE; - return; - } -} - -void POP3Protocol::listDir(const KURL &) -{ - bool isINT; - int num_messages = 0; - char buf[MAX_RESPONSE_LEN]; - TQCString q_buf; - - // Try and open a connection - if (!pop3_open()) { - POP3_DEBUG << "pop3_open failed" << endl; - error(ERR_COULD_NOT_CONNECT, m_sServer); - return; - } - // Check how many messages we have. STAT is by law required to - // at least return +OK num_messages total_size - memset(buf, 0, MAX_RESPONSE_LEN); - if ( command("STAT", buf, MAX_RESPONSE_LEN) != Ok ) { - error(ERR_INTERNAL, "??"); - return; - } - POP3_DEBUG << "The stat buf is :" << buf << ":" << endl; - q_buf = buf; - if (q_buf.find(" ") == -1) { - error(ERR_INTERNAL, - "Invalid POP3 response, we should have at least one space!"); - closeConnection(); - return; - } - q_buf.remove(q_buf.find(" "), q_buf.length()); - - num_messages = q_buf.toUInt(&isINT); - if (!isINT) { - error(ERR_INTERNAL, "Invalid POP3 STAT response!"); - closeConnection(); - return; - } - UDSEntry entry; - UDSAtom atom; - TQString fname; - for (int i = 0; i < num_messages; i++) { - fname = "Message %1"; - - atom.m_uds = UDS_NAME; - atom.m_long = 0; - atom.m_str = fname.arg(i + 1); - entry.append(atom); - - atom.m_uds = UDS_MIME_TYPE; - atom.m_long = 0; - atom.m_str = "text/plain"; - entry.append(atom); - POP3_DEBUG << "Mimetype is " << atom.m_str.ascii() << endl; - - atom.m_uds = UDS_URL; - KURL uds_url; - if (m_bIsSSL) { - uds_url.setProtocol("pop3s"); - } else { - uds_url.setProtocol("pop3"); - } - - uds_url.setUser(m_sUser); - uds_url.setPass(m_sPass); - uds_url.setHost(m_sServer); - uds_url.setPath(TQString::fromLatin1("/download/%1").arg(i + 1)); - atom.m_str = uds_url.url(); - atom.m_long = 0; - entry.append(atom); - - atom.m_uds = UDS_FILE_TYPE; - atom.m_str = ""; - atom.m_long = S_IFREG; - entry.append(atom); - - atom.m_uds = UDS_SIZE; - atom.m_str = ""; - atom.m_long = realGetSize(i + 1); - entry.append(atom); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR; - entry.append (atom); - - listEntry(entry, false); - entry.clear(); - } - listEntry(entry, true); // ready - - finished(); -} - -void POP3Protocol::stat(const KURL & url) -{ - TQString _path = url.path(); - - if (_path.at(0) == '/') - _path.remove(0, 1); - - UDSEntry entry; - UDSAtom atom; - - atom.m_uds = UDS_NAME; - atom.m_str = _path; - entry.append(atom); - - atom.m_uds = UDS_FILE_TYPE; - atom.m_str = ""; - atom.m_long = S_IFREG; - entry.append(atom); - - atom.m_uds = UDS_MIME_TYPE; - atom.m_str = "message/rfc822"; - entry.append(atom); - - // TODO: maybe get the size of the message? - statEntry(entry); - - finished(); -} - -void POP3Protocol::del(const KURL & url, bool /*isfile */ ) -{ - TQString invalidURI = TQString::null; - bool isInt; - - if (!pop3_open()) { - POP3_DEBUG << "pop3_open failed" << endl; - error(ERR_COULD_NOT_CONNECT, m_sServer); - return; - } - - TQString _path = url.path(); - if (_path.at(0) == '/') { - _path.remove(0, 1); - } - - _path.toUInt(&isInt); - if (!isInt) { - invalidURI = _path; - } else { - _path.prepend("DELE "); - if ( command(_path.ascii()) != Ok ) { - invalidURI = _path; - } - } - - POP3_DEBUG << "POP3Protocol::del " << _path << endl; - finished(); -} diff --git a/tdeioslave/pop3/pop3.cpp b/tdeioslave/pop3/pop3.cpp new file mode 100644 index 000000000..f45f06d60 --- /dev/null +++ b/tdeioslave/pop3/pop3.cpp @@ -0,0 +1,1263 @@ +/* + * Copyright (c) 1999-2001 Alex Zepeda + * Copyright (c) 2001-2002 Michael Haeckel + * All rights reserved. + * + * 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 above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#include +#include + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include +} +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "pop3.h" + +#define GREETING_BUF_LEN 1024 +#define MAX_RESPONSE_LEN 512 +#define MAX_COMMANDS 10 + +#define POP3_DEBUG kdDebug(7105) + +extern "C" { + int KDE_EXPORT kdemain(int argc, char **argv); +} + +using namespace TDEIO; + +#ifdef HAVE_LIBSASL2 +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 } +}; +#endif + +int kdemain(int argc, char **argv) +{ + + if (argc != 4) { + POP3_DEBUG << "Usage: tdeio_pop3 protocol domain-socket1 domain-socket2" + << endl; + return -1; + } + +#ifdef HAVE_LIBSASL2 + if ( sasl_client_init( NULL ) != SASL_OK ) { + fprintf(stderr, "SASL library initialization failed!\n"); + return -1; + } +#endif + + TDEInstance instance("tdeio_pop3"); + POP3Protocol *slave; + + // Are we looking to use SSL? + if (strcasecmp(argv[1], "pop3s") == 0) { + slave = new POP3Protocol(argv[2], argv[3], true); + } else { + slave = new POP3Protocol(argv[2], argv[3], false); + } + + slave->dispatchLoop(); + delete slave; + +#ifdef HAVE_LIBSASL2 + sasl_done(); +#endif + + return 0; +} + +POP3Protocol::POP3Protocol(const TQCString & pool, const TQCString & app, + bool isSSL) +: TCPSlaveBase((isSSL ? 995 : 110), (isSSL ? "pop3s" : "pop3"), pool, app, + isSSL) +{ + POP3_DEBUG << "POP3Protocol::POP3Protocol()" << endl; + m_bIsSSL = isSSL; + m_cmd = CMD_NONE; + m_iOldPort = 0; + m_tTimeout.tv_sec = 10; + m_tTimeout.tv_usec = 0; + supports_apop = false; + m_try_apop = true; + m_try_sasl = true; + opened = false; + readBufferLen = 0; +} + +POP3Protocol::~POP3Protocol() +{ + POP3_DEBUG << "POP3Protocol::~POP3Protocol()" << endl; + closeConnection(); +} + +void POP3Protocol::setHost(const TQString & _host, int _port, + const TQString & _user, const TQString & _pass) +{ + m_sServer = _host; + m_iPort = _port; + m_sUser = _user; + m_sPass = _pass; +} + +ssize_t POP3Protocol::myRead(void *data, ssize_t len) +{ + if (readBufferLen) { + ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; + memcpy(data, readBuffer, copyLen); + readBufferLen -= copyLen; + if (readBufferLen) + memmove(readBuffer, &readBuffer[copyLen], readBufferLen); + return copyLen; + } + waitForResponse(600); + return read(data, len); +} + +ssize_t POP3Protocol::myReadLine(char *data, ssize_t len) +{ + ssize_t copyLen = 0, readLen = 0; + while (true) { + while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') + copyLen++; + if (copyLen < readBufferLen || copyLen == len) { + copyLen++; + memcpy(data, readBuffer, copyLen); + data[copyLen] = '\0'; + readBufferLen -= copyLen; + if (readBufferLen) + memmove(readBuffer, &readBuffer[copyLen], readBufferLen); + return copyLen; + } + waitForResponse(600); + readLen = read(&readBuffer[readBufferLen], len - readBufferLen); + readBufferLen += readLen; + if (readLen <= 0) { + data[0] = '\0'; + return 0; + } + } +} + +POP3Protocol::Resp POP3Protocol::getResponse(char *r_buf, unsigned int r_len, + const char *cmd) +{ + char *buf = 0; + unsigned int recv_len = 0; + // fd_set FDs; + + // Give the buffer the appropriate size + r_len = r_len ? r_len : MAX_RESPONSE_LEN; + + buf = new char[r_len]; + + // Clear out the buffer + memset(buf, 0, r_len); + myReadLine(buf, r_len - 1); + + // This is really a funky crash waiting to happen if something isn't + // null terminated. + recv_len = strlen(buf); + + /* + * From rfc1939: + * + * Responses in the POP3 consist of a status indicator and a keyword + * possibly followed by additional information. All responses are + * terminated by a CRLF pair. Responses may be up to 512 characters + * long, including the terminating CRLF. There are currently two status + * indicators: positive ("+OK") and negative ("-ERR"). Servers MUST + * send the "+OK" and "-ERR" in upper case. + */ + + if (strncmp(buf, "+OK", 3) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, (buf[3] == ' ' ? buf + 4 : buf + 3), + TQMIN(r_len, (buf[3] == ' ' ? recv_len - 4 : recv_len - 3))); + } + + delete[]buf; + + return Ok; + } else if (strncmp(buf, "-ERR", 4) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, (buf[4] == ' ' ? buf + 5 : buf + 4), + TQMIN(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); + } + + TQString command = TQString::fromLatin1(cmd); + TQString serverMsg = TQString::fromLatin1(buf).mid(5).stripWhiteSpace(); + + if (command.left(4) == "PASS") { + command = i18n("PASS "); + } + + m_sError = i18n("The server said: \"%1\"").arg(serverMsg); + + delete[]buf; + + return Err; + } else if (strncmp(buf, "+ ", 2) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, buf + 2, TQMIN(r_len, recv_len - 4)); + r_buf[TQMIN(r_len - 1, recv_len - 4)] = '\0'; + } + + delete[]buf; + + return Cont; + } else { + POP3_DEBUG << "Invalid POP3 response received!" << endl; + + if (r_buf && r_len) { + memcpy(r_buf, buf, TQMIN(r_len, recv_len)); + } + + if (!buf || !*buf) { + m_sError = i18n("The server terminated the connection."); + } else { + m_sError = i18n("Invalid response from server:\n\"%1\"").arg(buf); + } + + delete[]buf; + + return Invalid; + } +} + +bool POP3Protocol::sendCommand(const char *cmd) +{ + /* + * From rfc1939: + * + * Commands in the POP3 consist of a case-insensitive keyword, possibly + * followed by one or more arguments. All commands are terminated by a + * CRLF pair. Keywords and arguments consist of printable ASCII + * characters. Keywords and arguments are each separated by a single + * SPACE character. Keywords are three or four characters long. Each + * argument may be up to 40 characters long. + */ + + if (!isConnectionValid()) return false; + + char *cmdrn = new char[strlen(cmd) + 3]; + sprintf(cmdrn, "%s\r\n", (cmd) ? cmd : ""); + + if (write(cmdrn, strlen(cmdrn)) != static_cast < ssize_t > + (strlen(cmdrn))) { + m_sError = i18n("Could not send to server.\n"); + delete[]cmdrn; + return false; + } + + delete[]cmdrn; + return true; +} + +POP3Protocol::Resp POP3Protocol::command(const char *cmd, char *recv_buf, + unsigned int len) +{ + sendCommand(cmd); + return getResponse(recv_buf, len, cmd); +} + +void POP3Protocol::openConnection() +{ + m_try_apop = !hasMetaData("auth") || metaData("auth") == "APOP"; + m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + } else { + connected(); + } +} + +void POP3Protocol::closeConnection() +{ + // If the file pointer exists, we can assume the socket is valid, + // and to make sure that the server doesn't magically undo any of + // our deletions and so-on, we should send a QUIT and wait for a + // response. We don't care if it's positive or negative. Also + // flush out any semblance of a persistant connection, i.e.: the + // old username and password are now invalid. + if (!opened) { + return; + } + + command("QUIT"); + closeDescriptor(); + readBufferLen = 0; + m_sOldUser = m_sOldPass = m_sOldServer = ""; + opened = false; +} + +int POP3Protocol::loginAPOP( char *challenge, TDEIO::AuthInfo &ai ) +{ + char buf[512]; + + TQString apop_string = TQString::fromLatin1("APOP "); + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + // Prompt for usernames + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + closeConnection(); + return -1; + } else { + m_sUser = ai.username; + m_sPass = ai.password; + } + } + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + apop_string.append(m_sUser); + + memset(buf, 0, sizeof(buf)); + + KMD5 ctx; + + POP3_DEBUG << "APOP challenge: " << challenge << endl; + + // Generate digest + ctx.update(challenge, strlen(challenge)); + ctx.update(m_sPass.latin1() ); + + // Genenerate APOP command + apop_string.append(" "); + apop_string.append(ctx.hexDigest()); + + if (command(apop_string.local8Bit(), buf, sizeof(buf)) == Ok) { + return 0; + } + + POP3_DEBUG << "Couldn't login via APOP. Falling back to USER/PASS" << + endl; + closeConnection(); + if (metaData("auth") == "APOP") { + error(ERR_COULD_NOT_LOGIN, + i18n + ("Login via APOP failed. The server %1 may not support APOP, although it claims to support it, or the password may be wrong.\n\n%2"). + arg(m_sServer). + arg(m_sError)); + return -1; + } + return 1; +} + +bool POP3Protocol::saslInteract( void *in, AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + POP3_DEBUG << "sasl_interact" << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so don'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 ) { + POP3_DEBUG << "SASL_INTERACT id: " << interact->id << endl; + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + POP3_DEBUG << "SASL_CB_[USER|AUTHNAME]: " << m_sUser << endl; + interact->result = strdup( m_sUser.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + POP3_DEBUG << "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; +#else + return false; +#endif +} + +#define SASLERROR closeConnection(); \ +error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occured during authentication: %1").arg \ +( TQString::fromUtf8( sasl_errdetail( conn ) ))); \ + +int POP3Protocol::loginSASL( TDEIO::AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + char buf[512]; + TQString sasl_buffer = TQString::fromLatin1("AUTH"); + + int result; + sasl_conn_t *conn = NULL; + sasl_interact_t *client_interact = NULL; + const char *out = NULL; + uint outlen; + const char *mechusing = NULL; + Resp resp; + + result = sasl_client_new( "pop", + m_sServer.latin1(), + 0, 0, callbacks, 0, &conn ); + + if ( result != SASL_OK ) { + POP3_DEBUG << "sasl_client_new failed with: " << result << endl; + SASLERROR + return false; + } + + // We need to check what methods the server supports... + // This is based on RFC 1734's wisdom + if ( hasMetaData("sasl") || command(sasl_buffer.local8Bit()) == Ok ) { + + TQStringList sasl_list; + if (hasMetaData("sasl")) { + sasl_list.append(metaData("sasl").latin1()); + } else + while (true /* !AtEOF() */ ) { + memset(buf, 0, sizeof(buf)); + myReadLine(buf, sizeof(buf) - 1); + + // HACK: This assumes fread stops at the first \n and not \r + if (strcmp(buf, ".\r\n") == 0) { + break; // End of data + } + // sanders, changed -2 to -1 below + buf[strlen(buf) - 2] = '\0'; + + sasl_list.append(buf); + } + + do { + result = sasl_client_start(conn, sasl_list.join(" ").latin1(), + &client_interact, &out, &outlen, &mechusing); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + closeConnection(); + sasl_dispose( &conn ); + return -1; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + POP3_DEBUG << "sasl_client_start failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return -1; + } + + POP3_DEBUG << "Preferred authentication method is " << mechusing << "." << endl; + + TQByteArray challenge, tmp; + + TQString firstCommand = "AUTH " + TQString::fromLatin1( mechusing ); + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); + if ( !tmp.isEmpty() ) { + firstCommand += " "; + firstCommand += TQString::fromLatin1( tmp.data(), tmp.size() ); + } + + challenge.resize( 2049 ); + resp = command( firstCommand.latin1(), challenge.data(), 2049 ); + while( resp == Cont ) { + challenge.resize(challenge.find(0)); +// POP3_DEBUG << "S: " << TQCString(challenge.data(),challenge.size()+1) << endl; + KCodecs::base64Decode( challenge, tmp ); + do { + result = sasl_client_step(conn, tmp.isEmpty() ? 0 : tmp.data(), + tmp.size(), + &client_interact, + &out, &outlen); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + closeConnection(); + sasl_dispose( &conn ); + return -1; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + POP3_DEBUG << "sasl_client_step failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return -1; + } + + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); +// POP3_DEBUG << "C: " << TQCString(tmp.data(),tmp.size()+1) << endl; + tmp.resize(tmp.size()+1); + tmp[tmp.size()-1] = '\0'; + challenge.resize(2049); + resp = command( tmp.data(), challenge.data(), 2049 ); + } + + sasl_dispose( &conn ); + if ( resp == Ok ) { + POP3_DEBUG << "SASL authenticated" << endl; + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + return 0; + } + + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, + i18n + ("Login via SASL (%1) failed. The server may not support %2, or the password may be wrong.\n\n%3"). + arg(mechusing).arg(mechusing).arg(m_sError)); + return -1; + } + } + + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, + i18n("Your POP3 server does not support SASL.\n" + "Choose a different authentication method.")); + return -1; + } + return 1; +#else + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into tdeio_pop3.")); + return -1; + } + return 1; //if SASL not explicitly required, try another method (USER/PASS) +#endif +} + +bool POP3Protocol::loginPASS( TDEIO::AuthInfo &ai ) +{ + char buf[512]; + + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + // Prompt for usernames + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + closeConnection(); + return false; + } else { + m_sUser = ai.username; + m_sPass = ai.password; + } + } + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + TQString one_string = TQString::fromLatin1("USER "); + one_string.append( m_sUser ); + + if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { + POP3_DEBUG << "Couldn't login. Bad username Sorry" << endl; + + m_sError = + i18n("Could not login to %1.\n\n").arg(m_sServer) + m_sError; + error(ERR_COULD_NOT_LOGIN, m_sError); + closeConnection(); + + return false; + } + + one_string = TQString::fromLatin1("PASS "); + one_string.append(m_sPass); + + if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { + POP3_DEBUG << "Couldn't login. Bad password Sorry." << endl; + m_sError = + i18n + ("Could not login to %1. The password may be wrong.\n\n%2"). + arg(m_sServer).arg(m_sError); + error(ERR_COULD_NOT_LOGIN, m_sError); + closeConnection(); + return false; + } + POP3_DEBUG << "USER/PASS login succeeded" << endl; + return true; +} + +bool POP3Protocol::pop3_open() +{ + POP3_DEBUG << "pop3_open()" << endl; + char *greeting_buf; + if ((m_iOldPort == port(m_iPort)) && (m_sOldServer == m_sServer) && + (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { + POP3_DEBUG << "Reusing old connection" << endl; + return true; + } + do { + closeConnection(); + + if (!connectToHost(m_sServer.ascii(), m_iPort)) { + // error(ERR_COULD_NOT_CONNECT, m_sServer); + // ConnectToHost has already send an error message. + return false; + } + opened = true; + + greeting_buf = new char[GREETING_BUF_LEN]; + memset(greeting_buf, 0, GREETING_BUF_LEN); + + // If the server doesn't respond with a greeting + if (getResponse(greeting_buf, GREETING_BUF_LEN, "") != Ok) { + m_sError = + i18n("Could not login to %1.\n\n").arg(m_sServer) + + ((!greeting_buf + || !*greeting_buf) ? + i18n("The server terminated the connection immediately.") : + i18n("Server does not respond properly:\n%1\n"). + arg(greeting_buf)); + error(ERR_COULD_NOT_LOGIN, m_sError); + delete[]greeting_buf; + closeConnection(); + return false; // we've got major problems, and possibly the + // wrong port + } + TQCString greeting(greeting_buf); + delete[]greeting_buf; + + if (greeting.length() > 0) { + greeting.truncate(greeting.length() - 2); + } + + // Does the server support APOP? + TQString apop_cmd; + TQRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", false); + + POP3_DEBUG << "greeting: " << greeting << endl; + int apop_pos = greeting.find(re); + supports_apop = (bool) (apop_pos != -1); + + if (metaData("nologin") == "on") + return true; + + if (metaData("auth") == "APOP" && !supports_apop) { + error(ERR_COULD_NOT_LOGIN, + i18n("Your POP3 server does not support APOP.\n" + "Choose a different authentication method.")); + closeConnection(); + return false; + } + + m_iOldPort = m_iPort; + m_sOldServer = m_sServer; + + // Try to go into TLS mode + if ((metaData("tls") == "on" || (canUseTLS() && + metaData("tls") != "off")) + && command("STLS") == Ok ) { + int tlsrc = startTLS(); + if (tlsrc == 1) { + POP3_DEBUG << "TLS mode has been enabled." << endl; + } else { + if (tlsrc != -3) { + POP3_DEBUG << "TLS mode setup has failed. Aborting." << endl; + error(ERR_COULD_NOT_CONNECT, + i18n("Your POP3 server claims to " + "support TLS but negotiation " + "was unsuccessful. You can " + "disable TLS in TDE using the " + "crypto settings module.")); + } + closeConnection(); + return false; + } + } else if (metaData("tls") == "on") { + error(ERR_COULD_NOT_CONNECT, + i18n("Your POP3 server does not support TLS. Disable " + "TLS, if you want to connect without encryption.")); + closeConnection(); + return false; + } + + TDEIO::AuthInfo authInfo; + authInfo.username = m_sUser; + authInfo.password = m_sPass; + authInfo.prompt = i18n("Username and password for your POP3 account:"); + + if ( supports_apop && m_try_apop ) { + POP3_DEBUG << "Trying APOP" << endl; + int retval = loginAPOP( greeting.data() + apop_pos, authInfo ); + switch ( retval ) { + case 0: return true; + case -1: return false; + default: + m_try_apop = false; + } + } else if ( m_try_sasl ) { + POP3_DEBUG << "Trying SASL" << endl; + int retval = loginSASL( authInfo ); + switch ( retval ) { + case 0: return true; + case -1: return false; + default: + m_try_sasl = false; + } + } else { + // Fall back to conventional USER/PASS scheme + POP3_DEBUG << "Trying USER/PASS" << endl; + return loginPASS( authInfo ); + } + } while ( true ); +} + +size_t POP3Protocol::realGetSize(unsigned int msg_num) +{ + char *buf; + TQCString cmd; + size_t ret = 0; + + buf = new char[MAX_RESPONSE_LEN]; + memset(buf, 0, MAX_RESPONSE_LEN); + cmd.sprintf("LIST %u", msg_num); + if ( command(cmd.data(), buf, MAX_RESPONSE_LEN) != Ok ) { + delete[]buf; + return 0; + } else { + cmd = buf; + cmd.remove(0, cmd.find(" ")); + ret = cmd.toLong(); + } + delete[]buf; + return ret; +} + +void POP3Protocol::special(const TQByteArray & aData) +{ + TQString result; + char buf[MAX_PACKET_LEN]; + TQDataStream stream(aData, IO_ReadOnly); + int tmp; + stream >> tmp; + + if (tmp != 'c') + return; + + for (int i = 0; i < 2; i++) { + TQCString cmd = (i) ? "AUTH" : "CAPA"; + if ( command(cmd) != Ok ) + continue; + while (true) { + myReadLine(buf, MAX_PACKET_LEN - 1); + if (qstrcmp(buf, ".\r\n") == 0) + break; + result += " " + TQString(buf).left(strlen(buf) - 2) + .replace(" ", "-"); + } + } + if (supports_apop) + result += " APOP"; + result = result.mid(1); + infoMessage(result); + finished(); +} + +void POP3Protocol::get(const KURL & url) +{ +// List of supported commands +// +// URI Command Result +// pop3://user:pass@domain/index LIST List message sizes +// pop3://user:pass@domain/uidl UIDL List message UIDs +// pop3://user:pass@domain/remove/#1 DELE #1 Mark a message for deletion +// pop3://user:pass@domain/download/#1 RETR #1 Get message header and body +// pop3://user:pass@domain/list/#1 LIST #1 Get size of a message +// pop3://user:pass@domain/uid/#1 UIDL #1 Get UID of a message +// pop3://user:pass@domain/commit QUIT Delete marked messages +// pop3://user:pass@domain/headers/#1 TOP #1 Get header of message +// +// Notes: +// Sizes are in bytes. +// No support for the STAT command has been implemented. +// commit closes the connection to the server after issuing the QUIT command. + + bool ok = true; + char buf[MAX_PACKET_LEN]; + char destbuf[MAX_PACKET_LEN]; + TQByteArray array; + TQString cmd, path = url.path(); + int maxCommands = (metaData("pipelining") == "on") ? MAX_COMMANDS : 1; + + if (path.at(0) == '/') + path.remove(0, 1); + if (path.isEmpty()) { + POP3_DEBUG << "We should be a dir!!" << endl; + error(ERR_IS_DIRECTORY, url.url()); + m_cmd = CMD_NONE; + return; + } + + if (((path.find('/') == -1) && (path != "index") && (path != "uidl") + && (path != "commit"))) { + error(ERR_MALFORMED_URL, url.url()); + m_cmd = CMD_NONE; + return; + } + + cmd = path.left(path.find('/')); + path.remove(0, path.find('/') + 1); + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + + if ((cmd == "index") || (cmd == "uidl")) { + unsigned long size = 0; + bool result; + + if (cmd == "index") { + result = ( command("LIST") == Ok ); + } else { + result = ( command("UIDL") == Ok ); + } + + /* + LIST + +OK Mailbox scan listing follows + 1 2979 + 2 1348 + . + */ + if (result) { + while (true /* !AtEOF() */ ) { + memset(buf, 0, sizeof(buf)); + myReadLine(buf, sizeof(buf) - 1); + + // HACK: This assumes fread stops at the first \n and not \r + if (strcmp(buf, ".\r\n") == 0) { + break; // End of data + } + // sanders, changed -2 to -1 below + int bufStrLen = strlen(buf); + buf[bufStrLen - 2] = '\0'; + size += bufStrLen; + array.setRawData(buf, bufStrLen); + data(array); + array.resetRawData(buf, bufStrLen); + totalSize(size); + } + } + POP3_DEBUG << "Finishing up list" << endl; + data(TQByteArray()); + finished(); + } else if (cmd == "remove") { + TQStringList waitingCommands = TQStringList::split(',', path); + int activeCommands = 0; + TQStringList::Iterator it = waitingCommands.begin(); + while (it != waitingCommands.end() || activeCommands > 0) { + while (activeCommands < maxCommands && it != waitingCommands.end()) { + sendCommand(("DELE " + *it).latin1()); + activeCommands++; + it++; + } + getResponse(buf, sizeof(buf) - 1, ""); + activeCommands--; + } + finished(); + m_cmd = CMD_NONE; + } else if (cmd == "download" || cmd == "headers") { + TQStringList waitingCommands = TQStringList::split(',', path); + bool noProgress = (metaData("progress") == "off" + || waitingCommands.count() > 1); + int p_size = 0; + unsigned int msg_len = 0; + TQString list_cmd("LIST "); + list_cmd += path; + memset(buf, 0, sizeof(buf)); + if ( !noProgress ) { + if ( command(list_cmd.ascii(), buf, sizeof(buf) - 1) == Ok ) { + list_cmd = buf; + // We need a space, otherwise we got an invalid reply + if (!list_cmd.find(" ")) { + POP3_DEBUG << "List command needs a space? " << list_cmd << endl; + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + list_cmd.remove(0, list_cmd.find(" ") + 1); + msg_len = list_cmd.toUInt(&ok); + if (!ok) { + POP3_DEBUG << "LIST command needs to return a number? :" << + list_cmd << ":" << endl; + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + } else { + closeConnection(); + error(ERR_COULD_NOT_READ, m_sError); + return; + } + } + + int activeCommands = 0; + TQStringList::Iterator it = waitingCommands.begin(); + while (it != waitingCommands.end() || activeCommands > 0) { + while (activeCommands < maxCommands && it != waitingCommands.end()) { + sendCommand(((cmd == + "headers") ? "TOP " + *it + " 0" : "RETR " + + *it).latin1()); + activeCommands++; + it++; + } + if ( getResponse(buf, sizeof(buf) - 1, "") == Ok ) { + activeCommands--; + mimeType("message/rfc822"); + totalSize(msg_len); + memset(buf, 0, sizeof(buf)); + char ending = '\n'; + bool endOfMail = false; + bool eat = false; + while (true /* !AtEOF() */ ) { + ssize_t readlen = myRead(buf, sizeof(buf) - 1); + if (readlen <= 0) { + if (isConnectionValid()) + error(ERR_SERVER_TIMEOUT, m_sServer); + else + error(ERR_CONNECTION_BROKEN, m_sServer); + closeConnection(); + return; + } + if (ending == '.' && readlen > 1 && buf[0] == '\r' + && buf[1] == '\n') { + readBufferLen = readlen - 2; + memcpy(readBuffer, &buf[2], readBufferLen); + break; + } + bool newline = (ending == '\n'); + + if (buf[readlen - 1] == '\n') + ending = '\n'; + else if (buf[readlen - 1] == '.' + && ((readlen > 1) ? buf[readlen - 2] == '\n' : ending == + '\n')) + ending = '.'; + else + ending = ' '; + + char *buf1 = buf, *buf2 = destbuf; + // ".." at start of a line means only "." + // "." means end of data + for (ssize_t i = 0; i < readlen; i++) { + if (*buf1 == '\r' && eat) { + endOfMail = true; + if (i == readlen - 1 /* && !AtEOF() */ ) + myRead(buf, 1); + else if (i < readlen - 2) { + readBufferLen = readlen - i - 2; + memcpy(readBuffer, &buf[i + 2], readBufferLen); + } + break; + } else if (*buf1 == '\n') { + newline = true; + eat = false; + } else if (*buf1 == '.' && newline) { + newline = false; + eat = true; + } else { + newline = false; + eat = false; + } + if (!eat) { + *buf2 = *buf1; + buf2++; + } + buf1++; + } + + if (buf2 > destbuf) { + array.setRawData(destbuf, buf2 - destbuf); + data(array); + array.resetRawData(destbuf, buf2 - destbuf); + } + + if (endOfMail) + break; + + if (!noProgress) { + p_size += readlen; + processedSize(p_size); + } + } + infoMessage("message complete"); + } else { + POP3_DEBUG << "Couldn't login. Bad RETR Sorry" << endl; + closeConnection(); + error(ERR_COULD_NOT_READ, m_sError); + return; + } + } + POP3_DEBUG << "Finishing up" << endl; + data(TQByteArray()); + finished(); + } else if ((cmd == "uid") || (cmd == "list")) { + TQString qbuf; + (void) path.toInt(&ok); + + if (!ok) { + return; // We fscking need a number! + } + + if (cmd == "uid") { + path.prepend("UIDL "); + } else { + path.prepend("LIST "); + } + + memset(buf, 0, sizeof(buf)); + if ( command(path.ascii(), buf, sizeof(buf) - 1) == Ok ) { + const int len = strlen(buf); + mimeType("text/plain"); + totalSize(len); + array.setRawData(buf, len); + data(array); + array.resetRawData(buf, len); + processedSize(len); + POP3_DEBUG << buf << endl; + POP3_DEBUG << "Finishing up uid" << endl; + data(TQByteArray()); + finished(); + } else { + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + } else if (cmd == "commit") { + POP3_DEBUG << "Issued QUIT" << endl; + closeConnection(); + finished(); + m_cmd = CMD_NONE; + return; + } +} + +void POP3Protocol::listDir(const KURL &) +{ + bool isINT; + int num_messages = 0; + char buf[MAX_RESPONSE_LEN]; + TQCString q_buf; + + // Try and open a connection + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + // Check how many messages we have. STAT is by law required to + // at least return +OK num_messages total_size + memset(buf, 0, MAX_RESPONSE_LEN); + if ( command("STAT", buf, MAX_RESPONSE_LEN) != Ok ) { + error(ERR_INTERNAL, "??"); + return; + } + POP3_DEBUG << "The stat buf is :" << buf << ":" << endl; + q_buf = buf; + if (q_buf.find(" ") == -1) { + error(ERR_INTERNAL, + "Invalid POP3 response, we should have at least one space!"); + closeConnection(); + return; + } + q_buf.remove(q_buf.find(" "), q_buf.length()); + + num_messages = q_buf.toUInt(&isINT); + if (!isINT) { + error(ERR_INTERNAL, "Invalid POP3 STAT response!"); + closeConnection(); + return; + } + UDSEntry entry; + UDSAtom atom; + TQString fname; + for (int i = 0; i < num_messages; i++) { + fname = "Message %1"; + + atom.m_uds = UDS_NAME; + atom.m_long = 0; + atom.m_str = fname.arg(i + 1); + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/plain"; + entry.append(atom); + POP3_DEBUG << "Mimetype is " << atom.m_str.ascii() << endl; + + atom.m_uds = UDS_URL; + KURL uds_url; + if (m_bIsSSL) { + uds_url.setProtocol("pop3s"); + } else { + uds_url.setProtocol("pop3"); + } + + uds_url.setUser(m_sUser); + uds_url.setPass(m_sPass); + uds_url.setHost(m_sServer); + uds_url.setPath(TQString::fromLatin1("/download/%1").arg(i + 1)); + atom.m_str = uds_url.url(); + atom.m_long = 0; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_SIZE; + atom.m_str = ""; + atom.m_long = realGetSize(i + 1); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR; + entry.append (atom); + + listEntry(entry, false); + entry.clear(); + } + listEntry(entry, true); // ready + + finished(); +} + +void POP3Protocol::stat(const KURL & url) +{ + TQString _path = url.path(); + + if (_path.at(0) == '/') + _path.remove(0, 1); + + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = UDS_NAME; + atom.m_str = _path; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_str = "message/rfc822"; + entry.append(atom); + + // TODO: maybe get the size of the message? + statEntry(entry); + + finished(); +} + +void POP3Protocol::del(const KURL & url, bool /*isfile */ ) +{ + TQString invalidURI = TQString::null; + bool isInt; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + + TQString _path = url.path(); + if (_path.at(0) == '/') { + _path.remove(0, 1); + } + + _path.toUInt(&isInt); + if (!isInt) { + invalidURI = _path; + } else { + _path.prepend("DELE "); + if ( command(_path.ascii()) != Ok ) { + invalidURI = _path; + } + } + + POP3_DEBUG << "POP3Protocol::del " << _path << endl; + finished(); +} diff --git a/tdeioslave/remote/Makefile.am b/tdeioslave/remote/Makefile.am index 9505065f3..988016d3c 100644 --- a/tdeioslave/remote/Makefile.am +++ b/tdeioslave/remote/Makefile.am @@ -28,5 +28,5 @@ check: testremote ./testremote messages: - $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_remote.pot + $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_remote.pot diff --git a/tdeioslave/settings/CMakeLists.txt b/tdeioslave/settings/CMakeLists.txt index 04fc4e69f..0b05ff0c1 100644 --- a/tdeioslave/settings/CMakeLists.txt +++ b/tdeioslave/settings/CMakeLists.txt @@ -35,7 +35,7 @@ tde_create_translated_desktop( set( target tdeio_settings ) tde_add_kpart( ${target} AUTOMOC - SOURCES tdeio_settings.cc + SOURCES tdeio_settings.cpp LINK tdeio-shared DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/settings/Makefile.am b/tdeioslave/settings/Makefile.am index b470a7782..5b804af90 100644 --- a/tdeioslave/settings/Makefile.am +++ b/tdeioslave/settings/Makefile.am @@ -4,7 +4,7 @@ INCLUDES= $(all_includes) kde_module_LTLIBRARIES = tdeio_settings.la -tdeio_settings_la_SOURCES = tdeio_settings.cc +tdeio_settings_la_SOURCES = tdeio_settings.cpp tdeio_settings_la_LIBADD = $(LIB_TDESYCOCA) tdeio_settings_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) @@ -17,5 +17,5 @@ kdelnkdir = $(kde_servicesdir) SUBDIRS=. messages: - $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_settings.pot + $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_settings.pot diff --git a/tdeioslave/settings/tdeio_settings.cc b/tdeioslave/settings/tdeio_settings.cc deleted file mode 100644 index 5d1e67a0e..000000000 --- a/tdeioslave/settings/tdeio_settings.cc +++ /dev/null @@ -1,296 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2003 Joseph Wenninger - - 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. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class SettingsProtocol : public TDEIO::SlaveBase -{ -public: - enum RunMode { SettingsMode, ProgramsMode, ApplicationsMode }; - SettingsProtocol(const TQCString &protocol, const TQCString &pool, const TQCString &app); - virtual ~SettingsProtocol(); - virtual void get( const KURL& url ); - virtual void stat(const KURL& url); - virtual void listDir(const KURL& url); - void listRoot(); - KServiceGroup::Ptr findGroup(const TQString &relPath); - -private: - DCOPClient *m_dcopClient; - RunMode m_runMode; -}; - -extern "C" { - KDE_EXPORT int kdemain( int, char **argv ) - { - kdDebug() << "kdemain for settings tdeioslave" << endl; - TDEInstance instance( "tdeio_settings" ); - SettingsProtocol slave(argv[1], argv[2], argv[3]); - slave.dispatchLoop(); - return 0; - } -} - - -static void addAtom(TDEIO::UDSEntry& entry, unsigned int ID, long l, const TQString& s = TQString::null) -{ - TDEIO::UDSAtom atom; - atom.m_uds = ID; - atom.m_long = l; - atom.m_str = s; - entry.append(atom); -} - -static void createFileEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime, const TQString& iconName, const TQString& localPath) -{ - entry.clear(); - addAtom(entry, TDEIO::UDS_NAME, 0, TDEIO::encodeFileName(name)); - addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFREG); - addAtom(entry, TDEIO::UDS_URL, 0, url); - addAtom(entry, TDEIO::UDS_ACCESS, 0500); - addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); - addAtom(entry, TDEIO::UDS_SIZE, 0); - addAtom(entry, TDEIO::UDS_LOCAL_PATH, 0, localPath); - addAtom(entry, TDEIO::UDS_CREATION_TIME, 1); - addAtom(entry, TDEIO::UDS_MODIFICATION_TIME, time(0)); - addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); -} - -static void createDirEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime,const TQString& iconName) -{ - entry.clear(); - addAtom(entry, TDEIO::UDS_NAME, 0, name); - addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); - addAtom(entry, TDEIO::UDS_ACCESS, 0500); - addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); - addAtom(entry, TDEIO::UDS_URL, 0, url); - addAtom(entry, TDEIO::UDS_SIZE, 0); - addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); -} - -SettingsProtocol::SettingsProtocol( const TQCString &protocol, const TQCString &pool, const TQCString &app): SlaveBase( protocol, pool, app ) -{ - // Adjusts which part of the TDE Menu to virtualize. - if ( protocol == "programs" ) - m_runMode = ProgramsMode; - else - if (protocol == "applications") - m_runMode = ApplicationsMode; - else - m_runMode = SettingsMode; - - m_dcopClient = new DCOPClient(); - if (!m_dcopClient->attach()) - { - kdDebug() << "ERROR WHILE CONNECTING TO DCOPSERVER" << endl; - } -} - -SettingsProtocol::~SettingsProtocol() -{ - delete m_dcopClient; -} - -KServiceGroup::Ptr SettingsProtocol::findGroup(const TQString &relPath) -{ - TQString nextPart; - TQString alreadyFound("Settings/"); - TQStringList rest = TQStringList::split('/', relPath); - - kdDebug() << "Trying harder to find group " << relPath << endl; - for (unsigned int i=0; iisValid()) - return 0; - - KServiceGroup::List list = tmp->entries(true, true); - KServiceGroup::List::ConstIterator it = list.begin(); - - bool found = false; - for (; it != list.end(); ++it) { - KSycocaEntry *e = *it; - if (e->isType(KST_KServiceGroup)) { - KServiceGroup::Ptr g(static_cast(e)); - if ((g->caption()==rest.front()) || (g->name()==alreadyFound+rest.front())) { - kdDebug() << "Found group with caption " << g->caption() - << " with real name: " << g->name() << endl; - found = true; - rest.remove(rest.begin()); - alreadyFound = g->name(); - kdDebug() << "ALREADY FOUND: " << alreadyFound << endl; - break; - } - } - } - - if (!found) { - kdDebug() << "Group with caption " << rest.front() << " not found within " - << alreadyFound << endl; - return 0; - } - - } - return KServiceGroup::group(alreadyFound); -} - -void SettingsProtocol::get( const KURL & url ) -{ - KService::Ptr service = KService::serviceByDesktopName(url.fileName()); - if (service && service->isValid()) { - KURL redirUrl; - redirUrl.setPath(locate("apps", service->desktopEntryPath())); - redirection(redirUrl); - finished(); - } else { - error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); - } -} - - -void SettingsProtocol::stat(const KURL& url) -{ - TDEIO::UDSEntry entry; - - TQString servicePath( url.path(1) ); - servicePath.remove(0, 1); // remove starting '/' - - if ( m_runMode == SettingsMode) - servicePath = "Settings/" + servicePath; - - KServiceGroup::Ptr grp = KServiceGroup::group(servicePath); - - if (grp && grp->isValid()) { - createDirEntry(entry, (m_runMode == SettingsMode) ? i18n("Settings") : ( (m_runMode==ApplicationsMode) ? i18n("Applications") : i18n("Programs")), - url.url(), "inode/directory",grp->icon() ); - } else { - KService::Ptr service = KService::serviceByDesktopName( url.fileName() ); - if (service && service->isValid()) { -// KURL newUrl; -// newUrl.setPath(locate("apps", service->desktopEntryPath())); -// createFileEntry(entry, service->name(), newUrl, "application/x-desktop", service->icon()); - - createFileEntry(entry, service->name(), url.url(1)+service->desktopEntryName(), - "application/x-desktop", service->icon(), locate("apps", service->desktopEntryPath()) ); - } else { - error(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); - return; - } - } - - statEntry(entry); - finished(); - return; -} - - -void SettingsProtocol::listDir(const KURL& url) -{ - TQString groupPath = url.path(1); - groupPath.remove(0, 1); // remove starting '/' - - if ( m_runMode == SettingsMode) - groupPath.prepend("Settings/"); - - KServiceGroup::Ptr grp = KServiceGroup::group(groupPath); - - if (!grp || !grp->isValid()) { - grp = findGroup(groupPath); - if (!grp || !grp->isValid()) { - error(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); - return; - } - } - - unsigned int count = 0; - TDEIO::UDSEntry entry; - - KServiceGroup::List list = grp->entries(true, true); - KServiceGroup::List::ConstIterator it; - - for (it = list.begin(); it != list.end(); ++it) { - KSycocaEntry * e = *it; - - if (e->isType(KST_KServiceGroup)) { - KServiceGroup::Ptr g(static_cast(e)); - TQString groupCaption = g->caption(); - - // Avoid adding empty groups. - KServiceGroup::Ptr subMenuRoot = KServiceGroup::group(g->relPath()); - if (subMenuRoot->childCount() == 0) - continue; - - // Ignore dotfiles. - if ((g->name().at(0) == '.')) - continue; - - TQString relPath = g->relPath(); - - // Do not display the "Settings" menu group in Programs Mode. - if( (m_runMode == ProgramsMode) && relPath.startsWith( "Settings" ) ) - { - kdDebug() << "SettingsProtocol: SKIPPING entry programs:/" << relPath << endl; - continue; - } - - switch( m_runMode ) - { - case( SettingsMode ): - relPath.remove(0, 9); // length("Settings/") ==9 - kdDebug() << "SettingsProtocol: adding entry settings:/" << relPath << endl; - createDirEntry(entry, groupCaption, "settings:/"+relPath, "inode/directory",g->icon()); - break; - case( ProgramsMode ): - kdDebug() << "SettingsProtocol: adding entry programs:/" << relPath << endl; - createDirEntry(entry, groupCaption, "programs:/"+relPath, "inode/directory",g->icon()); - break; - case( ApplicationsMode ): - kdDebug() << "SettingsProtocol: adding entry applications:/" << relPath << endl; - createDirEntry(entry, groupCaption, "applications:/"+relPath, "inode/directory",g->icon()); - break; - } - - } else { - KService::Ptr s(static_cast(e)); - kdDebug() << "SettingsProtocol: adding file entry " << url.url(1)+s->name() << endl; - createFileEntry(entry,s->name(),url.url(1)+s->desktopEntryName(), "application/x-desktop",s->icon(),locate("apps", s->desktopEntryPath())); - } - - listEntry(entry, false); - count++; - } - - totalSize(count); - listEntry(entry, true); - finished(); -} diff --git a/tdeioslave/settings/tdeio_settings.cpp b/tdeioslave/settings/tdeio_settings.cpp new file mode 100644 index 000000000..5d1e67a0e --- /dev/null +++ b/tdeioslave/settings/tdeio_settings.cpp @@ -0,0 +1,296 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger + + 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SettingsProtocol : public TDEIO::SlaveBase +{ +public: + enum RunMode { SettingsMode, ProgramsMode, ApplicationsMode }; + SettingsProtocol(const TQCString &protocol, const TQCString &pool, const TQCString &app); + virtual ~SettingsProtocol(); + virtual void get( const KURL& url ); + virtual void stat(const KURL& url); + virtual void listDir(const KURL& url); + void listRoot(); + KServiceGroup::Ptr findGroup(const TQString &relPath); + +private: + DCOPClient *m_dcopClient; + RunMode m_runMode; +}; + +extern "C" { + KDE_EXPORT int kdemain( int, char **argv ) + { + kdDebug() << "kdemain for settings tdeioslave" << endl; + TDEInstance instance( "tdeio_settings" ); + SettingsProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + + +static void addAtom(TDEIO::UDSEntry& entry, unsigned int ID, long l, const TQString& s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + +static void createFileEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime, const TQString& iconName, const TQString& localPath) +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, TDEIO::encodeFileName(name)); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFREG); + addAtom(entry, TDEIO::UDS_URL, 0, url); + addAtom(entry, TDEIO::UDS_ACCESS, 0500); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, TDEIO::UDS_SIZE, 0); + addAtom(entry, TDEIO::UDS_LOCAL_PATH, 0, localPath); + addAtom(entry, TDEIO::UDS_CREATION_TIME, 1); + addAtom(entry, TDEIO::UDS_MODIFICATION_TIME, time(0)); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); +} + +static void createDirEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime,const TQString& iconName) +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, name); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0500); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, TDEIO::UDS_URL, 0, url); + addAtom(entry, TDEIO::UDS_SIZE, 0); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); +} + +SettingsProtocol::SettingsProtocol( const TQCString &protocol, const TQCString &pool, const TQCString &app): SlaveBase( protocol, pool, app ) +{ + // Adjusts which part of the TDE Menu to virtualize. + if ( protocol == "programs" ) + m_runMode = ProgramsMode; + else + if (protocol == "applications") + m_runMode = ApplicationsMode; + else + m_runMode = SettingsMode; + + m_dcopClient = new DCOPClient(); + if (!m_dcopClient->attach()) + { + kdDebug() << "ERROR WHILE CONNECTING TO DCOPSERVER" << endl; + } +} + +SettingsProtocol::~SettingsProtocol() +{ + delete m_dcopClient; +} + +KServiceGroup::Ptr SettingsProtocol::findGroup(const TQString &relPath) +{ + TQString nextPart; + TQString alreadyFound("Settings/"); + TQStringList rest = TQStringList::split('/', relPath); + + kdDebug() << "Trying harder to find group " << relPath << endl; + for (unsigned int i=0; iisValid()) + return 0; + + KServiceGroup::List list = tmp->entries(true, true); + KServiceGroup::List::ConstIterator it = list.begin(); + + bool found = false; + for (; it != list.end(); ++it) { + KSycocaEntry *e = *it; + if (e->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr g(static_cast(e)); + if ((g->caption()==rest.front()) || (g->name()==alreadyFound+rest.front())) { + kdDebug() << "Found group with caption " << g->caption() + << " with real name: " << g->name() << endl; + found = true; + rest.remove(rest.begin()); + alreadyFound = g->name(); + kdDebug() << "ALREADY FOUND: " << alreadyFound << endl; + break; + } + } + } + + if (!found) { + kdDebug() << "Group with caption " << rest.front() << " not found within " + << alreadyFound << endl; + return 0; + } + + } + return KServiceGroup::group(alreadyFound); +} + +void SettingsProtocol::get( const KURL & url ) +{ + KService::Ptr service = KService::serviceByDesktopName(url.fileName()); + if (service && service->isValid()) { + KURL redirUrl; + redirUrl.setPath(locate("apps", service->desktopEntryPath())); + redirection(redirUrl); + finished(); + } else { + error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); + } +} + + +void SettingsProtocol::stat(const KURL& url) +{ + TDEIO::UDSEntry entry; + + TQString servicePath( url.path(1) ); + servicePath.remove(0, 1); // remove starting '/' + + if ( m_runMode == SettingsMode) + servicePath = "Settings/" + servicePath; + + KServiceGroup::Ptr grp = KServiceGroup::group(servicePath); + + if (grp && grp->isValid()) { + createDirEntry(entry, (m_runMode == SettingsMode) ? i18n("Settings") : ( (m_runMode==ApplicationsMode) ? i18n("Applications") : i18n("Programs")), + url.url(), "inode/directory",grp->icon() ); + } else { + KService::Ptr service = KService::serviceByDesktopName( url.fileName() ); + if (service && service->isValid()) { +// KURL newUrl; +// newUrl.setPath(locate("apps", service->desktopEntryPath())); +// createFileEntry(entry, service->name(), newUrl, "application/x-desktop", service->icon()); + + createFileEntry(entry, service->name(), url.url(1)+service->desktopEntryName(), + "application/x-desktop", service->icon(), locate("apps", service->desktopEntryPath()) ); + } else { + error(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + statEntry(entry); + finished(); + return; +} + + +void SettingsProtocol::listDir(const KURL& url) +{ + TQString groupPath = url.path(1); + groupPath.remove(0, 1); // remove starting '/' + + if ( m_runMode == SettingsMode) + groupPath.prepend("Settings/"); + + KServiceGroup::Ptr grp = KServiceGroup::group(groupPath); + + if (!grp || !grp->isValid()) { + grp = findGroup(groupPath); + if (!grp || !grp->isValid()) { + error(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + unsigned int count = 0; + TDEIO::UDSEntry entry; + + KServiceGroup::List list = grp->entries(true, true); + KServiceGroup::List::ConstIterator it; + + for (it = list.begin(); it != list.end(); ++it) { + KSycocaEntry * e = *it; + + if (e->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr g(static_cast(e)); + TQString groupCaption = g->caption(); + + // Avoid adding empty groups. + KServiceGroup::Ptr subMenuRoot = KServiceGroup::group(g->relPath()); + if (subMenuRoot->childCount() == 0) + continue; + + // Ignore dotfiles. + if ((g->name().at(0) == '.')) + continue; + + TQString relPath = g->relPath(); + + // Do not display the "Settings" menu group in Programs Mode. + if( (m_runMode == ProgramsMode) && relPath.startsWith( "Settings" ) ) + { + kdDebug() << "SettingsProtocol: SKIPPING entry programs:/" << relPath << endl; + continue; + } + + switch( m_runMode ) + { + case( SettingsMode ): + relPath.remove(0, 9); // length("Settings/") ==9 + kdDebug() << "SettingsProtocol: adding entry settings:/" << relPath << endl; + createDirEntry(entry, groupCaption, "settings:/"+relPath, "inode/directory",g->icon()); + break; + case( ProgramsMode ): + kdDebug() << "SettingsProtocol: adding entry programs:/" << relPath << endl; + createDirEntry(entry, groupCaption, "programs:/"+relPath, "inode/directory",g->icon()); + break; + case( ApplicationsMode ): + kdDebug() << "SettingsProtocol: adding entry applications:/" << relPath << endl; + createDirEntry(entry, groupCaption, "applications:/"+relPath, "inode/directory",g->icon()); + break; + } + + } else { + KService::Ptr s(static_cast(e)); + kdDebug() << "SettingsProtocol: adding file entry " << url.url(1)+s->name() << endl; + createFileEntry(entry,s->name(),url.url(1)+s->desktopEntryName(), "application/x-desktop",s->icon(),locate("apps", s->desktopEntryPath())); + } + + listEntry(entry, false); + count++; + } + + totalSize(count); + listEntry(entry, true); + finished(); +} diff --git a/tdeioslave/smtp/CMakeLists.txt b/tdeioslave/smtp/CMakeLists.txt index 89ef650ca..d3d476ec4 100644 --- a/tdeioslave/smtp/CMakeLists.txt +++ b/tdeioslave/smtp/CMakeLists.txt @@ -35,7 +35,7 @@ tde_create_translated_desktop( set( target tdeio_smtp ) tde_add_kpart( ${target} AUTOMOC - SOURCES smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc + SOURCES smtp.cpp request.cpp response.cpp capabilities.cpp command.cpp transactionstate.cpp LINK tdeio-shared ${SASL_LIBRARIES} DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/smtp/Makefile.am b/tdeioslave/smtp/Makefile.am index 9859048b1..22cf859a1 100644 --- a/tdeioslave/smtp/Makefile.am +++ b/tdeioslave/smtp/Makefile.am @@ -3,7 +3,7 @@ INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes) kde_module_LTLIBRARIES = tdeio_smtp.la -tdeio_smtp_la_SOURCES = smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc +tdeio_smtp_la_SOURCES = smtp.cpp request.cpp response.cpp capabilities.cpp command.cpp transactionstate.cpp tdeio_smtp_la_LIBADD = $(LIB_TDEIO) $(SASL2_LIBS) tdeio_smtp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) @@ -16,22 +16,22 @@ TESTS = test_headergeneration test_responseparser test_commands check_PROGRAMS = $(TESTS) interactivesmtpserver -test_headergeneration_SOURCES = test_headergeneration.cc +test_headergeneration_SOURCES = test_headergeneration.cpp test_headergeneration_LDADD = $(LIB_TDECORE) test_headergeneration_LDFLAGS = $(all_libraries) -test_responseparser_SOURCES = test_responseparser.cc +test_responseparser_SOURCES = test_responseparser.cpp test_responseparser_LDADD = $(LIB_TDECORE) test_responseparser_LDFLAGS = $(all_libraries) -test_commands_SOURCES = test_commands.cc +test_commands_SOURCES = test_commands.cpp test_commands_LDADD = $(tdeio_smtp_la_LIBADD) test_commands_LDFLAGS = $(all_libraries) -interactivesmtpserver_SOURCES = interactivesmtpserver.cc +interactivesmtpserver_SOURCES = interactivesmtpserver.cpp interactivesmtpserver_LDADD = $(LIB_TQT) interactivesmtpserver_LDFLAGS = $(all_libraries) interactivesmtpserver_METASOURCES = AUTO messages: - $(XGETTEXT) *.cc -o $(podir)/tdeio_smtp.pot + $(XGETTEXT) *.cpp -o $(podir)/tdeio_smtp.pot diff --git a/tdeioslave/smtp/capabilities.cc b/tdeioslave/smtp/capabilities.cc deleted file mode 100644 index f56d7739b..000000000 --- a/tdeioslave/smtp/capabilities.cc +++ /dev/null @@ -1,143 +0,0 @@ -/* - capabilities.cc - - This file is part of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2003 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include "capabilities.h" - -#include "response.h" - -#include - -namespace KioSMTP { - - Capabilities Capabilities::fromResponse( const Response & ehlo ) { - Capabilities c; - - // first, check whether the response was valid and indicates success: - if ( !ehlo.isOk() - || ehlo.code() / 10 != 25 // ### restrict to 250 only? - || ehlo.lines().empty() ) - return c; - - QCStringList l = ehlo.lines(); - - for ( QCStringList::const_iterator it = ++l.begin() ; it != l.end() ; ++it ) - c.add( *it ); - - return c; - } - - void Capabilities::add( const TQString & cap, bool replace ) { - TQStringList tokens = TQStringList::split( ' ', cap.upper() ); - if ( tokens.empty() ) - return; - TQString name = tokens.front(); tokens.pop_front(); - add( name, tokens, replace ); - } - - void Capabilities::add( const TQString & name, const TQStringList & args, bool replace ) { - if ( replace ) - mCapabilities[name] = args; - else - mCapabilities[name] += args; - } - - TQString Capabilities::asMetaDataString() const { - TQString result; - for ( TQMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { - result += it.key(); - if ( !it.data().empty() ) - result += ' ' + it.data().join( " " ); - result += '\n'; - } - return result; - } - - TQString Capabilities::authMethodMetaData() const { - TQStringList sl = saslMethodsQSL(); - TQString result; - for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) - result += "SASL/" + *it + '\n'; - return result; - } - - TQStrIList Capabilities::saslMethods() const { - TQStrIList result( true ); // deep copies to be safe - TQStringList sl = saslMethodsQSL(); - for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) - result.append( (*it).latin1() ); - return result; - } - - TQString Capabilities::createSpecialResponse( bool tls ) const { - TQStringList result; - if ( tls ) - result.push_back( "STARTTLS" ); - result += saslMethodsQSL(); - if ( have( "PIPELINING" ) ) - result.push_back( "PIPELINING" ); - if ( have( "8BITMIME" ) ) - result.push_back( "8BITMIME" ); - if ( have( "SIZE" ) ) { - bool ok = false; - unsigned int size = mCapabilities["SIZE"].front().toUInt( &ok ); - if ( ok && !size ) - result.push_back( "SIZE=*" ); // any size - else if ( ok ) - result.push_back( "SIZE=" + TQString::number( size ) ); // fixed max - else - result.push_back( "SIZE" ); // indetermined - } - return result.join( " " ); - } - - TQStringList Capabilities::saslMethodsQSL() const { - TQStringList result; - for ( TQMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { - if ( it.key() == "AUTH" ) - result += it.data(); - else if ( it.key().startsWith( "AUTH=" ) ) { - result.push_back( it.key().mid( tqstrlen("AUTH=") ) ); - result += it.data(); - } - } - result.sort(); - TQStringList::iterator it = result.begin(); - for (TQStringList::iterator ot = it++; it != result.end(); ot = it++) - if (*ot == *it) result.remove(ot); - return result; - } - - - -} // namespace KioSMTP - diff --git a/tdeioslave/smtp/capabilities.cpp b/tdeioslave/smtp/capabilities.cpp new file mode 100644 index 000000000..f0815c73e --- /dev/null +++ b/tdeioslave/smtp/capabilities.cpp @@ -0,0 +1,143 @@ +/* + capabilities.cpp + + This file is part of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "capabilities.h" + +#include "response.h" + +#include + +namespace KioSMTP { + + Capabilities Capabilities::fromResponse( const Response & ehlo ) { + Capabilities c; + + // first, check whether the response was valid and indicates success: + if ( !ehlo.isOk() + || ehlo.code() / 10 != 25 // ### restrict to 250 only? + || ehlo.lines().empty() ) + return c; + + QCStringList l = ehlo.lines(); + + for ( QCStringList::const_iterator it = ++l.begin() ; it != l.end() ; ++it ) + c.add( *it ); + + return c; + } + + void Capabilities::add( const TQString & cap, bool replace ) { + TQStringList tokens = TQStringList::split( ' ', cap.upper() ); + if ( tokens.empty() ) + return; + TQString name = tokens.front(); tokens.pop_front(); + add( name, tokens, replace ); + } + + void Capabilities::add( const TQString & name, const TQStringList & args, bool replace ) { + if ( replace ) + mCapabilities[name] = args; + else + mCapabilities[name] += args; + } + + TQString Capabilities::asMetaDataString() const { + TQString result; + for ( TQMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { + result += it.key(); + if ( !it.data().empty() ) + result += ' ' + it.data().join( " " ); + result += '\n'; + } + return result; + } + + TQString Capabilities::authMethodMetaData() const { + TQStringList sl = saslMethodsQSL(); + TQString result; + for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result += "SASL/" + *it + '\n'; + return result; + } + + TQStrIList Capabilities::saslMethods() const { + TQStrIList result( true ); // deep copies to be safe + TQStringList sl = saslMethodsQSL(); + for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result.append( (*it).latin1() ); + return result; + } + + TQString Capabilities::createSpecialResponse( bool tls ) const { + TQStringList result; + if ( tls ) + result.push_back( "STARTTLS" ); + result += saslMethodsQSL(); + if ( have( "PIPELINING" ) ) + result.push_back( "PIPELINING" ); + if ( have( "8BITMIME" ) ) + result.push_back( "8BITMIME" ); + if ( have( "SIZE" ) ) { + bool ok = false; + unsigned int size = mCapabilities["SIZE"].front().toUInt( &ok ); + if ( ok && !size ) + result.push_back( "SIZE=*" ); // any size + else if ( ok ) + result.push_back( "SIZE=" + TQString::number( size ) ); // fixed max + else + result.push_back( "SIZE" ); // indetermined + } + return result.join( " " ); + } + + TQStringList Capabilities::saslMethodsQSL() const { + TQStringList result; + for ( TQMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { + if ( it.key() == "AUTH" ) + result += it.data(); + else if ( it.key().startsWith( "AUTH=" ) ) { + result.push_back( it.key().mid( tqstrlen("AUTH=") ) ); + result += it.data(); + } + } + result.sort(); + TQStringList::iterator it = result.begin(); + for (TQStringList::iterator ot = it++; it != result.end(); ot = it++) + if (*ot == *it) result.remove(ot); + return result; + } + + + +} // namespace KioSMTP + diff --git a/tdeioslave/smtp/command.cc b/tdeioslave/smtp/command.cc deleted file mode 100644 index 4d58a1522..000000000 --- a/tdeioslave/smtp/command.cc +++ /dev/null @@ -1,606 +0,0 @@ -/* - command.cc - - This file is part of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2003 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include "command.h" - -#include "smtp.h" -#include "response.h" -#include "transactionstate.h" - -#include -#include -#include -#include -#include // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase - -#include - -namespace KioSMTP { - -#ifdef HAVE_LIBSASL2 -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 } -}; -#endif - - // - // Command (base class) - // - - Command::Command( SMTPProtocol * smtp, int flags ) - : mSMTP( smtp ), - mComplete( false ), mNeedResponse( false ), mFlags( flags ) - { - assert( smtp ); - } - - Command::~Command() {} - - bool Command::processResponse( const Response & r, TransactionState * ) { - mComplete = true; - mNeedResponse = false; - return r.isOk(); - } - - void Command::ungetCommandLine( const TQCString &, TransactionState * ) { - mComplete = false; - } - - Command * Command::createSimpleCommand( int which, SMTPProtocol * smtp ) { - switch ( which ) { - case STARTTLS: return new StartTLSCommand( smtp ); - case DATA: return new DataCommand( smtp ); - case NOOP: return new NoopCommand( smtp ); - case RSET: return new RsetCommand( smtp ); - case QUIT: return new QuitCommand( smtp ); - default: return 0; - } - } - - // - // relay methods: - // - - void Command::parseFeatures( const Response & r ) { - mSMTP->parseFeatures( r ); - } - - int Command::startTLS() { - return mSMTP->startTLS(); - } - - bool Command::usingSSL() const { - return mSMTP->usingSSL(); - } - - bool Command::usingTLS() const { - return mSMTP->usingTLS(); - } - - bool Command::haveCapability( const char * cap ) const { - return mSMTP->haveCapability( cap ); - } - - // - // EHLO / HELO - // - - TQCString EHLOCommand::nextCommandLine( TransactionState * ) { - mNeedResponse = true; - mComplete = mEHLONotSupported; - const char * cmd = mEHLONotSupported ? "HELO " : "EHLO " ; - return cmd + KIDNA::toAsciiCString( mHostname ) + "\r\n"; - } - - bool EHLOCommand::processResponse( const Response & r, TransactionState * ) { - mNeedResponse = false; - // "command not {recognized,implemented}" response: - if ( r.code() == 500 || r.code() == 502 ) { - if ( mEHLONotSupported ) { // HELO failed... - mSMTP->error( TDEIO::ERR_INTERNAL_SERVER, - i18n("The server rejected both EHLO and HELO commands " - "as unknown or unimplemented.\n" - "Please contact the server's system administrator.") ); - return false; - } - mEHLONotSupported = true; // EHLO failed, but that's ok. - return true; - } - mComplete = true; - if ( r.code() / 10 == 25 ) { // 25x: success - parseFeatures( r ); - return true; - } - mSMTP->error( TDEIO::ERR_UNKNOWN, - i18n("Unexpected server response to %1 command.\n%2") - .arg( mEHLONotSupported ? "HELO" : "EHLO" ) - .arg( r.errorMessage() ) ); - return false; - } - - // - // STARTTLS - rfc 3207 - // - - TQCString StartTLSCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - return "STARTTLS\r\n"; - } - - bool StartTLSCommand::processResponse( const Response & r, TransactionState * ) { - mNeedResponse = false; - if ( r.code() != 220 ) { - mSMTP->error( r.errorCode(), - i18n("Your SMTP server does not support TLS. " - "Disable TLS, if you want to connect " - "without encryption.") ); - return false; - } - - int tlsrc = startTLS(); - - if ( tlsrc == 1 ) - return true; - - if ( tlsrc != -3 ) - //kdDebug(7112) << "TLS negotiation failed!" << endl; - mSMTP->messageBox(TDEIO::SlaveBase::Information, - i18n("Your SMTP server claims to " - "support TLS, but negotiation " - "was unsuccessful.\nYou can " - "disable TLS in TDE using the " - "crypto settings module."), - i18n("Connection Failed")); - return false; - } - - -#define SASLERROR mSMTP->error(TDEIO::ERR_COULD_NOT_AUTHENTICATE, \ - i18n("An error occured during authentication: %1").arg \ - ( TQString::fromUtf8( sasl_errdetail( conn ) ))); - - // - // AUTH - rfc 2554 - // - AuthCommand::AuthCommand( SMTPProtocol * smtp, - const char *mechanisms, - const TQString &aFQDN, - TDEIO::AuthInfo &ai ) - : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), - mAi( &ai ), - mFirstTime( true ) - { -#ifdef HAVE_LIBSASL2 - int result; - mMechusing = 0; - conn = 0; - client_interact = 0; - mOut = 0; mOutlen = 0; - mOneStep = false; - - result = sasl_client_new( "smtp", aFQDN.latin1(), - 0, 0, callbacks, 0, &conn ); - if ( result != SASL_OK ) { - SASLERROR - return; - } - do { - result = sasl_client_start(conn, mechanisms, - &client_interact, &mOut, &mOutlen, &mMechusing); - - if (result == SASL_INTERACT) - if ( !saslInteract( client_interact ) ) { - return; - }; - } while ( result == SASL_INTERACT ); - if ( result != SASL_CONTINUE && result != SASL_OK ) { - SASLERROR - return; - } - if ( result == SASL_OK ) mOneStep = true; - kdDebug(7112) << "Mechanism: " << mMechusing << " one step: " << mOneStep << endl; -#else - mSMTP->error(TDEIO::ERR_COULD_NOT_AUTHENTICATE, - i18n("Authentication support is not compiled into tdeio_smtp.")); -#endif - } - - AuthCommand::~AuthCommand() - { -#ifdef HAVE_LIBSASL2 - if ( conn ) { - kdDebug(7112) << "dispose sasl connection" << endl; - sasl_dispose( &conn ); - conn = 0; - } -#endif - } - - bool AuthCommand::saslInteract( void *in ) - { -#ifdef HAVE_LIBSASL2 - kdDebug(7112) << "saslInteract: " << endl; - sasl_interact_t *interact = ( sasl_interact_t * ) in; - - //some mechanisms do not require username && pass, so don'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 ( mAi->username.isEmpty() || mAi->password.isEmpty()) { - if (!mSMTP->openPassDlg(*mAi)) { - mSMTP->error(TDEIO::ERR_ABORTED, i18n("No authentication details supplied.")); - return false; - } - } - break; - } - } - - interact = ( sasl_interact_t * ) in; - while( interact->id != SASL_CB_LIST_END ) { - switch( interact->id ) { - case SASL_CB_USER: - case SASL_CB_AUTHNAME: - kdDebug(7112) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username << endl; - interact->result = strdup( mAi->username.utf8() ); - interact->len = strlen( (const char *) interact->result ); - break; - case SASL_CB_PASS: - kdDebug(7112) << "SASL_CB_PASS: [HIDDEN]" << endl; - interact->result = strdup( mAi->password.utf8() ); - interact->len = strlen( (const char *) interact->result ); - break; - default: - interact->result = NULL; interact->len = 0; - break; - } - interact++; - } - return true; -#else - return false; -#endif - } - - bool AuthCommand::doNotExecute( const TransactionState * ) const { - return !mMechusing; - } - - void AuthCommand::ungetCommandLine( const TQCString & s, TransactionState * ) { - mUngetSASLResponse = s; - mComplete = false; - } - - TQCString AuthCommand::nextCommandLine( TransactionState * ) { - mNeedResponse = true; - TQCString cmd; -#ifdef HAVE_LIBSASL2 - TQByteArray tmp, challenge; - if ( !mUngetSASLResponse.isNull() ) { - // implement un-ungetCommandLine - cmd = mUngetSASLResponse; - mUngetSASLResponse = 0; - } else if ( mFirstTime ) { - TQString firstCommand = "AUTH " + TQString::fromLatin1( mMechusing ); - - tmp.setRawData( mOut, mOutlen ); - KCodecs::base64Encode( tmp, challenge ); - tmp.resetRawData( mOut, mOutlen ); - if ( !challenge.isEmpty() ) { - firstCommand += " "; - firstCommand += TQString::fromLatin1( challenge.data(), challenge.size() ); - } - cmd = firstCommand.latin1(); - - if ( mOneStep ) mComplete = true; - } else { -// kdDebug(7112) << "SS: '" << mLastChallenge << "'" << endl; - tmp.setRawData( mLastChallenge.data(), mLastChallenge.length() ); - KCodecs::base64Decode( tmp, challenge ); - tmp.resetRawData( mLastChallenge.data(), mLastChallenge.length() ); - int result; - do { - result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), - challenge.size(), - &client_interact, - &mOut, &mOutlen); - if (result == SASL_INTERACT) - if ( !saslInteract( client_interact ) ) { - return ""; - }; - } while ( result == SASL_INTERACT ); - if ( result != SASL_CONTINUE && result != SASL_OK ) { - kdDebug(7112) << "sasl_client_step failed with: " << result << endl; - SASLERROR - return ""; - } - tmp.setRawData( mOut, mOutlen ); - cmd = KCodecs::base64Encode( tmp ); - tmp.resetRawData( mOut, mOutlen ); - -// kdDebug(7112) << "CC: '" << cmd << "'" << endl; - mComplete = ( result == SASL_OK ); - } -#endif //HAVE_LIBSASL2 - cmd += "\r\n"; - return cmd; - } - - bool AuthCommand::processResponse( const Response & r, TransactionState * ) { - if ( !r.isOk() ) { - if ( mFirstTime ) - if ( haveCapability( "AUTH" ) ) - mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, - i18n("Your SMTP server does not support %1.\nChoose a different authentication method.\n%2") - .arg( mMechusing ).arg( r.errorMessage() ) ); - else - mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, - i18n("Your SMTP server does not support authentication.\n" - " %2").arg( r.errorMessage() ) ); - else - mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, - i18n("Authentication failed.\n" - "Most likely the password is wrong.\n" - "%1").arg( r.errorMessage() ) ); - return false; - } - mFirstTime = false; - mLastChallenge = r.lines().front(); // ### better join all lines with \n? - mNeedResponse = false; - return true; - } - - // - // MAIL FROM: - // - - TQCString MailFromCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - TQCString cmdLine = "MAIL FROM:<" + mAddr + '>'; - if ( m8Bit && haveCapability("8BITMIME") ) - cmdLine += " BODY=8BITMIME"; - if ( mSize && haveCapability("SIZE") ) - cmdLine += " SIZE=" + TQCString().setNum( mSize ); - return cmdLine + "\r\n"; - } - - bool MailFromCommand::processResponse( const Response & r, TransactionState * ts ) { - assert( ts ); - mNeedResponse = false; - - if ( r.code() == 250 ) - return true; - - ts->setMailFromFailed( mAddr, r ); - return false; - } - - // - // RCPT TO: - // - - TQCString RcptToCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - return "RCPT TO:<" + mAddr + ">\r\n"; - } - - bool RcptToCommand::processResponse( const Response & r, TransactionState * ts ) { - assert( ts ); - mNeedResponse = false; - - if ( r.code() == 250 ) { - ts->setRecipientAccepted(); - return true; - } - - ts->addRejectedRecipient( mAddr, r.errorMessage() ); - return false; - } - - // - // DATA (only initial processing!) - // - - TQCString DataCommand::nextCommandLine( TransactionState * ts ) { - assert( ts ); - mComplete = true; - mNeedResponse = true; - ts->setDataCommandIssued( true ); - return "DATA\r\n"; - } - - void DataCommand::ungetCommandLine( const TQCString &, TransactionState * ts ) { - assert( ts ); - mComplete = false; - ts->setDataCommandIssued( false ); - } - - bool DataCommand::processResponse( const Response & r, TransactionState * ts ) { - assert( ts ); - mNeedResponse = false; - - if ( r.code() == 354 ) { - ts->setDataCommandSucceeded( true, r ); - return true; - } - - ts->setDataCommandSucceeded( false, r ); - return false; - } - - // - // DATA (data transfer) - // - void TransferCommand::ungetCommandLine( const TQCString & cmd, TransactionState * ) { - if ( cmd.isEmpty() ) - return; // don't change state when we can't detect the unget in - // the next nextCommandLine !! - mWasComplete = mComplete; - mComplete = false; - mNeedResponse = false; - mUngetBuffer = cmd; - } - - bool TransferCommand::doNotExecute( const TransactionState * ts ) const { - assert( ts ); - return ts->failed(); - } - - TQCString TransferCommand::nextCommandLine( TransactionState * ts ) { - assert( ts ); // let's rely on it ( at least for the moment ) - assert( !isComplete() ); - assert( !ts->failed() ); - - static const TQCString dotCRLF = ".\r\n"; - static const TQCString CRLFdotCRLF = "\r\n.\r\n"; - - if ( !mUngetBuffer.isEmpty() ) { - const TQCString ret = mUngetBuffer; - mUngetBuffer = 0; - if ( mWasComplete ) { - mComplete = true; - mNeedResponse = true; - } - return ret; // don't prepare(), it's slave-generated or already prepare()d - } - - // normal processing: - - kdDebug(7112) << "requesting data" << endl; - mSMTP->dataReq(); - TQByteArray ba; - int result = mSMTP->readData( ba ); - kdDebug(7112) << "got " << result << " bytes" << endl; - if ( result > 0 ) - return prepare( ba ); - else if ( result < 0 ) { - ts->setFailedFatally( TDEIO::ERR_INTERNAL, - i18n("Could not read data from application.") ); - mComplete = true; - mNeedResponse = true; - return 0; - } - mComplete = true; - mNeedResponse = true; - return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF ; - } - - bool TransferCommand::processResponse( const Response & r, TransactionState * ts ) { - mNeedResponse = false; - assert( ts ); - ts->setComplete(); - if ( !r.isOk() ) { - ts->setFailed(); - mSMTP->error( r.errorCode(), - i18n("The message content was not accepted.\n" - "%1").arg( r.errorMessage() ) ); - return false; - } - return true; - } - - static TQCString dotstuff_lf2crlf( const TQByteArray & ba, char & last ) { - TQCString result( ba.size() * 2 + 1 ); // worst case: repeated "[.]\n" - const char * s = ba.data(); - const char * const send = ba.data() + ba.size(); - char * d = result.data(); - - while ( s < send ) { - const char ch = *s++; - if ( ch == '\n' && last != '\r' ) - *d++ = '\r'; // lf2crlf - else if ( ch == '.' && last == '\n' ) - *d++ = '.'; // dotstuff - last = *d++ = ch; - } - - result.truncate( d - result.data() ); - return result; - } - - TQCString TransferCommand::prepare( const TQByteArray & ba ) { - if ( ba.isEmpty() ) - return 0; - if ( mSMTP->metaData("lf2crlf+dotstuff") == "slave" ) { - kdDebug(7112) << "performing dotstuffing and LF->CRLF transformation" << endl; - return dotstuff_lf2crlf( ba, mLastChar ); - } else { - mLastChar = ba[ ba.size() - 1 ]; - return TQCString( ba.data(), ba.size() + 1 ); - } - } - - // - // NOOP - // - - TQCString NoopCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - return "NOOP\r\n"; - } - - // - // RSET - // - - TQCString RsetCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - return "RSET\r\n"; - } - - // - // QUIT - // - - TQCString QuitCommand::nextCommandLine( TransactionState * ) { - mComplete = true; - mNeedResponse = true; - return "QUIT\r\n"; - } - -} // namespace KioSMTP - diff --git a/tdeioslave/smtp/command.cpp b/tdeioslave/smtp/command.cpp new file mode 100644 index 000000000..461cf520e --- /dev/null +++ b/tdeioslave/smtp/command.cpp @@ -0,0 +1,606 @@ +/* + command.cpp + + This file is part of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "command.h" + +#include "smtp.h" +#include "response.h" +#include "transactionstate.h" + +#include +#include +#include +#include +#include // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase + +#include + +namespace KioSMTP { + +#ifdef HAVE_LIBSASL2 +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 } +}; +#endif + + // + // Command (base class) + // + + Command::Command( SMTPProtocol * smtp, int flags ) + : mSMTP( smtp ), + mComplete( false ), mNeedResponse( false ), mFlags( flags ) + { + assert( smtp ); + } + + Command::~Command() {} + + bool Command::processResponse( const Response & r, TransactionState * ) { + mComplete = true; + mNeedResponse = false; + return r.isOk(); + } + + void Command::ungetCommandLine( const TQCString &, TransactionState * ) { + mComplete = false; + } + + Command * Command::createSimpleCommand( int which, SMTPProtocol * smtp ) { + switch ( which ) { + case STARTTLS: return new StartTLSCommand( smtp ); + case DATA: return new DataCommand( smtp ); + case NOOP: return new NoopCommand( smtp ); + case RSET: return new RsetCommand( smtp ); + case QUIT: return new QuitCommand( smtp ); + default: return 0; + } + } + + // + // relay methods: + // + + void Command::parseFeatures( const Response & r ) { + mSMTP->parseFeatures( r ); + } + + int Command::startTLS() { + return mSMTP->startTLS(); + } + + bool Command::usingSSL() const { + return mSMTP->usingSSL(); + } + + bool Command::usingTLS() const { + return mSMTP->usingTLS(); + } + + bool Command::haveCapability( const char * cap ) const { + return mSMTP->haveCapability( cap ); + } + + // + // EHLO / HELO + // + + TQCString EHLOCommand::nextCommandLine( TransactionState * ) { + mNeedResponse = true; + mComplete = mEHLONotSupported; + const char * cmd = mEHLONotSupported ? "HELO " : "EHLO " ; + return cmd + KIDNA::toAsciiCString( mHostname ) + "\r\n"; + } + + bool EHLOCommand::processResponse( const Response & r, TransactionState * ) { + mNeedResponse = false; + // "command not {recognized,implemented}" response: + if ( r.code() == 500 || r.code() == 502 ) { + if ( mEHLONotSupported ) { // HELO failed... + mSMTP->error( TDEIO::ERR_INTERNAL_SERVER, + i18n("The server rejected both EHLO and HELO commands " + "as unknown or unimplemented.\n" + "Please contact the server's system administrator.") ); + return false; + } + mEHLONotSupported = true; // EHLO failed, but that's ok. + return true; + } + mComplete = true; + if ( r.code() / 10 == 25 ) { // 25x: success + parseFeatures( r ); + return true; + } + mSMTP->error( TDEIO::ERR_UNKNOWN, + i18n("Unexpected server response to %1 command.\n%2") + .arg( mEHLONotSupported ? "HELO" : "EHLO" ) + .arg( r.errorMessage() ) ); + return false; + } + + // + // STARTTLS - rfc 3207 + // + + TQCString StartTLSCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "STARTTLS\r\n"; + } + + bool StartTLSCommand::processResponse( const Response & r, TransactionState * ) { + mNeedResponse = false; + if ( r.code() != 220 ) { + mSMTP->error( r.errorCode(), + i18n("Your SMTP server does not support TLS. " + "Disable TLS, if you want to connect " + "without encryption.") ); + return false; + } + + int tlsrc = startTLS(); + + if ( tlsrc == 1 ) + return true; + + if ( tlsrc != -3 ) + //kdDebug(7112) << "TLS negotiation failed!" << endl; + mSMTP->messageBox(TDEIO::SlaveBase::Information, + i18n("Your SMTP server claims to " + "support TLS, but negotiation " + "was unsuccessful.\nYou can " + "disable TLS in TDE using the " + "crypto settings module."), + i18n("Connection Failed")); + return false; + } + + +#define SASLERROR mSMTP->error(TDEIO::ERR_COULD_NOT_AUTHENTICATE, \ + i18n("An error occured during authentication: %1").arg \ + ( TQString::fromUtf8( sasl_errdetail( conn ) ))); + + // + // AUTH - rfc 2554 + // + AuthCommand::AuthCommand( SMTPProtocol * smtp, + const char *mechanisms, + const TQString &aFQDN, + TDEIO::AuthInfo &ai ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), + mAi( &ai ), + mFirstTime( true ) + { +#ifdef HAVE_LIBSASL2 + int result; + mMechusing = 0; + conn = 0; + client_interact = 0; + mOut = 0; mOutlen = 0; + mOneStep = false; + + result = sasl_client_new( "smtp", aFQDN.latin1(), + 0, 0, callbacks, 0, &conn ); + if ( result != SASL_OK ) { + SASLERROR + return; + } + do { + result = sasl_client_start(conn, mechanisms, + &client_interact, &mOut, &mOutlen, &mMechusing); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact ) ) { + return; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + SASLERROR + return; + } + if ( result == SASL_OK ) mOneStep = true; + kdDebug(7112) << "Mechanism: " << mMechusing << " one step: " << mOneStep << endl; +#else + mSMTP->error(TDEIO::ERR_COULD_NOT_AUTHENTICATE, + i18n("Authentication support is not compiled into tdeio_smtp.")); +#endif + } + + AuthCommand::~AuthCommand() + { +#ifdef HAVE_LIBSASL2 + if ( conn ) { + kdDebug(7112) << "dispose sasl connection" << endl; + sasl_dispose( &conn ); + conn = 0; + } +#endif + } + + bool AuthCommand::saslInteract( void *in ) + { +#ifdef HAVE_LIBSASL2 + kdDebug(7112) << "saslInteract: " << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so don'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 ( mAi->username.isEmpty() || mAi->password.isEmpty()) { + if (!mSMTP->openPassDlg(*mAi)) { + mSMTP->error(TDEIO::ERR_ABORTED, i18n("No authentication details supplied.")); + return false; + } + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + while( interact->id != SASL_CB_LIST_END ) { + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + kdDebug(7112) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username << endl; + interact->result = strdup( mAi->username.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + kdDebug(7112) << "SASL_CB_PASS: [HIDDEN]" << endl; + interact->result = strdup( mAi->password.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + default: + interact->result = NULL; interact->len = 0; + break; + } + interact++; + } + return true; +#else + return false; +#endif + } + + bool AuthCommand::doNotExecute( const TransactionState * ) const { + return !mMechusing; + } + + void AuthCommand::ungetCommandLine( const TQCString & s, TransactionState * ) { + mUngetSASLResponse = s; + mComplete = false; + } + + TQCString AuthCommand::nextCommandLine( TransactionState * ) { + mNeedResponse = true; + TQCString cmd; +#ifdef HAVE_LIBSASL2 + TQByteArray tmp, challenge; + if ( !mUngetSASLResponse.isNull() ) { + // implement un-ungetCommandLine + cmd = mUngetSASLResponse; + mUngetSASLResponse = 0; + } else if ( mFirstTime ) { + TQString firstCommand = "AUTH " + TQString::fromLatin1( mMechusing ); + + tmp.setRawData( mOut, mOutlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( mOut, mOutlen ); + if ( !challenge.isEmpty() ) { + firstCommand += " "; + firstCommand += TQString::fromLatin1( challenge.data(), challenge.size() ); + } + cmd = firstCommand.latin1(); + + if ( mOneStep ) mComplete = true; + } else { +// kdDebug(7112) << "SS: '" << mLastChallenge << "'" << endl; + tmp.setRawData( mLastChallenge.data(), mLastChallenge.length() ); + KCodecs::base64Decode( tmp, challenge ); + tmp.resetRawData( mLastChallenge.data(), mLastChallenge.length() ); + int result; + do { + result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), + challenge.size(), + &client_interact, + &mOut, &mOutlen); + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact ) ) { + return ""; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + kdDebug(7112) << "sasl_client_step failed with: " << result << endl; + SASLERROR + return ""; + } + tmp.setRawData( mOut, mOutlen ); + cmd = KCodecs::base64Encode( tmp ); + tmp.resetRawData( mOut, mOutlen ); + +// kdDebug(7112) << "CC: '" << cmd << "'" << endl; + mComplete = ( result == SASL_OK ); + } +#endif //HAVE_LIBSASL2 + cmd += "\r\n"; + return cmd; + } + + bool AuthCommand::processResponse( const Response & r, TransactionState * ) { + if ( !r.isOk() ) { + if ( mFirstTime ) + if ( haveCapability( "AUTH" ) ) + mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, + i18n("Your SMTP server does not support %1.\nChoose a different authentication method.\n%2") + .arg( mMechusing ).arg( r.errorMessage() ) ); + else + mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, + i18n("Your SMTP server does not support authentication.\n" + " %2").arg( r.errorMessage() ) ); + else + mSMTP->error( TDEIO::ERR_COULD_NOT_LOGIN, + i18n("Authentication failed.\n" + "Most likely the password is wrong.\n" + "%1").arg( r.errorMessage() ) ); + return false; + } + mFirstTime = false; + mLastChallenge = r.lines().front(); // ### better join all lines with \n? + mNeedResponse = false; + return true; + } + + // + // MAIL FROM: + // + + TQCString MailFromCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + TQCString cmdLine = "MAIL FROM:<" + mAddr + '>'; + if ( m8Bit && haveCapability("8BITMIME") ) + cmdLine += " BODY=8BITMIME"; + if ( mSize && haveCapability("SIZE") ) + cmdLine += " SIZE=" + TQCString().setNum( mSize ); + return cmdLine + "\r\n"; + } + + bool MailFromCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 250 ) + return true; + + ts->setMailFromFailed( mAddr, r ); + return false; + } + + // + // RCPT TO: + // + + TQCString RcptToCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "RCPT TO:<" + mAddr + ">\r\n"; + } + + bool RcptToCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 250 ) { + ts->setRecipientAccepted(); + return true; + } + + ts->addRejectedRecipient( mAddr, r.errorMessage() ); + return false; + } + + // + // DATA (only initial processing!) + // + + TQCString DataCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); + mComplete = true; + mNeedResponse = true; + ts->setDataCommandIssued( true ); + return "DATA\r\n"; + } + + void DataCommand::ungetCommandLine( const TQCString &, TransactionState * ts ) { + assert( ts ); + mComplete = false; + ts->setDataCommandIssued( false ); + } + + bool DataCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 354 ) { + ts->setDataCommandSucceeded( true, r ); + return true; + } + + ts->setDataCommandSucceeded( false, r ); + return false; + } + + // + // DATA (data transfer) + // + void TransferCommand::ungetCommandLine( const TQCString & cmd, TransactionState * ) { + if ( cmd.isEmpty() ) + return; // don't change state when we can't detect the unget in + // the next nextCommandLine !! + mWasComplete = mComplete; + mComplete = false; + mNeedResponse = false; + mUngetBuffer = cmd; + } + + bool TransferCommand::doNotExecute( const TransactionState * ts ) const { + assert( ts ); + return ts->failed(); + } + + TQCString TransferCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); // let's rely on it ( at least for the moment ) + assert( !isComplete() ); + assert( !ts->failed() ); + + static const TQCString dotCRLF = ".\r\n"; + static const TQCString CRLFdotCRLF = "\r\n.\r\n"; + + if ( !mUngetBuffer.isEmpty() ) { + const TQCString ret = mUngetBuffer; + mUngetBuffer = 0; + if ( mWasComplete ) { + mComplete = true; + mNeedResponse = true; + } + return ret; // don't prepare(), it's slave-generated or already prepare()d + } + + // normal processing: + + kdDebug(7112) << "requesting data" << endl; + mSMTP->dataReq(); + TQByteArray ba; + int result = mSMTP->readData( ba ); + kdDebug(7112) << "got " << result << " bytes" << endl; + if ( result > 0 ) + return prepare( ba ); + else if ( result < 0 ) { + ts->setFailedFatally( TDEIO::ERR_INTERNAL, + i18n("Could not read data from application.") ); + mComplete = true; + mNeedResponse = true; + return 0; + } + mComplete = true; + mNeedResponse = true; + return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF ; + } + + bool TransferCommand::processResponse( const Response & r, TransactionState * ts ) { + mNeedResponse = false; + assert( ts ); + ts->setComplete(); + if ( !r.isOk() ) { + ts->setFailed(); + mSMTP->error( r.errorCode(), + i18n("The message content was not accepted.\n" + "%1").arg( r.errorMessage() ) ); + return false; + } + return true; + } + + static TQCString dotstuff_lf2crlf( const TQByteArray & ba, char & last ) { + TQCString result( ba.size() * 2 + 1 ); // worst case: repeated "[.]\n" + const char * s = ba.data(); + const char * const send = ba.data() + ba.size(); + char * d = result.data(); + + while ( s < send ) { + const char ch = *s++; + if ( ch == '\n' && last != '\r' ) + *d++ = '\r'; // lf2crlf + else if ( ch == '.' && last == '\n' ) + *d++ = '.'; // dotstuff + last = *d++ = ch; + } + + result.truncate( d - result.data() ); + return result; + } + + TQCString TransferCommand::prepare( const TQByteArray & ba ) { + if ( ba.isEmpty() ) + return 0; + if ( mSMTP->metaData("lf2crlf+dotstuff") == "slave" ) { + kdDebug(7112) << "performing dotstuffing and LF->CRLF transformation" << endl; + return dotstuff_lf2crlf( ba, mLastChar ); + } else { + mLastChar = ba[ ba.size() - 1 ]; + return TQCString( ba.data(), ba.size() + 1 ); + } + } + + // + // NOOP + // + + TQCString NoopCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "NOOP\r\n"; + } + + // + // RSET + // + + TQCString RsetCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "RSET\r\n"; + } + + // + // QUIT + // + + TQCString QuitCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "QUIT\r\n"; + } + +} // namespace KioSMTP + diff --git a/tdeioslave/smtp/interactivesmtpserver.cc b/tdeioslave/smtp/interactivesmtpserver.cc deleted file mode 100644 index 337299d26..000000000 --- a/tdeioslave/smtp/interactivesmtpserver.cc +++ /dev/null @@ -1,127 +0,0 @@ -/* - interactivesmtpserver.cc - - Code based on the serverSocket example by Jesper Pedersen. - - This file is part of the testsuite of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2004 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "interactivesmtpserver.h" - -static const TQHostAddress localhost( 0x7f000001 ); // 127.0.0.1 - -InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() { - if ( mSocket ) { - mSocket->close(); - if ( mSocket->state() == TQSocket::Closing ) - connect( mSocket, TQT_SIGNAL(delayedCloseFinished()), - mSocket, TQT_SLOT(deleteLater()) ); - else - mSocket->deleteLater(); - mSocket = 0; - } -} - -void InteractiveSMTPServerWindow::slotSendResponse() -{ - const TQString line = mLineEdit->text(); - mLineEdit->clear(); - TQTextStream s( mSocket ); - s << line + "\r\n"; - slotDisplayServer( line ); -} - -InteractiveSMTPServer::InteractiveSMTPServer( TQObject* parent ) - : TQServerSocket( localhost, 2525, 1, parent ) -{ -} - -int main( int argc, char * argv[] ) { - TQApplication app( argc, argv ); - - InteractiveSMTPServer server; - - tqDebug( "Server should now listen on localhost:2525" ); - tqDebug( "Hit CTRL-C to quit." ); - return app.exec(); -}; - - -InteractiveSMTPServerWindow::InteractiveSMTPServerWindow( TQSocket * socket, TQWidget * parent, const char * name, WFlags f ) - : TQWidget( parent, name, f ), mSocket( socket ) -{ - TQPushButton * but; - assert( socket ); - - TQVBoxLayout * vlay = new TQVBoxLayout( this, 6 ); - - mTextEdit = new TQTextEdit( this ); - mTextEdit->setTextFormat( TQTextEdit::LogText ); - vlay->addWidget( mTextEdit, 1 ); - - TQHBoxLayout * hlay = new TQHBoxLayout( vlay ); - - mLineEdit = new TQLineEdit( this ); - but = new TQPushButton( "&Send", this ); - hlay->addWidget( new TQLabel( mLineEdit, "&Response:", this ) ); - hlay->addWidget( mLineEdit, 1 ); - hlay->addWidget( but ); - - connect( mLineEdit, TQT_SIGNAL(returnPressed()), TQT_SLOT(slotSendResponse()) ); - connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotSendResponse()) ); - - but = new TQPushButton( "&Close Connection", this ); - vlay->addWidget( but ); - - connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotConnectionClosed()) ); - - connect( socket, TQT_SIGNAL(connectionClosed()), TQT_SLOT(slotConnectionClosed()) ); - connect( socket, TQT_SIGNAL(error(int)), TQT_SLOT(slotError(int)) ); - connect( socket, TQT_SIGNAL(readyRead()), TQT_SLOT(slotReadyRead()) ); - - mLineEdit->setText( "220 hi there" ); - mLineEdit->setFocus(); -} - -#include "interactivesmtpserver.moc" diff --git a/tdeioslave/smtp/interactivesmtpserver.cpp b/tdeioslave/smtp/interactivesmtpserver.cpp new file mode 100644 index 000000000..4055a47a4 --- /dev/null +++ b/tdeioslave/smtp/interactivesmtpserver.cpp @@ -0,0 +1,127 @@ +/* + interactivesmtpserver.cpp + + Code based on the serverSocket example by Jesper Pedersen. + + This file is part of the testsuite of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2004 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "interactivesmtpserver.h" + +static const TQHostAddress localhost( 0x7f000001 ); // 127.0.0.1 + +InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() { + if ( mSocket ) { + mSocket->close(); + if ( mSocket->state() == TQSocket::Closing ) + connect( mSocket, TQT_SIGNAL(delayedCloseFinished()), + mSocket, TQT_SLOT(deleteLater()) ); + else + mSocket->deleteLater(); + mSocket = 0; + } +} + +void InteractiveSMTPServerWindow::slotSendResponse() +{ + const TQString line = mLineEdit->text(); + mLineEdit->clear(); + TQTextStream s( mSocket ); + s << line + "\r\n"; + slotDisplayServer( line ); +} + +InteractiveSMTPServer::InteractiveSMTPServer( TQObject* parent ) + : TQServerSocket( localhost, 2525, 1, parent ) +{ +} + +int main( int argc, char * argv[] ) { + TQApplication app( argc, argv ); + + InteractiveSMTPServer server; + + tqDebug( "Server should now listen on localhost:2525" ); + tqDebug( "Hit CTRL-C to quit." ); + return app.exec(); +}; + + +InteractiveSMTPServerWindow::InteractiveSMTPServerWindow( TQSocket * socket, TQWidget * parent, const char * name, WFlags f ) + : TQWidget( parent, name, f ), mSocket( socket ) +{ + TQPushButton * but; + assert( socket ); + + TQVBoxLayout * vlay = new TQVBoxLayout( this, 6 ); + + mTextEdit = new TQTextEdit( this ); + mTextEdit->setTextFormat( TQTextEdit::LogText ); + vlay->addWidget( mTextEdit, 1 ); + + TQHBoxLayout * hlay = new TQHBoxLayout( vlay ); + + mLineEdit = new TQLineEdit( this ); + but = new TQPushButton( "&Send", this ); + hlay->addWidget( new TQLabel( mLineEdit, "&Response:", this ) ); + hlay->addWidget( mLineEdit, 1 ); + hlay->addWidget( but ); + + connect( mLineEdit, TQT_SIGNAL(returnPressed()), TQT_SLOT(slotSendResponse()) ); + connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotSendResponse()) ); + + but = new TQPushButton( "&Close Connection", this ); + vlay->addWidget( but ); + + connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotConnectionClosed()) ); + + connect( socket, TQT_SIGNAL(connectionClosed()), TQT_SLOT(slotConnectionClosed()) ); + connect( socket, TQT_SIGNAL(error(int)), TQT_SLOT(slotError(int)) ); + connect( socket, TQT_SIGNAL(readyRead()), TQT_SLOT(slotReadyRead()) ); + + mLineEdit->setText( "220 hi there" ); + mLineEdit->setFocus(); +} + +#include "interactivesmtpserver.moc" diff --git a/tdeioslave/smtp/request.cc b/tdeioslave/smtp/request.cc deleted file mode 100644 index 4e4e2bf84..000000000 --- a/tdeioslave/smtp/request.cc +++ /dev/null @@ -1,189 +0,0 @@ -/* - request.cc - - This file is part of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2003 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include "request.h" - -#include -#include -#include -#include - -#include - -namespace KioSMTP { - - Request Request::fromURL( const KURL & url ) { - Request request; - - const TQStringList query = TQStringList::split( '&', url.query().mid(1) ); -#ifndef NDEBUG - kdDebug(7112) << "Parsing request from query:\n" + query.join("\n" ) << endl; -#endif - for ( TQStringList::const_iterator it = query.begin() ; it != query.end() ; ++it ) { - int equalsPos = (*it).find( '=' ); - if ( equalsPos <= 0 ) - continue; - - const TQString key = (*it).left( equalsPos ).lower(); - const TQString value = KURL::decode_string( (*it).mid( equalsPos + 1 ) ); - - if ( key == "to" ) - request.addTo( value ); - else if ( key == "cc" ) - request.addCc( value ); - else if ( key == "bcc" ) - request.addBcc( value ); - else if ( key == "headers" ) { - request.setEmitHeaders( value == "0" ); - request.setEmitHeaders( false ); // ### ??? - } - else if ( key == "subject" ) - request.setSubject( value ); - else if ( key == "from" ) - request.setFromAddress( value ); - else if ( key == "profile" ) - request.setProfileName( value ); - else if ( key == "hostname" ) - request.setHeloHostname( value ); - else if ( key == "body" ) - request.set8BitBody( value.upper() == "8BIT" ); - else if ( key == "size" ) - request.setSize( value.toUInt() ); - else - kdWarning(7112) << "while parsing query: unknown query item \"" - << key << "\" with value \"" << value << "\"" << endl; - } - - return request; - } - - TQCString Request::heloHostnameCString() const { - return KIDNA::toAsciiCString( heloHostname() ); - } - - static bool isUsAscii( const TQString & s ) { - for ( uint i = 0 ; i < s.length() ; ++i ) - if ( s[i].unicode() > 127 ) return false; - return true; - } - - - - static inline bool isSpecial( char ch ) { - static const TQCString specials = "()<>[]:;@\\,.\""; - return specials.find( ch ) >= 0; - } - - - - static inline bool needsQuoting( char ch ) { - return ch == '\\' || ch == '"' || ch == '\n' ; - } - - - - static inline TQCString rfc2047Encode( const TQString & s ) { - TQCString r = KCodecs::base64Encode( s.stripWhiteSpace().utf8(), false ); - return "=?utf-8?b?" + r + "?=" ; // use base64 since that always gives a valid encoded-word - } - - - - static TQCString quote( const TQString & s ) { - assert( isUsAscii( s ) ); - - TQCString r( s.length() * 2 ); - bool needsQuotes = false; - - unsigned int j = 0; - for ( unsigned int i = 0 ; i < s.length() ; ++i ) { - char ch = s[i].latin1(); - if ( isSpecial( ch ) ) { - if ( needsQuoting( ch ) ) - r[j++] = '\\'; - needsQuotes = true; - } - r[j++] = ch; - } - r.truncate( j ); - - if ( needsQuotes ) - return '"' + r + '"'; - else - return r; - } - - - - static TQCString formatFromAddress( const TQString & fromRealName, const TQString & fromAddress ) { - if ( fromRealName.isEmpty() ) - return fromAddress.latin1(); // no real name: return "joe@user.org" - - // return "Joe User ", "\"User, Joe\" " - // or "=?utf-8?q?Joe_User?= ", depending on real name's nature. - TQCString r = isUsAscii( fromRealName ) ? quote( fromRealName ) : rfc2047Encode( fromRealName ); - return r + " <" + fromAddress.latin1() + '>'; - } - - - - static TQCString formatSubject( TQString s ) { - if ( isUsAscii( s ) ) - return TQString(s.remove( '\n' )).latin1(); // don't break header folding, - // so remove any line break - // that happen to be around - else - return rfc2047Encode( s ); - } - - - - TQCString Request::headerFields( const TQString & fromRealName ) const { - if ( !emitHeaders() ) - return 0; - - assert( hasFromAddress() ); // should have been checked for by - // caller (MAIL FROM comes before DATA) - - TQCString result = "From: " + formatFromAddress( fromRealName, fromAddress() ) + "\r\n"; - - if ( !subject().isEmpty() ) - result += "Subject: " + formatSubject( subject() ) + "\r\n"; - if ( !to().empty() ) - result += TQCString( "To: " ) + to().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; - if ( !cc().empty() ) - result += TQCString( "Cc: " ) + cc().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; - return result; - } - -} // namespace KioSMTP diff --git a/tdeioslave/smtp/request.cpp b/tdeioslave/smtp/request.cpp new file mode 100644 index 000000000..6d38fdd6e --- /dev/null +++ b/tdeioslave/smtp/request.cpp @@ -0,0 +1,189 @@ +/* + request.cpp + + This file is part of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "request.h" + +#include +#include +#include +#include + +#include + +namespace KioSMTP { + + Request Request::fromURL( const KURL & url ) { + Request request; + + const TQStringList query = TQStringList::split( '&', url.query().mid(1) ); +#ifndef NDEBUG + kdDebug(7112) << "Parsing request from query:\n" + query.join("\n" ) << endl; +#endif + for ( TQStringList::const_iterator it = query.begin() ; it != query.end() ; ++it ) { + int equalsPos = (*it).find( '=' ); + if ( equalsPos <= 0 ) + continue; + + const TQString key = (*it).left( equalsPos ).lower(); + const TQString value = KURL::decode_string( (*it).mid( equalsPos + 1 ) ); + + if ( key == "to" ) + request.addTo( value ); + else if ( key == "cc" ) + request.addCc( value ); + else if ( key == "bcc" ) + request.addBcc( value ); + else if ( key == "headers" ) { + request.setEmitHeaders( value == "0" ); + request.setEmitHeaders( false ); // ### ??? + } + else if ( key == "subject" ) + request.setSubject( value ); + else if ( key == "from" ) + request.setFromAddress( value ); + else if ( key == "profile" ) + request.setProfileName( value ); + else if ( key == "hostname" ) + request.setHeloHostname( value ); + else if ( key == "body" ) + request.set8BitBody( value.upper() == "8BIT" ); + else if ( key == "size" ) + request.setSize( value.toUInt() ); + else + kdWarning(7112) << "while parsing query: unknown query item \"" + << key << "\" with value \"" << value << "\"" << endl; + } + + return request; + } + + TQCString Request::heloHostnameCString() const { + return KIDNA::toAsciiCString( heloHostname() ); + } + + static bool isUsAscii( const TQString & s ) { + for ( uint i = 0 ; i < s.length() ; ++i ) + if ( s[i].unicode() > 127 ) return false; + return true; + } + + + + static inline bool isSpecial( char ch ) { + static const TQCString specials = "()<>[]:;@\\,.\""; + return specials.find( ch ) >= 0; + } + + + + static inline bool needsQuoting( char ch ) { + return ch == '\\' || ch == '"' || ch == '\n' ; + } + + + + static inline TQCString rfc2047Encode( const TQString & s ) { + TQCString r = KCodecs::base64Encode( s.stripWhiteSpace().utf8(), false ); + return "=?utf-8?b?" + r + "?=" ; // use base64 since that always gives a valid encoded-word + } + + + + static TQCString quote( const TQString & s ) { + assert( isUsAscii( s ) ); + + TQCString r( s.length() * 2 ); + bool needsQuotes = false; + + unsigned int j = 0; + for ( unsigned int i = 0 ; i < s.length() ; ++i ) { + char ch = s[i].latin1(); + if ( isSpecial( ch ) ) { + if ( needsQuoting( ch ) ) + r[j++] = '\\'; + needsQuotes = true; + } + r[j++] = ch; + } + r.truncate( j ); + + if ( needsQuotes ) + return '"' + r + '"'; + else + return r; + } + + + + static TQCString formatFromAddress( const TQString & fromRealName, const TQString & fromAddress ) { + if ( fromRealName.isEmpty() ) + return fromAddress.latin1(); // no real name: return "joe@user.org" + + // return "Joe User ", "\"User, Joe\" " + // or "=?utf-8?q?Joe_User?= ", depending on real name's nature. + TQCString r = isUsAscii( fromRealName ) ? quote( fromRealName ) : rfc2047Encode( fromRealName ); + return r + " <" + fromAddress.latin1() + '>'; + } + + + + static TQCString formatSubject( TQString s ) { + if ( isUsAscii( s ) ) + return TQString(s.remove( '\n' )).latin1(); // don't break header folding, + // so remove any line break + // that happen to be around + else + return rfc2047Encode( s ); + } + + + + TQCString Request::headerFields( const TQString & fromRealName ) const { + if ( !emitHeaders() ) + return 0; + + assert( hasFromAddress() ); // should have been checked for by + // caller (MAIL FROM comes before DATA) + + TQCString result = "From: " + formatFromAddress( fromRealName, fromAddress() ) + "\r\n"; + + if ( !subject().isEmpty() ) + result += "Subject: " + formatSubject( subject() ) + "\r\n"; + if ( !to().empty() ) + result += TQCString( "To: " ) + to().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + if ( !cc().empty() ) + result += TQCString( "Cc: " ) + cc().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + return result; + } + +} // namespace KioSMTP diff --git a/tdeioslave/smtp/response.cc b/tdeioslave/smtp/response.cc deleted file mode 100644 index 3ae5d8aa0..000000000 --- a/tdeioslave/smtp/response.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* - response.cc - - This file is part of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2003 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include "response.h" - -#include -#include - -#include - -namespace KioSMTP { - - void Response::parseLine( const char * line, int len ) { - - if ( !isWellFormed() ) return; // don't bother - - if ( isComplete() ) - // if the response is already complete, there can't be another line - mValid = false; - - if ( len > 1 && line[len-1] == '\n' && line[len-2] == '\r' ) - len -= 2; - - if ( len < 3 ) { - // can't be valid - too short - mValid = false; - mWellFormed = false; - return; - } - - bool ok = false; - unsigned int code = TQCString( line, 3+1 ).toUInt( &ok ); - if ( !ok || code < 100 || code > 559 ) { - // not a number or number out of range - mValid = false; - if ( !ok || code < 100 ) - mWellFormed = false; - return; - } - if ( mCode && code != mCode ) { - // different codes in one response are not allowed. - mValid = false; - return; - } - mCode = code; - - if ( len == 3 || line[3] == ' ' ) - mSawLastLine = true; - else if ( line[3] != '-' ) { - // code must be followed by either SP or hyphen (len == 3 is - // also accepted since broken servers exist); all else is - // invalid - mValid = false; - mWellFormed = false; - return; - } - - mLines.push_back( len > 4 ? TQCString( line+4, len-4+1 ).stripWhiteSpace() : TQCString() ); - } - - - // hackishly fixing QCStringList flaws... - static TQCString join( char sep, const QCStringList & list ) { - if ( list.empty() ) - return TQCString(); - TQCString result = list.front(); - for ( QCStringList::const_iterator it = ++list.begin() ; it != list.end() ; ++it ) - result += sep + *it; - return result; - } - - TQString Response::errorMessage() const { - TQString msg; - if ( lines().count() > 1 ) - msg = i18n("The server responded:\n%1") - .arg( static_cast(join( '\n', lines()) ) ); - else - msg = i18n("The server responded: \"%1\"") - .arg( static_cast(lines().front()) ); - if ( first() == 4 ) - msg += '\n' + i18n("This is a temporary failure. " - "You may try again later."); - return msg; - } - - int Response::errorCode() const { - switch ( code() ) { - case 421: // Service not available, closing transmission channel - case 454: // TLS not available due to temporary reason - // Temporary authentication failure - case 554: // Transaction failed / No SMTP service here / No valid recipients - return TDEIO::ERR_SERVICE_NOT_AVAILABLE; - - case 451: // Requested action aborted: local error in processing - return TDEIO::ERR_INTERNAL_SERVER; - - case 452: // Requested action not taken: insufficient system storage - case 552: // Requested mail action aborted: exceeded storage allocation - return TDEIO::ERR_DISK_FULL; - - case 500: // Syntax error, command unrecognized - case 501: // Syntax error in parameters or arguments - case 502: // Command not implemented - case 503: // Bad sequence of commands - case 504: // Command parameter not implemented - return TDEIO::ERR_INTERNAL; - - case 450: // Requested mail action not taken: mailbox unavailable - case 550: // Requested action not taken: mailbox unavailable - case 551: // User not local; please try - case 553: // Requested action not taken: mailbox name not allowed - return TDEIO::ERR_DOES_NOT_EXIST; - - case 530: // {STARTTLS,Authentication} required - case 538: // Encryption required for requested authentication mechanism - case 534: // Authentication mechanism is too weak - return TDEIO::ERR_UPGRADE_REQUIRED; - - case 432: // A password transition is needed - return TDEIO::ERR_COULD_NOT_AUTHENTICATE; - - default: - if ( isPositive() ) - return 0; - else - return TDEIO::ERR_UNKNOWN; - } - } - -} // namespace KioSMTP diff --git a/tdeioslave/smtp/response.cpp b/tdeioslave/smtp/response.cpp new file mode 100644 index 000000000..de6b98868 --- /dev/null +++ b/tdeioslave/smtp/response.cpp @@ -0,0 +1,160 @@ +/* + response.cpp + + This file is part of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "response.h" + +#include +#include + +#include + +namespace KioSMTP { + + void Response::parseLine( const char * line, int len ) { + + if ( !isWellFormed() ) return; // don't bother + + if ( isComplete() ) + // if the response is already complete, there can't be another line + mValid = false; + + if ( len > 1 && line[len-1] == '\n' && line[len-2] == '\r' ) + len -= 2; + + if ( len < 3 ) { + // can't be valid - too short + mValid = false; + mWellFormed = false; + return; + } + + bool ok = false; + unsigned int code = TQCString( line, 3+1 ).toUInt( &ok ); + if ( !ok || code < 100 || code > 559 ) { + // not a number or number out of range + mValid = false; + if ( !ok || code < 100 ) + mWellFormed = false; + return; + } + if ( mCode && code != mCode ) { + // different codes in one response are not allowed. + mValid = false; + return; + } + mCode = code; + + if ( len == 3 || line[3] == ' ' ) + mSawLastLine = true; + else if ( line[3] != '-' ) { + // code must be followed by either SP or hyphen (len == 3 is + // also accepted since broken servers exist); all else is + // invalid + mValid = false; + mWellFormed = false; + return; + } + + mLines.push_back( len > 4 ? TQCString( line+4, len-4+1 ).stripWhiteSpace() : TQCString() ); + } + + + // hackishly fixing QCStringList flaws... + static TQCString join( char sep, const QCStringList & list ) { + if ( list.empty() ) + return TQCString(); + TQCString result = list.front(); + for ( QCStringList::const_iterator it = ++list.begin() ; it != list.end() ; ++it ) + result += sep + *it; + return result; + } + + TQString Response::errorMessage() const { + TQString msg; + if ( lines().count() > 1 ) + msg = i18n("The server responded:\n%1") + .arg( static_cast(join( '\n', lines()) ) ); + else + msg = i18n("The server responded: \"%1\"") + .arg( static_cast(lines().front()) ); + if ( first() == 4 ) + msg += '\n' + i18n("This is a temporary failure. " + "You may try again later."); + return msg; + } + + int Response::errorCode() const { + switch ( code() ) { + case 421: // Service not available, closing transmission channel + case 454: // TLS not available due to temporary reason + // Temporary authentication failure + case 554: // Transaction failed / No SMTP service here / No valid recipients + return TDEIO::ERR_SERVICE_NOT_AVAILABLE; + + case 451: // Requested action aborted: local error in processing + return TDEIO::ERR_INTERNAL_SERVER; + + case 452: // Requested action not taken: insufficient system storage + case 552: // Requested mail action aborted: exceeded storage allocation + return TDEIO::ERR_DISK_FULL; + + case 500: // Syntax error, command unrecognized + case 501: // Syntax error in parameters or arguments + case 502: // Command not implemented + case 503: // Bad sequence of commands + case 504: // Command parameter not implemented + return TDEIO::ERR_INTERNAL; + + case 450: // Requested mail action not taken: mailbox unavailable + case 550: // Requested action not taken: mailbox unavailable + case 551: // User not local; please try + case 553: // Requested action not taken: mailbox name not allowed + return TDEIO::ERR_DOES_NOT_EXIST; + + case 530: // {STARTTLS,Authentication} required + case 538: // Encryption required for requested authentication mechanism + case 534: // Authentication mechanism is too weak + return TDEIO::ERR_UPGRADE_REQUIRED; + + case 432: // A password transition is needed + return TDEIO::ERR_COULD_NOT_AUTHENTICATE; + + default: + if ( isPositive() ) + return 0; + else + return TDEIO::ERR_UNKNOWN; + } + } + +} // namespace KioSMTP diff --git a/tdeioslave/smtp/smtp.cc b/tdeioslave/smtp/smtp.cc deleted file mode 100644 index 00dff3684..000000000 --- a/tdeioslave/smtp/smtp.cc +++ /dev/null @@ -1,648 +0,0 @@ -/* - * Copyright (c) 2000, 2001 Alex Zepeda - * Copyright (c) 2001 Michael Häckel - * Copyright (c) 2002 Aaron J. Seigo - * Copyright (c) 2003 Marc Mutz - * All rights reserved. - * - * 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 above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. - * - */ - -#include - -#ifdef HAVE_SYS_TYPES_H -# include -#endif -#include - -#ifdef HAVE_LIBSASL2 -extern "C" { -#include -} -#endif - -#include "smtp.h" -#include "request.h" -#include "response.h" -#include "transactionstate.h" -#include "command.h" -using KioSMTP::Capabilities; -using KioSMTP::Command; -using KioSMTP::EHLOCommand; -using KioSMTP::AuthCommand; -using KioSMTP::MailFromCommand; -using KioSMTP::RcptToCommand; -using KioSMTP::DataCommand; -using KioSMTP::TransferCommand; -using KioSMTP::Request; -using KioSMTP::Response; -using KioSMTP::TransactionState; - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -using std::auto_ptr; - -#include -#include -#include -#include - -#ifdef HAVE_SYS_SOCKET_H -# include -#endif -#include - -#ifndef NI_NAMEREQD -// FIXME for KDE 3.3: fake defintion -// API design flaw in KExtendedSocket::resolve -# define NI_NAMEREQD 0 -#endif - - -extern "C" { - KDE_EXPORT int kdemain(int argc, char **argv); -} - -int kdemain(int argc, char **argv) -{ - TDEInstance instance("tdeio_smtp"); - - if (argc != 4) { - fprintf(stderr, - "Usage: tdeio_smtp protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - -#ifdef HAVE_LIBSASL2 - if ( sasl_client_init( NULL ) != SASL_OK ) { - fprintf(stderr, "SASL library initialization failed!\n"); - exit(-1); - } -#endif - SMTPProtocol slave( argv[2], argv[3], tqstricmp( argv[1], "smtps" ) == 0 ); - slave.dispatchLoop(); -#ifdef HAVE_LIBSASL2 - sasl_done(); -#endif - return 0; -} - -SMTPProtocol::SMTPProtocol(const TQCString & pool, const TQCString & app, - bool useSSL) -: TCPSlaveBase(useSSL ? 465 : 25, - useSSL ? "smtps" : "smtp", - pool, app, useSSL), - m_iOldPort(0), - m_opened(false) -{ - //kdDebug(7112) << "SMTPProtocol::SMTPProtocol" << endl; - mPendingCommandQueue.setAutoDelete( true ); - mSentCommandQueue.setAutoDelete( true ); -} - -unsigned int SMTPProtocol::sendBufferSize() const { - // ### how much is eaten by SSL/TLS overhead? - const int fd = fileno( fp ); - int value = -1; - kde_socklen_t len = sizeof(value); - if ( fd < 0 || ::getsockopt( fd, SOL_SOCKET, SO_SNDBUF, (char*)&value, &len ) ) - value = 1024; // let's be conservative - kdDebug(7112) << "send buffer size seems to be " << value << " octets." << endl; - return value > 0 ? value : 1024 ; -} - -SMTPProtocol::~SMTPProtocol() { - //kdDebug(7112) << "SMTPProtocol::~SMTPProtocol" << endl; - smtp_close(); -} - -void SMTPProtocol::openConnection() { - if ( smtp_open() ) - connected(); - else - closeConnection(); -} - -void SMTPProtocol::closeConnection() { - smtp_close(); -} - -void SMTPProtocol::special( const TQByteArray & aData ) { - TQDataStream s( aData, IO_ReadOnly ); - int what; - s >> what; - if ( what == 'c' ) { - infoMessage( createSpecialResponse() ); -#ifndef NDEBUG - kdDebug(7112) << "special('c') returns \"" << createSpecialResponse() << "\"" << endl; -#endif - } else if ( what == 'N' ) { - if ( !execute( Command::NOOP ) ) - return; - } else { - error( TDEIO::ERR_INTERNAL, - i18n("The application sent an invalid request.") ); - return; - } - finished(); -} - - -// Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah -// If smtphost is the name of a profile, it'll use the information -// provided by that profile. If it's not a profile name, it'll use it as -// nature intended. -// One can also specify in the query: -// headers=0 (turns off header generation) -// to=emailaddress -// cc=emailaddress -// bcc=emailaddress -// subject=text -// profile=text (this will override the "host" setting) -// hostname=text (used in the HELO) -// body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension) -void SMTPProtocol::put(const KURL & url, int /*permissions */ , - bool /*overwrite */ , bool /*resume */ ) -{ - Request request = Request::fromURL( url ); // parse settings from URL's query - - KEMailSettings mset; - KURL open_url = url; - if ( !request.hasProfile() ) { - //kdDebug(7112) << "tdeio_smtp: Profile is null" << endl; - bool hasProfile = mset.profiles().contains( open_url.host() ); - if ( hasProfile ) { - mset.setProfile(open_url.host()); - open_url.setHost(mset.getSetting(KEMailSettings::OutServer)); - m_sUser = mset.getSetting(KEMailSettings::OutServerLogin); - m_sPass = mset.getSetting(KEMailSettings::OutServerPass); - - if (m_sUser.isEmpty()) - m_sUser = TQString::null; - if (m_sPass.isEmpty()) - m_sPass = TQString::null; - open_url.setUser(m_sUser); - open_url.setPass(m_sPass); - m_sServer = open_url.host(); - m_iPort = open_url.port(); - } - else { - mset.setProfile(mset.defaultProfileName()); - } - } - else { - mset.setProfile( request.profileName() ); - } - - // Check KEMailSettings to see if we've specified an E-Mail address - // if that worked, check to see if we've specified a real name - // and then format accordingly (either: emailaddress@host.com or - // Real Name ) - if ( !request.hasFromAddress() ) { - const TQString from = mset.getSetting( KEMailSettings::EmailAddress ); - if ( !from.isNull() ) - request.setFromAddress( from ); - else if ( request.emitHeaders() ) { - error(TDEIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); - return; - } - } - - if ( !smtp_open( request.heloHostname() ) ) - { - error(TDEIO::ERR_SERVICE_NOT_AVAILABLE, - i18n("SMTPProtocol::smtp_open failed (%1)") // ### better error message? - .arg(open_url.path())); - return; - } - - if ( request.is8BitBody() - && !haveCapability("8BITMIME") && metaData("8bitmime") != "on" ) { - error( TDEIO::ERR_SERVICE_NOT_AVAILABLE, - i18n("Your server does not support sending of 8-bit messages.\n" - "Please use base64 or quoted-printable encoding.") ); - return; - } - - queueCommand( new MailFromCommand( this, request.fromAddress().latin1(), - request.is8BitBody(), request.size() ) ); - - // Loop through our To and CC recipients, and send the proper - // SMTP commands, for the benefit of the server. - TQStringList recipients = request.recipients(); - for ( TQStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it ) - queueCommand( new RcptToCommand( this, (*it).latin1() ) ); - - queueCommand( Command::DATA ); - queueCommand( new TransferCommand( this, request.headerFields( mset.getSetting( KEMailSettings::RealName ) ) ) ); - - TransactionState ts; - if ( !executeQueuedCommands( &ts ) ) { - if ( ts.errorCode() ) - error( ts.errorCode(), ts.errorMessage() ); - } else - finished(); -} - - -void SMTPProtocol::setHost(const TQString & host, int port, - const TQString & user, const TQString & pass) -{ - m_sServer = host; - m_iPort = port; - m_sUser = user; - m_sPass = pass; -} - -bool SMTPProtocol::sendCommandLine( const TQCString & cmdline ) { - //kdDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); - //kdDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>" << endl; - kdDebug( 7112) << "C: <" << cmdline.length() << " bytes>" << endl; - ssize_t cmdline_len = cmdline.length(); - if ( write( cmdline.data(), cmdline_len ) != cmdline_len ) { - error( TDEIO::ERR_COULD_NOT_WRITE, m_sServer ); - return false; - } - return true; -} - -Response SMTPProtocol::getResponse( bool * ok ) { - - if ( ok ) - *ok = false; - - Response response; - char buf[2048]; - - int recv_len = 0; - do { - // wait for data... - if ( !waitForResponse( 600 ) ) { - error( TDEIO::ERR_SERVER_TIMEOUT, m_sServer ); - return response; - } - - // ...read data... - recv_len = readLine( buf, sizeof(buf) - 1 ); - if ( recv_len < 1 && !isConnectionValid() ) { - error( TDEIO::ERR_CONNECTION_BROKEN, m_sServer ); - return response; - } - - kdDebug(7112) << "S: " << TQCString( buf, recv_len + 1 ).data(); - // ...and parse lines... - response.parseLine( buf, recv_len ); - - // ...until the response is complete or the parser is so confused - // that it doesn't think a RSET would help anymore: - } while ( !response.isComplete() && response.isWellFormed() ); - - if ( !response.isValid() ) { - error( TDEIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.").arg(response.code()) ); - return response; - } - - if ( ok ) - *ok = true; - - return response; -} - -bool SMTPProtocol::executeQueuedCommands( TransactionState * ts ) { - assert( ts ); - - kdDebug( canPipelineCommands(), 7112 ) << "using pipelining" << endl; - - while( !mPendingCommandQueue.isEmpty() ) { - TQCString cmdline = collectPipelineCommands( ts ); - if ( ts->failedFatally() ) { - smtp_close( false ); // _hard_ shutdown - return false; - } - if ( ts->failed() ) - break; - if ( cmdline.isEmpty() ) - continue; - if ( !sendCommandLine( cmdline ) || - !batchProcessResponses( ts ) || - ts->failedFatally() ) { - smtp_close( false ); // _hard_ shutdown - return false; - } - } - - if ( ts->failed() ) { - if ( !execute( Command::RSET ) ) - smtp_close( false ); - return false; - } - return true; -} - -TQCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { - assert( ts ); - - TQCString cmdLine; - unsigned int cmdLine_len = 0; - - while ( mPendingCommandQueue.head() ) { - - Command * cmd = mPendingCommandQueue.head(); - - if ( cmd->doNotExecute( ts ) ) { - delete mPendingCommandQueue.dequeue(); - if ( cmdLine_len ) - break; - else - continue; - } - - if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) - break; - - if ( cmdLine_len && !canPipelineCommands() ) - break; - - while ( !cmd->isComplete() && !cmd->needsResponse() ) { - const TQCString currentCmdLine = cmd->nextCommandLine( ts ); - if ( ts->failedFatally() ) - return cmdLine; - const unsigned int currentCmdLine_len = currentCmdLine.length(); - - if ( cmdLine_len && cmdLine_len + currentCmdLine_len > sendBufferSize() ) { - // must all fit into the send buffer, else connection deadlocks, - // but we need to have at least _one_ command to send - cmd->ungetCommandLine( currentCmdLine, ts ); - return cmdLine; - } - cmdLine_len += currentCmdLine_len; - cmdLine += currentCmdLine; - } - - mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() ); - - if ( cmd->mustBeLastInPipeline() ) - break; - } - - return cmdLine; -} - -bool SMTPProtocol::batchProcessResponses( TransactionState * ts ) { - assert( ts ); - - while ( !mSentCommandQueue.isEmpty() ) { - - Command * cmd = mSentCommandQueue.head(); - assert( cmd->isComplete() ); - - bool ok = false; - Response r = getResponse( &ok ); - if ( !ok ) - return false; - cmd->processResponse( r, ts ); - if ( ts->failedFatally() ) - return false; - - mSentCommandQueue.remove(); - } - - return true; -} - -void SMTPProtocol::queueCommand( int type ) { - queueCommand( Command::createSimpleCommand( type, this ) ); -} - -bool SMTPProtocol::execute( int type, TransactionState * ts ) { - auto_ptr cmd( Command::createSimpleCommand( type, this ) ); - kdFatal( !cmd.get(), 7112 ) << "Command::createSimpleCommand( " << type << " ) returned null!" << endl; - return execute( cmd.get(), ts ); -} - -// ### fold into pipelining engine? How? (execute() is often called -// ### when command queues are _not_ empty!) -bool SMTPProtocol::execute( Command * cmd, TransactionState * ts ) -{ - kdFatal( !cmd, 7112 ) << "SMTPProtocol::execute() called with no command to run!" << endl; - - if (!cmd) - return false; - - if ( cmd->doNotExecute( ts ) ) - return true; - - do { - while ( !cmd->isComplete() && !cmd->needsResponse() ) { - const TQCString cmdLine = cmd->nextCommandLine( ts ); - if ( ts && ts->failedFatally() ) { - smtp_close( false ); - return false; - } - if ( cmdLine.isEmpty() ) - continue; - if ( !sendCommandLine( cmdLine ) ) { - smtp_close( false ); - return false; - } - } - - bool ok = false; - Response r = getResponse( &ok ); - if ( !ok ) { - smtp_close( false ); - return false; - } - if ( !cmd->processResponse( r, ts ) ) { - if ( ts && ts->failedFatally() || - cmd->closeConnectionOnError() || - !execute( Command::RSET ) ) - smtp_close( false ); - return false; - } - } while ( !cmd->isComplete() ); - - return true; -} - -bool SMTPProtocol::smtp_open(const TQString& fakeHostname) -{ - if (m_opened && - m_iOldPort == port(m_iPort) && - m_sOldServer == m_sServer && - m_sOldUser == m_sUser && - (fakeHostname.isNull() || m_hostname == fakeHostname)) - return true; - - smtp_close(); - if (!connectToHost(m_sServer, m_iPort)) - return false; // connectToHost has already send an error message. - m_opened = true; - - bool ok = false; - Response greeting = getResponse( &ok ); - if ( !ok || !greeting.isOk() ) - { - if ( ok ) - error( TDEIO::ERR_COULD_NOT_LOGIN, - i18n("The server did not accept the connection.\n" - "%1").arg( greeting.errorMessage() ) ); - smtp_close(); - return false; - } - - if (!fakeHostname.isNull()) - { - m_hostname = fakeHostname; - } - else - { - TQString tmpPort; - TDESocketAddress* addr = KExtendedSocket::localAddress(m_iSock); - // perform name lookup. NI_NAMEREQD means: don't return a numeric - // value (we need to know when we get have the IP address, so we - // can enclose it in sqaure brackets (domain-literal). Failure to - // do so is normally harmless with IPv4, but fails for IPv6: - if (KExtendedSocket::resolve(addr, m_hostname, tmpPort, NI_NAMEREQD) != 0) - // FQDN resolution failed - // use the IP address as domain-literal - m_hostname = '[' + addr->nodeName() + ']'; - delete addr; - - if(m_hostname.isEmpty()) - { - m_hostname = "localhost.invalid"; - } - } - - EHLOCommand ehloCmdPreTLS( this, m_hostname ); - if ( !execute( &ehloCmdPreTLS ) ) { - smtp_close(); - return false; - } - - if ( ( haveCapability("STARTTLS") && canUseTLS() && metaData("tls") != "off" ) - || metaData("tls") == "on" ) { - // For now we're gonna force it on. - - if ( execute( Command::STARTTLS ) ) { - - // re-issue EHLO to refresh the capability list (could be have - // been faked before TLS was enabled): - EHLOCommand ehloCmdPostTLS( this, m_hostname ); - if ( !execute( &ehloCmdPostTLS ) ) { - smtp_close(); - return false; - } - } - } - // Now we try and login - if (!authenticate()) { - smtp_close(); - return false; - } - - m_iOldPort = m_iPort; - m_sOldServer = m_sServer; - m_sOldUser = m_sUser; - m_sOldPass = m_sPass; - - return true; -} - -bool SMTPProtocol::authenticate() -{ - // return with success if the server doesn't support SMTP-AUTH or an user - // name is not specified and metadata doesn't tell us to force it. - if ( (m_sUser.isEmpty() || !haveCapability( "AUTH" )) && - metaData( "sasl" ).isEmpty() ) return true; - - TDEIO::AuthInfo authInfo; - authInfo.username = m_sUser; - authInfo.password = m_sPass; - authInfo.prompt = i18n("Username and password for your SMTP account:"); - - TQStringList strList; - - if (!metaData("sasl").isEmpty()) - strList.append(metaData("sasl").latin1()); - else - strList = mCapabilities.saslMethodsQSL(); - - AuthCommand authCmd( this, strList.join(" ").latin1(), m_sServer, authInfo ); - bool ret = execute( &authCmd ); - m_sUser = authInfo.username; - m_sPass = authInfo.password; - return ret; -} - -void SMTPProtocol::parseFeatures( const Response & ehloResponse ) { - mCapabilities = Capabilities::fromResponse( ehloResponse ); - - TQString category = usingTLS() ? "TLS" : usingSSL() ? "SSL" : "PLAIN" ; - setMetaData( category + " AUTH METHODS", mCapabilities.authMethodMetaData() ); - setMetaData( category + " CAPABILITIES", mCapabilities.asMetaDataString() ); -#ifndef NDEBUG - kdDebug(7112) << "parseFeatures() " << category << " AUTH METHODS:" - << '\n' + mCapabilities.authMethodMetaData() << endl - << "parseFeatures() " << category << " CAPABILITIES:" - << '\n' + mCapabilities.asMetaDataString() << endl; -#endif -} - -void SMTPProtocol::smtp_close( bool nice ) { - if (!m_opened) // We're already closed - return; - - if ( nice ) - execute( Command::QUIT ); - kdDebug( 7112 ) << "closing connection" << endl; - closeDescriptor(); - m_sOldServer = TQString::null; - m_sOldUser = TQString::null; - m_sOldPass = TQString::null; - - mCapabilities.clear(); - mPendingCommandQueue.clear(); - mSentCommandQueue.clear(); - - m_opened = false; -} - -void SMTPProtocol::stat(const KURL & url) -{ - TQString path = url.path(); - error(TDEIO::ERR_DOES_NOT_EXIST, url.path()); -} - diff --git a/tdeioslave/smtp/smtp.cpp b/tdeioslave/smtp/smtp.cpp new file mode 100644 index 000000000..00dff3684 --- /dev/null +++ b/tdeioslave/smtp/smtp.cpp @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2000, 2001 Alex Zepeda + * Copyright (c) 2001 Michael Häckel + * Copyright (c) 2002 Aaron J. Seigo + * Copyright (c) 2003 Marc Mutz + * All rights reserved. + * + * 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 above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + */ + +#include + +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#include + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include +} +#endif + +#include "smtp.h" +#include "request.h" +#include "response.h" +#include "transactionstate.h" +#include "command.h" +using KioSMTP::Capabilities; +using KioSMTP::Command; +using KioSMTP::EHLOCommand; +using KioSMTP::AuthCommand; +using KioSMTP::MailFromCommand; +using KioSMTP::RcptToCommand; +using KioSMTP::DataCommand; +using KioSMTP::TransferCommand; +using KioSMTP::Request; +using KioSMTP::Response; +using KioSMTP::TransactionState; + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +using std::auto_ptr; + +#include +#include +#include +#include + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#include + +#ifndef NI_NAMEREQD +// FIXME for KDE 3.3: fake defintion +// API design flaw in KExtendedSocket::resolve +# define NI_NAMEREQD 0 +#endif + + +extern "C" { + KDE_EXPORT int kdemain(int argc, char **argv); +} + +int kdemain(int argc, char **argv) +{ + TDEInstance instance("tdeio_smtp"); + + if (argc != 4) { + fprintf(stderr, + "Usage: tdeio_smtp protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + +#ifdef HAVE_LIBSASL2 + if ( sasl_client_init( NULL ) != SASL_OK ) { + fprintf(stderr, "SASL library initialization failed!\n"); + exit(-1); + } +#endif + SMTPProtocol slave( argv[2], argv[3], tqstricmp( argv[1], "smtps" ) == 0 ); + slave.dispatchLoop(); +#ifdef HAVE_LIBSASL2 + sasl_done(); +#endif + return 0; +} + +SMTPProtocol::SMTPProtocol(const TQCString & pool, const TQCString & app, + bool useSSL) +: TCPSlaveBase(useSSL ? 465 : 25, + useSSL ? "smtps" : "smtp", + pool, app, useSSL), + m_iOldPort(0), + m_opened(false) +{ + //kdDebug(7112) << "SMTPProtocol::SMTPProtocol" << endl; + mPendingCommandQueue.setAutoDelete( true ); + mSentCommandQueue.setAutoDelete( true ); +} + +unsigned int SMTPProtocol::sendBufferSize() const { + // ### how much is eaten by SSL/TLS overhead? + const int fd = fileno( fp ); + int value = -1; + kde_socklen_t len = sizeof(value); + if ( fd < 0 || ::getsockopt( fd, SOL_SOCKET, SO_SNDBUF, (char*)&value, &len ) ) + value = 1024; // let's be conservative + kdDebug(7112) << "send buffer size seems to be " << value << " octets." << endl; + return value > 0 ? value : 1024 ; +} + +SMTPProtocol::~SMTPProtocol() { + //kdDebug(7112) << "SMTPProtocol::~SMTPProtocol" << endl; + smtp_close(); +} + +void SMTPProtocol::openConnection() { + if ( smtp_open() ) + connected(); + else + closeConnection(); +} + +void SMTPProtocol::closeConnection() { + smtp_close(); +} + +void SMTPProtocol::special( const TQByteArray & aData ) { + TQDataStream s( aData, IO_ReadOnly ); + int what; + s >> what; + if ( what == 'c' ) { + infoMessage( createSpecialResponse() ); +#ifndef NDEBUG + kdDebug(7112) << "special('c') returns \"" << createSpecialResponse() << "\"" << endl; +#endif + } else if ( what == 'N' ) { + if ( !execute( Command::NOOP ) ) + return; + } else { + error( TDEIO::ERR_INTERNAL, + i18n("The application sent an invalid request.") ); + return; + } + finished(); +} + + +// Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah +// If smtphost is the name of a profile, it'll use the information +// provided by that profile. If it's not a profile name, it'll use it as +// nature intended. +// One can also specify in the query: +// headers=0 (turns off header generation) +// to=emailaddress +// cc=emailaddress +// bcc=emailaddress +// subject=text +// profile=text (this will override the "host" setting) +// hostname=text (used in the HELO) +// body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension) +void SMTPProtocol::put(const KURL & url, int /*permissions */ , + bool /*overwrite */ , bool /*resume */ ) +{ + Request request = Request::fromURL( url ); // parse settings from URL's query + + KEMailSettings mset; + KURL open_url = url; + if ( !request.hasProfile() ) { + //kdDebug(7112) << "tdeio_smtp: Profile is null" << endl; + bool hasProfile = mset.profiles().contains( open_url.host() ); + if ( hasProfile ) { + mset.setProfile(open_url.host()); + open_url.setHost(mset.getSetting(KEMailSettings::OutServer)); + m_sUser = mset.getSetting(KEMailSettings::OutServerLogin); + m_sPass = mset.getSetting(KEMailSettings::OutServerPass); + + if (m_sUser.isEmpty()) + m_sUser = TQString::null; + if (m_sPass.isEmpty()) + m_sPass = TQString::null; + open_url.setUser(m_sUser); + open_url.setPass(m_sPass); + m_sServer = open_url.host(); + m_iPort = open_url.port(); + } + else { + mset.setProfile(mset.defaultProfileName()); + } + } + else { + mset.setProfile( request.profileName() ); + } + + // Check KEMailSettings to see if we've specified an E-Mail address + // if that worked, check to see if we've specified a real name + // and then format accordingly (either: emailaddress@host.com or + // Real Name ) + if ( !request.hasFromAddress() ) { + const TQString from = mset.getSetting( KEMailSettings::EmailAddress ); + if ( !from.isNull() ) + request.setFromAddress( from ); + else if ( request.emitHeaders() ) { + error(TDEIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); + return; + } + } + + if ( !smtp_open( request.heloHostname() ) ) + { + error(TDEIO::ERR_SERVICE_NOT_AVAILABLE, + i18n("SMTPProtocol::smtp_open failed (%1)") // ### better error message? + .arg(open_url.path())); + return; + } + + if ( request.is8BitBody() + && !haveCapability("8BITMIME") && metaData("8bitmime") != "on" ) { + error( TDEIO::ERR_SERVICE_NOT_AVAILABLE, + i18n("Your server does not support sending of 8-bit messages.\n" + "Please use base64 or quoted-printable encoding.") ); + return; + } + + queueCommand( new MailFromCommand( this, request.fromAddress().latin1(), + request.is8BitBody(), request.size() ) ); + + // Loop through our To and CC recipients, and send the proper + // SMTP commands, for the benefit of the server. + TQStringList recipients = request.recipients(); + for ( TQStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it ) + queueCommand( new RcptToCommand( this, (*it).latin1() ) ); + + queueCommand( Command::DATA ); + queueCommand( new TransferCommand( this, request.headerFields( mset.getSetting( KEMailSettings::RealName ) ) ) ); + + TransactionState ts; + if ( !executeQueuedCommands( &ts ) ) { + if ( ts.errorCode() ) + error( ts.errorCode(), ts.errorMessage() ); + } else + finished(); +} + + +void SMTPProtocol::setHost(const TQString & host, int port, + const TQString & user, const TQString & pass) +{ + m_sServer = host; + m_iPort = port; + m_sUser = user; + m_sPass = pass; +} + +bool SMTPProtocol::sendCommandLine( const TQCString & cmdline ) { + //kdDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); + //kdDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>" << endl; + kdDebug( 7112) << "C: <" << cmdline.length() << " bytes>" << endl; + ssize_t cmdline_len = cmdline.length(); + if ( write( cmdline.data(), cmdline_len ) != cmdline_len ) { + error( TDEIO::ERR_COULD_NOT_WRITE, m_sServer ); + return false; + } + return true; +} + +Response SMTPProtocol::getResponse( bool * ok ) { + + if ( ok ) + *ok = false; + + Response response; + char buf[2048]; + + int recv_len = 0; + do { + // wait for data... + if ( !waitForResponse( 600 ) ) { + error( TDEIO::ERR_SERVER_TIMEOUT, m_sServer ); + return response; + } + + // ...read data... + recv_len = readLine( buf, sizeof(buf) - 1 ); + if ( recv_len < 1 && !isConnectionValid() ) { + error( TDEIO::ERR_CONNECTION_BROKEN, m_sServer ); + return response; + } + + kdDebug(7112) << "S: " << TQCString( buf, recv_len + 1 ).data(); + // ...and parse lines... + response.parseLine( buf, recv_len ); + + // ...until the response is complete or the parser is so confused + // that it doesn't think a RSET would help anymore: + } while ( !response.isComplete() && response.isWellFormed() ); + + if ( !response.isValid() ) { + error( TDEIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.").arg(response.code()) ); + return response; + } + + if ( ok ) + *ok = true; + + return response; +} + +bool SMTPProtocol::executeQueuedCommands( TransactionState * ts ) { + assert( ts ); + + kdDebug( canPipelineCommands(), 7112 ) << "using pipelining" << endl; + + while( !mPendingCommandQueue.isEmpty() ) { + TQCString cmdline = collectPipelineCommands( ts ); + if ( ts->failedFatally() ) { + smtp_close( false ); // _hard_ shutdown + return false; + } + if ( ts->failed() ) + break; + if ( cmdline.isEmpty() ) + continue; + if ( !sendCommandLine( cmdline ) || + !batchProcessResponses( ts ) || + ts->failedFatally() ) { + smtp_close( false ); // _hard_ shutdown + return false; + } + } + + if ( ts->failed() ) { + if ( !execute( Command::RSET ) ) + smtp_close( false ); + return false; + } + return true; +} + +TQCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { + assert( ts ); + + TQCString cmdLine; + unsigned int cmdLine_len = 0; + + while ( mPendingCommandQueue.head() ) { + + Command * cmd = mPendingCommandQueue.head(); + + if ( cmd->doNotExecute( ts ) ) { + delete mPendingCommandQueue.dequeue(); + if ( cmdLine_len ) + break; + else + continue; + } + + if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) + break; + + if ( cmdLine_len && !canPipelineCommands() ) + break; + + while ( !cmd->isComplete() && !cmd->needsResponse() ) { + const TQCString currentCmdLine = cmd->nextCommandLine( ts ); + if ( ts->failedFatally() ) + return cmdLine; + const unsigned int currentCmdLine_len = currentCmdLine.length(); + + if ( cmdLine_len && cmdLine_len + currentCmdLine_len > sendBufferSize() ) { + // must all fit into the send buffer, else connection deadlocks, + // but we need to have at least _one_ command to send + cmd->ungetCommandLine( currentCmdLine, ts ); + return cmdLine; + } + cmdLine_len += currentCmdLine_len; + cmdLine += currentCmdLine; + } + + mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() ); + + if ( cmd->mustBeLastInPipeline() ) + break; + } + + return cmdLine; +} + +bool SMTPProtocol::batchProcessResponses( TransactionState * ts ) { + assert( ts ); + + while ( !mSentCommandQueue.isEmpty() ) { + + Command * cmd = mSentCommandQueue.head(); + assert( cmd->isComplete() ); + + bool ok = false; + Response r = getResponse( &ok ); + if ( !ok ) + return false; + cmd->processResponse( r, ts ); + if ( ts->failedFatally() ) + return false; + + mSentCommandQueue.remove(); + } + + return true; +} + +void SMTPProtocol::queueCommand( int type ) { + queueCommand( Command::createSimpleCommand( type, this ) ); +} + +bool SMTPProtocol::execute( int type, TransactionState * ts ) { + auto_ptr cmd( Command::createSimpleCommand( type, this ) ); + kdFatal( !cmd.get(), 7112 ) << "Command::createSimpleCommand( " << type << " ) returned null!" << endl; + return execute( cmd.get(), ts ); +} + +// ### fold into pipelining engine? How? (execute() is often called +// ### when command queues are _not_ empty!) +bool SMTPProtocol::execute( Command * cmd, TransactionState * ts ) +{ + kdFatal( !cmd, 7112 ) << "SMTPProtocol::execute() called with no command to run!" << endl; + + if (!cmd) + return false; + + if ( cmd->doNotExecute( ts ) ) + return true; + + do { + while ( !cmd->isComplete() && !cmd->needsResponse() ) { + const TQCString cmdLine = cmd->nextCommandLine( ts ); + if ( ts && ts->failedFatally() ) { + smtp_close( false ); + return false; + } + if ( cmdLine.isEmpty() ) + continue; + if ( !sendCommandLine( cmdLine ) ) { + smtp_close( false ); + return false; + } + } + + bool ok = false; + Response r = getResponse( &ok ); + if ( !ok ) { + smtp_close( false ); + return false; + } + if ( !cmd->processResponse( r, ts ) ) { + if ( ts && ts->failedFatally() || + cmd->closeConnectionOnError() || + !execute( Command::RSET ) ) + smtp_close( false ); + return false; + } + } while ( !cmd->isComplete() ); + + return true; +} + +bool SMTPProtocol::smtp_open(const TQString& fakeHostname) +{ + if (m_opened && + m_iOldPort == port(m_iPort) && + m_sOldServer == m_sServer && + m_sOldUser == m_sUser && + (fakeHostname.isNull() || m_hostname == fakeHostname)) + return true; + + smtp_close(); + if (!connectToHost(m_sServer, m_iPort)) + return false; // connectToHost has already send an error message. + m_opened = true; + + bool ok = false; + Response greeting = getResponse( &ok ); + if ( !ok || !greeting.isOk() ) + { + if ( ok ) + error( TDEIO::ERR_COULD_NOT_LOGIN, + i18n("The server did not accept the connection.\n" + "%1").arg( greeting.errorMessage() ) ); + smtp_close(); + return false; + } + + if (!fakeHostname.isNull()) + { + m_hostname = fakeHostname; + } + else + { + TQString tmpPort; + TDESocketAddress* addr = KExtendedSocket::localAddress(m_iSock); + // perform name lookup. NI_NAMEREQD means: don't return a numeric + // value (we need to know when we get have the IP address, so we + // can enclose it in sqaure brackets (domain-literal). Failure to + // do so is normally harmless with IPv4, but fails for IPv6: + if (KExtendedSocket::resolve(addr, m_hostname, tmpPort, NI_NAMEREQD) != 0) + // FQDN resolution failed + // use the IP address as domain-literal + m_hostname = '[' + addr->nodeName() + ']'; + delete addr; + + if(m_hostname.isEmpty()) + { + m_hostname = "localhost.invalid"; + } + } + + EHLOCommand ehloCmdPreTLS( this, m_hostname ); + if ( !execute( &ehloCmdPreTLS ) ) { + smtp_close(); + return false; + } + + if ( ( haveCapability("STARTTLS") && canUseTLS() && metaData("tls") != "off" ) + || metaData("tls") == "on" ) { + // For now we're gonna force it on. + + if ( execute( Command::STARTTLS ) ) { + + // re-issue EHLO to refresh the capability list (could be have + // been faked before TLS was enabled): + EHLOCommand ehloCmdPostTLS( this, m_hostname ); + if ( !execute( &ehloCmdPostTLS ) ) { + smtp_close(); + return false; + } + } + } + // Now we try and login + if (!authenticate()) { + smtp_close(); + return false; + } + + m_iOldPort = m_iPort; + m_sOldServer = m_sServer; + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + return true; +} + +bool SMTPProtocol::authenticate() +{ + // return with success if the server doesn't support SMTP-AUTH or an user + // name is not specified and metadata doesn't tell us to force it. + if ( (m_sUser.isEmpty() || !haveCapability( "AUTH" )) && + metaData( "sasl" ).isEmpty() ) return true; + + TDEIO::AuthInfo authInfo; + authInfo.username = m_sUser; + authInfo.password = m_sPass; + authInfo.prompt = i18n("Username and password for your SMTP account:"); + + TQStringList strList; + + if (!metaData("sasl").isEmpty()) + strList.append(metaData("sasl").latin1()); + else + strList = mCapabilities.saslMethodsQSL(); + + AuthCommand authCmd( this, strList.join(" ").latin1(), m_sServer, authInfo ); + bool ret = execute( &authCmd ); + m_sUser = authInfo.username; + m_sPass = authInfo.password; + return ret; +} + +void SMTPProtocol::parseFeatures( const Response & ehloResponse ) { + mCapabilities = Capabilities::fromResponse( ehloResponse ); + + TQString category = usingTLS() ? "TLS" : usingSSL() ? "SSL" : "PLAIN" ; + setMetaData( category + " AUTH METHODS", mCapabilities.authMethodMetaData() ); + setMetaData( category + " CAPABILITIES", mCapabilities.asMetaDataString() ); +#ifndef NDEBUG + kdDebug(7112) << "parseFeatures() " << category << " AUTH METHODS:" + << '\n' + mCapabilities.authMethodMetaData() << endl + << "parseFeatures() " << category << " CAPABILITIES:" + << '\n' + mCapabilities.asMetaDataString() << endl; +#endif +} + +void SMTPProtocol::smtp_close( bool nice ) { + if (!m_opened) // We're already closed + return; + + if ( nice ) + execute( Command::QUIT ); + kdDebug( 7112 ) << "closing connection" << endl; + closeDescriptor(); + m_sOldServer = TQString::null; + m_sOldUser = TQString::null; + m_sOldPass = TQString::null; + + mCapabilities.clear(); + mPendingCommandQueue.clear(); + mSentCommandQueue.clear(); + + m_opened = false; +} + +void SMTPProtocol::stat(const KURL & url) +{ + TQString path = url.path(); + error(TDEIO::ERR_DOES_NOT_EXIST, url.path()); +} + diff --git a/tdeioslave/smtp/test_commands.cc b/tdeioslave/smtp/test_commands.cc deleted file mode 100644 index d8a5f0d4e..000000000 --- a/tdeioslave/smtp/test_commands.cc +++ /dev/null @@ -1,728 +0,0 @@ -#include -#include - -#include -#include -#include - -//#include -//using std::cout; -//using std::endl; - -namespace KioSMTP { - class Response; -}; - -// fake -class SMTPProtocol { -public: - SMTPProtocol() { clear(); } - - // - // public members to control the API emulation below: - // - int startTLSReturnCode; - bool usesSSL; - bool usesTLS; - int lastErrorCode; - TQString lastErrorMessage; - int lastMessageBoxCode; - TQString lastMessageBoxText; - TQByteArray nextData; - int nextDataReturnCode; - TQStringList caps; - TDEIO::MetaData metadata; - - void clear() { - startTLSReturnCode = 1; - usesSSL = usesTLS = false; - lastErrorCode = lastMessageBoxCode = 0; - lastErrorMessage = lastMessageBoxText = TQString::null; - nextData.resize( 0 ); - nextDataReturnCode = -1; - caps.clear(); - metadata.clear(); - } - - // - // emulated API: - // - void parseFeatures( const KioSMTP::Response & ) { /* noop */ } - int startTLS() { - if ( startTLSReturnCode == 1 ) - usesTLS = true; - return startTLSReturnCode; - } - bool usingSSL() const { return usesSSL; } - bool usingTLS() const { return usesTLS; } - bool haveCapability( const char * cap ) const { return caps.contains( cap ); } - void error( int id, const TQString & msg ) { - lastErrorCode = id; - lastErrorMessage = msg; - } - void messageBox( int id, const TQString & msg, const TQString & ) { - lastMessageBoxCode = id; - lastMessageBoxText = msg; - } - void dataReq() { /* noop */ } - int readData( TQByteArray & ba ) { ba = nextData; return nextDataReturnCode; } - TQString metaData( const TQString & key ) const { return metadata[key]; } - -}; - -#define _SMTP_H - -#define KIOSMTP_COMPARATORS // for TransactionState::operator== -#include "command.h" -#include "response.h" -#include "transactionstate.h" - -#include - -using namespace KioSMTP; - -static const char * foobarbaz = ".Foo bar baz"; -static const unsigned int foobarbaz_len = tqstrlen( foobarbaz ); - -static const char * foobarbaz_dotstuffed = "..Foo bar baz"; -static const unsigned int foobarbaz_dotstuffed_len = tqstrlen( foobarbaz_dotstuffed ); - -static const char * foobarbaz_lf = ".Foo bar baz\n"; -static const unsigned int foobarbaz_lf_len = tqstrlen( foobarbaz_lf ); - -static const char * foobarbaz_crlf = "..Foo bar baz\r\n"; -static const unsigned int foobarbaz_crlf_len = tqstrlen( foobarbaz_crlf ); - -static void checkSuccessfulTransferCommand( bool, bool, bool, bool, bool ); - -int main( int, char** ) { - - // FIXME: Port this to new API. -#if 0 - SMTPProtocol smtp; - Response r; - TransactionState ts, ts2; - - // - // EHLO / HELO - // - - smtp.clear(); - EHLOCommand ehlo( &smtp, "mail.example.com" ); - // flags - assert( ehlo.closeConnectionOnError() ); - assert( ehlo.mustBeLastInPipeline() ); - assert( !ehlo.mustBeFirstInPipeline() ); - - // initial state - assert( !ehlo.isComplete() ); - assert( !ehlo.doNotExecute( 0 ) ); - assert( !ehlo.needsResponse() ); - - // dynamics 1: EHLO succeeds - assert( ehlo.nextCommandLine( 0 ) == "EHLO mail.example.com\r\n" ); - assert( !ehlo.isComplete() ); // EHLO may fail and we then try HELO - assert( ehlo.needsResponse() ); - r.clear(); - r.parseLine( "250-mail.example.net\r\n" ); - r.parseLine( "250-PIPELINING\r\n" ); - r.parseLine( "250 8BITMIME\r\n" ); - assert( ehlo.processResponse( r, 0 ) == true ); - assert( ehlo.isComplete() ); - assert( !ehlo.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); - assert( smtp.lastErrorMessage.isNull() ); - - // dynamics 2: EHLO fails with "unknown command" - smtp.clear(); - EHLOCommand ehlo2( &smtp, "mail.example.com" ); - ehlo2.nextCommandLine( 0 ); - r.clear(); - r.parseLine( "500 unknown command\r\n" ); - assert( ehlo2.processResponse( r, 0 ) == true ); - assert( !ehlo2.isComplete() ); - assert( !ehlo2.needsResponse() ); - assert( ehlo2.nextCommandLine( 0 ) == "HELO mail.example.com\r\n" ); - assert( ehlo2.isComplete() ); - assert( ehlo2.needsResponse() ); - r.clear(); - r.parseLine( "250 mail.example.net\r\n" ); - assert( ehlo2.processResponse( r, 0 ) == true ); - assert( !ehlo2.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); - assert( smtp.lastErrorMessage.isNull() ); - - // dynamics 3: EHLO fails with unknown response code - smtp.clear(); - EHLOCommand ehlo3( &smtp, "mail.example.com" ); - ehlo3.nextCommandLine( 0 ); - r.clear(); - r.parseLine( "545 you don't know me\r\n" ); - assert( ehlo3.processResponse( r, 0 ) == false ); - assert( ehlo3.isComplete() ); - assert( !ehlo3.needsResponse() ); - assert( smtp.lastErrorCode == TDEIO::ERR_UNKNOWN ); - - // dynamics 4: EHLO _and_ HELO fail with "command unknown" - smtp.clear(); - EHLOCommand ehlo4( &smtp, "mail.example.com" ); - ehlo4.nextCommandLine( 0 ); - r.clear(); - r.parseLine( "500 unknown command\r\n" ); - ehlo4.processResponse( r, 0 ); - ehlo4.nextCommandLine( 0 ); - r.clear(); - r.parseLine( "500 unknown command\r\n" ); - assert( ehlo4.processResponse( r, 0 ) == false ); - assert( ehlo4.isComplete() ); - assert( !ehlo4.needsResponse() ); - assert( smtp.lastErrorCode == TDEIO::ERR_INTERNAL_SERVER ); - - // - // STARTTLS - // - - smtp.clear(); - StartTLSCommand tls( &smtp ); - // flags - assert( tls.closeConnectionOnError() ); - assert( tls.mustBeLastInPipeline() ); - assert( !tls.mustBeFirstInPipeline() ); - - // initial state - assert( !tls.isComplete() ); - assert( !tls.doNotExecute( 0 ) ); - assert( !tls.needsResponse() ); - - // dynamics 1: ok from server, TLS negotiation successful - ts.clear(); - ts2 = ts; - assert( tls.nextCommandLine( &ts ) == "STARTTLS\r\n" ); - assert( ts == ts2 ); - assert( tls.isComplete() ); - assert( tls.needsResponse() ); - r.clear(); - r.parseLine( "220 Go ahead" ); - smtp.startTLSReturnCode = 1; - assert( tls.processResponse( r, &ts ) == true ); - assert( !tls.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); - - // dynamics 2: NAK from server - smtp.clear(); - StartTLSCommand tls2( &smtp ); - ts.clear(); - tls2.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "454 TLS temporarily disabled" ); - smtp.startTLSReturnCode = 1; - assert( tls2.processResponse( r, &ts ) == false ); - assert( !tls2.needsResponse() ); - assert( smtp.lastErrorCode == TDEIO::ERR_SERVICE_NOT_AVAILABLE ); - - // dynamics 3: ok from server, TLS negotiation unsuccessful - smtp.clear(); - StartTLSCommand tls3( &smtp ); - ts.clear(); - tls3.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "220 Go ahead" ); - smtp.startTLSReturnCode = -1; - assert( tls.processResponse( r, &ts ) == false ); - assert( !tls.needsResponse() ); - - // - // AUTH - // - - smtp.clear(); - TQStrIList mechs; - mechs.append( "PLAIN" ); - smtp.metadata["sasl"] = "PLAIN"; - AuthCommand auth( &smtp, mechs, "user", "pass" ); - // flags - assert( auth.closeConnectionOnError() ); - assert( auth.mustBeLastInPipeline() ); - assert( !auth.mustBeFirstInPipeline() ); - - // initial state - assert( !auth.isComplete() ); - assert( !auth.doNotExecute( 0 ) ); - assert( !auth.needsResponse() ); - - // dynamics 1: TLS, so AUTH should include initial-response: - smtp.usesTLS = true; - ts.clear(); - ts2 = ts; - assert( auth.nextCommandLine( &ts ) == "AUTH PLAIN dXNlcgB1c2VyAHBhc3M=\r\n" ); - assert( auth.isComplete() ); - assert( auth.needsResponse() ); - assert( ts == ts2 ); - r.clear(); - r.parseLine( "250 OK" ); - - // dynamics 2: No TLS, so AUTH should not include initial-response: - smtp.clear(); - smtp.metadata["sasl"] = "PLAIN"; - smtp.usesTLS = false; - AuthCommand auth2( &smtp, mechs, "user", "pass" ); - ts.clear(); - assert( auth2.nextCommandLine( &ts ) == "AUTH PLAIN\r\n" ); - assert( !auth2.isComplete() ); - assert( auth2.needsResponse() ); - r.clear(); - r.parseLine( "334 Go on" ); - assert( auth2.processResponse( r, &ts ) == true ); - assert( auth2.nextCommandLine( &ts ) == "dXNlcgB1c2VyAHBhc3M=\r\n" ); - assert( auth2.isComplete() ); - assert( auth2.needsResponse() ); - - // dynamics 3: LOGIN - smtp.clear(); - smtp.metadata["sasl"] = "LOGIN"; - mechs.clear(); - mechs.append( "LOGIN" ); - AuthCommand auth3( &smtp, mechs, "user", "pass" ); - ts.clear(); - ts2 = ts; - assert( auth3.nextCommandLine( &ts ) == "AUTH LOGIN\r\n" ); - assert( !auth3.isComplete() ); - assert( auth3.needsResponse() ); - r.clear(); - r.parseLine( "334 VXNlcm5hbWU6" ); - assert( auth3.processResponse( r, &ts ) == true ); - assert( !auth3.needsResponse() ); - assert( auth3.nextCommandLine( &ts ) == "dXNlcg==\r\n" ); - assert( !auth3.isComplete() ); - assert( auth3.needsResponse() ); - r.clear(); - r.parseLine( "334 go on" ); - assert( auth3.processResponse( r, &ts ) == true ); - assert( !auth3.needsResponse() ); - assert( auth3.nextCommandLine( &ts ) == "cGFzcw==\r\n" ); - assert( auth3.isComplete() ); - assert( auth3.needsResponse() ); - r.clear(); - r.parseLine( "250 OK" ); - assert( auth3.processResponse( r, &ts ) == true ); - assert( !auth3.needsResponse() ); - assert( !smtp.lastErrorCode ); - assert( ts == ts2 ); - - // - // MAIL FROM: - // - - smtp.clear(); - MailFromCommand mail( &smtp, "joe@user.org" ); - // flags - assert( !mail.closeConnectionOnError() ); - assert( !mail.mustBeLastInPipeline() ); - assert( !mail.mustBeFirstInPipeline() ); - - // initial state - assert( !mail.isComplete() ); - assert( !mail.doNotExecute( 0 ) ); - assert( !mail.needsResponse() ); - - // dynamics: success, no size, no 8bit - ts.clear(); - ts2 = ts; - assert( mail.nextCommandLine( &ts ) == "MAIL FROM:\r\n" ); - assert( ts2 == ts ); - assert( mail.isComplete() ); - assert( mail.needsResponse() ); - r.clear(); - r.parseLine( "250 Ok" ); - assert( mail.processResponse( r, &ts ) == true ); - assert( !mail.needsResponse() ); - assert( ts == ts2 ); - assert( smtp.lastErrorCode == 0 ); - - // dynamics: success, size, 8bit, but no SIZE, 8BITMIME caps - smtp.clear(); - MailFromCommand mail2( &smtp, "joe@user.org", true, 500 ); - ts.clear(); - ts2 = ts; - assert( mail2.nextCommandLine( &ts ) == "MAIL FROM:\r\n" ); - assert( ts == ts2 ); - - // dynamics: success, size, 8bit, SIZE, 8BITMIME caps - smtp.clear(); - MailFromCommand mail3( &smtp, "joe@user.org", true, 500 ); - ts.clear(); - ts2 = ts; - smtp.caps << "SIZE" << "8BITMIME" ; - assert( mail3.nextCommandLine( &ts ) == "MAIL FROM: BODY=8BITMIME SIZE=500\r\n" ); - assert( ts == ts2 ); - - // dynamics: failure - smtp.clear(); - MailFromCommand mail4( &smtp, "joe@user.org" ); - ts.clear(); - mail4.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "503 Bad sequence of commands" ); - assert( mail4.processResponse( r, &ts ) == false ); - assert( mail4.isComplete() ); - assert( !mail4.needsResponse() ); - assert( ts.failed() ); - assert( !ts.failedFatally() ); - assert( smtp.lastErrorCode == 0 ); - - // - // RCPT TO: - // - - smtp.clear(); - RcptToCommand rcpt( &smtp, "joe@user.org" ); - // flags - assert( !rcpt.closeConnectionOnError() ); - assert( !rcpt.mustBeLastInPipeline() ); - assert( !rcpt.mustBeFirstInPipeline() ); - - // initial state - assert( !rcpt.isComplete() ); - assert( !rcpt.doNotExecute( 0 ) ); - assert( !rcpt.needsResponse() ); - - // dynamics: success - ts.clear(); - ts2 = ts; - assert( rcpt.nextCommandLine( &ts ) == "RCPT TO:\r\n" ); - assert( ts == ts2 ); - assert( rcpt.isComplete() ); - assert( rcpt.needsResponse() ); - r.clear(); - r.parseLine( "250 Ok" ); - assert( rcpt.processResponse( r, &ts ) == true ); - assert( !rcpt.needsResponse() ); - assert( ts.atLeastOneRecipientWasAccepted() ); - assert( !ts.haveRejectedRecipients() ); - assert( !ts.failed() ); - assert( !ts.failedFatally() ); - assert( smtp.lastErrorCode == 0 ); - - // dynamics: failure - smtp.clear(); - RcptToCommand rcpt2( &smtp, "joe@user.org" ); - ts.clear(); - rcpt2.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "530 5.7.1 Relaying not allowed!" ); - assert( rcpt2.processResponse( r, &ts ) == false ); - assert( rcpt2.isComplete() ); - assert( !rcpt2.needsResponse() ); - assert( !ts.atLeastOneRecipientWasAccepted() ); - assert( ts.haveRejectedRecipients() ); - assert( ts.rejectedRecipients().count() == 1 ); - assert( ts.rejectedRecipients().front().recipient == "joe@user.org" ); - assert( ts.failed() ); - assert( !ts.failedFatally() ); - assert( smtp.lastErrorCode == 0 ); - - // dynamics: success and failure combined - smtp.clear(); - RcptToCommand rcpt3( &smtp, "info@example.com" ); - RcptToCommand rcpt4( &smtp, "halloween@microsoft.com" ); - RcptToCommand rcpt5( &smtp, "joe@user.org" ); - ts.clear(); - rcpt3.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "530 5.7.1 Relaying not allowed!" ); - rcpt3.processResponse( r, &ts ); - - rcpt4.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "250 Ok" ); - rcpt4.processResponse( r, &ts ); - - rcpt5.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "250 Ok" ); - assert( ts.failed() ); - assert( !ts.failedFatally() ); - assert( ts.haveRejectedRecipients() ); - assert( ts.atLeastOneRecipientWasAccepted() ); - assert( smtp.lastErrorCode == 0 ); - - // - // DATA (init) - // - - smtp.clear(); - DataCommand data( &smtp ); - // flags - assert( !data.closeConnectionOnError() ); - assert( data.mustBeLastInPipeline() ); - assert( !data.mustBeFirstInPipeline() ); - - // initial state - assert( !data.isComplete() ); - assert( !data.doNotExecute( 0 ) ); - assert( !data.needsResponse() ); - - // dynamics: success - ts.clear(); - assert( data.nextCommandLine( &ts ) == "DATA\r\n" ); - assert( data.isComplete() ); - assert( data.needsResponse() ); - assert( ts.dataCommandIssued() ); - assert( !ts.dataCommandSucceeded() ); - r.clear(); - r.parseLine( "354 Send data, end in ." ); - assert( data.processResponse( r, &ts ) == true ); - assert( !data.needsResponse() ); - assert( ts.dataCommandSucceeded() ); - assert( ts.dataResponse() == r ); - assert( smtp.lastErrorCode == 0 ); - - // dynamics: failure - smtp.clear(); - DataCommand data2( &smtp ); - ts.clear(); - data2.nextCommandLine( &ts ); - r.clear(); - r.parseLine( "551 No valid recipients" ); - assert( data2.processResponse( r, &ts ) == false ); - assert( !data2.needsResponse() ); - assert( !ts.dataCommandSucceeded() ); - assert( ts.dataResponse() == r ); - assert( smtp.lastErrorCode == 0 ); - - // - // DATA (transfer) - // - - TransferCommand xfer( &smtp, 0 ); - // flags - assert( !xfer.closeConnectionOnError() ); - assert( !xfer.mustBeLastInPipeline() ); - assert( xfer.mustBeFirstInPipeline() ); - - // initial state - assert( !xfer.isComplete() ); - assert( !xfer.needsResponse() ); - - // dynamics 1: DATA command failed - ts.clear(); - r.clear(); - r.parseLine( "551 no valid recipients" ); - ts.setDataCommandIssued( true ); - ts.setDataCommandSucceeded( false, r ); - assert( xfer.doNotExecute( &ts ) ); - - // dynamics 2: some recipients rejected, but not all - smtp.clear(); - TransferCommand xfer2( &smtp, 0 ); - ts.clear(); - ts.setRecipientAccepted(); - ts.addRejectedRecipient( "joe@user.org", "No relaying allowed" ); - ts.setDataCommandIssued( true ); - r.clear(); - r.parseLine( "354 go on" ); - ts.setDataCommandSucceeded( true, r ); - // ### will change with allow-partial-delivery option: - assert( xfer.doNotExecute( &ts ) ); - - // successful dynamics with all combinations of: - enum { - EndInLF = 1, - PerformDotStuff = 2, - UngetLast = 4, - Preloading = 8, - Error = 16, - EndOfOptions = 32 - }; - for ( unsigned int i = 0 ; i < EndOfOptions ; ++i ) - checkSuccessfulTransferCommand( i & Error, i & Preloading, i & UngetLast, - i & PerformDotStuff, i & EndInLF ); - - // - // NOOP - // - - smtp.clear(); - NoopCommand noop( &smtp ); - // flags - assert( !noop.closeConnectionOnError() ); - assert( noop.mustBeLastInPipeline() ); - assert( !noop.mustBeFirstInPipeline() ); - - // initial state - assert( !noop.isComplete() ); - assert( !noop.doNotExecute( &ts ) ); - assert( !noop.needsResponse() ); - - // dynamics: success (failure is tested with RSET) - assert( noop.nextCommandLine( 0 ) == "NOOP\r\n" ); - assert( noop.isComplete() ); - assert( noop.needsResponse() ); - r.clear(); - r.parseLine( "250 Ok" ); - assert( noop.processResponse( r, 0 ) == true ); - assert( noop.isComplete() ); - assert( !noop.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); - assert( smtp.lastErrorMessage.isNull() ); - - // - // RSET - // - - smtp.clear(); - RsetCommand rset( &smtp ); - // flags - assert( rset.closeConnectionOnError() ); - assert( !rset.mustBeLastInPipeline() ); - assert( !rset.mustBeFirstInPipeline() ); - - // initial state - assert( !rset.isComplete() ); - assert( !rset.doNotExecute( &ts ) ); - assert( !rset.needsResponse() ); - - // dynamics: failure (success is tested with NOOP/QUIT) - assert( rset.nextCommandLine( 0 ) == "RSET\r\n" ); - assert( rset.isComplete() ); - assert( rset.needsResponse() ); - r.clear(); - r.parseLine( "502 command not implemented" ); - assert( rset.processResponse( r, 0 ) == false ); - assert( rset.isComplete() ); - assert( !rset.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); // an RSET failure isn't worth it, is it? - assert( smtp.lastErrorMessage.isNull() ); - - // - // QUIT - // - - smtp.clear(); - QuitCommand quit( &smtp ); - // flags - assert( quit.closeConnectionOnError() ); - assert( quit.mustBeLastInPipeline() ); - assert( !quit.mustBeFirstInPipeline() ); - - // initial state - assert( !quit.isComplete() ); - assert( !quit.doNotExecute( 0 ) ); - assert( !quit.needsResponse() ); - - // dynamics 1: success - assert( quit.nextCommandLine( 0 ) == "QUIT\r\n" ); - assert( quit.isComplete() ); - assert( quit.needsResponse() ); - r.clear(); - r.parseLine( "221 Goodbye" ); - assert( quit.processResponse( r, 0 ) == true ); - assert( quit.isComplete() ); - assert( !quit.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); - assert( smtp.lastErrorMessage.isNull() ); - - // dynamics 2: success - smtp.clear(); - QuitCommand quit2( &smtp ); - quit2.nextCommandLine( 0 ); - r.clear(); - r.parseLine( "500 unknown command" ); - assert( quit2.processResponse( r, 0 ) == false ); - assert( quit2.isComplete() ); - assert( !quit2.needsResponse() ); - assert( smtp.lastErrorCode == 0 ); // an QUIT failure isn't worth it, is it? - assert( smtp.lastErrorMessage.isNull() ); -#endif - - return 0; -} - -void checkSuccessfulTransferCommand( bool error, bool preload, bool ungetLast, - bool slaveDotStuff, bool mailEndsInNewline ) { - kdDebug() << " ===== checkTransferCommand( " - << error << ", " - << preload << ", " - << ungetLast << ", " - << slaveDotStuff << ", " - << mailEndsInNewline << " ) =====" << endl; - - SMTPProtocol smtp; - if ( slaveDotStuff ) - smtp.metadata["lf2crlf+dotstuff"] = "slave"; - - Response r; - - const char * s_pre = slaveDotStuff ? - mailEndsInNewline ? foobarbaz_lf : foobarbaz - : - mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; - const unsigned int s_pre_len = tqstrlen( s_pre ); - - const char * s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; - //const unsigned int s_post_len = tqstrlen( s_post ); - - TransferCommand xfer( &smtp, preload ? s_post : 0 ); - - TransactionState ts; - ts.setRecipientAccepted(); - ts.setDataCommandIssued( true ); - r.clear(); - r.parseLine( "354 ok" ); - ts.setDataCommandSucceeded( true, r ); - assert( !xfer.doNotExecute( &ts ) ); - if ( preload ) { - assert( xfer.nextCommandLine( &ts ) == s_post ); - assert( !xfer.isComplete() ); - assert( !xfer.needsResponse() ); - assert( !ts.failed() ); - assert( smtp.lastErrorCode == 0 ); - } - smtp.nextData.duplicate( s_pre, s_pre_len ); - smtp.nextDataReturnCode = s_pre_len; - assert( xfer.nextCommandLine( &ts ) == s_post ); - assert( !xfer.isComplete() ); - assert( !xfer.needsResponse() ); - assert( !ts.failed() ); - assert( smtp.lastErrorCode == 0 ); - smtp.nextData.resize( 0 ); - smtp.nextDataReturnCode = 0; - if ( ungetLast ) { - xfer.ungetCommandLine( xfer.nextCommandLine( &ts ), &ts ); - assert( !xfer.isComplete() ); - assert( !xfer.needsResponse() ); - assert( !ts.complete() ); - smtp.nextDataReturnCode = -1; // double read -> error - } - if ( mailEndsInNewline ) - assert( xfer.nextCommandLine( &ts ) == ".\r\n" ); - else - assert( xfer.nextCommandLine( &ts ) == "\r\n.\r\n" ); - assert( xfer.isComplete() ); - assert( xfer.needsResponse() ); - assert( !ts.complete() ); - assert( !ts.failed() ); - assert( smtp.lastErrorCode == 0 ); - r.clear(); - if ( error ) { - r.parseLine( "552 Exceeded storage allocation" ); - assert( xfer.processResponse( r, &ts ) == false ); - assert( !xfer.needsResponse() ); - assert( ts.complete() ); - assert( ts.failed() ); - assert( smtp.lastErrorCode == TDEIO::ERR_DISK_FULL ); - } else { - r.parseLine( "250 Message accepted" ); - assert( xfer.processResponse( r, &ts ) == true ); - assert( !xfer.needsResponse() ); - assert( ts.complete() ); - assert( !ts.failed() ); - assert( smtp.lastErrorCode == 0 ); - } -}; - -#define NDEBUG - -#include "command.cc" -#include "response.cc" -#include "transactionstate.cc" diff --git a/tdeioslave/smtp/test_commands.cpp b/tdeioslave/smtp/test_commands.cpp new file mode 100644 index 000000000..61181bc35 --- /dev/null +++ b/tdeioslave/smtp/test_commands.cpp @@ -0,0 +1,728 @@ +#include +#include + +#include +#include +#include + +//#include +//using std::cout; +//using std::endl; + +namespace KioSMTP { + class Response; +}; + +// fake +class SMTPProtocol { +public: + SMTPProtocol() { clear(); } + + // + // public members to control the API emulation below: + // + int startTLSReturnCode; + bool usesSSL; + bool usesTLS; + int lastErrorCode; + TQString lastErrorMessage; + int lastMessageBoxCode; + TQString lastMessageBoxText; + TQByteArray nextData; + int nextDataReturnCode; + TQStringList caps; + TDEIO::MetaData metadata; + + void clear() { + startTLSReturnCode = 1; + usesSSL = usesTLS = false; + lastErrorCode = lastMessageBoxCode = 0; + lastErrorMessage = lastMessageBoxText = TQString::null; + nextData.resize( 0 ); + nextDataReturnCode = -1; + caps.clear(); + metadata.clear(); + } + + // + // emulated API: + // + void parseFeatures( const KioSMTP::Response & ) { /* noop */ } + int startTLS() { + if ( startTLSReturnCode == 1 ) + usesTLS = true; + return startTLSReturnCode; + } + bool usingSSL() const { return usesSSL; } + bool usingTLS() const { return usesTLS; } + bool haveCapability( const char * cap ) const { return caps.contains( cap ); } + void error( int id, const TQString & msg ) { + lastErrorCode = id; + lastErrorMessage = msg; + } + void messageBox( int id, const TQString & msg, const TQString & ) { + lastMessageBoxCode = id; + lastMessageBoxText = msg; + } + void dataReq() { /* noop */ } + int readData( TQByteArray & ba ) { ba = nextData; return nextDataReturnCode; } + TQString metaData( const TQString & key ) const { return metadata[key]; } + +}; + +#define _SMTP_H + +#define KIOSMTP_COMPARATORS // for TransactionState::operator== +#include "command.h" +#include "response.h" +#include "transactionstate.h" + +#include + +using namespace KioSMTP; + +static const char * foobarbaz = ".Foo bar baz"; +static const unsigned int foobarbaz_len = tqstrlen( foobarbaz ); + +static const char * foobarbaz_dotstuffed = "..Foo bar baz"; +static const unsigned int foobarbaz_dotstuffed_len = tqstrlen( foobarbaz_dotstuffed ); + +static const char * foobarbaz_lf = ".Foo bar baz\n"; +static const unsigned int foobarbaz_lf_len = tqstrlen( foobarbaz_lf ); + +static const char * foobarbaz_crlf = "..Foo bar baz\r\n"; +static const unsigned int foobarbaz_crlf_len = tqstrlen( foobarbaz_crlf ); + +static void checkSuccessfulTransferCommand( bool, bool, bool, bool, bool ); + +int main( int, char** ) { + + // FIXME: Port this to new API. +#if 0 + SMTPProtocol smtp; + Response r; + TransactionState ts, ts2; + + // + // EHLO / HELO + // + + smtp.clear(); + EHLOCommand ehlo( &smtp, "mail.example.com" ); + // flags + assert( ehlo.closeConnectionOnError() ); + assert( ehlo.mustBeLastInPipeline() ); + assert( !ehlo.mustBeFirstInPipeline() ); + + // initial state + assert( !ehlo.isComplete() ); + assert( !ehlo.doNotExecute( 0 ) ); + assert( !ehlo.needsResponse() ); + + // dynamics 1: EHLO succeeds + assert( ehlo.nextCommandLine( 0 ) == "EHLO mail.example.com\r\n" ); + assert( !ehlo.isComplete() ); // EHLO may fail and we then try HELO + assert( ehlo.needsResponse() ); + r.clear(); + r.parseLine( "250-mail.example.net\r\n" ); + r.parseLine( "250-PIPELINING\r\n" ); + r.parseLine( "250 8BITMIME\r\n" ); + assert( ehlo.processResponse( r, 0 ) == true ); + assert( ehlo.isComplete() ); + assert( !ehlo.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 2: EHLO fails with "unknown command" + smtp.clear(); + EHLOCommand ehlo2( &smtp, "mail.example.com" ); + ehlo2.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + assert( ehlo2.processResponse( r, 0 ) == true ); + assert( !ehlo2.isComplete() ); + assert( !ehlo2.needsResponse() ); + assert( ehlo2.nextCommandLine( 0 ) == "HELO mail.example.com\r\n" ); + assert( ehlo2.isComplete() ); + assert( ehlo2.needsResponse() ); + r.clear(); + r.parseLine( "250 mail.example.net\r\n" ); + assert( ehlo2.processResponse( r, 0 ) == true ); + assert( !ehlo2.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 3: EHLO fails with unknown response code + smtp.clear(); + EHLOCommand ehlo3( &smtp, "mail.example.com" ); + ehlo3.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "545 you don't know me\r\n" ); + assert( ehlo3.processResponse( r, 0 ) == false ); + assert( ehlo3.isComplete() ); + assert( !ehlo3.needsResponse() ); + assert( smtp.lastErrorCode == TDEIO::ERR_UNKNOWN ); + + // dynamics 4: EHLO _and_ HELO fail with "command unknown" + smtp.clear(); + EHLOCommand ehlo4( &smtp, "mail.example.com" ); + ehlo4.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + ehlo4.processResponse( r, 0 ); + ehlo4.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + assert( ehlo4.processResponse( r, 0 ) == false ); + assert( ehlo4.isComplete() ); + assert( !ehlo4.needsResponse() ); + assert( smtp.lastErrorCode == TDEIO::ERR_INTERNAL_SERVER ); + + // + // STARTTLS + // + + smtp.clear(); + StartTLSCommand tls( &smtp ); + // flags + assert( tls.closeConnectionOnError() ); + assert( tls.mustBeLastInPipeline() ); + assert( !tls.mustBeFirstInPipeline() ); + + // initial state + assert( !tls.isComplete() ); + assert( !tls.doNotExecute( 0 ) ); + assert( !tls.needsResponse() ); + + // dynamics 1: ok from server, TLS negotiation successful + ts.clear(); + ts2 = ts; + assert( tls.nextCommandLine( &ts ) == "STARTTLS\r\n" ); + assert( ts == ts2 ); + assert( tls.isComplete() ); + assert( tls.needsResponse() ); + r.clear(); + r.parseLine( "220 Go ahead" ); + smtp.startTLSReturnCode = 1; + assert( tls.processResponse( r, &ts ) == true ); + assert( !tls.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics 2: NAK from server + smtp.clear(); + StartTLSCommand tls2( &smtp ); + ts.clear(); + tls2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "454 TLS temporarily disabled" ); + smtp.startTLSReturnCode = 1; + assert( tls2.processResponse( r, &ts ) == false ); + assert( !tls2.needsResponse() ); + assert( smtp.lastErrorCode == TDEIO::ERR_SERVICE_NOT_AVAILABLE ); + + // dynamics 3: ok from server, TLS negotiation unsuccessful + smtp.clear(); + StartTLSCommand tls3( &smtp ); + ts.clear(); + tls3.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "220 Go ahead" ); + smtp.startTLSReturnCode = -1; + assert( tls.processResponse( r, &ts ) == false ); + assert( !tls.needsResponse() ); + + // + // AUTH + // + + smtp.clear(); + TQStrIList mechs; + mechs.append( "PLAIN" ); + smtp.metadata["sasl"] = "PLAIN"; + AuthCommand auth( &smtp, mechs, "user", "pass" ); + // flags + assert( auth.closeConnectionOnError() ); + assert( auth.mustBeLastInPipeline() ); + assert( !auth.mustBeFirstInPipeline() ); + + // initial state + assert( !auth.isComplete() ); + assert( !auth.doNotExecute( 0 ) ); + assert( !auth.needsResponse() ); + + // dynamics 1: TLS, so AUTH should include initial-response: + smtp.usesTLS = true; + ts.clear(); + ts2 = ts; + assert( auth.nextCommandLine( &ts ) == "AUTH PLAIN dXNlcgB1c2VyAHBhc3M=\r\n" ); + assert( auth.isComplete() ); + assert( auth.needsResponse() ); + assert( ts == ts2 ); + r.clear(); + r.parseLine( "250 OK" ); + + // dynamics 2: No TLS, so AUTH should not include initial-response: + smtp.clear(); + smtp.metadata["sasl"] = "PLAIN"; + smtp.usesTLS = false; + AuthCommand auth2( &smtp, mechs, "user", "pass" ); + ts.clear(); + assert( auth2.nextCommandLine( &ts ) == "AUTH PLAIN\r\n" ); + assert( !auth2.isComplete() ); + assert( auth2.needsResponse() ); + r.clear(); + r.parseLine( "334 Go on" ); + assert( auth2.processResponse( r, &ts ) == true ); + assert( auth2.nextCommandLine( &ts ) == "dXNlcgB1c2VyAHBhc3M=\r\n" ); + assert( auth2.isComplete() ); + assert( auth2.needsResponse() ); + + // dynamics 3: LOGIN + smtp.clear(); + smtp.metadata["sasl"] = "LOGIN"; + mechs.clear(); + mechs.append( "LOGIN" ); + AuthCommand auth3( &smtp, mechs, "user", "pass" ); + ts.clear(); + ts2 = ts; + assert( auth3.nextCommandLine( &ts ) == "AUTH LOGIN\r\n" ); + assert( !auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "334 VXNlcm5hbWU6" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( auth3.nextCommandLine( &ts ) == "dXNlcg==\r\n" ); + assert( !auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "334 go on" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( auth3.nextCommandLine( &ts ) == "cGFzcw==\r\n" ); + assert( auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "250 OK" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( !smtp.lastErrorCode ); + assert( ts == ts2 ); + + // + // MAIL FROM: + // + + smtp.clear(); + MailFromCommand mail( &smtp, "joe@user.org" ); + // flags + assert( !mail.closeConnectionOnError() ); + assert( !mail.mustBeLastInPipeline() ); + assert( !mail.mustBeFirstInPipeline() ); + + // initial state + assert( !mail.isComplete() ); + assert( !mail.doNotExecute( 0 ) ); + assert( !mail.needsResponse() ); + + // dynamics: success, no size, no 8bit + ts.clear(); + ts2 = ts; + assert( mail.nextCommandLine( &ts ) == "MAIL FROM:\r\n" ); + assert( ts2 == ts ); + assert( mail.isComplete() ); + assert( mail.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( mail.processResponse( r, &ts ) == true ); + assert( !mail.needsResponse() ); + assert( ts == ts2 ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: success, size, 8bit, but no SIZE, 8BITMIME caps + smtp.clear(); + MailFromCommand mail2( &smtp, "joe@user.org", true, 500 ); + ts.clear(); + ts2 = ts; + assert( mail2.nextCommandLine( &ts ) == "MAIL FROM:\r\n" ); + assert( ts == ts2 ); + + // dynamics: success, size, 8bit, SIZE, 8BITMIME caps + smtp.clear(); + MailFromCommand mail3( &smtp, "joe@user.org", true, 500 ); + ts.clear(); + ts2 = ts; + smtp.caps << "SIZE" << "8BITMIME" ; + assert( mail3.nextCommandLine( &ts ) == "MAIL FROM: BODY=8BITMIME SIZE=500\r\n" ); + assert( ts == ts2 ); + + // dynamics: failure + smtp.clear(); + MailFromCommand mail4( &smtp, "joe@user.org" ); + ts.clear(); + mail4.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "503 Bad sequence of commands" ); + assert( mail4.processResponse( r, &ts ) == false ); + assert( mail4.isComplete() ); + assert( !mail4.needsResponse() ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // + // RCPT TO: + // + + smtp.clear(); + RcptToCommand rcpt( &smtp, "joe@user.org" ); + // flags + assert( !rcpt.closeConnectionOnError() ); + assert( !rcpt.mustBeLastInPipeline() ); + assert( !rcpt.mustBeFirstInPipeline() ); + + // initial state + assert( !rcpt.isComplete() ); + assert( !rcpt.doNotExecute( 0 ) ); + assert( !rcpt.needsResponse() ); + + // dynamics: success + ts.clear(); + ts2 = ts; + assert( rcpt.nextCommandLine( &ts ) == "RCPT TO:\r\n" ); + assert( ts == ts2 ); + assert( rcpt.isComplete() ); + assert( rcpt.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( rcpt.processResponse( r, &ts ) == true ); + assert( !rcpt.needsResponse() ); + assert( ts.atLeastOneRecipientWasAccepted() ); + assert( !ts.haveRejectedRecipients() ); + assert( !ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: failure + smtp.clear(); + RcptToCommand rcpt2( &smtp, "joe@user.org" ); + ts.clear(); + rcpt2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "530 5.7.1 Relaying not allowed!" ); + assert( rcpt2.processResponse( r, &ts ) == false ); + assert( rcpt2.isComplete() ); + assert( !rcpt2.needsResponse() ); + assert( !ts.atLeastOneRecipientWasAccepted() ); + assert( ts.haveRejectedRecipients() ); + assert( ts.rejectedRecipients().count() == 1 ); + assert( ts.rejectedRecipients().front().recipient == "joe@user.org" ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: success and failure combined + smtp.clear(); + RcptToCommand rcpt3( &smtp, "info@example.com" ); + RcptToCommand rcpt4( &smtp, "halloween@microsoft.com" ); + RcptToCommand rcpt5( &smtp, "joe@user.org" ); + ts.clear(); + rcpt3.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "530 5.7.1 Relaying not allowed!" ); + rcpt3.processResponse( r, &ts ); + + rcpt4.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "250 Ok" ); + rcpt4.processResponse( r, &ts ); + + rcpt5.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( ts.haveRejectedRecipients() ); + assert( ts.atLeastOneRecipientWasAccepted() ); + assert( smtp.lastErrorCode == 0 ); + + // + // DATA (init) + // + + smtp.clear(); + DataCommand data( &smtp ); + // flags + assert( !data.closeConnectionOnError() ); + assert( data.mustBeLastInPipeline() ); + assert( !data.mustBeFirstInPipeline() ); + + // initial state + assert( !data.isComplete() ); + assert( !data.doNotExecute( 0 ) ); + assert( !data.needsResponse() ); + + // dynamics: success + ts.clear(); + assert( data.nextCommandLine( &ts ) == "DATA\r\n" ); + assert( data.isComplete() ); + assert( data.needsResponse() ); + assert( ts.dataCommandIssued() ); + assert( !ts.dataCommandSucceeded() ); + r.clear(); + r.parseLine( "354 Send data, end in ." ); + assert( data.processResponse( r, &ts ) == true ); + assert( !data.needsResponse() ); + assert( ts.dataCommandSucceeded() ); + assert( ts.dataResponse() == r ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: failure + smtp.clear(); + DataCommand data2( &smtp ); + ts.clear(); + data2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "551 No valid recipients" ); + assert( data2.processResponse( r, &ts ) == false ); + assert( !data2.needsResponse() ); + assert( !ts.dataCommandSucceeded() ); + assert( ts.dataResponse() == r ); + assert( smtp.lastErrorCode == 0 ); + + // + // DATA (transfer) + // + + TransferCommand xfer( &smtp, 0 ); + // flags + assert( !xfer.closeConnectionOnError() ); + assert( !xfer.mustBeLastInPipeline() ); + assert( xfer.mustBeFirstInPipeline() ); + + // initial state + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + + // dynamics 1: DATA command failed + ts.clear(); + r.clear(); + r.parseLine( "551 no valid recipients" ); + ts.setDataCommandIssued( true ); + ts.setDataCommandSucceeded( false, r ); + assert( xfer.doNotExecute( &ts ) ); + + // dynamics 2: some recipients rejected, but not all + smtp.clear(); + TransferCommand xfer2( &smtp, 0 ); + ts.clear(); + ts.setRecipientAccepted(); + ts.addRejectedRecipient( "joe@user.org", "No relaying allowed" ); + ts.setDataCommandIssued( true ); + r.clear(); + r.parseLine( "354 go on" ); + ts.setDataCommandSucceeded( true, r ); + // ### will change with allow-partial-delivery option: + assert( xfer.doNotExecute( &ts ) ); + + // successful dynamics with all combinations of: + enum { + EndInLF = 1, + PerformDotStuff = 2, + UngetLast = 4, + Preloading = 8, + Error = 16, + EndOfOptions = 32 + }; + for ( unsigned int i = 0 ; i < EndOfOptions ; ++i ) + checkSuccessfulTransferCommand( i & Error, i & Preloading, i & UngetLast, + i & PerformDotStuff, i & EndInLF ); + + // + // NOOP + // + + smtp.clear(); + NoopCommand noop( &smtp ); + // flags + assert( !noop.closeConnectionOnError() ); + assert( noop.mustBeLastInPipeline() ); + assert( !noop.mustBeFirstInPipeline() ); + + // initial state + assert( !noop.isComplete() ); + assert( !noop.doNotExecute( &ts ) ); + assert( !noop.needsResponse() ); + + // dynamics: success (failure is tested with RSET) + assert( noop.nextCommandLine( 0 ) == "NOOP\r\n" ); + assert( noop.isComplete() ); + assert( noop.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( noop.processResponse( r, 0 ) == true ); + assert( noop.isComplete() ); + assert( !noop.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // + // RSET + // + + smtp.clear(); + RsetCommand rset( &smtp ); + // flags + assert( rset.closeConnectionOnError() ); + assert( !rset.mustBeLastInPipeline() ); + assert( !rset.mustBeFirstInPipeline() ); + + // initial state + assert( !rset.isComplete() ); + assert( !rset.doNotExecute( &ts ) ); + assert( !rset.needsResponse() ); + + // dynamics: failure (success is tested with NOOP/QUIT) + assert( rset.nextCommandLine( 0 ) == "RSET\r\n" ); + assert( rset.isComplete() ); + assert( rset.needsResponse() ); + r.clear(); + r.parseLine( "502 command not implemented" ); + assert( rset.processResponse( r, 0 ) == false ); + assert( rset.isComplete() ); + assert( !rset.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); // an RSET failure isn't worth it, is it? + assert( smtp.lastErrorMessage.isNull() ); + + // + // QUIT + // + + smtp.clear(); + QuitCommand quit( &smtp ); + // flags + assert( quit.closeConnectionOnError() ); + assert( quit.mustBeLastInPipeline() ); + assert( !quit.mustBeFirstInPipeline() ); + + // initial state + assert( !quit.isComplete() ); + assert( !quit.doNotExecute( 0 ) ); + assert( !quit.needsResponse() ); + + // dynamics 1: success + assert( quit.nextCommandLine( 0 ) == "QUIT\r\n" ); + assert( quit.isComplete() ); + assert( quit.needsResponse() ); + r.clear(); + r.parseLine( "221 Goodbye" ); + assert( quit.processResponse( r, 0 ) == true ); + assert( quit.isComplete() ); + assert( !quit.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 2: success + smtp.clear(); + QuitCommand quit2( &smtp ); + quit2.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command" ); + assert( quit2.processResponse( r, 0 ) == false ); + assert( quit2.isComplete() ); + assert( !quit2.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); // an QUIT failure isn't worth it, is it? + assert( smtp.lastErrorMessage.isNull() ); +#endif + + return 0; +} + +void checkSuccessfulTransferCommand( bool error, bool preload, bool ungetLast, + bool slaveDotStuff, bool mailEndsInNewline ) { + kdDebug() << " ===== checkTransferCommand( " + << error << ", " + << preload << ", " + << ungetLast << ", " + << slaveDotStuff << ", " + << mailEndsInNewline << " ) =====" << endl; + + SMTPProtocol smtp; + if ( slaveDotStuff ) + smtp.metadata["lf2crlf+dotstuff"] = "slave"; + + Response r; + + const char * s_pre = slaveDotStuff ? + mailEndsInNewline ? foobarbaz_lf : foobarbaz + : + mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; + const unsigned int s_pre_len = tqstrlen( s_pre ); + + const char * s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; + //const unsigned int s_post_len = tqstrlen( s_post ); + + TransferCommand xfer( &smtp, preload ? s_post : 0 ); + + TransactionState ts; + ts.setRecipientAccepted(); + ts.setDataCommandIssued( true ); + r.clear(); + r.parseLine( "354 ok" ); + ts.setDataCommandSucceeded( true, r ); + assert( !xfer.doNotExecute( &ts ) ); + if ( preload ) { + assert( xfer.nextCommandLine( &ts ) == s_post ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + } + smtp.nextData.duplicate( s_pre, s_pre_len ); + smtp.nextDataReturnCode = s_pre_len; + assert( xfer.nextCommandLine( &ts ) == s_post ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + smtp.nextData.resize( 0 ); + smtp.nextDataReturnCode = 0; + if ( ungetLast ) { + xfer.ungetCommandLine( xfer.nextCommandLine( &ts ), &ts ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.complete() ); + smtp.nextDataReturnCode = -1; // double read -> error + } + if ( mailEndsInNewline ) + assert( xfer.nextCommandLine( &ts ) == ".\r\n" ); + else + assert( xfer.nextCommandLine( &ts ) == "\r\n.\r\n" ); + assert( xfer.isComplete() ); + assert( xfer.needsResponse() ); + assert( !ts.complete() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + r.clear(); + if ( error ) { + r.parseLine( "552 Exceeded storage allocation" ); + assert( xfer.processResponse( r, &ts ) == false ); + assert( !xfer.needsResponse() ); + assert( ts.complete() ); + assert( ts.failed() ); + assert( smtp.lastErrorCode == TDEIO::ERR_DISK_FULL ); + } else { + r.parseLine( "250 Message accepted" ); + assert( xfer.processResponse( r, &ts ) == true ); + assert( !xfer.needsResponse() ); + assert( ts.complete() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + } +}; + +#define NDEBUG + +#include "command.cpp" +#include "response.cpp" +#include "transactionstate.cpp" diff --git a/tdeioslave/smtp/test_headergeneration.cc b/tdeioslave/smtp/test_headergeneration.cc deleted file mode 100644 index 83d999c4a..000000000 --- a/tdeioslave/smtp/test_headergeneration.cc +++ /dev/null @@ -1,86 +0,0 @@ -#include "request.h" - -//#include - -//using std::cout; -//using std::endl; - -int main( int , char ** ) { - static TQCString expected = - "From: mutz@kde.org\r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: Marc Mutz \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: \"Mutz, Marc\" \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" - "Subject: missing subject\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: mutz@kde.org\r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: Marc Mutz \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: \"Mutz, Marc\" \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n" - "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" - "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" - "To: joe@user.org,\r\n" - "\tvalentine@14th.february.org\r\n" - "Cc: boss@example.com\r\n" - "\n"; - - KioSMTP::Request request; - TQCString result; - - request.setEmitHeaders( true ); - request.setFromAddress( "mutz@kde.org" ); - request.addTo( "joe@user.org" ); - request.addTo( "valentine@14th.february.org" ); - request.addCc( "boss@example.com" ); - - result += request.headerFields() + '\n'; - result += request.headerFields( "Marc Mutz" ) + '\n'; - result += request.headerFields( "Mutz, Marc" ) + '\n'; - result += request.headerFields( "Marc Mötz" ) + '\n'; - - request.setSubject( "Blödes Subject" ); - - result += request.headerFields() + '\n'; - result += request.headerFields( "Marc Mutz" ) + '\n'; - result += request.headerFields( "Mutz, Marc" ) + '\n'; - result += request.headerFields( "Marc Mötz" ) + '\n'; - - //cout << "Result:\n" << result.data() << endl; - - return result == expected ? 0 : 1 ; -} - -#include "request.cc" - diff --git a/tdeioslave/smtp/test_headergeneration.cpp b/tdeioslave/smtp/test_headergeneration.cpp new file mode 100644 index 000000000..9e4cb971f --- /dev/null +++ b/tdeioslave/smtp/test_headergeneration.cpp @@ -0,0 +1,86 @@ +#include "request.h" + +//#include + +//using std::cout; +//using std::endl; + +int main( int , char ** ) { + static TQCString expected = + "From: mutz@kde.org\r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: Marc Mutz \r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: \"Mutz, Marc\" \r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: mutz@kde.org\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: Marc Mutz \r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: \"Mutz, Marc\" \r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= \r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n"; + + KioSMTP::Request request; + TQCString result; + + request.setEmitHeaders( true ); + request.setFromAddress( "mutz@kde.org" ); + request.addTo( "joe@user.org" ); + request.addTo( "valentine@14th.february.org" ); + request.addCc( "boss@example.com" ); + + result += request.headerFields() + '\n'; + result += request.headerFields( "Marc Mutz" ) + '\n'; + result += request.headerFields( "Mutz, Marc" ) + '\n'; + result += request.headerFields( "Marc Mötz" ) + '\n'; + + request.setSubject( "Blödes Subject" ); + + result += request.headerFields() + '\n'; + result += request.headerFields( "Marc Mutz" ) + '\n'; + result += request.headerFields( "Mutz, Marc" ) + '\n'; + result += request.headerFields( "Marc Mötz" ) + '\n'; + + //cout << "Result:\n" << result.data() << endl; + + return result == expected ? 0 : 1 ; +} + +#include "request.cpp" + diff --git a/tdeioslave/smtp/test_responseparser.cc b/tdeioslave/smtp/test_responseparser.cc deleted file mode 100644 index e251aa291..000000000 --- a/tdeioslave/smtp/test_responseparser.cc +++ /dev/null @@ -1,107 +0,0 @@ -#include "response.h" -#include - -static const TQCString singleLineResponseCRLF = "250 OK\r\n"; -static const TQCString singleLineResponse = "250 OK"; - -static const TQCString multiLineResponse[] = { - "250-ktown.kde.org\r\n", - "250-STARTTLS\r\n", - "250-AUTH PLAIN DIGEST-MD5\r\n", - "250 PIPELINING\r\n" -}; -static const unsigned int numMultiLineLines = sizeof multiLineResponse / sizeof *multiLineResponse ; - -int main ( int, char** ) { - - KioSMTP::Response r; - assert( r.isValid() ); - assert( r.lines().empty() ); - assert( r.isWellFormed() ); - assert( r.code() == 0 ); - assert( r.isUnknown() ); - assert( !r.isComplete() ); - assert( !r.isOk() ); - r.parseLine( singleLineResponseCRLF.data(), - singleLineResponseCRLF.length() ); - assert( r.isWellFormed() ); - assert( r.isComplete() ); - assert( r.isValid() ); - assert( r.isPositive() ); - assert( r.isOk() ); - assert( r.code() == 250 ); - assert( r.errorCode() == 0 ); - assert( r.first() == 2 ); - assert( r.second() == 5 ); - assert( r.third() == 0 ); - assert( r.lines().count() == 1 ); - assert( r.lines().front() == "OK" ); - r.parseLine( singleLineResponse.data(), - singleLineResponse.length() ); - assert( !r.isValid() ); - r.clear(); - assert( r.isValid() ); - assert( r.lines().empty() ); - - r.parseLine( singleLineResponse.data(), - singleLineResponse.length() ); - assert( r.isWellFormed() ); - assert( r.isComplete() ); - assert( r.isValid() ); - assert( r.isPositive() ); - assert( r.isOk() ); - assert( r.code() == 250 ); - assert( r.first() == 2 ); - assert( r.second() == 5 ); - assert( r.third() == 0 ); - assert( r.lines().count() == 1 ); - assert( r.lines().front() == "OK" ); - r.parseLine( singleLineResponse.data(), - singleLineResponse.length() ); - assert( !r.isValid() ); - r.clear(); - assert( r.isValid() ); - - for ( unsigned int i = 0 ; i < numMultiLineLines ; ++i ) { - r.parseLine( multiLineResponse[i].data(), - multiLineResponse[i].length() ); - assert( r.isWellFormed() ); - if ( i < numMultiLineLines-1 ) - assert( !r.isComplete() ); - else - assert( r.isComplete() ); - assert( r.isValid() ); - assert( r.isPositive() ); - assert( r.code() == 250 ); - assert( r.first() == 2 ); - assert( r.second() == 5 ); - assert( r.third() == 0 ); - assert( r.lines().count() == i + 1 ); - } - assert( r.lines().back() == "PIPELINING" ); - - r.clear(); - r.parseLine( "230", 3 ); - assert( r.isValid() ); - assert( r.isWellFormed() ); // even though it isn't ;-) - assert( r.code() == 230 ); - assert( r.lines().count() == 1 ); - assert( r.lines().front().isNull() ); - - r.clear(); - r.parseLine( "230\r\n", 5 ); - assert( r.isValid() ); - assert( r.isWellFormed() ); // even though it isn't ;-) - assert( r.code() == 230 ); - assert( r.lines().count() == 1 ); - assert( r.lines().front().isNull() ); - - r.clear(); - r.parseLine( " 23 ok", 6 ); - assert( !r.isValid() ); - assert( !r.isWellFormed() ); - - return 0; -} - -#include "response.cc" diff --git a/tdeioslave/smtp/test_responseparser.cpp b/tdeioslave/smtp/test_responseparser.cpp new file mode 100644 index 000000000..4786bc717 --- /dev/null +++ b/tdeioslave/smtp/test_responseparser.cpp @@ -0,0 +1,107 @@ +#include "response.h" +#include + +static const TQCString singleLineResponseCRLF = "250 OK\r\n"; +static const TQCString singleLineResponse = "250 OK"; + +static const TQCString multiLineResponse[] = { + "250-ktown.kde.org\r\n", + "250-STARTTLS\r\n", + "250-AUTH PLAIN DIGEST-MD5\r\n", + "250 PIPELINING\r\n" +}; +static const unsigned int numMultiLineLines = sizeof multiLineResponse / sizeof *multiLineResponse ; + +int main ( int, char** ) { + + KioSMTP::Response r; + assert( r.isValid() ); + assert( r.lines().empty() ); + assert( r.isWellFormed() ); + assert( r.code() == 0 ); + assert( r.isUnknown() ); + assert( !r.isComplete() ); + assert( !r.isOk() ); + r.parseLine( singleLineResponseCRLF.data(), + singleLineResponseCRLF.length() ); + assert( r.isWellFormed() ); + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.isOk() ); + assert( r.code() == 250 ); + assert( r.errorCode() == 0 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front() == "OK" ); + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( !r.isValid() ); + r.clear(); + assert( r.isValid() ); + assert( r.lines().empty() ); + + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( r.isWellFormed() ); + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.isOk() ); + assert( r.code() == 250 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front() == "OK" ); + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( !r.isValid() ); + r.clear(); + assert( r.isValid() ); + + for ( unsigned int i = 0 ; i < numMultiLineLines ; ++i ) { + r.parseLine( multiLineResponse[i].data(), + multiLineResponse[i].length() ); + assert( r.isWellFormed() ); + if ( i < numMultiLineLines-1 ) + assert( !r.isComplete() ); + else + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.code() == 250 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == i + 1 ); + } + assert( r.lines().back() == "PIPELINING" ); + + r.clear(); + r.parseLine( "230", 3 ); + assert( r.isValid() ); + assert( r.isWellFormed() ); // even though it isn't ;-) + assert( r.code() == 230 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front().isNull() ); + + r.clear(); + r.parseLine( "230\r\n", 5 ); + assert( r.isValid() ); + assert( r.isWellFormed() ); // even though it isn't ;-) + assert( r.code() == 230 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front().isNull() ); + + r.clear(); + r.parseLine( " 23 ok", 6 ); + assert( !r.isValid() ); + assert( !r.isWellFormed() ); + + return 0; +} + +#include "response.cpp" diff --git a/tdeioslave/smtp/transactionstate.cc b/tdeioslave/smtp/transactionstate.cc deleted file mode 100644 index c33d6b639..000000000 --- a/tdeioslave/smtp/transactionstate.cc +++ /dev/null @@ -1,114 +0,0 @@ -/* - transactionstate.cc - - This file is part of tdeio_smtp, the KDE SMTP tdeioslave. - Copyright (c) 2003 Marc Mutz - - 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. - - This program 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 - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - In addition, as a special exception, the copyright holders give - permission to link the code of this program with any edition of - the Qt library by Trolltech AS, Norway (or with modified versions - of Qt that use the same license as Qt), and distribute linked - combinations including the two. You must obey the GNU General - Public License in all respects for all of the code used other than - Qt. If you modify this file, you may extend this exception to - your version of the file, but you are not obligated to do so. If - you do not wish to do so, delete this exception statement from - your version. -*/ - -#include - -#include "transactionstate.h" - -#include -#include - -#include - -namespace KioSMTP { - - void TransactionState::setFailedFatally( int code, const TQString & msg ) { - mFailed = mFailedFatally = true; - mErrorCode = code; - mErrorMessage = msg; - } - - void TransactionState::setMailFromFailed( const TQString & addr, const Response & r ) { - setFailed(); - mErrorCode = TDEIO::ERR_NO_CONTENT; - if ( addr.isEmpty() ) - mErrorMessage = i18n("The server did not accept a blank sender address.\n" - "%1").arg( r.errorMessage() ); - else - mErrorMessage = i18n("The server did not accept the sender address \"%1\".\n" - "%2").arg( addr ).arg( r.errorMessage() ); - } - - void TransactionState::addRejectedRecipient( const RecipientRejection & r ) { - mRejectedRecipients.push_back( r ); - if ( mRcptToDenyIsFailure ) - setFailed(); - } - - void TransactionState::setDataCommandSucceeded( bool succeeded, const Response & r ) { - mDataCommandSucceeded = succeeded; - mDataResponse = r; - if ( !succeeded ) - setFailed(); - else if ( failed() ) - // can happen with pipelining: the server accepts the DATA, but - // we don't want to send the data, so force a connection - // shutdown: - setFailedFatally(); - } - - int TransactionState::errorCode() const { - if ( !failed() ) - return 0; - if ( mErrorCode ) - return mErrorCode; - if ( haveRejectedRecipients() || !dataCommandSucceeded() ) - return TDEIO::ERR_NO_CONTENT; - // ### what else? - return TDEIO::ERR_INTERNAL; - } - - TQString TransactionState::errorMessage() const { - if ( !failed() ) - return TQString::null; - - if ( !mErrorMessage.isEmpty() ) - return mErrorMessage; - - if ( haveRejectedRecipients() ) { - TQString msg = i18n("Message sending failed since the following recipients were rejected by the server:\n" - "%1"); - TQStringList recip; - for ( RejectedRecipientList::const_iterator it = mRejectedRecipients.begin() ; - it != mRejectedRecipients.end() ; ++it ) - recip.push_back( (*it).recipient + " (" + (*it).reason + ')' ); - return msg.arg( recip.join("\n") ); - } - - if ( !dataCommandSucceeded() ) - return i18n("The attempt to start sending the message content failed.\n" - "%1").arg( mDataResponse.errorMessage() ); - - // ### what else? - return i18n("Unhandled error condition. Please send a bug report."); - } - -} diff --git a/tdeioslave/smtp/transactionstate.cpp b/tdeioslave/smtp/transactionstate.cpp new file mode 100644 index 000000000..1d96e44b7 --- /dev/null +++ b/tdeioslave/smtp/transactionstate.cpp @@ -0,0 +1,114 @@ +/* + transactionstate.cpp + + This file is part of tdeio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz + + 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. + + This program 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "transactionstate.h" + +#include +#include + +#include + +namespace KioSMTP { + + void TransactionState::setFailedFatally( int code, const TQString & msg ) { + mFailed = mFailedFatally = true; + mErrorCode = code; + mErrorMessage = msg; + } + + void TransactionState::setMailFromFailed( const TQString & addr, const Response & r ) { + setFailed(); + mErrorCode = TDEIO::ERR_NO_CONTENT; + if ( addr.isEmpty() ) + mErrorMessage = i18n("The server did not accept a blank sender address.\n" + "%1").arg( r.errorMessage() ); + else + mErrorMessage = i18n("The server did not accept the sender address \"%1\".\n" + "%2").arg( addr ).arg( r.errorMessage() ); + } + + void TransactionState::addRejectedRecipient( const RecipientRejection & r ) { + mRejectedRecipients.push_back( r ); + if ( mRcptToDenyIsFailure ) + setFailed(); + } + + void TransactionState::setDataCommandSucceeded( bool succeeded, const Response & r ) { + mDataCommandSucceeded = succeeded; + mDataResponse = r; + if ( !succeeded ) + setFailed(); + else if ( failed() ) + // can happen with pipelining: the server accepts the DATA, but + // we don't want to send the data, so force a connection + // shutdown: + setFailedFatally(); + } + + int TransactionState::errorCode() const { + if ( !failed() ) + return 0; + if ( mErrorCode ) + return mErrorCode; + if ( haveRejectedRecipients() || !dataCommandSucceeded() ) + return TDEIO::ERR_NO_CONTENT; + // ### what else? + return TDEIO::ERR_INTERNAL; + } + + TQString TransactionState::errorMessage() const { + if ( !failed() ) + return TQString::null; + + if ( !mErrorMessage.isEmpty() ) + return mErrorMessage; + + if ( haveRejectedRecipients() ) { + TQString msg = i18n("Message sending failed since the following recipients were rejected by the server:\n" + "%1"); + TQStringList recip; + for ( RejectedRecipientList::const_iterator it = mRejectedRecipients.begin() ; + it != mRejectedRecipients.end() ; ++it ) + recip.push_back( (*it).recipient + " (" + (*it).reason + ')' ); + return msg.arg( recip.join("\n") ); + } + + if ( !dataCommandSucceeded() ) + return i18n("The attempt to start sending the message content failed.\n" + "%1").arg( mDataResponse.errorMessage() ); + + // ### what else? + return i18n("Unhandled error condition. Please send a bug report."); + } + +} diff --git a/tdeioslave/system/Makefile.am b/tdeioslave/system/Makefile.am index da49adca3..71475c4bc 100644 --- a/tdeioslave/system/Makefile.am +++ b/tdeioslave/system/Makefile.am @@ -27,5 +27,5 @@ check: testsystem ./testsystem messages: - $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_system.pot + $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_system.pot diff --git a/tdeioslave/tar/CMakeLists.txt b/tdeioslave/tar/CMakeLists.txt index 30f2f6b1b..f08ac3aa9 100644 --- a/tdeioslave/tar/CMakeLists.txt +++ b/tdeioslave/tar/CMakeLists.txt @@ -35,7 +35,7 @@ tde_create_translated_desktop( set( target tdeio_tar ) tde_add_kpart( ${target} AUTOMOC - SOURCES tar.cc + SOURCES tar.cpp LINK tdeio-shared DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/tdeioslave/tar/Makefile.am b/tdeioslave/tar/Makefile.am index 98acbb726..fdeb13c86 100644 --- a/tdeioslave/tar/Makefile.am +++ b/tdeioslave/tar/Makefile.am @@ -6,7 +6,7 @@ METASOURCES = AUTO kde_module_LTLIBRARIES = tdeio_tar.la -tdeio_tar_la_SOURCES = tar.cc +tdeio_tar_la_SOURCES = tar.cpp tdeio_tar_la_LIBADD = $(LIB_TDESYCOCA) tdeio_tar_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) @@ -21,4 +21,4 @@ kdelnk_DATA = tar.protocol ar.protocol zip.protocol kdelnkdir = $(kde_servicesdir) messages: - $(XGETTEXT) *.cc -o $(podir)/tdeio_tar.pot + $(XGETTEXT) *.cpp -o $(podir)/tdeio_tar.pot diff --git a/tdeioslave/tar/tar.cc b/tdeioslave/tar/tar.cc deleted file mode 100644 index b7ba70687..000000000 --- a/tdeioslave/tar/tar.cc +++ /dev/null @@ -1,621 +0,0 @@ -#include - -#include -#ifdef HAVE_SYS_STAT_H -#include -#endif -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // to be removed - -#include "tar.h" - -using namespace TDEIO; - -extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } - -int kdemain( int argc, char **argv ) -{ - TDEInstance instance( "tdeio_tar" ); - - kdDebug(7109) << "Starting " << getpid() << endl; - - if (argc != 4) - { - fprintf(stderr, "Usage: tdeio_tar protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - ArchiveProtocol slave(argv[2], argv[3]); - slave.dispatchLoop(); - - kdDebug(7109) << "Done" << endl; - return 0; -} - -ArchiveProtocol::ArchiveProtocol( const TQCString &pool, const TQCString &app ) : SlaveBase( "tar", pool, app ) -{ - kdDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol" << endl; - m_archiveFile = 0L; -} - -ArchiveProtocol::~ArchiveProtocol() -{ - delete m_archiveFile; -} - -bool ArchiveProtocol::checkNewFile( const KURL & url, TQString & path, TDEIO::Error& errorNum ) -{ - TQString fullPath = url.path(); - kdDebug(7109) << "ArchiveProtocol::checkNewFile " << fullPath << endl; - - - // Are we already looking at that file ? - if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) ) - { - // Has it changed ? - KDE_struct_stat statbuf; - if ( KDE_stat( TQFile::encodeName( m_archiveName ), &statbuf ) == 0 ) - { - if ( m_mtime == statbuf.st_mtime ) - { - path = fullPath.mid( m_archiveName.length() ); - kdDebug(7109) << "ArchiveProtocol::checkNewFile returning " << path << endl; - return true; - } - } - } - kdDebug(7109) << "Need to open a new file" << endl; - - // Close previous file - if ( m_archiveFile ) - { - m_archiveFile->close(); - delete m_archiveFile; - m_archiveFile = 0L; - } - - // Find where the tar file is in the full path - int pos = 0; - TQString archiveFile; - path = TQString::null; - - int len = fullPath.length(); - if ( len != 0 && fullPath[ len - 1 ] != '/' ) - fullPath += '/'; - - kdDebug(7109) << "the full path is " << fullPath << endl; - KDE_struct_stat statbuf; - statbuf.st_mode = 0; // be sure to clear the directory bit - while ( (pos=fullPath.find( '/', pos+1 )) != -1 ) - { - TQString tryPath = fullPath.left( pos ); - kdDebug(7109) << fullPath << " trying " << tryPath << endl; - if ( KDE_stat( TQFile::encodeName(tryPath), &statbuf ) == -1 ) - { - // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore - break; - } - if ( !S_ISDIR(statbuf.st_mode) ) - { - archiveFile = tryPath; - m_mtime = statbuf.st_mtime; - path = fullPath.mid( pos + 1 ); - kdDebug(7109) << "fullPath=" << fullPath << " path=" << path << endl; - len = path.length(); - if ( len > 1 ) - { - if ( path[ len - 1 ] == '/' ) - path.truncate( len - 1 ); - } - else - path = TQString::fromLatin1("/"); - kdDebug(7109) << "Found. archiveFile=" << archiveFile << " path=" << path << endl; - break; - } - } - if ( archiveFile.isEmpty() ) - { - kdDebug(7109) << "ArchiveProtocol::checkNewFile: not found" << endl; - if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory? - { - // Too bad, it is a directory, not an archive. - kdDebug(7109) << "Path is a directory, not an archive." << endl; - errorNum = TDEIO::ERR_IS_DIRECTORY; - } - else - errorNum = TDEIO::ERR_DOES_NOT_EXIST; - return false; - } - - // Open new file - if ( url.protocol() == "tar" ) { - kdDebug(7109) << "Opening KTar on " << archiveFile << endl; - m_archiveFile = new KTar( archiveFile ); - } else if ( url.protocol() == "ar" ) { - kdDebug(7109) << "Opening KAr on " << archiveFile << endl; - m_archiveFile = new KAr( archiveFile ); - } else if ( url.protocol() == "zip" ) { - kdDebug(7109) << "Opening KZip on " << archiveFile << endl; - m_archiveFile = new KZip( archiveFile ); - } else { - kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave" << endl; - errorNum = TDEIO::ERR_UNSUPPORTED_PROTOCOL; - return false; - } - - if ( !m_archiveFile->open( IO_ReadOnly ) ) - { - kdDebug(7109) << "Opening " << archiveFile << "failed." << endl; - delete m_archiveFile; - m_archiveFile = 0L; - errorNum = TDEIO::ERR_CANNOT_OPEN_FOR_READING; - return false; - } - - m_archiveName = archiveFile; - return true; -} - - -void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry ) -{ - UDSAtom atom; - entry.clear(); - atom.m_uds = UDS_NAME; - atom.m_str = remoteEncoding()->decode(archiveEntry->name().local8Bit()); - entry.append(atom); - - atom.m_uds = UDS_FILE_TYPE; - atom.m_long = archiveEntry->permissions() & S_IFMT; // keep file type only - entry.append( atom ); - - atom.m_uds = UDS_SIZE; - atom.m_long = archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L ; - entry.append( atom ); - - atom.m_uds = UDS_MODIFICATION_TIME; - atom.m_long = archiveEntry->date(); - entry.append( atom ); - - atom.m_uds = UDS_ACCESS; - atom.m_long = archiveEntry->permissions() & 07777; // keep permissions only - entry.append( atom ); - - atom.m_uds = UDS_USER; - atom.m_str = remoteEncoding()->decode(archiveEntry->user().local8Bit()); - entry.append( atom ); - - atom.m_uds = UDS_GROUP; - atom.m_str = remoteEncoding()->decode(archiveEntry->group().local8Bit()); - entry.append( atom ); - - atom.m_uds = UDS_LINK_DEST; - atom.m_str = remoteEncoding()->decode(archiveEntry->symlink().local8Bit()); - entry.append( atom ); -} - -void ArchiveProtocol::listDir( const KURL & url ) -{ - kdDebug( 7109 ) << "ArchiveProtocol::listDir " << url << endl; - - TQString path; - TDEIO::Error errorNum; - if ( !checkNewFile( url, path, errorNum ) ) - { - if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) - { - // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) - // Therefore give a more specific error message - error( TDEIO::ERR_SLAVE_DEFINED, - i18n( "Could not open the file, probably due to an unsupported file format.\n%1") - .arg( url.prettyURL() ) ); - return; - } - else if ( errorNum != ERR_IS_DIRECTORY ) - { - // We have any other error - error( errorNum, url.prettyURL() ); - return; - } - // It's a real dir -> redirect - KURL redir; - redir.setPath( url.path() ); - kdDebug( 7109 ) << "Ok, redirection to " << redir.url() << endl; - redirection( redir ); - finished(); - // And let go of the tar file - for people who want to unmount a cdrom after that - delete m_archiveFile; - m_archiveFile = 0L; - return; - } - - if ( path.isEmpty() ) - { - KURL redir( url.protocol() + TQString::fromLatin1( ":/") ); - kdDebug( 7109 ) << "url.path()==" << url.path() << endl; - redir.setPath( url.path() + TQString::fromLatin1("/") ); - kdDebug( 7109 ) << "ArchiveProtocol::listDir: redirection " << redir.url() << endl; - redirection( redir ); - finished(); - return; - } - - path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); - - kdDebug( 7109 ) << "checkNewFile done" << endl; - const KArchiveDirectory* root = m_archiveFile->directory(); - const KArchiveDirectory* dir; - if (!path.isEmpty() && path != "/") - { - kdDebug(7109) << TQString(TQString("Looking for entry %1").arg(path)) << endl; - const KArchiveEntry* e = root->entry( path ); - if ( !e ) - { - error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); - return; - } - if ( ! e->isDirectory() ) - { - error( TDEIO::ERR_IS_FILE, url.prettyURL() ); - return; - } - dir = (KArchiveDirectory*)e; - } else { - dir = root; - } - - TQStringList l = dir->entries(); - totalSize( l.count() ); - - UDSEntry entry; - TQStringList::Iterator it = l.begin(); - for( ; it != l.end(); ++it ) - { - kdDebug(7109) << (*it) << endl; - const KArchiveEntry* archiveEntry = dir->entry( (*it) ); - - createUDSEntry( archiveEntry, entry ); - - listEntry( entry, false ); - } - - listEntry( entry, true ); // ready - - finished(); - - kdDebug( 7109 ) << "ArchiveProtocol::listDir done" << endl; -} - -void ArchiveProtocol::stat( const KURL & url ) -{ - TQString path; - UDSEntry entry; - TDEIO::Error errorNum; - if ( !checkNewFile( url, path, errorNum ) ) - { - // We may be looking at a real directory - this happens - // when pressing up after being in the root of an archive - if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) - { - // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) - // Therefore give a more specific error message - error( TDEIO::ERR_SLAVE_DEFINED, - i18n( "Could not open the file, probably due to an unsupported file format.\n%1") - .arg( url.prettyURL() ) ); - return; - } - else if ( errorNum != ERR_IS_DIRECTORY ) - { - // We have any other error - error( errorNum, url.prettyURL() ); - return; - } - // Real directory. Return just enough information for KRun to work - UDSAtom atom; - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = url.fileName(); - entry.append( atom ); - kdDebug( 7109 ) << "ArchiveProtocol::stat returning name=" << url.fileName() << endl; - - KDE_struct_stat buff; - if ( KDE_stat( TQFile::encodeName( url.path() ), &buff ) == -1 ) - { - // Should not happen, as the file was already stated by checkNewFile - error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL() ); - return; - } - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = buff.st_mode & S_IFMT; - entry.append( atom ); - - statEntry( entry ); - - finished(); - - // And let go of the tar file - for people who want to unmount a cdrom after that - delete m_archiveFile; - m_archiveFile = 0L; - return; - } - - const KArchiveDirectory* root = m_archiveFile->directory(); - const KArchiveEntry* archiveEntry; - if ( path.isEmpty() ) - { - path = TQString::fromLatin1( "/" ); - archiveEntry = root; - } else { - path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); - archiveEntry = root->entry( path ); - } - if ( !archiveEntry ) - { - error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); - return; - } - - createUDSEntry( archiveEntry, entry ); - statEntry( entry ); - - finished(); -} - -void ArchiveProtocol::get( const KURL & url ) -{ - kdDebug( 7109 ) << "ArchiveProtocol::get " << url << endl; - - TQString path; - TDEIO::Error errorNum; - if ( !checkNewFile( url, path, errorNum ) ) - { - if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) - { - // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) - // Therefore give a more specific error message - error( TDEIO::ERR_SLAVE_DEFINED, - i18n( "Could not open the file, probably due to an unsupported file format.\n%1") - .arg( url.prettyURL() ) ); - return; - } - else - { - // We have any other error - error( errorNum, url.prettyURL() ); - return; - } - } - - path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); - - const KArchiveDirectory* root = m_archiveFile->directory(); - const KArchiveEntry* archiveEntry = root->entry( path ); - - if ( !archiveEntry ) - { - error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); - return; - } - if ( archiveEntry->isDirectory() ) - { - error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); - return; - } - const KArchiveFile* archiveFileEntry = static_cast(archiveEntry); - if ( !archiveEntry->symlink().isEmpty() ) - { - kdDebug(7109) << "Redirection to " << archiveEntry->symlink() << endl; - KURL realURL; - if (archiveEntry->symlink().startsWith("/")) { // absolute path - realURL.setPath(archiveEntry->symlink() ); // goes out of tar:/, back into file: - } else { - realURL = KURL( url, archiveEntry->symlink() ); - } - kdDebug(7109) << "realURL= " << realURL << endl; - redirection( realURL ); - finished(); - return; - } - - //kdDebug(7109) << "Preparing to get the archive data" << endl; - - /* - * The easy way would be to get the data by calling archiveFileEntry->data() - * However this has drawbacks: - * - the complete file must be read into the memory - * - errors are skipped, resulting in an empty file - */ - - TQIODevice* io = 0; - // Getting the device is hard, as archiveFileEntry->device() is not virtual! - if ( url.protocol() == "tar" ) - { - io = archiveFileEntry->device(); - } - else if ( url.protocol() == "ar" ) - { - io = archiveFileEntry->device(); - } - else if ( url.protocol() == "zip" ) - { - io = ((KZipFileEntry*) archiveFileEntry)->device(); - } - else - { - // Wrong protocol? Why was this not catched by checkNewFile? - kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave; " << k_funcinfo << endl; - error( TDEIO::ERR_UNSUPPORTED_PROTOCOL, url.protocol() ); - return; - } - - if (!io) - { - error( TDEIO::ERR_SLAVE_DEFINED, - i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ) - .arg( url.prettyURL() ) ); - return; - } - - if ( !io->open( IO_ReadOnly ) ) - { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL() ); - return; - } - - totalSize( archiveFileEntry->size() ); - - // Size of a TQIODevice read. It must be large enough so that the mime type check will not fail - const int maxSize = 0x100000; // 1MB - - int bufferSize = kMin( maxSize, archiveFileEntry->size() ); - TQByteArray buffer ( bufferSize ); - if ( buffer.isEmpty() && bufferSize > 0 ) - { - // Something went wrong - error( TDEIO::ERR_OUT_OF_MEMORY, url.prettyURL() ); - return; - } - - bool firstRead = true; - - // How much file do we still have to process? - int fileSize = archiveFileEntry->size(); - TDEIO::filesize_t processed = 0; - - while ( !io->atEnd() && fileSize > 0 ) - { - if ( !firstRead ) - { - bufferSize = kMin( maxSize, fileSize ); - buffer.resize( bufferSize, TQGArray::SpeedOptim ); - } - const TQ_LONG read = io->readBlock( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong. - if ( read != bufferSize ) - { - kdWarning(7109) << "Read " << read << " bytes but expected " << bufferSize << endl; - error( TDEIO::ERR_COULD_NOT_READ, url.prettyURL() ); - return; - } - if ( firstRead ) - { - // We use the magic one the first data read - // (As magic detection is about fixed positions, we can be sure that it is enough data.) - KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( buffer, path ); - kdDebug(7109) << "Emitting mimetype " << result->mimeType() << endl; - mimeType( result->mimeType() ); - firstRead = false; - } - data( buffer ); - processed += read; - processedSize( processed ); - fileSize -= bufferSize; - } - io->close(); - delete io; - - data( TQByteArray() ); - - finished(); -} - -/* - In case someone wonders how the old filter stuff looked like : :) -void TARProtocol::slotData(void *_p, int _len) -{ - switch (m_cmd) { - case CMD_PUT: - assert(m_pFilter); - m_pFilter->send(_p, _len); - break; - default: - abort(); - break; - } -} - -void TARProtocol::slotDataEnd() -{ - switch (m_cmd) { - case CMD_PUT: - assert(m_pFilter && m_pJob); - m_pFilter->finish(); - m_pJob->dataEnd(); - m_cmd = CMD_NONE; - break; - default: - abort(); - break; - } -} - -void TARProtocol::jobData(void *_p, int _len) -{ - switch (m_cmd) { - case CMD_GET: - assert(m_pFilter); - m_pFilter->send(_p, _len); - break; - case CMD_COPY: - assert(m_pFilter); - m_pFilter->send(_p, _len); - break; - default: - abort(); - } -} - -void TARProtocol::jobDataEnd() -{ - switch (m_cmd) { - case CMD_GET: - assert(m_pFilter); - m_pFilter->finish(); - dataEnd(); - break; - case CMD_COPY: - assert(m_pFilter); - m_pFilter->finish(); - m_pJob->dataEnd(); - break; - default: - abort(); - } -} - -void TARProtocol::filterData(void *_p, int _len) -{ -debug("void TARProtocol::filterData"); - switch (m_cmd) { - case CMD_GET: - data(_p, _len); - break; - case CMD_PUT: - assert (m_pJob); - m_pJob->data(_p, _len); - break; - case CMD_COPY: - assert(m_pJob); - m_pJob->data(_p, _len); - break; - default: - abort(); - } -} -*/ diff --git a/tdeioslave/tar/tar.cpp b/tdeioslave/tar/tar.cpp new file mode 100644 index 000000000..b7ba70687 --- /dev/null +++ b/tdeioslave/tar/tar.cpp @@ -0,0 +1,621 @@ +#include + +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // to be removed + +#include "tar.h" + +using namespace TDEIO; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + TDEInstance instance( "tdeio_tar" ); + + kdDebug(7109) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: tdeio_tar protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + ArchiveProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7109) << "Done" << endl; + return 0; +} + +ArchiveProtocol::ArchiveProtocol( const TQCString &pool, const TQCString &app ) : SlaveBase( "tar", pool, app ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol" << endl; + m_archiveFile = 0L; +} + +ArchiveProtocol::~ArchiveProtocol() +{ + delete m_archiveFile; +} + +bool ArchiveProtocol::checkNewFile( const KURL & url, TQString & path, TDEIO::Error& errorNum ) +{ + TQString fullPath = url.path(); + kdDebug(7109) << "ArchiveProtocol::checkNewFile " << fullPath << endl; + + + // Are we already looking at that file ? + if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) ) + { + // Has it changed ? + KDE_struct_stat statbuf; + if ( KDE_stat( TQFile::encodeName( m_archiveName ), &statbuf ) == 0 ) + { + if ( m_mtime == statbuf.st_mtime ) + { + path = fullPath.mid( m_archiveName.length() ); + kdDebug(7109) << "ArchiveProtocol::checkNewFile returning " << path << endl; + return true; + } + } + } + kdDebug(7109) << "Need to open a new file" << endl; + + // Close previous file + if ( m_archiveFile ) + { + m_archiveFile->close(); + delete m_archiveFile; + m_archiveFile = 0L; + } + + // Find where the tar file is in the full path + int pos = 0; + TQString archiveFile; + path = TQString::null; + + int len = fullPath.length(); + if ( len != 0 && fullPath[ len - 1 ] != '/' ) + fullPath += '/'; + + kdDebug(7109) << "the full path is " << fullPath << endl; + KDE_struct_stat statbuf; + statbuf.st_mode = 0; // be sure to clear the directory bit + while ( (pos=fullPath.find( '/', pos+1 )) != -1 ) + { + TQString tryPath = fullPath.left( pos ); + kdDebug(7109) << fullPath << " trying " << tryPath << endl; + if ( KDE_stat( TQFile::encodeName(tryPath), &statbuf ) == -1 ) + { + // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore + break; + } + if ( !S_ISDIR(statbuf.st_mode) ) + { + archiveFile = tryPath; + m_mtime = statbuf.st_mtime; + path = fullPath.mid( pos + 1 ); + kdDebug(7109) << "fullPath=" << fullPath << " path=" << path << endl; + len = path.length(); + if ( len > 1 ) + { + if ( path[ len - 1 ] == '/' ) + path.truncate( len - 1 ); + } + else + path = TQString::fromLatin1("/"); + kdDebug(7109) << "Found. archiveFile=" << archiveFile << " path=" << path << endl; + break; + } + } + if ( archiveFile.isEmpty() ) + { + kdDebug(7109) << "ArchiveProtocol::checkNewFile: not found" << endl; + if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory? + { + // Too bad, it is a directory, not an archive. + kdDebug(7109) << "Path is a directory, not an archive." << endl; + errorNum = TDEIO::ERR_IS_DIRECTORY; + } + else + errorNum = TDEIO::ERR_DOES_NOT_EXIST; + return false; + } + + // Open new file + if ( url.protocol() == "tar" ) { + kdDebug(7109) << "Opening KTar on " << archiveFile << endl; + m_archiveFile = new KTar( archiveFile ); + } else if ( url.protocol() == "ar" ) { + kdDebug(7109) << "Opening KAr on " << archiveFile << endl; + m_archiveFile = new KAr( archiveFile ); + } else if ( url.protocol() == "zip" ) { + kdDebug(7109) << "Opening KZip on " << archiveFile << endl; + m_archiveFile = new KZip( archiveFile ); + } else { + kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave" << endl; + errorNum = TDEIO::ERR_UNSUPPORTED_PROTOCOL; + return false; + } + + if ( !m_archiveFile->open( IO_ReadOnly ) ) + { + kdDebug(7109) << "Opening " << archiveFile << "failed." << endl; + delete m_archiveFile; + m_archiveFile = 0L; + errorNum = TDEIO::ERR_CANNOT_OPEN_FOR_READING; + return false; + } + + m_archiveName = archiveFile; + return true; +} + + +void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry ) +{ + UDSAtom atom; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_str = remoteEncoding()->decode(archiveEntry->name().local8Bit()); + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = archiveEntry->permissions() & S_IFMT; // keep file type only + entry.append( atom ); + + atom.m_uds = UDS_SIZE; + atom.m_long = archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L ; + entry.append( atom ); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = archiveEntry->date(); + entry.append( atom ); + + atom.m_uds = UDS_ACCESS; + atom.m_long = archiveEntry->permissions() & 07777; // keep permissions only + entry.append( atom ); + + atom.m_uds = UDS_USER; + atom.m_str = remoteEncoding()->decode(archiveEntry->user().local8Bit()); + entry.append( atom ); + + atom.m_uds = UDS_GROUP; + atom.m_str = remoteEncoding()->decode(archiveEntry->group().local8Bit()); + entry.append( atom ); + + atom.m_uds = UDS_LINK_DEST; + atom.m_str = remoteEncoding()->decode(archiveEntry->symlink().local8Bit()); + entry.append( atom ); +} + +void ArchiveProtocol::listDir( const KURL & url ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::listDir " << url << endl; + + TQString path; + TDEIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( TDEIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else if ( errorNum != ERR_IS_DIRECTORY ) + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + // It's a real dir -> redirect + KURL redir; + redir.setPath( url.path() ); + kdDebug( 7109 ) << "Ok, redirection to " << redir.url() << endl; + redirection( redir ); + finished(); + // And let go of the tar file - for people who want to unmount a cdrom after that + delete m_archiveFile; + m_archiveFile = 0L; + return; + } + + if ( path.isEmpty() ) + { + KURL redir( url.protocol() + TQString::fromLatin1( ":/") ); + kdDebug( 7109 ) << "url.path()==" << url.path() << endl; + redir.setPath( url.path() + TQString::fromLatin1("/") ); + kdDebug( 7109 ) << "ArchiveProtocol::listDir: redirection " << redir.url() << endl; + redirection( redir ); + finished(); + return; + } + + path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + + kdDebug( 7109 ) << "checkNewFile done" << endl; + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveDirectory* dir; + if (!path.isEmpty() && path != "/") + { + kdDebug(7109) << TQString(TQString("Looking for entry %1").arg(path)) << endl; + const KArchiveEntry* e = root->entry( path ); + if ( !e ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( ! e->isDirectory() ) + { + error( TDEIO::ERR_IS_FILE, url.prettyURL() ); + return; + } + dir = (KArchiveDirectory*)e; + } else { + dir = root; + } + + TQStringList l = dir->entries(); + totalSize( l.count() ); + + UDSEntry entry; + TQStringList::Iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + kdDebug(7109) << (*it) << endl; + const KArchiveEntry* archiveEntry = dir->entry( (*it) ); + + createUDSEntry( archiveEntry, entry ); + + listEntry( entry, false ); + } + + listEntry( entry, true ); // ready + + finished(); + + kdDebug( 7109 ) << "ArchiveProtocol::listDir done" << endl; +} + +void ArchiveProtocol::stat( const KURL & url ) +{ + TQString path; + UDSEntry entry; + TDEIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + // We may be looking at a real directory - this happens + // when pressing up after being in the root of an archive + if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( TDEIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else if ( errorNum != ERR_IS_DIRECTORY ) + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + // Real directory. Return just enough information for KRun to work + UDSAtom atom; + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = url.fileName(); + entry.append( atom ); + kdDebug( 7109 ) << "ArchiveProtocol::stat returning name=" << url.fileName() << endl; + + KDE_struct_stat buff; + if ( KDE_stat( TQFile::encodeName( url.path() ), &buff ) == -1 ) + { + // Should not happen, as the file was already stated by checkNewFile + error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL() ); + return; + } + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = buff.st_mode & S_IFMT; + entry.append( atom ); + + statEntry( entry ); + + finished(); + + // And let go of the tar file - for people who want to unmount a cdrom after that + delete m_archiveFile; + m_archiveFile = 0L; + return; + } + + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveEntry* archiveEntry; + if ( path.isEmpty() ) + { + path = TQString::fromLatin1( "/" ); + archiveEntry = root; + } else { + path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + archiveEntry = root->entry( path ); + } + if ( !archiveEntry ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + + createUDSEntry( archiveEntry, entry ); + statEntry( entry ); + + finished(); +} + +void ArchiveProtocol::get( const KURL & url ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::get " << url << endl; + + TQString path; + TDEIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( TDEIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + } + + path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveEntry* archiveEntry = root->entry( path ); + + if ( !archiveEntry ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( archiveEntry->isDirectory() ) + { + error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); + return; + } + const KArchiveFile* archiveFileEntry = static_cast(archiveEntry); + if ( !archiveEntry->symlink().isEmpty() ) + { + kdDebug(7109) << "Redirection to " << archiveEntry->symlink() << endl; + KURL realURL; + if (archiveEntry->symlink().startsWith("/")) { // absolute path + realURL.setPath(archiveEntry->symlink() ); // goes out of tar:/, back into file: + } else { + realURL = KURL( url, archiveEntry->symlink() ); + } + kdDebug(7109) << "realURL= " << realURL << endl; + redirection( realURL ); + finished(); + return; + } + + //kdDebug(7109) << "Preparing to get the archive data" << endl; + + /* + * The easy way would be to get the data by calling archiveFileEntry->data() + * However this has drawbacks: + * - the complete file must be read into the memory + * - errors are skipped, resulting in an empty file + */ + + TQIODevice* io = 0; + // Getting the device is hard, as archiveFileEntry->device() is not virtual! + if ( url.protocol() == "tar" ) + { + io = archiveFileEntry->device(); + } + else if ( url.protocol() == "ar" ) + { + io = archiveFileEntry->device(); + } + else if ( url.protocol() == "zip" ) + { + io = ((KZipFileEntry*) archiveFileEntry)->device(); + } + else + { + // Wrong protocol? Why was this not catched by checkNewFile? + kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave; " << k_funcinfo << endl; + error( TDEIO::ERR_UNSUPPORTED_PROTOCOL, url.protocol() ); + return; + } + + if (!io) + { + error( TDEIO::ERR_SLAVE_DEFINED, + i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ) + .arg( url.prettyURL() ) ); + return; + } + + if ( !io->open( IO_ReadOnly ) ) + { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL() ); + return; + } + + totalSize( archiveFileEntry->size() ); + + // Size of a TQIODevice read. It must be large enough so that the mime type check will not fail + const int maxSize = 0x100000; // 1MB + + int bufferSize = kMin( maxSize, archiveFileEntry->size() ); + TQByteArray buffer ( bufferSize ); + if ( buffer.isEmpty() && bufferSize > 0 ) + { + // Something went wrong + error( TDEIO::ERR_OUT_OF_MEMORY, url.prettyURL() ); + return; + } + + bool firstRead = true; + + // How much file do we still have to process? + int fileSize = archiveFileEntry->size(); + TDEIO::filesize_t processed = 0; + + while ( !io->atEnd() && fileSize > 0 ) + { + if ( !firstRead ) + { + bufferSize = kMin( maxSize, fileSize ); + buffer.resize( bufferSize, TQGArray::SpeedOptim ); + } + const TQ_LONG read = io->readBlock( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong. + if ( read != bufferSize ) + { + kdWarning(7109) << "Read " << read << " bytes but expected " << bufferSize << endl; + error( TDEIO::ERR_COULD_NOT_READ, url.prettyURL() ); + return; + } + if ( firstRead ) + { + // We use the magic one the first data read + // (As magic detection is about fixed positions, we can be sure that it is enough data.) + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( buffer, path ); + kdDebug(7109) << "Emitting mimetype " << result->mimeType() << endl; + mimeType( result->mimeType() ); + firstRead = false; + } + data( buffer ); + processed += read; + processedSize( processed ); + fileSize -= bufferSize; + } + io->close(); + delete io; + + data( TQByteArray() ); + + finished(); +} + +/* + In case someone wonders how the old filter stuff looked like : :) +void TARProtocol::slotData(void *_p, int _len) +{ + switch (m_cmd) { + case CMD_PUT: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + default: + abort(); + break; + } +} + +void TARProtocol::slotDataEnd() +{ + switch (m_cmd) { + case CMD_PUT: + assert(m_pFilter && m_pJob); + m_pFilter->finish(); + m_pJob->dataEnd(); + m_cmd = CMD_NONE; + break; + default: + abort(); + break; + } +} + +void TARProtocol::jobData(void *_p, int _len) +{ + switch (m_cmd) { + case CMD_GET: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + case CMD_COPY: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + default: + abort(); + } +} + +void TARProtocol::jobDataEnd() +{ + switch (m_cmd) { + case CMD_GET: + assert(m_pFilter); + m_pFilter->finish(); + dataEnd(); + break; + case CMD_COPY: + assert(m_pFilter); + m_pFilter->finish(); + m_pJob->dataEnd(); + break; + default: + abort(); + } +} + +void TARProtocol::filterData(void *_p, int _len) +{ +debug("void TARProtocol::filterData"); + switch (m_cmd) { + case CMD_GET: + data(_p, _len); + break; + case CMD_PUT: + assert (m_pJob); + m_pJob->data(_p, _len); + break; + case CMD_COPY: + assert(m_pJob); + m_pJob->data(_p, _len); + break; + default: + abort(); + } +} +*/ diff --git a/tdeioslave/trash/DESIGN b/tdeioslave/trash/DESIGN index 2bb6f19df..9420c53ce 100644 --- a/tdeioslave/trash/DESIGN +++ b/tdeioslave/trash/DESIGN @@ -13,7 +13,7 @@ BUGS TODO ==== -* Clean up konq_popupmenu.cc for Type=Link URL=trash:/ :( +* Clean up konq_popupmenu.cpp for Type=Link URL=trash:/ :( * Also, provide metainfo for trash contents for that desktop link. => maybe we need a new mimetype? Like application/x-trash-desktop, inheriting application/x-desktop. diff --git a/tdeioslave/trash/Makefile.am b/tdeioslave/trash/Makefile.am index 5d0d9b3e6..1c2ec23cc 100644 --- a/tdeioslave/trash/Makefile.am +++ b/tdeioslave/trash/Makefile.am @@ -31,7 +31,7 @@ testtrash_LDFLAGS = $(all_libraries) TESTS = testtrash messages: - $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_trash.pot + $(XGETTEXT) `find . -name "*.cpp" -o -name "*.h"` -o $(podir)/tdeio_trash.pot # ktrashpropsdlgplugin target noinst_HEADERS = ktrashpropsdlgplugin.h discspaceutil.h trash_constant.h diff --git a/tdeioslave/trash/trashimpl.cpp b/tdeioslave/trash/trashimpl.cpp index c2fc53352..40fc8e6ce 100644 --- a/tdeioslave/trash/trashimpl.cpp +++ b/tdeioslave/trash/trashimpl.cpp @@ -130,7 +130,7 @@ bool TrashImpl::init() return false; // Check the trash directory and its info and files subdirs - // see also kdesktop/init.cc for first time initialization + // see also kdesktop/init.cpp for first time initialization m_initStatus = InitError; // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default. const TQString xdgDataDir = TDEGlobal::dirs()->localxdgdatadir(); -- cgit v1.2.3