summaryrefslogtreecommitdiffstats
path: root/kioslave/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/smtp')
-rw-r--r--kioslave/smtp/Makefile.am37
-rw-r--r--kioslave/smtp/TODO11
-rw-r--r--kioslave/smtp/capabilities.cc143
-rw-r--r--kioslave/smtp/capabilities.h77
-rw-r--r--kioslave/smtp/command.cc606
-rw-r--r--kioslave/smtp/command.h283
-rw-r--r--kioslave/smtp/compliance.txt33
-rw-r--r--kioslave/smtp/interactivesmtpserver.cc127
-rw-r--r--kioslave/smtp/interactivesmtpserver.h121
-rw-r--r--kioslave/smtp/request.cc189
-rw-r--r--kioslave/smtp/request.h100
-rw-r--r--kioslave/smtp/response.cc160
-rw-r--r--kioslave/smtp/response.h125
-rw-r--r--kioslave/smtp/smtp.cc647
-rw-r--r--kioslave/smtp/smtp.h146
-rw-r--r--kioslave/smtp/smtp.protocol16
-rw-r--r--kioslave/smtp/smtps.protocol15
-rw-r--r--kioslave/smtp/test_commands.cc728
-rw-r--r--kioslave/smtp/test_headergeneration.cc86
-rw-r--r--kioslave/smtp/test_responseparser.cc107
-rw-r--r--kioslave/smtp/transactionstate.cc114
-rw-r--r--kioslave/smtp/transactionstate.h185
22 files changed, 4056 insertions, 0 deletions
diff --git a/kioslave/smtp/Makefile.am b/kioslave/smtp/Makefile.am
new file mode 100644
index 000000000..4ebe8ddd8
--- /dev/null
+++ b/kioslave/smtp/Makefile.am
@@ -0,0 +1,37 @@
+
+INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes)
+
+kde_module_LTLIBRARIES = kio_smtp.la
+
+kio_smtp_la_SOURCES = smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc
+kio_smtp_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS)
+kio_smtp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+noinst_HEADERS = smtp.h request.h response.h capabilities.h command.h transactionstate.h
+
+kdelnk_DATA = smtp.protocol smtps.protocol
+kdelnkdir = $(kde_servicesdir)
+
+TESTS = test_headergeneration test_responseparser test_commands
+
+check_PROGRAMS = $(TESTS) interactivesmtpserver
+
+test_headergeneration_SOURCES = test_headergeneration.cc
+test_headergeneration_LDADD = $(LIB_KDECORE)
+test_headergeneration_LDFLAGS = $(all_libraries)
+
+test_responseparser_SOURCES = test_responseparser.cc
+test_responseparser_LDADD = $(LIB_KDECORE)
+test_responseparser_LDFLAGS = $(all_libraries)
+
+test_commands_SOURCES = test_commands.cc
+test_commands_LDADD = $(kio_smtp_la_LIBADD)
+test_commands_LDFLAGS = $(all_libraries)
+
+interactivesmtpserver_SOURCES = interactivesmtpserver.cc
+interactivesmtpserver_LDADD = $(LIB_QT)
+interactivesmtpserver_LDFLAGS = $(all_libraries)
+interactivesmtpserver_METASOURCES = AUTO
+
+messages:
+ $(XGETTEXT) *.cc -o $(podir)/kio_smtp.pot
diff --git a/kioslave/smtp/TODO b/kioslave/smtp/TODO
new file mode 100644
index 000000000..cad79f139
--- /dev/null
+++ b/kioslave/smtp/TODO
@@ -0,0 +1,11 @@
+1. Double check the error handling and review error message in various
+ failure modes.
+2. Implement the CHUNKING extension (rfc 3030; as soon as I find an
+ SMTP server that supports it).
+3. Better error message (translated standard meanings of the known
+ response codes, ENHANCEDSTATUSCODES extension (rfc2034)).
+4. (KIO) MultiPutJob to support pipelining across messages.
+5. Ged rid of slave's header generation after checking who on earth
+ uses that...
+
+and further refactoring to make the code pleasant to look at ;-)
diff --git a/kioslave/smtp/capabilities.cc b/kioslave/smtp/capabilities.cc
new file mode 100644
index 000000000..a26626ce1
--- /dev/null
+++ b/kioslave/smtp/capabilities.cc
@@ -0,0 +1,143 @@
+/* -*- c++ -*-
+ capabilities.cc
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include "capabilities.h"
+
+#include "response.h"
+
+#include <qstrlist.h>
+
+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 QString & cap, bool replace ) {
+ QStringList tokens = QStringList::split( ' ', cap.upper() );
+ if ( tokens.empty() )
+ return;
+ QString name = tokens.front(); tokens.pop_front();
+ add( name, tokens, replace );
+ }
+
+ void Capabilities::add( const QString & name, const QStringList & args, bool replace ) {
+ if ( replace )
+ mCapabilities[name] = args;
+ else
+ mCapabilities[name] += args;
+ }
+
+ QString Capabilities::asMetaDataString() const {
+ QString result;
+ for ( QMap<QString,QStringList>::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) {
+ result += it.key();
+ if ( !it.data().empty() )
+ result += ' ' + it.data().join( " " );
+ result += '\n';
+ }
+ return result;
+ }
+
+ QString Capabilities::authMethodMetaData() const {
+ QStringList sl = saslMethodsQSL();
+ QString result;
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ result += "SASL/" + *it + '\n';
+ return result;
+ }
+
+ QStrIList Capabilities::saslMethods() const {
+ QStrIList result( true ); // deep copies to be safe
+ QStringList sl = saslMethodsQSL();
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ result.append( (*it).latin1() );
+ return result;
+ }
+
+ QString Capabilities::createSpecialResponse( bool tls ) const {
+ QStringList 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=" + QString::number( size ) ); // fixed max
+ else
+ result.push_back( "SIZE" ); // indetermined
+ }
+ return result.join( " " );
+ }
+
+ QStringList Capabilities::saslMethodsQSL() const {
+ QStringList result;
+ for ( QMap<QString,QStringList>::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( qstrlen("AUTH=") ) );
+ result += it.data();
+ }
+ }
+ result.sort();
+ QStringList::iterator it = result.begin();
+ for (QStringList::iterator ot = it++; it != result.end(); ot = it++)
+ if (*ot == *it) result.remove(ot);
+ return result;
+ }
+
+
+
+} // namespace KioSMTP
+
diff --git a/kioslave/smtp/capabilities.h b/kioslave/smtp/capabilities.h
new file mode 100644
index 000000000..1ae5972aa
--- /dev/null
+++ b/kioslave/smtp/capabilities.h
@@ -0,0 +1,77 @@
+/* -*- c++ -*-
+ capabilities.h
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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.
+*/
+
+#ifndef __KIOSMTP_CAPABILITIES_H__
+#define __KIOSMTP_CAPABILITIES_H__
+
+#include <qmap.h>
+#include <qcstring.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+class QStrIList;
+
+namespace KioSMTP {
+
+ class Response;
+
+ class Capabilities {
+ public:
+ Capabilities() {}
+
+ static Capabilities fromResponse( const Response & response );
+
+ void add( const QString & cap, bool replace=false );
+ void add( const QString & name, const QStringList & args, bool replace=false );
+ void clear() { mCapabilities.clear(); }
+
+ bool have( const QString & cap ) const {
+ return mCapabilities.find( cap.upper() ) != mCapabilities.end();
+ }
+ bool have( const QCString & cap ) const { return have( QString( cap.data() ) ); }
+ bool have( const char * cap ) const { return have( QString::fromLatin1( cap ) ); }
+
+ QString asMetaDataString() const;
+
+ QString authMethodMetaData() const;
+ QStrIList saslMethods() const;
+
+ QString createSpecialResponse( bool tls ) const;
+
+ QStringList saslMethodsQSL() const;
+ private:
+
+ QMap<QString,QStringList> mCapabilities;
+ };
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_CAPABILITIES_H__
diff --git a/kioslave/smtp/command.cc b/kioslave/smtp/command.cc
new file mode 100644
index 000000000..9fb7281c9
--- /dev/null
+++ b/kioslave/smtp/command.cc
@@ -0,0 +1,606 @@
+/* -*- c++ -*-
+ command.cc
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include "command.h"
+
+#include "smtp.h"
+#include "response.h"
+#include "transactionstate.h"
+
+#include <kidna.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmdcodec.h>
+#include <kio/slavebase.h> // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase
+
+#include <assert.h>
+
+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 QCString &, 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
+ //
+
+ QCString 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( KIO::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( KIO::ERR_UNKNOWN,
+ i18n("Unexpected server response to %1 command.\n%2")
+ .arg( mEHLONotSupported ? "HELO" : "EHLO" )
+ .arg( r.errorMessage() ) );
+ return false;
+ }
+
+ //
+ // STARTTLS - rfc 3207
+ //
+
+ QCString 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(KIO::SlaveBase::Information,
+ i18n("Your SMTP server claims to "
+ "support TLS, but negotiation "
+ "was unsuccessful.\nYou can "
+ "disable TLS in KDE using the "
+ "crypto settings module."),
+ i18n("Connection Failed"));
+ return false;
+ }
+
+
+#define SASLERROR mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, \
+ i18n("An error occured during authentication: %1").arg \
+ ( QString::fromUtf8( sasl_errdetail( conn ) )));
+
+ //
+ // AUTH - rfc 2554
+ //
+ AuthCommand::AuthCommand( SMTPProtocol * smtp,
+ const char *mechanisms,
+ const QString &aFQDN,
+ KIO::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(KIO::ERR_COULD_NOT_AUTHENTICATE,
+ i18n("Authentication support is not compiled into kio_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(KIO::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 QCString & s, TransactionState * ) {
+ mUngetSASLResponse = s;
+ mComplete = false;
+ }
+
+ QCString AuthCommand::nextCommandLine( TransactionState * ) {
+ mNeedResponse = true;
+ QCString cmd;
+#ifdef HAVE_LIBSASL2
+ QByteArray tmp, challenge;
+ if ( !mUngetSASLResponse.isNull() ) {
+ // implement un-ungetCommandLine
+ cmd = mUngetSASLResponse;
+ mUngetSASLResponse = 0;
+ } else if ( mFirstTime ) {
+ QString firstCommand = "AUTH " + QString::fromLatin1( mMechusing );
+
+ tmp.setRawData( mOut, mOutlen );
+ KCodecs::base64Encode( tmp, challenge );
+ tmp.resetRawData( mOut, mOutlen );
+ if ( !challenge.isEmpty() ) {
+ firstCommand += " ";
+ firstCommand += QString::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( KIO::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( KIO::ERR_COULD_NOT_LOGIN,
+ i18n("Your SMTP server does not support authentication.\n"
+ " %2").arg( r.errorMessage() ) );
+ else
+ mSMTP->error( KIO::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:
+ //
+
+ QCString MailFromCommand::nextCommandLine( TransactionState * ) {
+ mComplete = true;
+ mNeedResponse = true;
+ QCString cmdLine = "MAIL FROM:<" + mAddr + '>';
+ if ( m8Bit && haveCapability("8BITMIME") )
+ cmdLine += " BODY=8BITMIME";
+ if ( mSize && haveCapability("SIZE") )
+ cmdLine += " SIZE=" + QCString().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:
+ //
+
+ QCString 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!)
+ //
+
+ QCString DataCommand::nextCommandLine( TransactionState * ts ) {
+ assert( ts );
+ mComplete = true;
+ mNeedResponse = true;
+ ts->setDataCommandIssued( true );
+ return "DATA\r\n";
+ }
+
+ void DataCommand::ungetCommandLine( const QCString &, 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 QCString & 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();
+ }
+
+ QCString TransferCommand::nextCommandLine( TransactionState * ts ) {
+ assert( ts ); // let's rely on it ( at least for the moment )
+ assert( !isComplete() );
+ assert( !ts->failed() );
+
+ static const QCString dotCRLF = ".\r\n";
+ static const QCString CRLFdotCRLF = "\r\n.\r\n";
+
+ if ( !mUngetBuffer.isEmpty() ) {
+ const QCString 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();
+ QByteArray ba;
+ int result = mSMTP->readData( ba );
+ kdDebug(7112) << "got " << result << " bytes" << endl;
+ if ( result > 0 )
+ return prepare( ba );
+ else if ( result < 0 ) {
+ ts->setFailedFatally( KIO::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 QCString dotstuff_lf2crlf( const QByteArray & ba, char & last ) {
+ QCString 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;
+ }
+
+ QCString TransferCommand::prepare( const QByteArray & 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 QCString( ba.data(), ba.size() + 1 );
+ }
+ }
+
+ //
+ // NOOP
+ //
+
+ QCString NoopCommand::nextCommandLine( TransactionState * ) {
+ mComplete = true;
+ mNeedResponse = true;
+ return "NOOP\r\n";
+ }
+
+ //
+ // RSET
+ //
+
+ QCString RsetCommand::nextCommandLine( TransactionState * ) {
+ mComplete = true;
+ mNeedResponse = true;
+ return "RSET\r\n";
+ }
+
+ //
+ // QUIT
+ //
+
+ QCString QuitCommand::nextCommandLine( TransactionState * ) {
+ mComplete = true;
+ mNeedResponse = true;
+ return "QUIT\r\n";
+ }
+
+} // namespace KioSMTP
+
diff --git a/kioslave/smtp/command.h b/kioslave/smtp/command.h
new file mode 100644
index 000000000..e67f02556
--- /dev/null
+++ b/kioslave/smtp/command.h
@@ -0,0 +1,283 @@
+/* -*- c++ -*-
+ command.h
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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.
+*/
+
+#ifndef __KIOSMTP_COMMAND_H__
+#define __KIOSMTP_COMMAND_H__
+
+
+#include <qstring.h>
+#include <qcstring.h>
+
+#ifdef HAVE_LIBSASL2
+extern "C" {
+#include <sasl/sasl.h>
+}
+#endif
+
+#include <kio/authinfo.h>
+
+class SMTPProtocol;
+class QStrIList;
+
+namespace KioSMTP {
+
+ class Response;
+ class TransactionState;
+
+ /**
+ * @short Represents an SMTP command
+ *
+ * Semantics: A command consists of a series of "command lines"
+ * (though that's stretching it a bit for @ref TransferJob and @ref
+ * AuthCommand) and responses. There's typically one response for
+ * one command line and the command is completed.
+ *
+ * However, some commands consist of a dialog (command line,
+ * response, command line, response,...) where each successive
+ * command line is dependant on the previously received response
+ * (and thus those commands are not pipelinable). That's why each
+ * command signals completion by having it's @ref #isComplete()
+ * method return true @em after the last command line to be sent,
+ * but @em before the last response to receive. @ref AuthCommand is
+ * the principal representative of this kind of command. Because
+ * @ref EHLOCommand automatically falls back to HELO in case EHLO
+ * isn't supported, it is also of this kind. If completion is
+ * signalled before the first command line is issued, it is not to
+ * be executed at all.
+ *
+ * Other commands need to send multiple "command lines" before
+ * receiving a single (final) response. @ref TransferCommand is the
+ * only representative of this kind of "command". That's why each
+ * command signals whether it now expects a response before being
+ * able to issue the next command line (if any) by having it's @ref
+ * #needsResponse() method return true.
+ *
+ * Commands whose @ref #nextCommandLine() does not support being
+ * called multiple times in a row without changing command state,
+ * must reimplement @ref #ungetCommandLine().
+ **/
+ class Command {
+ public:
+ enum Flags {
+ OnlyLastInPipeline = 1,
+ OnlyFirstInPipeline = 2,
+ CloseConnectionOnError = 4
+ };
+
+ Command( SMTPProtocol * smtp, int flags=0 );
+ virtual ~Command();
+
+ enum Type {
+ STARTTLS, DATA, NOOP, RSET, QUIT
+ };
+
+ static Command * createSimpleCommand( int which, SMTPProtocol * smtp );
+
+ virtual QCString nextCommandLine( TransactionState * ts=0 ) = 0;
+ /* Reimplement this if your @ref #nextCommandLine() implementation
+ changes state other than @ref mComplete. The default
+ implementation just resets @ref mComplete to false. */
+ virtual void ungetCommandLine( const QCString & cmdLine, TransactionState * ts=0 );
+ /* Reimplement this if your command need more sophisicated
+ response processing than just checking for @ref
+ Response::isOk(). The default implementation sets @ref
+ mComplete to true, @ref mNeedResponse to false and returns
+ whether the response isOk(). */
+ virtual bool processResponse( const Response & response, TransactionState * ts=0 );
+
+ virtual bool doNotExecute( const TransactionState * ) const { return false; }
+
+ bool isComplete() const { return mComplete; }
+ /** @return whether the command expects a response now. Some
+ commands (most notably AUTH) may consist of a series of
+ commands and associated responses until they are
+ complete. Others (most notably @ref TransferCommand usually
+ send multiple "command lines" before expecting a response. */
+ bool needsResponse() const { return mNeedResponse; }
+ /** @return whether an error in executing this command is so fatal
+ that closing the connection is the only option */
+ bool closeConnectionOnError() const {
+ return mFlags & CloseConnectionOnError;
+ }
+ bool mustBeLastInPipeline() const {
+ return mFlags & OnlyLastInPipeline;
+ }
+ bool mustBeFirstInPipeline() const {
+ return mFlags & OnlyFirstInPipeline;
+ }
+
+ protected:
+ SMTPProtocol * mSMTP;
+ bool mComplete;
+ bool mNeedResponse;
+ const int mFlags;
+
+ protected:
+ // only relay methods to enable access to slave-protected methods
+ // for subclasses of Command:
+ void parseFeatures( const Response & r );
+ int startTLS();
+ bool usingSSL() const;
+ bool usingTLS() const;
+ bool haveCapability( const char * cap ) const;
+ };
+
+ class EHLOCommand : public Command {
+ public:
+ EHLOCommand( SMTPProtocol * smtp, const QString & hostname )
+ : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ),
+ mEHLONotSupported( false ),
+ mHostname( hostname.stripWhiteSpace() ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ bool processResponse( const Response & response, TransactionState * );
+ private:
+ bool mEHLONotSupported;
+ QString mHostname;
+ };
+
+ class StartTLSCommand : public Command {
+ public:
+ StartTLSCommand( SMTPProtocol * smtp )
+ : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ bool processResponse( const Response & response, TransactionState * );
+ };
+
+ class AuthCommand : public Command {
+ public:
+ AuthCommand( SMTPProtocol * smtp, const char *mechanisms,
+ const QString &aFQDN, KIO::AuthInfo &ai );
+ ~AuthCommand();
+ bool doNotExecute( const TransactionState * ts ) const;
+ QCString nextCommandLine( TransactionState * );
+ void ungetCommandLine( const QCString & cmdLine, TransactionState * );
+ bool processResponse( const Response & response, TransactionState * );
+ private:
+ bool saslInteract( void *in );
+
+#ifdef HAVE_LIBSASL2
+ sasl_conn_t *conn;
+ sasl_interact_t *client_interact;
+#endif
+ const char *mOut, *mMechusing;
+ uint mOutlen;
+ bool mOneStep;
+
+ KIO::AuthInfo *mAi;
+ QCString mLastChallenge;
+ QCString mUngetSASLResponse;
+ bool mFirstTime;
+ };
+
+ class MailFromCommand : public Command {
+ public:
+ MailFromCommand( SMTPProtocol * smtp, const QCString & addr,
+ bool eightBit=false, unsigned int size=0 )
+ : Command( smtp ), mAddr( addr ), m8Bit( eightBit ), mSize( size ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ bool processResponse( const Response & response, TransactionState * );
+ private:
+ QCString mAddr;
+ bool m8Bit;
+ unsigned int mSize;
+ };
+
+ class RcptToCommand : public Command {
+ public:
+ RcptToCommand( SMTPProtocol * smtp, const QCString & addr )
+ : Command( smtp ), mAddr( addr ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ bool processResponse( const Response & response, TransactionState * );
+ private:
+ QCString mAddr;
+ };
+
+ /** Handles only the initial intermediate response and compltetes at
+ the point where the mail contents need to be sent */
+ class DataCommand : public Command {
+ public:
+ DataCommand( SMTPProtocol * smtp )
+ : Command( smtp, OnlyLastInPipeline ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ void ungetCommandLine( const QCString & cmd, TransactionState * ts );
+ bool processResponse( const Response & response, TransactionState * );
+ };
+
+ /** Handles the data transfer following a successful DATA command */
+ class TransferCommand : public Command {
+ public:
+ TransferCommand( SMTPProtocol * smtp, const QCString & initialBuffer )
+ : Command( smtp, OnlyFirstInPipeline ),
+ mUngetBuffer( initialBuffer ), mLastChar( '\n' ), mWasComplete( false ) {}
+
+ bool doNotExecute( const TransactionState * ts ) const;
+ QCString nextCommandLine( TransactionState * );
+ void ungetCommandLine( const QCString & cmd, TransactionState * ts );
+ bool processResponse( const Response & response, TransactionState * );
+ private:
+ QCString prepare( const QByteArray & ba );
+ QCString mUngetBuffer;
+ char mLastChar;
+ bool mWasComplete; // ... before ungetting
+ };
+
+ class NoopCommand : public Command {
+ public:
+ NoopCommand( SMTPProtocol * smtp )
+ : Command( smtp, OnlyLastInPipeline ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ };
+
+ class RsetCommand : public Command {
+ public:
+ RsetCommand( SMTPProtocol * smtp )
+ : Command( smtp, CloseConnectionOnError ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ };
+
+ class QuitCommand : public Command {
+ public:
+ QuitCommand( SMTPProtocol * smtp )
+ : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {}
+
+ QCString nextCommandLine( TransactionState * );
+ };
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_COMMAND_H__
diff --git a/kioslave/smtp/compliance.txt b/kioslave/smtp/compliance.txt
new file mode 100644
index 000000000..b6b9874c8
--- /dev/null
+++ b/kioslave/smtp/compliance.txt
@@ -0,0 +1,33 @@
+The SMTP kioslave currently conforms to the following SMTP-related RFCs:
+
+Base Spec:
+2821 Simple Mail Transfer Protocol. J. Klensin, Ed.. April 2001.
+ (Format: TXT=192504 bytes) (Obsoletes RFC0821, RFC0974, RFC1869)
+ (Status: PROPOSED STANDARD)
+
+Encryption/Auth:
+3207 SMTP Service Extension for Secure SMTP over Transport Layer
+ Security. P. Hoffman. February 2002. (Format: TXT=18679 bytes)
+ (Obsoletes RFC2487) (Status: PROPOSED STANDARD)
+
+2554 SMTP Service Extension for Authentication. J. Myers. March 1999.
+ (Format: TXT=20534 bytes) (Status: PROPOSED STANDARD)
+(with all SASL mechanisms supported by KDESasl)
+
+General:
+1652 SMTP Service Extension for 8bit-MIMEtransport. J. Klensin, N.
+ Freed, M. Rose, E. Stefferud, D. Crocker. July 1994. (Format:
+ TXT=11842 bytes) (Obsoletes RFC1426) (Status: DRAFT STANDARD)
+
+1870 SMTP Service Extension for Message Size Declaration. J. Klensin,
+ N. Freed, K. Moore. November 1995. (Format: TXT=18226 bytes)
+ (Obsoletes RFC1653) (Also STD0010) (Status: STANDARD)
+
+2920 SMTP Service Extension for Command Pipelining. N. Freed.
+ September 2000. (Format: TXT=17065 bytes) (Obsoletes RFC2197) (Also
+ STD0060) (Status: STANDARD)
+
+Known shortcomings:
+- Doesn't enforce the CRLF lineending convention on user-supplied data.
+- Due to the lack of a Mulit_Put_ in the KIO infrastructure, pipelining
+ across messages isn't supported.
diff --git a/kioslave/smtp/interactivesmtpserver.cc b/kioslave/smtp/interactivesmtpserver.cc
new file mode 100644
index 000000000..4deddd3ca
--- /dev/null
+++ b/kioslave/smtp/interactivesmtpserver.cc
@@ -0,0 +1,127 @@
+/* -*- c++ -*-
+ interactivesmtpserver.cc
+
+ Code based on the serverSocket example by Jesper Pedersen.
+
+ This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include <qserversocket.h>
+#include <qsocket.h>
+#include <qwidget.h>
+#include <qapplication.h>
+#include <qhostaddress.h>
+#include <qtextedit.h>
+#include <qlineedit.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+
+#include <cassert>
+
+#include "interactivesmtpserver.h"
+
+static const QHostAddress localhost( 0x7f000001 ); // 127.0.0.1
+
+InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() {
+ if ( mSocket ) {
+ mSocket->close();
+ if ( mSocket->state() == QSocket::Closing )
+ connect( mSocket, SIGNAL(delayedCloseFinished()),
+ mSocket, SLOT(deleteLater()) );
+ else
+ mSocket->deleteLater();
+ mSocket = 0;
+ }
+}
+
+void InteractiveSMTPServerWindow::slotSendResponse()
+{
+ const QString line = mLineEdit->text();
+ mLineEdit->clear();
+ QTextStream s( mSocket );
+ s << line + "\r\n";
+ slotDisplayServer( line );
+}
+
+InteractiveSMTPServer::InteractiveSMTPServer( QObject* parent )
+ : QServerSocket( localhost, 2525, 1, parent )
+{
+}
+
+int main( int argc, char * argv[] ) {
+ QApplication app( argc, argv );
+
+ InteractiveSMTPServer server;
+
+ qDebug( "Server should now listen on localhost:2525" );
+ qDebug( "Hit CTRL-C to quit." );
+ return app.exec();
+};
+
+
+InteractiveSMTPServerWindow::InteractiveSMTPServerWindow( QSocket * socket, QWidget * parent, const char * name, WFlags f )
+ : QWidget( parent, name, f ), mSocket( socket )
+{
+ QPushButton * but;
+ assert( socket );
+
+ QVBoxLayout * vlay = new QVBoxLayout( this, 6 );
+
+ mTextEdit = new QTextEdit( this );
+ mTextEdit->setTextFormat( QTextEdit::LogText );
+ vlay->addWidget( mTextEdit, 1 );
+
+ QHBoxLayout * hlay = new QHBoxLayout( vlay );
+
+ mLineEdit = new QLineEdit( this );
+ but = new QPushButton( "&Send", this );
+ hlay->addWidget( new QLabel( mLineEdit, "&Response:", this ) );
+ hlay->addWidget( mLineEdit, 1 );
+ hlay->addWidget( but );
+
+ connect( mLineEdit, SIGNAL(returnPressed()), SLOT(slotSendResponse()) );
+ connect( but, SIGNAL(clicked()), SLOT(slotSendResponse()) );
+
+ but = new QPushButton( "&Close Connection", this );
+ vlay->addWidget( but );
+
+ connect( but, SIGNAL(clicked()), SLOT(slotConnectionClosed()) );
+
+ connect( socket, SIGNAL(connectionClosed()), SLOT(slotConnectionClosed()) );
+ connect( socket, SIGNAL(error(int)), SLOT(slotError(int)) );
+ connect( socket, SIGNAL(readyRead()), SLOT(slotReadyRead()) );
+
+ mLineEdit->setText( "220 hi there" );
+ mLineEdit->setFocus();
+}
+
+#include "interactivesmtpserver.moc"
diff --git a/kioslave/smtp/interactivesmtpserver.h b/kioslave/smtp/interactivesmtpserver.h
new file mode 100644
index 000000000..3ae210a74
--- /dev/null
+++ b/kioslave/smtp/interactivesmtpserver.h
@@ -0,0 +1,121 @@
+#ifndef INTERACTIVESMTPSERVER_H
+#define INTERACTIVESMTPSERVER_H
+
+/* -*- c++ -*-
+ interactivesmtpserver.h
+
+ Code based on the serverSocket example by Jesper Pedersen.
+
+ This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 <qwidget.h>
+
+
+static QString err2str( int err ) {
+ switch ( err ) {
+ case QSocket::ErrConnectionRefused: return "Connection refused";
+ case QSocket::ErrHostNotFound: return "Host not found";
+ case QSocket::ErrSocketRead: return "Failed to read from socket";
+ default: return "Unknown error";
+ }
+}
+
+
+static QString escape( QString s ) {
+ return s
+ .replace( '&', "&amp;" )
+ .replace( '>', "&gt;" )
+ .replace( '<', "&lt;" )
+ .replace( '"', "&quot;" )
+ ;
+}
+
+
+static QString trim( const QString & s ) {
+ if ( s.endsWith( "\r\n" ) )
+ return s.left( s.length() - 2 );
+ if ( s.endsWith( "\r" ) || s.endsWith( "\n" ) )
+ return s.left( s.length() - 1 );
+ return s;
+}
+
+
+class InteractiveSMTPServerWindow : public QWidget {
+ Q_OBJECT
+public:
+ InteractiveSMTPServerWindow( QSocket * socket, QWidget * parent=0, const char * name=0, WFlags f=0 );
+ ~InteractiveSMTPServerWindow();
+
+public slots:
+ void slotSendResponse();
+ void slotDisplayClient( const QString & s ) {
+ mTextEdit->append( "C:" + escape(s) );
+ }
+ void slotDisplayServer( const QString & s ) {
+ mTextEdit->append( "S:" + escape(s) );
+ }
+ void slotDisplayMeta( const QString & s ) {
+ mTextEdit->append( "<font color=\"red\">" + escape(s) + "</font>" );
+ }
+ void slotReadyRead() {
+ while ( mSocket->canReadLine() )
+ slotDisplayClient( trim( mSocket->readLine() ) );
+ }
+ void slotError( int err ) {
+ slotDisplayMeta( QString( "E: %1 (%2)" ).arg( err2str( err ) ).arg( err ) );
+ }
+ void slotConnectionClosed() {
+ slotDisplayMeta( "Connection closed by peer" );
+ }
+ void slotCloseConnection() {
+ mSocket->close();
+ }
+private:
+ QSocket * mSocket;
+ QTextEdit * mTextEdit;
+ QLineEdit * mLineEdit;
+};
+
+class InteractiveSMTPServer : public QServerSocket {
+ Q_OBJECT
+public:
+ InteractiveSMTPServer( QObject * parent=0 );
+ ~InteractiveSMTPServer() {}
+
+ /*! \reimp */
+ void newConnection( int fd ) {
+ QSocket * socket = new QSocket();
+ socket->setSocket( fd );
+ InteractiveSMTPServerWindow * w = new InteractiveSMTPServerWindow( socket );
+ w->show();
+ }
+};
+
+
+#endif
diff --git a/kioslave/smtp/request.cc b/kioslave/smtp/request.cc
new file mode 100644
index 000000000..566d2c758
--- /dev/null
+++ b/kioslave/smtp/request.cc
@@ -0,0 +1,189 @@
+/* -*- c++ -*-
+ request.cc
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include "request.h"
+
+#include <kurl.h>
+#include <kidna.h>
+#include <kmdcodec.h>
+#include <kdebug.h>
+
+#include <assert.h>
+
+namespace KioSMTP {
+
+ Request Request::fromURL( const KURL & url ) {
+ Request request;
+
+ const QStringList query = QStringList::split( '&', url.query().mid(1) );
+#ifndef NDEBUG
+ kdDebug(7112) << "Parsing request from query:\n" + query.join("\n" ) << endl;
+#endif
+ for ( QStringList::const_iterator it = query.begin() ; it != query.end() ; ++it ) {
+ int equalsPos = (*it).find( '=' );
+ if ( equalsPos <= 0 )
+ continue;
+
+ const QString key = (*it).left( equalsPos ).lower();
+ const QString 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;
+ }
+
+ QCString Request::heloHostnameCString() const {
+ return KIDNA::toAsciiCString( heloHostname() );
+ }
+
+ static bool isUsAscii( const QString & 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 QCString specials = "()<>[]:;@\\,.\"";
+ return specials.find( ch ) >= 0;
+ }
+
+
+
+ static inline bool needsQuoting( char ch ) {
+ return ch == '\\' || ch == '"' || ch == '\n' ;
+ }
+
+
+
+ static inline QCString rfc2047Encode( const QString & s ) {
+ QCString r = KCodecs::base64Encode( s.stripWhiteSpace().utf8(), false );
+ return "=?utf-8?b?" + r + "?=" ; // use base64 since that always gives a valid encoded-word
+ }
+
+
+
+ static QCString quote( const QString & s ) {
+ assert( isUsAscii( s ) );
+
+ QCString 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 QCString formatFromAddress( const QString & fromRealName, const QString & fromAddress ) {
+ if ( fromRealName.isEmpty() )
+ return fromAddress.latin1(); // no real name: return "joe@user.org"
+
+ // return "Joe User <joe@user.org>", "\"User, Joe\" <joe@user.org>"
+ // or "=?utf-8?q?Joe_User?= <joe@user.org>", depending on real name's nature.
+ QCString r = isUsAscii( fromRealName ) ? quote( fromRealName ) : rfc2047Encode( fromRealName );
+ return r + " <" + fromAddress.latin1() + '>';
+ }
+
+
+
+ static QCString formatSubject( QString s ) {
+ if ( isUsAscii( s ) )
+ return s.remove( '\n' ).latin1(); // don't break header folding,
+ // so remove any line break
+ // that happen to be around
+ else
+ return rfc2047Encode( s );
+ }
+
+
+
+ QCString Request::headerFields( const QString & fromRealName ) const {
+ if ( !emitHeaders() )
+ return 0;
+
+ assert( hasFromAddress() ); // should have been checked for by
+ // caller (MAIL FROM comes before DATA)
+
+ QCString result = "From: " + formatFromAddress( fromRealName, fromAddress() ) + "\r\n";
+
+ if ( !subject().isEmpty() )
+ result += "Subject: " + formatSubject( subject() ) + "\r\n";
+ if ( !to().empty() )
+ result += QCString( "To: " ) + to().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n";
+ if ( !cc().empty() )
+ result += QCString( "Cc: " ) + cc().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n";
+ return result;
+ }
+
+} // namespace KioSMTP
diff --git a/kioslave/smtp/request.h b/kioslave/smtp/request.h
new file mode 100644
index 000000000..15bbd76b9
--- /dev/null
+++ b/kioslave/smtp/request.h
@@ -0,0 +1,100 @@
+/* -*- c++ -*-
+ request.h
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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.
+*/
+
+#ifndef __KIOSMTP_REQUEST_H__
+#define __KIOSMTP_REQUEST_H__
+
+#include <qstring.h>
+#include <qstringlist.h>
+
+class KURL;
+
+namespace KioSMTP {
+
+ class Request {
+ public:
+ Request()
+ : mSubject( "missing subject" ), mEmitHeaders( true ),
+ m8Bit( false ), mSize( 0 ) {}
+
+ static Request fromURL( const KURL & url );
+
+ QString profileName() const { return mProfileName; }
+ void setProfileName( const QString & profileName ) { mProfileName = profileName; }
+ bool hasProfile() const { return !profileName().isNull(); }
+
+ QString subject() const { return mSubject; }
+ void setSubject( const QString & subject ) { mSubject = subject; }
+
+ QString fromAddress() const { return mFromAddress; }
+ void setFromAddress( const QString & fromAddress ) { mFromAddress = fromAddress; }
+ bool hasFromAddress() const { return !mFromAddress.isEmpty(); }
+
+ QStringList recipients() const { return to() + cc() + bcc() ; }
+ bool hasRecipients() const { return !to().empty() || !cc().empty() || !bcc().empty() ; }
+
+ QStringList to() const { return mTo; }
+ QStringList cc() const { return mCc; }
+ QStringList bcc() const { return mBcc; }
+ void addTo( const QString & to ) { mTo.push_back( to ); }
+ void addCc( const QString & cc ) { mCc.push_back( cc ); }
+ void addBcc( const QString & bcc ) { mBcc.push_back( bcc ); }
+
+ QString heloHostname() const { return mHeloHostname; }
+ QCString heloHostnameCString() const;
+ void setHeloHostname( const QString & hostname ) { mHeloHostname = hostname; }
+
+ bool emitHeaders() const { return mEmitHeaders; }
+ void setEmitHeaders( bool emitHeaders ) { mEmitHeaders = emitHeaders; }
+
+ bool is8BitBody() const { return m8Bit; }
+ void set8BitBody( bool a8Bit ) { m8Bit = a8Bit; }
+
+ unsigned int size() const { return mSize; }
+ void setSize( unsigned int size ) { mSize = size; }
+
+ /** If @ref #emitHeaders() is true, returns the rfc2822
+ serialization of the header fields "To", "Cc", "Subject" and
+ "From", as determined by the respective settings. If @ref
+ #emitHeaders() is false, returns a null string. */
+ QCString headerFields( const QString & fromRealName=QString::null ) const;
+
+ private:
+ QStringList mTo, mCc, mBcc;
+ QString mProfileName, mSubject, mFromAddress, mHeloHostname;
+ bool mEmitHeaders;
+ bool m8Bit;
+ unsigned int mSize;
+ };
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_REQUEST_H__
diff --git a/kioslave/smtp/response.cc b/kioslave/smtp/response.cc
new file mode 100644
index 000000000..b1745865a
--- /dev/null
+++ b/kioslave/smtp/response.cc
@@ -0,0 +1,160 @@
+/* -*- c++ -*-
+ response.cc
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include "response.h"
+
+#include <klocale.h>
+#include <kio/global.h>
+
+#include <qstring.h>
+
+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 = QCString( 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 ? QCString( line+4, len-4+1 ).stripWhiteSpace() : QCString() );
+ }
+
+
+ // hackishly fixing QCStringList flaws...
+ static QCString join( char sep, const QCStringList & list ) {
+ if ( list.empty() )
+ return QCString();
+ QCString result = list.front();
+ for ( QCStringList::const_iterator it = ++list.begin() ; it != list.end() ; ++it )
+ result += sep + *it;
+ return result;
+ }
+
+ QString Response::errorMessage() const {
+ QString msg;
+ if ( lines().count() > 1 )
+ msg = i18n("The server responded:\n%1")
+ .arg( join( '\n', lines() ) );
+ else
+ msg = i18n("The server responded: \"%1\"")
+ .arg( 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 KIO::ERR_SERVICE_NOT_AVAILABLE;
+
+ case 451: // Requested action aborted: local error in processing
+ return KIO::ERR_INTERNAL_SERVER;
+
+ case 452: // Requested action not taken: insufficient system storage
+ case 552: // Requested mail action aborted: exceeded storage allocation
+ return KIO::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 KIO::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 <forward-path>
+ case 553: // Requested action not taken: mailbox name not allowed
+ return KIO::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 KIO::ERR_UPGRADE_REQUIRED;
+
+ case 432: // A password transition is needed
+ return KIO::ERR_COULD_NOT_AUTHENTICATE;
+
+ default:
+ if ( isPositive() )
+ return 0;
+ else
+ return KIO::ERR_UNKNOWN;
+ }
+ }
+
+} // namespace KioSMTP
diff --git a/kioslave/smtp/response.h b/kioslave/smtp/response.h
new file mode 100644
index 000000000..22835690a
--- /dev/null
+++ b/kioslave/smtp/response.h
@@ -0,0 +1,125 @@
+/* -*- c++ -*-
+ response.h
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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.
+*/
+
+#ifndef __KIOSMTP_RESPONSE_H__
+#define __KIOSMTP_RESPONSE_H__
+
+#include <qcstring.h>
+#include <qvaluelist.h>
+typedef QValueList<QCString> QCStringList;
+
+class QString;
+
+namespace KioSMTP {
+
+ class Response {
+ public:
+ Response()
+ : mCode(0),
+ mValid(true),
+ mSawLastLine(false),
+ mWellFormed(true) {}
+
+ void parseLine( const char * line ) {
+ parseLine( line, qstrlen( line ) );
+ }
+ void parseLine( const char * line, int len );
+
+ /** Return an internationalized error message according to the
+ response's code. */
+ QString errorMessage() const;
+ /** Translate the SMTP error code into a KIO one */
+ int errorCode() const;
+
+ enum Reply {
+ UnknownReply = -1,
+ PositivePreliminary = 1,
+ PositiveCompletion = 2,
+ PositiveIntermediate = 3,
+ TransientNegative = 4,
+ PermanentNegative = 5
+ };
+
+ enum Category {
+ UnknownCategory = -1,
+ SyntaxError = 0,
+ Information = 1,
+ Connections = 2,
+ MailSystem = 5
+ };
+
+ unsigned int code() const { return mCode; }
+ unsigned int first() const { return code() / 100 ; }
+ unsigned int second() const { return ( code() % 100 ) / 10 ; }
+ unsigned int third() const { return code() % 10 ; }
+
+ bool isPositive() const { return first() <= 3 && first() >= 1 ; }
+ bool isNegative() const { return first() == 4 || first() == 5 ; }
+ bool isUnknown() const { return !isPositive() && !isNegative() ; }
+
+ QCStringList lines() const { return mLines; }
+
+ bool isValid() const { return mValid; }
+ bool isComplete() const { return mSawLastLine; }
+
+ /** Shortcut method.
+ @return true iff the response is valid, complete and positive */
+ bool isOk() const { return isValid() && isComplete() && isPositive() ; }
+ /** Indicates whether the response was well-formed, meaning it
+ obeyed the syntax of smtp responses. That the response
+ nevertheless is not valid may be caused by e.g. different
+ response codes in a multilie response. A non-well-formed
+ response is never valid. */
+ bool isWellFormed() const { return mWellFormed; }
+
+ void clear() { *this = Response(); }
+
+#ifdef KIOSMTP_COMPARATORS
+ bool operator==( const Response & other ) const {
+ return mCode == other.mCode &&
+ mValid == other.mValid &&
+ mSawLastLine == other.mSawLastLine &&
+ mWellFormed == other.mWellFormed &&
+ mLines == other.mLines;
+ }
+#endif
+
+ private:
+ unsigned int mCode;
+ QCStringList mLines;
+ bool mValid;
+ bool mSawLastLine;
+ bool mWellFormed;
+ };
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_RESPONSE_H__
diff --git a/kioslave/smtp/smtp.cc b/kioslave/smtp/smtp.cc
new file mode 100644
index 000000000..dc621f533
--- /dev/null
+++ b/kioslave/smtp/smtp.cc
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy@sonic.net>
+ * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net>
+ * Copyright (c) 2002 Aaron J. Seigo <aseigo@olympusproject.org>
+ * Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+ * 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 <config.h>
+
+#ifdef HAVE_LIBSASL2
+extern "C" {
+#include <sasl/sasl.h>
+}
+#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 <kemailsettings.h>
+#include <ksock.h>
+#include <kdebug.h>
+#include <kinstance.h>
+#include <kio/connection.h>
+#include <kio/slaveinterface.h>
+#include <klocale.h>
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qcstring.h>
+
+#include <memory>
+using std::auto_ptr;
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#include <netdb.h>
+
+#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)
+{
+ KInstance instance("kio_smtp");
+
+ if (argc != 4) {
+ fprintf(stderr,
+ "Usage: kio_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], qstricmp( argv[1], "smtps" ) == 0 );
+ slave.dispatchLoop();
+#ifdef HAVE_LIBSASL2
+ sasl_done();
+#endif
+ return 0;
+}
+
+SMTPProtocol::SMTPProtocol(const QCString & pool, const QCString & 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 QByteArray & aData ) {
+ QDataStream 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( KIO::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) << "kio_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 = QString::null;
+ if (m_sPass.isEmpty())
+ m_sPass = QString::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 <emailaddress@host.com>)
+ if ( !request.hasFromAddress() ) {
+ const QString from = mset.getSetting( KEMailSettings::EmailAddress );
+ if ( !from.isNull() )
+ request.setFromAddress( from );
+ else if ( request.emitHeaders() ) {
+ error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing."));
+ return;
+ }
+ }
+
+ if ( !smtp_open( request.heloHostname() ) )
+ {
+ error(KIO::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( KIO::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.
+ QStringList recipients = request.recipients();
+ for ( QStringList::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 QString & host, int port,
+ const QString & user, const QString & pass)
+{
+ m_sServer = host;
+ m_iPort = port;
+ m_sUser = user;
+ m_sPass = pass;
+}
+
+bool SMTPProtocol::sendCommandLine( const QCString & 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( KIO::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( KIO::ERR_SERVER_TIMEOUT, m_sServer );
+ return response;
+ }
+
+ // ...read data...
+ recv_len = readLine( buf, sizeof(buf) - 1 );
+ if ( recv_len < 1 && !isConnectionValid() ) {
+ error( KIO::ERR_CONNECTION_BROKEN, m_sServer );
+ return response;
+ }
+
+ kdDebug(7112) << "S: " << QCString( 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( KIO::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() ) {
+ QCString 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;
+}
+
+QCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) {
+ assert( ts );
+
+ QCString 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 QCString 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<Command> 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 QCString 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 QString& 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( KIO::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
+ {
+ QString tmpPort;
+ KSocketAddress* 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;
+
+ KIO::AuthInfo authInfo;
+ authInfo.username = m_sUser;
+ authInfo.password = m_sPass;
+ authInfo.prompt = i18n("Username and password for your SMTP account:");
+
+ QStringList 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 );
+
+ QString 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 = QString::null;
+ m_sOldUser = QString::null;
+ m_sOldPass = QString::null;
+
+ mCapabilities.clear();
+ mPendingCommandQueue.clear();
+ mSentCommandQueue.clear();
+
+ m_opened = false;
+}
+
+void SMTPProtocol::stat(const KURL & url)
+{
+ QString path = url.path();
+ error(KIO::ERR_DOES_NOT_EXIST, url.path());
+}
+
diff --git a/kioslave/smtp/smtp.h b/kioslave/smtp/smtp.h
new file mode 100644
index 000000000..571375bd4
--- /dev/null
+++ b/kioslave/smtp/smtp.h
@@ -0,0 +1,146 @@
+/* -*- c++ -*-
+ * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy@sonic.net>
+ * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net>
+ * 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.
+ *
+ */
+
+#ifndef _SMTP_H
+#define _SMTP_H
+
+#include <kio/tcpslavebase.h>
+
+#include "capabilities.h"
+
+#include <qstring.h>
+#include <qptrqueue.h>
+
+class KURL;
+class QCString;
+template <typename T> class QMemArray;
+typedef QMemArray<char> QByteArray;
+
+namespace KioSMTP {
+ class Response;
+ class TransactionState;
+ class Command;
+}
+
+class SMTPProtocol : public KIO::TCPSlaveBase {
+ friend class KioSMTP::Command;
+public:
+ SMTPProtocol(const QCString & pool, const QCString & app, bool useSSL);
+ virtual ~ SMTPProtocol();
+
+ virtual void setHost(const QString & host, int port,
+ const QString & user, const QString & pass);
+
+ virtual void special(const QByteArray & aData);
+ virtual void put(const KURL & url, int permissions, bool overwrite,
+ bool resume);
+ virtual void stat(const KURL & url);
+ virtual void openConnection();
+ virtual void closeConnection();
+
+protected:
+
+ bool smtp_open(const QString& fakeHostname = QString::null);
+
+ /** Closes the connection. If @p nice is true (default), then QUIT
+ is sent and it's reponse waited for. */
+ void smtp_close( bool nice=true );
+
+ /** Execute command @p cmd */
+ bool execute( KioSMTP::Command * cmd, KioSMTP::TransactionState * ts=0 );
+ /** Execute a command of type @p type */
+ bool execute( int type, KioSMTP::TransactionState * ts=0 );
+ /** Execute the queued commands. If something goes horribly wrong
+ (sending command oline fails, getting response fails or some
+ command raises the failedFatally() flag in @p ts, shuts down the
+ connection with <code>smtp_close( false )</code>. If The
+ transaction fails gracefully (<code>ts->failed()</code> is
+ true), issues a RSET command.
+
+ @return true if transaction succeeded, false otherwise.
+ **/
+ bool executeQueuedCommands( KioSMTP::TransactionState * ts );
+
+ /** Parse a single response from the server. Single- vs. multiline
+ responses are correctly detected.
+
+ @param ok if not 0, returns whether response parsing was
+ successful. Don't confuse this with negative responses
+ (e.g. 5xx), which you can check for using
+ @ref Response::isNegative()
+ @return the @ref Response object representing the server response.
+ **/
+ KioSMTP::Response getResponse( bool * ok );
+
+ bool authenticate();
+ void parseFeatures( const KioSMTP::Response & ehloResponse );
+
+ bool sendCommandLine( const QCString & cmd );
+ QCString collectPipelineCommands( KioSMTP::TransactionState * ts );
+ bool batchProcessResponses( KioSMTP::TransactionState * ts );
+
+ /** This is a pure convenience wrapper around
+ @ref KioSMTP::Capabilities::have() */
+ bool haveCapability( const char * cap ) const {
+ return mCapabilities.have( cap );
+ }
+
+ /** @return true is pipelining is available and allowed by metadata */
+ bool canPipelineCommands() const {
+ return haveCapability("PIPELINING") && metaData("pipelining") != "off" ;
+ }
+
+ /** Wrapper around getsockopt(..., SO_SNDBUF,...) */
+ unsigned int sendBufferSize() const;
+
+ /** This is a pure convenience wrapper around
+ @ref KioSMTP::Capabilities::createSpecialResponse */
+ QString createSpecialResponse() const {
+ return mCapabilities.createSpecialResponse( usingTLS() || haveCapability( "STARTTLS" ) );
+ }
+
+ void queueCommand( KioSMTP::Command * command ) {
+ mPendingCommandQueue.enqueue( command );
+ }
+ void queueCommand( int type );
+
+ unsigned short m_iOldPort;
+ bool m_opened;
+ QString m_sServer, m_sOldServer;
+ QString m_sUser, m_sOldUser;
+ QString m_sPass, m_sOldPass;
+ QString m_hostname;
+
+ KioSMTP::Capabilities mCapabilities;
+
+ typedef QPtrQueue<KioSMTP::Command> CommandQueue;
+ CommandQueue mPendingCommandQueue;
+ CommandQueue mSentCommandQueue;
+};
+
+#endif // _SMTP_H
diff --git a/kioslave/smtp/smtp.protocol b/kioslave/smtp/smtp.protocol
new file mode 100644
index 000000000..e01be9c7f
--- /dev/null
+++ b/kioslave/smtp/smtp.protocol
@@ -0,0 +1,16 @@
+[Protocol]
+exec=kio_smtp
+protocol=smtp
+Capabilities=SASL
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=false
+writing=true
+deleting=false
+source=true
+makedir=false
+linking=false
+moving=false
+DocPath=kioslave/smtp.html
+Icon=folder_outbox
diff --git a/kioslave/smtp/smtps.protocol b/kioslave/smtp/smtps.protocol
new file mode 100644
index 000000000..2087845f2
--- /dev/null
+++ b/kioslave/smtp/smtps.protocol
@@ -0,0 +1,15 @@
+[Protocol]
+exec=kio_smtp
+protocol=smtps
+Capabilities=SASL
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=false
+writing=true
+deleting=false
+source=true
+makedir=false
+linking=false
+moving=false
+Icon=folder_outbox
diff --git a/kioslave/smtp/test_commands.cc b/kioslave/smtp/test_commands.cc
new file mode 100644
index 000000000..a6969490f
--- /dev/null
+++ b/kioslave/smtp/test_commands.cc
@@ -0,0 +1,728 @@
+#include <kio/global.h>
+#include <kdebug.h>
+
+#include <qstring.h>
+#include <qcstring.h>
+#include <qstringlist.h>
+
+//#include <iostream>
+//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;
+ QString lastErrorMessage;
+ int lastMessageBoxCode;
+ QString lastMessageBoxText;
+ QByteArray nextData;
+ int nextDataReturnCode;
+ QStringList caps;
+ KIO::MetaData metadata;
+
+ void clear() {
+ startTLSReturnCode = 1;
+ usesSSL = usesTLS = false;
+ lastErrorCode = lastMessageBoxCode = 0;
+ lastErrorMessage = lastMessageBoxText = QString::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 QString & msg ) {
+ lastErrorCode = id;
+ lastErrorMessage = msg;
+ }
+ void messageBox( int id, const QString & msg, const QString & ) {
+ lastMessageBoxCode = id;
+ lastMessageBoxText = msg;
+ }
+ void dataReq() { /* noop */ }
+ int readData( QByteArray & ba ) { ba = nextData; return nextDataReturnCode; }
+ QString metaData( const QString & key ) const { return metadata[key]; }
+
+};
+
+#define _SMTP_H
+
+#define KIOSMTP_COMPARATORS // for TransactionState::operator==
+#include "command.h"
+#include "response.h"
+#include "transactionstate.h"
+
+#include <assert.h>
+
+using namespace KioSMTP;
+
+static const char * foobarbaz = ".Foo bar baz";
+static const unsigned int foobarbaz_len = qstrlen( foobarbaz );
+
+static const char * foobarbaz_dotstuffed = "..Foo bar baz";
+static const unsigned int foobarbaz_dotstuffed_len = qstrlen( foobarbaz_dotstuffed );
+
+static const char * foobarbaz_lf = ".Foo bar baz\n";
+static const unsigned int foobarbaz_lf_len = qstrlen( foobarbaz_lf );
+
+static const char * foobarbaz_crlf = "..Foo bar baz\r\n";
+static const unsigned int foobarbaz_crlf_len = qstrlen( 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 == KIO::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 == KIO::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 == KIO::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();
+ QStrIList 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:<joe@user.org>\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:<joe@user.org>\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:<joe@user.org> 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:<joe@user.org>\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 <CR><LF>.<CR><LF>" );
+ 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 = qstrlen( s_pre );
+
+ const char * s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ;
+ //const unsigned int s_post_len = qstrlen( 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 == KIO::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/kioslave/smtp/test_headergeneration.cc b/kioslave/smtp/test_headergeneration.cc
new file mode 100644
index 000000000..bdf8b251f
--- /dev/null
+++ b/kioslave/smtp/test_headergeneration.cc
@@ -0,0 +1,86 @@
+#include "request.h"
+
+//#include <iostream>
+
+//using std::cout;
+//using std::endl;
+
+int main( int , char ** ) {
+ static QCString 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 <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: \"Mutz, Marc\" <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: =?utf-8?b?TWFyYyBNw7Z0eg==?= <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: 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 <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: \"Mutz, Marc\" <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: =?utf-8?b?TWFyYyBNw7Z0eg==?= <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";
+
+ KioSMTP::Request request;
+ QCString 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/kioslave/smtp/test_responseparser.cc b/kioslave/smtp/test_responseparser.cc
new file mode 100644
index 000000000..5daa2fb3b
--- /dev/null
+++ b/kioslave/smtp/test_responseparser.cc
@@ -0,0 +1,107 @@
+#include "response.h"
+#include <assert.h>
+
+static const QCString singleLineResponseCRLF = "250 OK\r\n";
+static const QCString singleLineResponse = "250 OK";
+
+static const QCString 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/kioslave/smtp/transactionstate.cc b/kioslave/smtp/transactionstate.cc
new file mode 100644
index 000000000..3bf3c7614
--- /dev/null
+++ b/kioslave/smtp/transactionstate.cc
@@ -0,0 +1,114 @@
+/* -*- c++ -*-
+ transactionstate.cc
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 <config.h>
+
+#include "transactionstate.h"
+
+#include <kio/global.h>
+#include <klocale.h>
+
+#include <qstringlist.h>
+
+namespace KioSMTP {
+
+ void TransactionState::setFailedFatally( int code, const QString & msg ) {
+ mFailed = mFailedFatally = true;
+ mErrorCode = code;
+ mErrorMessage = msg;
+ }
+
+ void TransactionState::setMailFromFailed( const QString & addr, const Response & r ) {
+ setFailed();
+ mErrorCode = KIO::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 KIO::ERR_NO_CONTENT;
+ // ### what else?
+ return KIO::ERR_INTERNAL;
+ }
+
+ QString TransactionState::errorMessage() const {
+ if ( !failed() )
+ return QString::null;
+
+ if ( !mErrorMessage.isEmpty() )
+ return mErrorMessage;
+
+ if ( haveRejectedRecipients() ) {
+ QString msg = i18n("Message sending failed since the following recipients were rejected by the server:\n"
+ "%1");
+ QStringList 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/kioslave/smtp/transactionstate.h b/kioslave/smtp/transactionstate.h
new file mode 100644
index 000000000..96376718e
--- /dev/null
+++ b/kioslave/smtp/transactionstate.h
@@ -0,0 +1,185 @@
+/* -*- c++ -*-
+ transactionstate.h
+
+ This file is part of kio_smtp, the KDE SMTP kioslave.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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.
+*/
+
+#ifndef __KIOSMTP_TRANSACTIONSTATE_H__
+#define __KIOSMTP_TRANSACTIONSTATE_H__
+
+#include "response.h"
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+namespace KioSMTP {
+
+ /**
+ @short A class modelling an SMTP transaction's state
+
+ This class models SMTP transaction state, ie. the collective
+ result of the MAIL FROM:, RCPT TO: and DATA commands. This is
+ needed since e.g. a single failed RCPT TO: command does not
+ neccessarily fail the whole transaction (servers are free to
+ accept delivery for some recipients, but not for others).
+
+ The class can operate in two modes, which differ in the way
+ failed recipients are handled. If @p rcptToDenyIsFailure is true
+ (the default), then any failing RCPT TO: will cause the
+ transaction to fail. Since at the point of RCPT TO: failure
+ detection, the DATA command may have already been sent
+ (pipelining), the only way to cancel the transaction is to take
+ down the connection hard (ie. without proper quit).
+
+ Since that is not very nice behaviour, a second mode that is more
+ to the spirit of SMTP is provided that can cope with partially
+ failed RCPT TO: commands.
+ */
+ class TransactionState {
+ public:
+ struct RecipientRejection {
+ RecipientRejection( const QString & who=QString::null,
+ const QString & why=QString::null )
+ : recipient( who ), reason( why ) {}
+ QString recipient;
+ QString reason;
+#ifdef KIOSMTP_COMPARATORS
+ bool operator==( const RecipientRejection & other ) const {
+ return recipient == other.recipient && reason == other.reason;
+ }
+#endif
+ };
+ typedef QValueList<RecipientRejection> RejectedRecipientList;
+
+ TransactionState( bool rcptToDenyIsFailure=true )
+ : mErrorCode( 0 ),
+ mRcptToDenyIsFailure( rcptToDenyIsFailure ),
+ mAtLeastOneRecipientWasAccepted( false ),
+ mDataCommandIssued( false ),
+ mDataCommandSucceeded( false ),
+ mFailed( false ),
+ mFailedFatally( false ),
+ mComplete( false ) {}
+
+ /** @return whether the transaction failed (e.g. the server
+ rejected all recipients. Graceful failure is handled after
+ transaction ends. */
+ bool failed() const { return mFailed || mFailedFatally; }
+ void setFailed() { mFailed = true; }
+
+ /** @return whether the failure was so grave that an immediate
+ untidy connection shutdown is in order (ie. @ref
+ smtp_close(false)). Fatal failure is handled immediately */
+ bool failedFatally() const { return mFailedFatally; }
+ void setFailedFatally( int code=0, const QString & msg=QString::null );
+
+ /** @return whether the transaction was completed successfully */
+ bool complete() const { return mComplete; }
+ void setComplete() { mComplete = true; }
+
+ /** @return an appropriate KIO error code in case the transaction
+ failed, or 0 otherwise */
+ int errorCode() const;
+ /** @return an appropriate error message in case the transaction
+ failed or QString::null otherwise */
+ QString errorMessage() const;
+
+ void setMailFromFailed( const QString & addr, const Response & r );
+
+ bool dataCommandIssued() const { return mDataCommandIssued; }
+ void setDataCommandIssued( bool issued ) { mDataCommandIssued = issued; }
+
+ bool dataCommandSucceeded() const {
+ return mDataCommandIssued && mDataCommandSucceeded;
+ }
+ void setDataCommandSucceeded( bool succeeded, const Response & r );
+
+ Response dataResponse() const {
+ return mDataResponse;
+ }
+
+ bool atLeastOneRecipientWasAccepted() const {
+ return mAtLeastOneRecipientWasAccepted;
+ }
+ void setRecipientAccepted() {
+ mAtLeastOneRecipientWasAccepted = true;
+ }
+
+ bool haveRejectedRecipients() const {
+ return !mRejectedRecipients.empty();
+ }
+ RejectedRecipientList rejectedRecipients() const {
+ return mRejectedRecipients;
+ }
+ void addRejectedRecipient( const RecipientRejection & r );
+ void addRejectedRecipient( const QString & who, const QString & why ) {
+ addRejectedRecipient( RecipientRejection( who, why ) );
+ }
+
+ void clear() {
+ mRejectedRecipients.clear();
+ mDataResponse.clear();
+ mAtLeastOneRecipientWasAccepted
+ = mDataCommandIssued
+ = mDataCommandSucceeded
+ = mFailed = mFailedFatally
+ = mComplete = false;
+ }
+
+#ifdef KIOSMTP_COMPARATORS
+ bool operator==( const TransactionState & other ) const {
+ return
+ mAtLeastOneRecipientWasAccepted == other.mAtLeastOneRecipientWasAccepted &&
+ mDataCommandIssued == other.mDataCommandIssued &&
+ mDataCommandSucceeded == other.mDataCommandSucceeded &&
+ mFailed == other.mFailed &&
+ mFailedFatally == other.mFailedFatally &&
+ mComplete == other.mComplete &&
+ mDataResponse.code() == other.mDataResponse.code() &&
+ mRejectedRecipients == other.mRejectedRecipients;
+ }
+#endif
+
+
+ private:
+ RejectedRecipientList mRejectedRecipients;
+ Response mDataResponse;
+ QString mErrorMessage;
+ int mErrorCode;
+ bool mRcptToDenyIsFailure;
+ bool mAtLeastOneRecipientWasAccepted;
+ bool mDataCommandIssued;
+ bool mDataCommandSucceeded;
+ bool mFailed;
+ bool mFailedFatally;
+ bool mComplete;
+ };
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_TRANSACTIONSTATE_H__