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/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 +++++ 22 files changed, 2915 insertions(+), 2915 deletions(-) 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 (limited to 'tdeioslave/smtp') 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."); + } + +} -- cgit v1.2.1