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.
tdepim/kmail/kmsender.cpp

1232 lines
37 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 TQString &msg)
{
if ( mProgressItem )
mProgressItem->setStatus(msg);
}
//-----------------------------------------------------------------------------
void KMSender::readConfig(void)
{
TQString 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 TQString from = m->headerField("X-KMail-Redirect-From");
const TQString 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 TQString &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, TQT_SIGNAL(msgAdded(int)),
this, TQT_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() );
TQCString 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, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
this, TQT_SLOT( slotAbortSend() ) );
kapp->ref();
mSendInProgress = true;
}
TQString msgTransport = mCustomTransport;
if ( msgTransport.isEmpty() ) {
msgTransport = mCurrentMsg->headerField("X-KMail-Transport");
}
if ( msgTransport.isEmpty() ) {
const TQStringList 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, TQT_SIGNAL(idle()), TQT_SLOT(slotIdle()));
connect(mSendProc, TQT_SIGNAL(started(bool)), TQT_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 TQString & cmd ) {
setStatusMsg( i18n("Executing precommand %1").arg( cmd ) );
mPrecommand = new KMPrecommand( cmd );
connect( mPrecommand, TQT_SIGNAL(finished(bool)),
TQT_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 TQStringList addrSpecListToStringList( const AddrSpecList & l, bool allowEmpty=false ) {
TQStringList result;
for ( AddrSpecList::const_iterator it = l.begin(), end = l.end() ; it != end ; ++it ) {
const TQString s = (*it).asString();
if ( allowEmpty || !s.isEmpty() )
result.push_back( s );
}
return result;
}
static void extractSenderToCCAndBcc( KMMessage * aMsg, TQString * sender, TQStringList * to, TQStringList * cc, TQStringList * 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()));
TQStringList to, cc, bcc;
TQString 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 TQByteArray 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, TQT_SIGNAL(msgAdded(int)),
this, TQT_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);
TQString msg;
TQString 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
TQMapIterator <TQString,TQString> 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 TQString & transport )
{
mTransportInfo->type = TQString::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";
TQString 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";
TQString 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 )
: TQObject( 0 ),
mSender( sender ),
mLastErrorMessage(),
mSendOk( false ),
mSending( false )
{
}
//-----------------------------------------------------------------------------
void KMSendProc::reset()
{
mSending = false;
mSendOk = false;
mLastErrorMessage = TQString::null;
}
//-----------------------------------------------------------------------------
void KMSendProc::failed(const TQString &aMsg)
{
mSending = false;
mSendOk = false;
mLastErrorMessage = aMsg;
}
//-----------------------------------------------------------------------------
void KMSendProc::statusMsg(const TQString& 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 TQString str = i18n("Please specify a mailer program in the settings.");
const TQString 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,TQT_SIGNAL(processExited(KProcess*)),
this, TQT_SLOT(sendmailExited(KProcess*)));
connect(mMailerProc,TQT_SIGNAL(wroteStdin(KProcess*)),
this, TQT_SLOT(wroteStdin(KProcess*)));
connect(mMailerProc,TQT_SIGNAL(receivedStderr(KProcess*,char*,int)),
this, TQT_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 TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & 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(TQT_SIGNAL(slaveError(KIO::Slave *, int,
const TQString &)), this, TQT_SLOT(slaveError(KIO::Slave *, int,
const TQString &)));
}
KMSendSMTP::~KMSendSMTP()
{
if (mJob) mJob->kill();
}
bool KMSendSMTP::doSend( const TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & message ) {
TQString query = "headers=0&from=";
query += KURL::encode_string( sender );
TQStringList::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)
{
TQMapIterator<TQString,TQString> tpc = mSender->mPasswdCache.find( ti->name );
TQString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):TQString::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());
TQString 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, TQString::null, ti->name, TQString::null);
if ( result != TQDialog::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=" + TQString::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, TQT_SIGNAL(result(KIO::Job *)), this, TQT_SLOT(result(KIO::Job *)));
connect(mJob, TQT_SIGNAL(dataReq(KIO::Job *, TQByteArray &)),
this, TQT_SLOT(dataReq(KIO::Job *, TQByteArray &)));
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 *, TQByteArray &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 TQString &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"