summaryrefslogtreecommitdiffstats
path: root/kmail/kmsender.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kmail/kmsender.cpp')
-rw-r--r--kmail/kmsender.cpp1231
1 files changed, 1231 insertions, 0 deletions
diff --git a/kmail/kmsender.cpp b/kmail/kmsender.cpp
new file mode 100644
index 000000000..fc2bfba1f
--- /dev/null
+++ b/kmail/kmsender.cpp
@@ -0,0 +1,1231 @@
+// kmsender.cpp
+
+#include <config.h>
+
+#define REALLY_WANT_KMSENDER
+#include "kmsender.h"
+#include "kmsender_p.h"
+#undef REALLY_WANT_KMSENDER
+
+#include <kmime_header_parsing.h>
+using namespace KMime::Types;
+
+#include <kio/passdlg.h>
+#include <kio/scheduler.h>
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kdeversion.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include "globalsettings.h"
+#include "kmfiltermgr.h"
+
+#include "kcursorsaver.h"
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include "progressmanager.h"
+#include "kmaccount.h"
+#include "kmtransport.h"
+#include "kmfolderindex.h"
+#include "kmfoldermgr.h"
+#include "kmmsgdict.h"
+#include "kmmsgpart.h"
+#include "protocols.h"
+#include "kmcommands.h"
+#include <mimelib/mediatyp.h>
+#include <mimelib/enum.h>
+#include <mimelib/param.h>
+
+#define SENDER_GROUP "sending mail"
+
+//-----------------------------------------------------------------------------
+KMSender::KMSender()
+ : mOutboxFolder( 0 ), mSentFolder( 0 )
+{
+ mPrecommand = 0;
+ mSendProc = 0;
+ mSendProcStarted = false;
+ mSendInProgress = false;
+ mCurrentMsg = 0;
+ mTransportInfo = new KMTransportInfo();
+ readConfig();
+ mSendAborted = false;
+ mSentMessages = 0;
+ mTotalMessages = 0;
+ mFailedMessages = 0;
+ mSentBytes = 0;
+ mTotalBytes = 0;
+ mProgressItem = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMSender::~KMSender()
+{
+ writeConfig(false);
+ delete mSendProc;
+ delete mPrecommand;
+ delete mTransportInfo;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::setStatusMsg(const QString &msg)
+{
+ if ( mProgressItem )
+ mProgressItem->setStatus(msg);
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::readConfig(void)
+{
+ QString str;
+ KConfigGroup config(KMKernel::config(), SENDER_GROUP);
+
+ mSendImmediate = config.readBoolEntry("Immediate", true);
+ mSendQuotedPrintable = config.readBoolEntry("Quoted-Printable", true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::writeConfig(bool aWithSync)
+{
+ KConfigGroup config(KMKernel::config(), SENDER_GROUP);
+
+ config.writeEntry("Immediate", mSendImmediate);
+ config.writeEntry("Quoted-Printable", mSendQuotedPrintable);
+
+ if (aWithSync) config.sync();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMSender::settingsOk() const
+{
+ if (KMTransportInfo::availableTransports().isEmpty())
+ {
+ KMessageBox::information(0,i18n("Please create an account for sending and try again."));
+ return false;
+ }
+ return true;
+}
+
+static void handleRedirections( KMMessage * m ) {
+ const QString from = m->headerField("X-KMail-Redirect-From");
+ const QString msgId = m->msgId();
+ if( from.isEmpty() || msgId.isEmpty() )
+ m->setMsgId( KMMessage::generateMessageId( m->sender() ) );
+}
+
+//-----------------------------------------------------------------------------
+bool KMSender::doSend(KMMessage* aMsg, short sendNow)
+{
+ if(!aMsg)
+ return false;
+
+ if (!settingsOk()) return false;
+
+ if (aMsg->to().isEmpty())
+ {
+ // RFC822 says:
+ // Note that the "Bcc" field may be empty, while the "To" field is required to
+ // have at least one address.
+ //
+ // however:
+ //
+ // The following string is accepted according to RFC 2822,
+ // section 3.4 "Address Specification" where they say:
+ //
+ // "An address may either be an individual mailbox,
+ // or a group of mailboxes."
+ // and:
+ // "group + display-name ":" [mailbox-list / CFWS] ";"
+ // [CFWS]"
+ //
+ // In this syntax our "undisclosed-recipients: ;"
+ // just specifies an empty group.
+ //
+ // In further explanations RFC 2822 states that it *is*
+ // allowed to have a ZERO number of mailboxes in the "mailbox-list".
+ aMsg->setTo("Undisclosed.Recipients: ;");
+ }
+
+ handleRedirections( aMsg );
+
+ if (sendNow==-1) sendNow = mSendImmediate;
+
+ KMFolder * const outbox = kmkernel->outboxFolder();
+ const KMFolderOpener openOutbox( outbox, "outbox" );
+
+ aMsg->setStatus(KMMsgStatusQueued);
+
+ if ( const int err = outbox->addMsg(aMsg) ) {
+ Q_UNUSED( err );
+ KMessageBox::information(0,i18n("Cannot add message to outbox folder"));
+ return false;
+ }
+
+ //Ensure the message is correctly and fully parsed
+
+ /* The above was added by Marc and seems to be necessary to ensure
+ * the mail is in a sane state before sending. The unGet makes the
+ * attached unencrypted version of the mail (if there is one ) disappear.
+ * though, so we need to make sure to keep it around and restore it
+ * afterwards. The real fix would be to replace the unGet with
+ * whatever parsing is triggered by it, but I'm too chicken to do that,
+ * in this branch.
+ * Note that the unencrypted mail will be lost if the mail remains in
+ * the outbox across a restart anyhow, but that never worked, afaikt. */
+ const int idx = outbox->count() - 1;
+ KMMessage * const unencryptedMsg = aMsg->unencryptedMsg();
+ outbox->unGetMsg( idx );
+ KMMessage * const tempMsg = outbox->getMsg( idx );
+ tempMsg->setUnencryptedMsg( unencryptedMsg );
+
+ if ( !sendNow || mSendInProgress )
+ return true;
+
+ return sendQueued();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::outboxMsgAdded(int idx)
+{
+ ++mTotalMessages;
+ KMMsgBase* msg = kmkernel->outboxFolder()->getMsgBase(idx);
+ Q_ASSERT(msg);
+ if ( msg )
+ mTotalBytes += msg->msgSize();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMSender::doSendQueued( const QString &customTransport )
+{
+ if (!settingsOk()) return false;
+
+ if (mSendInProgress)
+ {
+ return false;
+ }
+
+ // open necessary folders
+ mOutboxFolder = kmkernel->outboxFolder();
+ mOutboxFolder->open("dosendoutbox");
+ mTotalMessages = mOutboxFolder->count();
+ if (mTotalMessages == 0) {
+ // Nothing in the outbox. We are done.
+ mOutboxFolder->close("dosendoutbox");
+ mOutboxFolder = 0;
+ return true;
+ }
+ mTotalBytes = 0;
+ for( int i = 0 ; i<mTotalMessages ; ++i )
+ mTotalBytes += mOutboxFolder->getMsgBase(i)->msgSize();
+
+ connect( mOutboxFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(outboxMsgAdded(int)) );
+ mCurrentMsg = 0;
+
+ mSentFolder = kmkernel->sentFolder();
+ mSentFolder->open("dosendsent");
+ kmkernel->filterMgr()->ref();
+
+ // start sending the messages
+ mCustomTransport = customTransport;
+ doSendMsg();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::emitProgressInfo( int currentFileProgress )
+{
+ int percent = (mTotalBytes) ? ( 100 * (mSentBytes+currentFileProgress) / mTotalBytes ) : 0;
+ if (percent > 100) percent = 100;
+ mProgressItem->setProgress(percent);
+}
+
+static bool messageIsDispositionNotificationReport( KMMessage *msg )
+{
+ if ( msg->type() == DwMime::kTypeMessage &&
+ msg->subtype() == DwMime::kSubtypeDispositionNotification )
+ return true;
+
+ if ( msg->type() != DwMime::kTypeMultipart ||
+ msg->subtype() != DwMime::kSubtypeReport )
+ return false;
+
+ DwMediaType& ct = msg->dwContentType();
+ DwParameter *param = ct.FirstParameter();
+ while( param ) {
+ if ( !qstricmp( param->Attribute().c_str(), "report-type")
+ && !qstricmp( param->Value().c_str(), "disposition-notification" ) )
+ return true;
+ else
+ param = param->Next();
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::doSendMsg()
+{
+ if (!kmkernel) //To handle message sending in progress when kaplan is exited
+ return; //TODO: handle this case better
+
+ const bool someSent = mCurrentMsg;
+ if (someSent) {
+ mSentMessages++;
+ mSentBytes += mCurrentMsg->msgSize();
+ }
+
+ // Post-process sent message (filtering)
+ KMFolder *sentFolder = 0, *imapSentFolder = 0;
+ if (mCurrentMsg && kmkernel->filterMgr())
+ {
+ mCurrentMsg->setTransferInProgress( false );
+ if( mCurrentMsg->hasUnencryptedMsg() ) {
+ kdDebug(5006) << "KMSender::doSendMsg() post-processing: replace mCurrentMsg body by unencryptedMsg data" << endl;
+ // delete all current body parts
+ mCurrentMsg->deleteBodyParts();
+ // copy Content-[..] headers from unencrypted message to current one
+ KMMessage & newMsg( *mCurrentMsg->unencryptedMsg() );
+ mCurrentMsg->dwContentType() = newMsg.dwContentType();
+ mCurrentMsg->setContentTransferEncodingStr( newMsg.contentTransferEncodingStr() );
+ QCString newDispo = newMsg.headerField("Content-Disposition").latin1();
+ if( newDispo.isEmpty() )
+ mCurrentMsg->removeHeaderField( "Content-Disposition" );
+ else
+ mCurrentMsg->setHeaderField( "Content-Disposition", newDispo );
+ // copy the body
+ mCurrentMsg->setBody( newMsg.body() );
+ // copy all the body parts
+ KMMessagePart msgPart;
+ for( int i = 0; i < newMsg.numBodyParts(); ++i ) {
+ newMsg.bodyPart( i, &msgPart );
+ mCurrentMsg->addBodyPart( &msgPart );
+ }
+ }
+ mCurrentMsg->setStatus(KMMsgStatusSent);
+ mCurrentMsg->setStatus(KMMsgStatusRead); // otherwise it defaults to new on imap
+ mCurrentMsg->updateAttachmentState();
+
+ const KPIM::Identity & id = kmkernel->identityManager()
+ ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
+ if ( !mCurrentMsg->fcc().isEmpty() )
+ {
+ sentFolder = kmkernel->folderMgr()->findIdString( mCurrentMsg->fcc() );
+ if ( sentFolder == 0 )
+ // This is *NOT* supposed to be imapSentFolder!
+ sentFolder =
+ kmkernel->dimapFolderMgr()->findIdString( mCurrentMsg->fcc() );
+ if ( sentFolder == 0 )
+ imapSentFolder =
+ kmkernel->imapFolderMgr()->findIdString( mCurrentMsg->fcc() );
+ }
+ // No, or no usable sentFolder, and no, or no usable imapSentFolder,
+ // let's try the on in the identity
+ if ( ( sentFolder == 0 || sentFolder->isReadOnly() )
+ && ( imapSentFolder == 0 || imapSentFolder->isReadOnly() )
+ && !id.fcc().isEmpty() )
+ {
+ sentFolder = kmkernel->folderMgr()->findIdString( id.fcc() );
+ if ( sentFolder == 0 )
+ // This is *NOT* supposed to be imapSentFolder!
+ sentFolder = kmkernel->dimapFolderMgr()->findIdString( id.fcc() );
+ if ( sentFolder == 0 )
+ imapSentFolder = kmkernel->imapFolderMgr()->findIdString( id.fcc() );
+ }
+ if (imapSentFolder
+ && ( imapSentFolder->noContent() || imapSentFolder->isReadOnly() ) )
+ imapSentFolder = 0;
+
+ if ( sentFolder == 0 || sentFolder->isReadOnly() )
+ sentFolder = kmkernel->sentFolder();
+
+ if ( sentFolder ) {
+ if ( const int err = sentFolder->open("sentFolder") ) {
+ Q_UNUSED( err );
+ cleanup();
+ return;
+ }
+ }
+
+ // Disable the emitting of msgAdded signal, because the message is taken out of the
+ // current folder (outbox) and re-added, to make filter actions changing the message
+ // work. We don't want that to screw up message counts.
+ if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( true );
+ const int processResult = kmkernel->filterMgr()->process(mCurrentMsg,KMFilterMgr::Outbound);
+ if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( false );
+
+ // 0==processed ok, 1==no filter matched, 2==critical error, abort!
+ switch (processResult) {
+ case 2:
+ perror("Critical error: Unable to process sent mail (out of space?)");
+ KMessageBox::information(0, i18n("Critical error: "
+ "Unable to process sent mail (out of space?)"
+ "Moving failing message to \"sent-mail\" folder."));
+ if ( sentFolder ) {
+ sentFolder->moveMsg(mCurrentMsg);
+ sentFolder->close("sentFolder");
+ }
+ cleanup();
+ return;
+ case 1:
+ if ( sentFolder && sentFolder->moveMsg(mCurrentMsg) != 0 )
+ {
+ KMessageBox::error(0, i18n("Moving the sent message \"%1\" from the "
+ "\"outbox\" to the \"sent-mail\" folder failed.\n"
+ "Possible reasons are lack of disk space or write permission. "
+ "Please try to fix the problem and move the message manually.")
+ .arg(mCurrentMsg->subject()));
+ cleanup();
+ return;
+ }
+ if (imapSentFolder) {
+ // Does proper folder refcounting and message locking
+ KMCommand *command = new KMMoveCommand( imapSentFolder, mCurrentMsg );
+ command->keepFolderOpen( sentFolder ); // will open it, and close it once done
+ command->start();
+ }
+ default:
+ break;
+ }
+ setStatusByLink( mCurrentMsg );
+ if (mCurrentMsg->parent() && !imapSentFolder) {
+ // for speed optimization, this code assumes that mCurrentMsg is the
+ // last one in it's parent folder; make sure that's really the case:
+ assert( mCurrentMsg->parent()->find( mCurrentMsg )
+ == mCurrentMsg->parent()->count() - 1 );
+ // unGet this message:
+ mCurrentMsg->parent()->unGetMsg( mCurrentMsg->parent()->count() -1 );
+ }
+
+ mCurrentMsg = 0;
+ }
+
+ // See if there is another queued message
+ mCurrentMsg = mOutboxFolder->getMsg(mFailedMessages);
+ if ( mCurrentMsg && !mCurrentMsg->transferInProgress() &&
+ mCurrentMsg->sender().isEmpty() ) {
+ // if we do not have a sender address then use the email address of the
+ // message's identity or of the default identity unless those two are also
+ // empty
+ const KPIM::Identity & id = kmkernel->identityManager()
+ ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
+ if ( !id.emailAddr().isEmpty() ) {
+ mCurrentMsg->setFrom( id.fullEmailAddr() );
+ }
+ else if ( !kmkernel->identityManager()->defaultIdentity().emailAddr().isEmpty() ) {
+ mCurrentMsg->setFrom( kmkernel->identityManager()->defaultIdentity().fullEmailAddr() );
+ }
+ else {
+ KMessageBox::sorry( 0, i18n( "It's not possible to send messages "
+ "without specifying a sender address.\n"
+ "Please set the email address of "
+ "identity '%1' in the Identities "
+ "section of the configuration dialog "
+ "and then try again." )
+ .arg( id.identityName() ) );
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ }
+ }
+ if (!mCurrentMsg || mCurrentMsg->transferInProgress())
+ {
+ // a message is locked finish the send
+ if (mCurrentMsg && mCurrentMsg->transferInProgress())
+ mCurrentMsg = 0;
+ // no more message: cleanup and done
+ if ( sentFolder != 0 )
+ sentFolder->close("sentFolder");
+ if ( someSent ) {
+ if ( mSentMessages == mTotalMessages ) {
+ setStatusMsg(i18n("%n queued message successfully sent.",
+ "%n queued messages successfully sent.",
+ mSentMessages));
+ } else {
+ setStatusMsg(i18n("%1 of %2 queued messages successfully sent.")
+ .arg(mSentMessages).arg( mTotalMessages ));
+ }
+ }
+ cleanup();
+ return;
+ }
+ mCurrentMsg->setTransferInProgress( true );
+
+ // start the sender process or initialize communication
+ if (!mSendInProgress)
+ {
+ Q_ASSERT( !mProgressItem );
+ mProgressItem = KPIM::ProgressManager::createProgressItem(
+ "Sender",
+ i18n( "Sending messages" ),
+ i18n("Initiating sender process..."),
+ true );
+ connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
+ this, SLOT( slotAbortSend() ) );
+ kapp->ref();
+ mSendInProgress = true;
+ }
+
+ QString msgTransport = mCustomTransport;
+ if ( msgTransport.isEmpty() ) {
+ msgTransport = mCurrentMsg->headerField("X-KMail-Transport");
+ }
+ if ( msgTransport.isEmpty() ) {
+ const QStringList sl = KMTransportInfo::availableTransports();
+ if (!sl.empty()) msgTransport = sl.front();
+ }
+
+ if (!mSendProc || msgTransport != mMethodStr) {
+ if (mSendProcStarted && mSendProc) {
+ mSendProc->finish();
+ mSendProcStarted = false;
+ }
+
+ mSendProc = createSendProcFromString(msgTransport);
+ mMethodStr = msgTransport;
+
+ if( mTransportInfo->encryption == "TLS" || mTransportInfo->encryption == "SSL" ) {
+ mProgressItem->setUsesCrypto( true );
+ } else if ( !mCustomTransport.isEmpty() ) {
+ int result = KMessageBox::warningContinueCancel( 0,
+ i18n( "You have chosen to send all queued email using an unencrypted transport, do you want to continue? "),
+ i18n( "Security Warning" ),
+ i18n( "Send Unencrypted" ),
+ "useCustomTransportWithoutAsking", false);
+
+ if( result == KMessageBox::Cancel ) {
+ mProgressItem->cancel();
+ mProgressItem->setComplete();
+ slotAbortSend();
+ cleanup();
+ return;
+ }
+ }
+
+ if (!mSendProc)
+ sendProcStarted(false);
+ else {
+ connect(mSendProc, SIGNAL(idle()), SLOT(slotIdle()));
+ connect(mSendProc, SIGNAL(started(bool)), SLOT(sendProcStarted(bool)));
+
+ // Run the precommand if there is one
+ if ( !mTransportInfo->precommand.isEmpty() ) {
+ runPrecommand( mTransportInfo->precommand );
+ return;
+ }
+
+ mSendProc->start();
+ }
+ }
+ else if (!mSendProcStarted)
+ mSendProc->start();
+ else
+ doSendMsgAux();
+}
+
+bool KMSender::runPrecommand( const QString & cmd ) {
+ setStatusMsg( i18n("Executing precommand %1").arg( cmd ) );
+ mPrecommand = new KMPrecommand( cmd );
+ connect( mPrecommand, SIGNAL(finished(bool)),
+ SLOT(slotPrecommandFinished(bool)) );
+ if ( !mPrecommand->start() ) {
+ delete mPrecommand; mPrecommand = 0;
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::sendProcStarted(bool success)
+{
+ if (!success) {
+ if (mSendProc)
+ mSendProc->finish();
+ else
+ setStatusMsg(i18n("Unrecognized transport protocol. Unable to send message."));
+ mSendProc = 0;
+ mSendProcStarted = false;
+ cleanup();
+ return;
+ }
+ doSendMsgAux();
+}
+
+
+static QStringList addrSpecListToStringList( const AddrSpecList & l, bool allowEmpty=false ) {
+ QStringList result;
+ for ( AddrSpecList::const_iterator it = l.begin(), end = l.end() ; it != end ; ++it ) {
+ const QString s = (*it).asString();
+ if ( allowEmpty || !s.isEmpty() )
+ result.push_back( s );
+ }
+ return result;
+}
+
+static void extractSenderToCCAndBcc( KMMessage * aMsg, QString * sender, QStringList * to, QStringList * cc, QStringList * bcc ) {
+ if ( sender ) *sender = aMsg->sender();
+ if( !aMsg->headerField("X-KMail-Recipients").isEmpty() ) {
+ // extended BCC handling to prevent TOs and CCs from seeing
+ // BBC information by looking at source of an OpenPGP encrypted mail
+ if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "X-KMail-Recipients" ) );
+ aMsg->removeHeaderField( "X-KMail-Recipients" );
+ } else {
+ if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "To" ) );
+ if ( cc ) *cc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Cc" ) );
+ if ( bcc ) *bcc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Bcc" ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::doSendMsgAux()
+{
+ mSendProcStarted = true;
+
+ // start sending the current message
+
+ setStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3")
+ .arg(mSentMessages+mFailedMessages+1).arg(mTotalMessages)
+ .arg(mCurrentMsg->subject()));
+ QStringList to, cc, bcc;
+ QString sender;
+ extractSenderToCCAndBcc( mCurrentMsg, &sender, &to, &cc, &bcc );
+
+ // MDNs are required to have an empty envelope from as per RFC2298.
+ if ( messageIsDispositionNotificationReport( mCurrentMsg ) && GlobalSettings::self()->sendMDNsWithEmptySender() )
+ sender = "<>";
+
+ const QByteArray message = mCurrentMsg->asSendableString();
+ if ( sender.isEmpty() || !mSendProc->send( sender, to, cc, bcc, message ) ) {
+ if ( mCurrentMsg )
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ cleanup();
+ setStatusMsg(i18n("Failed to send (some) queued messages."));
+ return;
+ }
+ // Do *not* add code here, after send(). It can happen that this method
+ // is called recursively if send() emits the idle signal directly.
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::cleanup(void)
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ if (mSendProc && mSendProcStarted) mSendProc->finish();
+ mSendProc = 0;
+ mSendProcStarted = false;
+ if (mSendInProgress) kapp->deref();
+ mSendInProgress = false;
+ if (mCurrentMsg)
+ {
+ mCurrentMsg->setTransferInProgress( false );
+ mCurrentMsg = 0;
+ }
+ if ( mSentFolder ) {
+ mSentFolder->close("dosendsent");
+ mSentFolder = 0;
+ }
+ if ( mOutboxFolder ) {
+ disconnect( mOutboxFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(outboxMsgAdded(int)) );
+ mOutboxFolder->close("dosendoutbox");
+ if ( mOutboxFolder->count( true ) == 0 ) {
+ mOutboxFolder->expunge();
+ }
+ else if ( mOutboxFolder->needsCompacting() ) {
+ mOutboxFolder->compact( KMFolder::CompactSilentlyNow );
+ }
+ mOutboxFolder = 0;
+ }
+
+ mSendAborted = false;
+ mSentMessages = 0;
+ mFailedMessages = 0;
+ mSentBytes = 0;
+ if ( mProgressItem )
+ mProgressItem->setComplete();
+ mProgressItem = 0;
+ kmkernel->filterMgr()->deref();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::slotAbortSend()
+{
+ mSendAborted = true;
+ delete mPrecommand;
+ mPrecommand = 0;
+ if (mSendProc) mSendProc->abort();
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::slotIdle()
+{
+ assert(mSendProc != 0);
+
+ QString msg;
+ QString errString;
+ if (mSendProc)
+ errString = mSendProc->lastErrorMessage();
+
+ if (mSendAborted) {
+ // sending of message aborted
+ if ( mCurrentMsg ) {
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ }
+ msg = i18n("Sending aborted:\n%1\n"
+ "The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(errString)
+ .arg(mMethodStr);
+ if (!errString.isEmpty()) KMessageBox::error(0,msg);
+ setStatusMsg( i18n( "Sending aborted." ) );
+ } else {
+ if (!mSendProc->sendOk()) {
+ if ( mCurrentMsg )
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ mFailedMessages++;
+ // reset cached password
+ QMapIterator <QString,QString> pc;
+ if ( (pc = mPasswdCache.find( mMethodStr )) != mPasswdCache.end() ) {
+ mPasswdCache.erase(pc);
+ }
+ // Sending of message failed.
+ if (!errString.isEmpty()) {
+ int res = KMessageBox::Yes;
+ if (mSentMessages+mFailedMessages != mTotalMessages) {
+ msg = i18n("<p>Sending failed:</p>"
+ "<p>%1</p>"
+ "<p>The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.</p>"
+ "<p>The following transport protocol was used: %2</p>"
+ "<p>Do you want me to continue sending the remaining messages?</p>")
+ .arg(errString)
+ .arg(mMethodStr);
+ res = KMessageBox::warningYesNo( 0 , msg ,
+ i18n( "Continue Sending" ), i18n( "&Continue Sending" ),
+ i18n("&Abort Sending") );
+ } else {
+ msg = i18n("Sending failed:\n%1\n"
+ "The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(errString)
+ .arg(mMethodStr);
+ KMessageBox::error(0,msg);
+ }
+ if (res == KMessageBox::Yes) {
+ // Try the next one.
+ doSendMsg();
+ return;
+ } else {
+ setStatusMsg( i18n( "Sending aborted." ) );
+ }
+ }
+ } else {
+ // Sending suceeded.
+ doSendMsg();
+ return;
+ }
+ }
+ mSendProc->finish();
+ mSendProc = 0;
+ mSendProcStarted = false;
+
+ cleanup();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::slotPrecommandFinished(bool normalExit)
+{
+ delete mPrecommand;
+ mPrecommand = 0;
+ if (normalExit) mSendProc->start();
+ else slotIdle();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::setSendImmediate(bool aSendImmediate)
+{
+ mSendImmediate = aSendImmediate;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::setSendQuotedPrintable(bool aSendQuotedPrintable)
+{
+ mSendQuotedPrintable = aSendQuotedPrintable;
+}
+
+
+//-----------------------------------------------------------------------------
+KMSendProc* KMSender::createSendProcFromString( const QString & transport )
+{
+ mTransportInfo->type = QString::null;
+ int nr = KMTransportInfo::findTransport(transport);
+ if (nr)
+ {
+ mTransportInfo->readConfig(nr);
+ } else {
+ if (transport.startsWith("smtp://")) // should probably use KURL and SMTP_PROTOCOL
+ {
+ mTransportInfo->type = "smtp";
+ mTransportInfo->auth = false;
+ mTransportInfo->encryption = "NONE";
+ QString serverport = transport.mid(7);
+ int colon = serverport.find(':');
+ if (colon != -1) {
+ mTransportInfo->host = serverport.left(colon);
+ mTransportInfo->port = serverport.mid(colon + 1);
+ } else {
+ mTransportInfo->host = serverport;
+ mTransportInfo->port = "25";
+ }
+ } else
+ if (transport.startsWith("smtps://")) // should probably use KURL and SMTPS_PROTOCOL
+ {
+ mTransportInfo->type = "smtps";
+ mTransportInfo->auth = false;
+ mTransportInfo->encryption = "ssl";
+ QString serverport = transport.mid(7);
+ int colon = serverport.find(':');
+ if (colon != -1) {
+ mTransportInfo->host = serverport.left(colon);
+ mTransportInfo->port = serverport.mid(colon + 1);
+ } else {
+ mTransportInfo->host = serverport;
+ mTransportInfo->port = "465";
+ }
+ }
+ else if (transport.startsWith("file://"))
+ {
+ mTransportInfo->type = "sendmail";
+ mTransportInfo->host = transport.mid(7);
+ }
+ }
+ // strip off a trailing "/"
+ while (mTransportInfo->host.endsWith("/")) {
+ mTransportInfo->host.truncate(mTransportInfo->host.length()-1);
+ }
+
+
+ if (mTransportInfo->type == "sendmail")
+ return new KMSendSendmail(this);
+ if (mTransportInfo->type == "smtp" || mTransportInfo->type == "smtps")
+ return new KMSendSMTP(this);
+
+ return 0L;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::setStatusByLink(const KMMessage *aMsg)
+{
+ int n = 0;
+ while (1) {
+ ulong msn;
+ KMMsgStatus status;
+ aMsg->getLink(n, &msn, &status);
+ if (!msn || !status)
+ break;
+ n++;
+
+ KMFolder *folder = 0;
+ int index = -1;
+ KMMsgDict::instance()->getLocation(msn, &folder, &index);
+ if (folder && index != -1) {
+ KMFolderOpener openFolder(folder, "setstatus");
+ if ( status == KMMsgStatusDeleted ) {
+ // Move the message to the trash folder
+ KMDeleteMsgCommand *cmd =
+ new KMDeleteMsgCommand( folder, folder->getMsg( index ) );
+ cmd->start();
+ } else {
+ folder->setStatus(index, status);
+ }
+ } else {
+ kdWarning(5006) << k_funcinfo << "Cannot update linked message, it could not be found!" << endl;
+ }
+ }
+}
+
+//=============================================================================
+//=============================================================================
+KMSendProc::KMSendProc( KMSender * sender )
+ : QObject( 0 ),
+ mSender( sender ),
+ mLastErrorMessage(),
+ mSendOk( false ),
+ mSending( false )
+{
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::reset()
+{
+ mSending = false;
+ mSendOk = false;
+ mLastErrorMessage = QString::null;
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::failed(const QString &aMsg)
+{
+ mSending = false;
+ mSendOk = false;
+ mLastErrorMessage = aMsg;
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::statusMsg(const QString& aMsg)
+{
+ if (mSender) mSender->setStatusMsg(aMsg);
+}
+
+//=============================================================================
+//=============================================================================
+KMSendSendmail::KMSendSendmail( KMSender * sender )
+ : KMSendProc( sender ),
+ mMsgStr(),
+ mMsgPos( 0 ),
+ mMsgRest( 0 ),
+ mMailerProc( 0 )
+{
+
+}
+
+KMSendSendmail::~KMSendSendmail() {
+ delete mMailerProc; mMailerProc = 0;
+}
+
+bool KMSendSendmail::doStart() {
+
+ if (mSender->transportInfo()->host.isEmpty())
+ {
+ const QString str = i18n("Please specify a mailer program in the settings.");
+ const QString msg = i18n("Sending failed:\n%1\n"
+ "The message will stay in the 'outbox' folder and will be resent.\n"
+ "Please remove it from there if you do not want the message to "
+ "be resent.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(str + "\n")
+ .arg("sendmail://");
+ KMessageBox::information(0,msg);
+ return false;
+ }
+
+ if (!mMailerProc)
+ {
+ mMailerProc = new KProcess;
+ assert(mMailerProc != 0);
+ connect(mMailerProc,SIGNAL(processExited(KProcess*)),
+ this, SLOT(sendmailExited(KProcess*)));
+ connect(mMailerProc,SIGNAL(wroteStdin(KProcess*)),
+ this, SLOT(wroteStdin(KProcess*)));
+ connect(mMailerProc,SIGNAL(receivedStderr(KProcess*,char*,int)),
+ this, SLOT(receivedStderr(KProcess*, char*, int)));
+ }
+ return true;
+}
+
+void KMSendSendmail::doFinish() {
+ delete mMailerProc;
+ mMailerProc = 0;
+}
+
+void KMSendSendmail::abort()
+{
+ delete mMailerProc;
+ mMailerProc = 0;
+ mSendOk = false;
+ mMsgStr = 0;
+ idle();
+}
+
+bool KMSendSendmail::doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) {
+ mMailerProc->clearArguments();
+ *mMailerProc << mSender->transportInfo()->host
+ << "-i" << "-f" << sender
+ << to << cc << bcc ;
+
+ mMsgStr = message;
+
+ if ( !mMailerProc->start( KProcess::NotifyOnExit, KProcess::All ) ) {
+ KMessageBox::information( 0, i18n("Failed to execute mailer program %1")
+ .arg( mSender->transportInfo()->host ) );
+ return false;
+ }
+ mMsgPos = mMsgStr.data();
+ mMsgRest = mMsgStr.size();
+ wroteStdin( mMailerProc );
+
+ return true;
+}
+
+
+void KMSendSendmail::wroteStdin(KProcess *proc)
+{
+ char* str;
+ int len;
+
+ assert(proc!=0);
+ Q_UNUSED( proc );
+
+ str = mMsgPos;
+ len = (mMsgRest>1024 ? 1024 : mMsgRest);
+
+ if (len <= 0)
+ {
+ mMailerProc->closeStdin();
+ }
+ else
+ {
+ mMsgRest -= len;
+ mMsgPos += len;
+ mMailerProc->writeStdin(str,len);
+ // if code is added after writeStdin() KProcess probably initiates
+ // a race condition.
+ }
+}
+
+
+void KMSendSendmail::receivedStderr(KProcess *proc, char *buffer, int buflen)
+{
+ assert(proc!=0);
+ Q_UNUSED( proc );
+ mLastErrorMessage.replace(mLastErrorMessage.length(), buflen, buffer);
+}
+
+
+void KMSendSendmail::sendmailExited(KProcess *proc)
+{
+ assert(proc!=0);
+ mSendOk = (proc->normalExit() && proc->exitStatus()==0);
+ if (!mSendOk) failed(i18n("Sendmail exited abnormally."));
+ mMsgStr = 0;
+ emit idle();
+}
+
+
+
+//-----------------------------------------------------------------------------
+//=============================================================================
+//=============================================================================
+KMSendSMTP::KMSendSMTP(KMSender *sender)
+ : KMSendProc(sender),
+ mInProcess(false),
+ mJob(0),
+ mSlave(0)
+{
+ KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *, int,
+ const QString &)), this, SLOT(slaveError(KIO::Slave *, int,
+ const QString &)));
+}
+
+KMSendSMTP::~KMSendSMTP()
+{
+ if (mJob) mJob->kill();
+}
+
+bool KMSendSMTP::doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) {
+ QString query = "headers=0&from=";
+ query += KURL::encode_string( sender );
+
+ QStringList::ConstIterator it;
+
+ for ( it = to.begin(); it != to.end(); ++it )
+ query += "&to=" + KURL::encode_string(*it);
+ for ( it = cc.begin(); it != cc.end(); ++it )
+ query += "&cc=" + KURL::encode_string(*it);
+ for ( it = bcc.begin(); it != bcc.end(); ++it )
+ query += "&bcc=" + KURL::encode_string(*it);
+
+ KMTransportInfo * ti = mSender->transportInfo();
+
+ if ( ti->specifyHostname )
+ query += "&hostname=" + KURL::encode_string( ti->localHostname );
+
+ if ( !kmkernel->msgSender()->sendQuotedPrintable() )
+ query += "&body=8bit";
+
+ KURL destination;
+
+ destination.setProtocol((ti->encryption == "SSL") ? SMTPS_PROTOCOL : SMTP_PROTOCOL);
+ destination.setHost(ti->host);
+ destination.setPort(ti->port.toUShort());
+
+ if (ti->auth)
+ {
+ QMapIterator<QString,QString> tpc = mSender->mPasswdCache.find( ti->name );
+ QString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):QString::null;
+
+ if ( ti->passwd().isEmpty() )
+ ti->setPasswd( tpwd );
+
+ if( (ti->user.isEmpty() || ti->passwd().isEmpty()) &&
+ ti->authType != "GSSAPI" )
+ {
+ bool b = false;
+ int result;
+
+ KCursorSaver idle(KBusyPtr::idle());
+ QString passwd = ti->passwd();
+ result = KIO::PasswordDialog::getNameAndPassword(ti->user, passwd,
+ &b, i18n("You need to supply a username and a password to use this "
+ "SMTP server."), false, QString::null, ti->name, QString::null);
+
+ if ( result != QDialog::Accepted )
+ {
+ abort();
+ return false;
+ }
+ if (int id = KMTransportInfo::findTransport(ti->name)) {
+ ti->setPasswd( passwd );
+ ti->writeConfig(id);
+
+ // save the password into the cache
+ mSender->mPasswdCache[ti->name] = passwd;
+ }
+ }
+ destination.setUser(ti->user);
+ destination.setPass(ti->passwd());
+ }
+
+ if (!mSlave || !mInProcess)
+ {
+ KIO::MetaData slaveConfig;
+ slaveConfig.insert("tls", (ti->encryption == "TLS") ? "on" : "off");
+ if (ti->auth) slaveConfig.insert("sasl", ti->authType);
+ mSlave = KIO::Scheduler::getConnectedSlave(destination, slaveConfig);
+ }
+
+ if (!mSlave)
+ {
+ abort();
+ return false;
+ }
+
+ // dotstuffing is now done by the slave (see setting of metadata)
+ mMessage = message;
+ mMessageLength = mMessage.size();
+ mMessageOffset = 0;
+
+ if ( mMessageLength )
+ // allow +5% for subsequent LF->CRLF and dotstuffing (an average
+ // over 2G-lines gives an average line length of 42-43):
+ query += "&size=" + QString::number( qRound( mMessageLength * 1.05 ) );
+
+ destination.setPath("/send");
+ destination.setQuery( query );
+
+ mJob = KIO::put( destination, -1, false, false, false );
+ if ( !mJob ) {
+ abort();
+ return false;
+ }
+ mJob->addMetaData( "lf2crlf+dotstuff", "slave" );
+ KIO::Scheduler::assignJobToSlave(mSlave, mJob);
+ connect(mJob, SIGNAL(result(KIO::Job *)), this, SLOT(result(KIO::Job *)));
+ connect(mJob, SIGNAL(dataReq(KIO::Job *, QByteArray &)),
+ this, SLOT(dataReq(KIO::Job *, QByteArray &)));
+ mSendOk = true;
+ mInProcess = true;
+ return true;
+}
+
+void KMSendSMTP::cleanup() {
+ if(mJob)
+ {
+ mJob->kill(true);
+ mJob = 0;
+ mSlave = 0;
+ }
+
+ if (mSlave)
+ {
+ KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ }
+
+ mInProcess = false;
+}
+
+void KMSendSMTP::abort() {
+ cleanup();
+ emit idle();
+}
+
+void KMSendSMTP::doFinish() {
+ cleanup();
+}
+
+void KMSendSMTP::dataReq(KIO::Job *, QByteArray &array)
+{
+ // Send it by 32K chuncks
+ const int chunkSize = QMIN( mMessageLength - mMessageOffset, 32*1024 );
+ if ( chunkSize > 0 ) {
+ array.duplicate(mMessage.data() + mMessageOffset, chunkSize);
+ mMessageOffset += chunkSize;
+ } else
+ {
+ array.resize(0);
+ mMessage.resize(0);
+ }
+ mSender->emitProgressInfo( mMessageOffset );
+}
+
+void KMSendSMTP::result(KIO::Job *_job)
+{
+ if (!mJob) return;
+ mJob = 0;
+
+ if(_job->error())
+ {
+ mSendOk = false;
+ if (_job->error() == KIO::ERR_SLAVE_DIED) mSlave = 0;
+ failed(_job->errorString());
+ abort();
+ } else {
+ emit idle();
+ }
+}
+
+void KMSendSMTP::slaveError(KIO::Slave *aSlave, int error, const QString &errorMsg)
+{
+ if (aSlave == mSlave)
+ {
+ if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
+ mSendOk = false;
+ mJob = 0;
+ failed(KIO::buildErrorString(error, errorMsg));
+ abort();
+ }
+}
+
+#include "kmsender.moc"
+#include "kmsender_p.moc"