You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1254 lines
36 KiB

// 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;
}
//-----------------------------------------------------------------------------
bool KMSender::doSend(KMMessage* aMsg, short sendNow)
{
int rc;
//assert(aMsg != 0);
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: ;");
}
aMsg->removeHeaderField( "X-KMail-CryptoFormat" );
// Handle redirections
QString from = aMsg->headerField("X-KMail-Redirect-From");
QString msgId = aMsg->msgId();
if( from.isEmpty() || msgId.isEmpty() ) {
msgId = KMMessage::generateMessageId( aMsg->sender() );
//kdDebug(5006) << "Setting Message-Id to '" << msgId << "'\n";
aMsg->setMsgId( msgId );
}
if (sendNow==-1) sendNow = mSendImmediate;
kmkernel->outboxFolder()->open();
aMsg->setStatus(KMMsgStatusQueued);
rc = kmkernel->outboxFolder()->addMsg(aMsg);
if (rc)
{
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 = kmkernel->outboxFolder()->count() - 1;
KMMessage * const unencryptedMsg = aMsg->unencryptedMsg();
kmkernel->outboxFolder()->unGetMsg( idx );
KMMessage * const tempMsg = kmkernel->outboxFolder()->getMsg( idx );
tempMsg->setUnencryptedMsg( unencryptedMsg );
if (sendNow && !mSendInProgress) rc = sendQueued();
else rc = TRUE;
kmkernel->outboxFolder()->close();
return rc;
}
//-----------------------------------------------------------------------------
void KMSender::outboxMsgAdded(int idx)
{
++mTotalMessages;
KMMsgBase* msg = kmkernel->outboxFolder()->getMsgBase(idx);
Q_ASSERT(msg);
if ( msg )
mTotalBytes += msg->msgSize();
}
//-----------------------------------------------------------------------------
bool KMSender::sendQueued(void)
{
if (!settingsOk()) return FALSE;
if (mSendInProgress)
{
return FALSE;
}
// open necessary folders
mOutboxFolder = kmkernel->outboxFolder();
mOutboxFolder->open();
mTotalMessages = mOutboxFolder->count();
if (mTotalMessages == 0) {
// Nothing in the outbox. We are done.
mOutboxFolder->close();
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();
kmkernel->filterMgr()->ref();
// start sending the messages
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
KMFolder *sentFolder = 0, *imapSentFolder = 0;
bool someSent = mCurrentMsg;
int rc;
if (someSent) {
mSentMessages++;
mSentBytes += mCurrentMsg->msgSize();
}
// Post-process sent message (filtering)
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
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 ) {
rc = sentFolder->open();
if (rc != 0) {
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 );
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."));
sentFolder->moveMsg(mCurrentMsg);
sentFolder->close();
cleanup();
return;
case 1:
if (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();
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( ProgressItem* ) ),
this, SLOT( slotAbortSend() ) );
kapp->ref();
mSendInProgress = TRUE;
}
QString msgTransport = mCurrentMsg->headerField("X-KMail-Transport");
if (msgTransport.isEmpty())
{
QStringList sl = KMTransportInfo::availableTransports();
if (!sl.isEmpty()) msgTransport = sl[0];
}
if (!mSendProc || msgTransport != mMethodStr) {
if (mSendProcStarted && mSendProc) {
mSendProc->finish(true);
mSendProcStarted = FALSE;
}
mSendProc = createSendProcFromString(msgTransport);
mMethodStr = msgTransport;
if( mTransportInfo->encryption == "TLS" || mTransportInfo->encryption == "SSL" )
mProgressItem->setUsesCrypto( true );
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())
{
setStatusMsg(i18n("Executing precommand %1")
.arg(mTransportInfo->precommand));
mPrecommand = new KMPrecommand(mTransportInfo->precommand);
connect(mPrecommand, SIGNAL(finished(bool)),
SLOT(slotPrecommandFinished(bool)));
if (!mPrecommand->start())
{
delete mPrecommand;
mPrecommand = 0;
}
return;
}
mSendProc->start();
}
}
else if (!mSendProcStarted)
mSendProc->start();
else
doSendMsgAux();
}
//-----------------------------------------------------------------------------
void KMSender::sendProcStarted(bool success)
{
if (!success) {
if (mSendProc)
mSendProc->finish(true);
else
setStatusMsg(i18n("Unrecognized transport protocol. Unable to send message."));
mSendProc = 0;
mSendProcStarted = false;
cleanup();
return;
}
doSendMsgAux();
}
//-----------------------------------------------------------------------------
void KMSender::doSendMsgAux()
{
mSendProcStarted = TRUE;
// start sending the current message
mSendProc->preSendInit();
setStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3")
.arg(mSentMessages+mFailedMessages+1).arg(mTotalMessages)
.arg(mCurrentMsg->subject()));
if (!mSendProc->send(mCurrentMsg))
{
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(true);
mSendProc = 0;
mSendProcStarted = FALSE;
if (mSendInProgress) kapp->deref();
mSendInProgress = FALSE;
if (mCurrentMsg)
{
mCurrentMsg->setTransferInProgress( FALSE );
mCurrentMsg = 0;
}
if ( mSentFolder ) {
mSentFolder->close();
mSentFolder = 0;
}
if ( mOutboxFolder ) {
disconnect( mOutboxFolder, SIGNAL(msgAdded(int)),
this, SLOT(outboxMsgAdded(int)) );
mOutboxFolder->close();
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->message();
if (mSendAborted) {
// sending of message aborted
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()) {
mCurrentMsg->setTransferInProgress( false );
mCurrentMsg = 0;
mFailedMessages++;
// 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(true);
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(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;
kmkernel->msgDict()->getLocation(msn, &folder, &index);
if (folder && index != -1) {
folder->open();
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);
}
folder->close();
} else {
kdWarning(5006) << k_funcinfo << "Cannot update linked message, it could not be found!" << endl;
}
}
}
//=============================================================================
//=============================================================================
KMSendProc::KMSendProc(KMSender* aSender): QObject()
{
mSender = aSender;
preSendInit();
}
//-----------------------------------------------------------------------------
void KMSendProc::preSendInit(void)
{
mSending = FALSE;
mSendOk = FALSE;
mMsg = QString::null;
}
//-----------------------------------------------------------------------------
void KMSendProc::failed(const QString &aMsg)
{
mSending = FALSE;
mSendOk = FALSE;
mMsg = aMsg;
}
//-----------------------------------------------------------------------------
void KMSendProc::start(void)
{
emit started(true);
}
//-----------------------------------------------------------------------------
bool KMSendProc::finish(bool destructive)
{
if (destructive) deleteLater();
return TRUE;
}
//-----------------------------------------------------------------------------
void KMSendProc::statusMsg(const QString& aMsg)
{
if (mSender) mSender->setStatusMsg(aMsg);
}
//-----------------------------------------------------------------------------
bool KMSendProc::addRecipients( const AddrSpecList & al )
{
for ( AddrSpecList::const_iterator it = al.begin() ; it != al.end() ; ++it )
if ( !addOneRecipient( (*it).asString() ) )
return false;
return true;
}
//=============================================================================
//=============================================================================
KMSendSendmail::KMSendSendmail(KMSender* aSender):
KMSendProc(aSender)
{
mMailerProc = 0;
}
//-----------------------------------------------------------------------------
KMSendSendmail::~KMSendSendmail()
{
delete mMailerProc;
}
//-----------------------------------------------------------------------------
void KMSendSendmail::start(void)
{
if (mSender->transportInfo()->host.isEmpty())
{
QString str = i18n("Please specify a mailer program in the settings.");
QString msg;
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);
emit started(false);
return;
}
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)));
}
emit started(true);
}
//-----------------------------------------------------------------------------
bool KMSendSendmail::finish(bool destructive)
{
delete mMailerProc;
mMailerProc = 0;
if (destructive)
deleteLater();
return TRUE;
}
//-----------------------------------------------------------------------------
void KMSendSendmail::abort()
{
delete mMailerProc;
mMailerProc = 0;
mSendOk = false;
mMsgStr = 0;
idle();
}
//-----------------------------------------------------------------------------
bool KMSendSendmail::send(KMMessage* aMsg)
{
QString bccStr;
mMailerProc->clearArguments();
*mMailerProc << mSender->transportInfo()->host;
*mMailerProc << "-i";
*mMailerProc << "-f";
*mMailerProc << aMsg->sender().latin1();
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
addRecipients(aMsg->extractAddrSpecs("X-KMail-Recipients"));
aMsg->removeHeaderField( "X-KMail-Recipients" );
} else {
addRecipients(aMsg->extractAddrSpecs("To"));
addRecipients(aMsg->extractAddrSpecs("Cc"));
addRecipients(aMsg->extractAddrSpecs("Bcc"));
}
mMsgStr = aMsg->asSendableString();
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.length();
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 );
mMsg.replace(mMsg.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();
}
//-----------------------------------------------------------------------------
bool KMSendSendmail::addOneRecipient(const QString& aRcpt)
{
assert(mMailerProc!=0);
if (!aRcpt.isEmpty()) *mMailerProc << aRcpt;
return TRUE;
}
//-----------------------------------------------------------------------------
//=============================================================================
//=============================================================================
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::send(KMMessage *aMsg)
{
KMTransportInfo *ti = mSender->transportInfo();
assert(aMsg != 0);
// MDNs are required to have an empty envelope from as per RFC2298.
QString sender = aMsg->sender();
if ( messageIsDispositionNotificationReport( aMsg ) && GlobalSettings::self()->sendMDNsWithEmptySender() )
sender = "<>";
if ( sender.isEmpty() )
return false;
// email this is from
mQuery = "headers=0&from=";
mQuery += KURL::encode_string( sender );
// recipients
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
mQueryField = "&to=";
if( !addRecipients( aMsg->extractAddrSpecs("X-KMail-Recipients")) ) {
return FALSE;
}
aMsg->removeHeaderField( "X-KMail-Recipients" );
} else {
mQueryField = "&to=";
if(!addRecipients(aMsg->extractAddrSpecs("To")))
{
return FALSE;
}
if(!aMsg->cc().isEmpty())
{
mQueryField = "&cc=";
if(!addRecipients(aMsg->extractAddrSpecs("Cc"))) return FALSE;
}
QString bccStr = aMsg->bcc();
if(!bccStr.isEmpty())
{
mQueryField = "&bcc=";
if (!addRecipients(aMsg->extractAddrSpecs("Bcc"))) return FALSE;
}
}
if (ti->specifyHostname)
mQuery += "&hostname=" + KURL::encode_string(ti->localHostname);
if ( !kmkernel->msgSender()->sendQuotedPrintable() )
mQuery += "&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)
{
if(ti->user.isEmpty() || ti->pass.isEmpty())
{
bool b = FALSE;
int result;
KCursorSaver idle(KBusyPtr::idle());
result = KIO::PasswordDialog::getNameAndPassword(ti->user, ti->pass,
&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->writeConfig(id);
}
destination.setUser(ti->user);
destination.setPass(ti->pass);
}
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 = aMsg->asSendableString();
mMessageLength = mMessage.length();
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):
mQuery += "&size=" + QString::number( qRound( mMessageLength * 1.05 ) );
destination.setPath("/send");
destination.setQuery(mQuery);
mQuery = QString::null;
if ((mJob = KIO::put(destination, -1, false, false, 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 mSendOk;
}
else
{
abort();
return false;
}
}
void KMSendSMTP::abort()
{
finish(false);
emit idle();
}
bool KMSendSMTP::finish(bool b)
{
if(mJob)
{
mJob->kill(TRUE);
mJob = 0;
mSlave = 0;
}
if (mSlave)
{
KIO::Scheduler::disconnectSlave(mSlave);
mSlave = 0;
}
mInProcess = false;
return KMSendProc::finish(b);
}
bool KMSendSMTP::addOneRecipient(const QString& _addr)
{
if(!_addr.isEmpty())
mQuery += mQueryField + KURL::encode_string(_addr);
return true;
}
void KMSendSMTP::dataReq(KIO::Job *, QByteArray &array)
{
// Send it by 32K chuncks
int chunkSize = QMIN( mMessageLength - mMessageOffset, 0x8000 );
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"