// kmsender.cpp #include #define REALLY_WANT_KMSENDER #include "kmsender.h" #include "kmsender_p.h" #undef REALLY_WANT_KMSENDER #include using namespace KMime::Types; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalsettings.h" #include "kmfiltermgr.h" #include "kcursorsaver.h" #include #include #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 #include #include #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::seStatusMsg(const TQString &msg) { if ( mProgressItem ) mProgressItem->seStatus(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->seStatus(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 ; igetMsgBase(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->seStatus(KMMsgStatusSent); mCurrentMsg->seStatus(KMMsgStatusRead); // otherwise it defaults to new on imap mCurrentMsg->updateAttachmentState(); mCurrentMsg->updateInvitationState(); 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.") .tqarg(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; } seStatusByLink( 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.primaryEmailAddress().isEmpty() ) { mCurrentMsg->setFrom( id.fullEmailAddr() ); } else if ( !kmkernel->identityManager()->defaultIdentity().primaryEmailAddress().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." ) .tqarg( 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 ) { seStatusMsg(i18n("%n queued message successfully sent.", "%n queued messages successfully sent.", mSentMessages)); } else { seStatusMsg(i18n("%1 of %2 queued messages successfully sent.") .tqarg(mSentMessages).tqarg( 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 ) { seStatusMsg( i18n("Executing precommand %1").tqarg( 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 seStatusMsg(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 seStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3") .tqarg(mSentMessages+mFailedMessages+1).tqarg(mTotalMessages) .tqarg(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(); seStatusMsg(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") .tqarg(errString) .tqarg(mMethodStr); if (!errString.isEmpty()) KMessageBox::error(0,msg); seStatusMsg( i18n( "Sending aborted." ) ); } else { if (!mSendProc->sendOk()) { if ( mCurrentMsg ) mCurrentMsg->setTransferInProgress( false ); if ( mOutboxFolder ) mOutboxFolder->unGetMsg( mFailedMessages ); mCurrentMsg = 0; mFailedMessages++; // reset cached password TQMapIterator 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("

Sending failed:

" "

%1

" "

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.

" "

The following transport protocol was used: %2

" "

Do you want me to continue sending the remaining messages?

") .tqarg(errString) .tqarg(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") .tqarg(errString) .tqarg(mMethodStr); KMessageBox::error(0,msg); } if (res == KMessageBox::Yes) { // Try the next one. doSendMsg(); return; } else { seStatusMsg( 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(); 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::seStatusByLink(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->seStatus(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(); } //----------------------------------------------------------------------------- void KMSendProc::failed(const TQString &aMsg) { mSending = false; mSendOk = false; mLastErrorMessage = aMsg; } //----------------------------------------------------------------------------- void KMSendProc::statusMsg(const TQString& aMsg) { if (mSender) mSender->seStatusMsg(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") .tqarg(str + "\n") .tqarg("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") .tqarg( 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 tpc = mSender->mPasswdCache.find( ti->name ); TQString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):TQString(); 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(), ti->name, TQString()); 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( tqRound( 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 = TQMIN( 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"