kmail

kmsender.cpp
1// kmsender.cpp
2
3#include <config.h>
4
5#define REALLY_WANT_KMSENDER
6#include "kmsender.h"
7#include "kmsender_p.h"
8#undef REALLY_WANT_KMSENDER
9
10#include <kmime_header_parsing.h>
11using namespace KMime::Types;
12
13#include <tdeio/passdlg.h>
14#include <tdeio/scheduler.h>
15#include <tdeapplication.h>
16#include <tdemessagebox.h>
17#include <tdeversion.h>
18#include <tdelocale.h>
19#include <kdebug.h>
20#include <tdeconfig.h>
21
22#include <assert.h>
23#include <stdio.h>
24#include <unistd.h>
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <sys/wait.h>
28#include "globalsettings.h"
29#include "kmfiltermgr.h"
30
31#include "kcursorsaver.h"
32#include <libkpimidentities/identity.h>
33#include <libkpimidentities/identitymanager.h>
34#include "progressmanager.h"
35#include "kmaccount.h"
36#include "kmtransport.h"
37#include "kmfolderindex.h"
38#include "kmfoldermgr.h"
39#include "kmmsgdict.h"
40#include "kmmsgpart.h"
41#include "protocols.h"
42#include "kmcommands.h"
43#include <mimelib/mediatyp.h>
44#include <mimelib/enum.h>
45#include <mimelib/param.h>
46
47#define SENDER_GROUP "sending mail"
48
49//-----------------------------------------------------------------------------
50KMSender::KMSender()
51 : mOutboxFolder( 0 ), mSentFolder( 0 )
52{
53 mPrecommand = 0;
54 mSendProc = 0;
55 mSendProcStarted = false;
56 mSendInProgress = false;
57 mCurrentMsg = 0;
58 mTransportInfo = new KMTransportInfo();
59 readConfig();
60 mSendAborted = false;
61 mSentMessages = 0;
62 mTotalMessages = 0;
63 mFailedMessages = 0;
64 mSentBytes = 0;
65 mTotalBytes = 0;
66 mProgressItem = 0;
67}
68
69
70//-----------------------------------------------------------------------------
71KMSender::~KMSender()
72{
73 writeConfig(false);
74 delete mSendProc;
75 delete mPrecommand;
76 delete mTransportInfo;
77}
78
79//-----------------------------------------------------------------------------
80void KMSender::setStatusMsg(const TQString &msg)
81{
82 if ( mProgressItem )
83 mProgressItem->setStatus(msg);
84}
85
86//-----------------------------------------------------------------------------
87void KMSender::readConfig(void)
88{
89 TQString str;
90 TDEConfigGroup config(KMKernel::config(), SENDER_GROUP);
91
92 mSendImmediate = config.readBoolEntry("Immediate", true);
93 mSendQuotedPrintable = config.readBoolEntry("Quoted-Printable", true);
94}
95
96
97//-----------------------------------------------------------------------------
98void KMSender::writeConfig(bool aWithSync)
99{
100 TDEConfigGroup config(KMKernel::config(), SENDER_GROUP);
101
102 config.writeEntry("Immediate", mSendImmediate);
103 config.writeEntry("Quoted-Printable", mSendQuotedPrintable);
104
105 if (aWithSync) config.sync();
106}
107
108
109//-----------------------------------------------------------------------------
110bool KMSender::settingsOk() const
111{
112 if (KMTransportInfo::availableTransports().isEmpty())
113 {
114 KMessageBox::information(0,i18n("Please create an account for sending and try again."));
115 return false;
116 }
117 return true;
118}
119
120static void handleRedirections( KMMessage * m ) {
121 const TQString from = m->headerField("X-KMail-Redirect-From");
122 const TQString msgId = m->msgId();
123 if( from.isEmpty() || msgId.isEmpty() )
124 m->setMsgId( KMMessage::generateMessageId( m->sender() ) );
125}
126
127//-----------------------------------------------------------------------------
128bool KMSender::doSend(KMMessage* aMsg, short sendNow)
129{
130 if(!aMsg)
131 return false;
132
133 if (!settingsOk()) return false;
134
135 if (aMsg->to().isEmpty())
136 {
137 // RFC822 says:
138 // Note that the "Bcc" field may be empty, while the "To" field is required to
139 // have at least one address.
140 //
141 // however:
142 //
143 // The following string is accepted according to RFC 2822,
144 // section 3.4 "Address Specification" where they say:
145 //
146 // "An address may either be an individual mailbox,
147 // or a group of mailboxes."
148 // and:
149 // "group + display-name ":" [mailbox-list / CFWS] ";"
150 // [CFWS]"
151 //
152 // In this syntax our "undisclosed-recipients: ;"
153 // just specifies an empty group.
154 //
155 // In further explanations RFC 2822 states that it *is*
156 // allowed to have a ZERO number of mailboxes in the "mailbox-list".
157 aMsg->setTo("Undisclosed.Recipients: ;");
158 }
159
160 handleRedirections( aMsg );
161
162 if (sendNow==-1) sendNow = mSendImmediate;
163
164 KMFolder * const outbox = kmkernel->outboxFolder();
165 const KMFolderOpener openOutbox( outbox, "outbox" );
166
167 aMsg->setStatus(KMMsgStatusQueued);
168
169 if ( const int err = outbox->addMsg(aMsg) ) {
170 Q_UNUSED( err );
171 KMessageBox::information(0,i18n("Cannot add message to outbox folder"));
172 return false;
173 }
174
175 //Ensure the message is correctly and fully parsed
176
177 /* The above was added by Marc and seems to be necessary to ensure
178 * the mail is in a sane state before sending. The unGet makes the
179 * attached unencrypted version of the mail (if there is one ) disappear.
180 * though, so we need to make sure to keep it around and restore it
181 * afterwards. The real fix would be to replace the unGet with
182 * whatever parsing is triggered by it, but I'm too chicken to do that,
183 * in this branch.
184 * Note that the unencrypted mail will be lost if the mail remains in
185 * the outbox across a restart anyhow, but that never worked, afaikt. */
186 const int idx = outbox->count() - 1;
187 KMMessage * const unencryptedMsg = aMsg->unencryptedMsg();
188 outbox->unGetMsg( idx );
189 KMMessage * const tempMsg = outbox->getMsg( idx );
190 tempMsg->setUnencryptedMsg( unencryptedMsg );
191
192 if ( !sendNow || mSendInProgress )
193 return true;
194
195 return sendQueued();
196}
197
198
199//-----------------------------------------------------------------------------
200void KMSender::outboxMsgAdded(int idx)
201{
202 ++mTotalMessages;
203 KMMsgBase* msg = kmkernel->outboxFolder()->getMsgBase(idx);
204 Q_ASSERT(msg);
205 if ( msg )
206 mTotalBytes += msg->msgSize();
207}
208
209
210//-----------------------------------------------------------------------------
211bool KMSender::doSendQueued( const TQString &customTransport )
212{
213 if (!settingsOk()) return false;
214
215 if (mSendInProgress)
216 {
217 return false;
218 }
219
220 // open necessary folders
221 mOutboxFolder = kmkernel->outboxFolder();
222 mOutboxFolder->open("dosendoutbox");
223 mTotalMessages = mOutboxFolder->count();
224 if (mTotalMessages == 0) {
225 // Nothing in the outbox. We are done.
226 mOutboxFolder->close("dosendoutbox");
227 mOutboxFolder = 0;
228 return true;
229 }
230 mTotalBytes = 0;
231 for( int i = 0 ; i<mTotalMessages ; ++i )
232 mTotalBytes += mOutboxFolder->getMsgBase(i)->msgSize();
233
234 connect( mOutboxFolder, TQ_SIGNAL(msgAdded(int)),
235 this, TQ_SLOT(outboxMsgAdded(int)) );
236 mCurrentMsg = 0;
237
238 mSentFolder = kmkernel->sentFolder();
239 mSentFolder->open("dosendsent");
240 kmkernel->filterMgr()->ref();
241
242 // start sending the messages
243 mCustomTransport = customTransport;
244 doSendMsg();
245 return true;
246}
247
248//-----------------------------------------------------------------------------
249void KMSender::emitProgressInfo( int currentFileProgress )
250{
251 int percent = (mTotalBytes) ? ( 100 * (mSentBytes+currentFileProgress) / mTotalBytes ) : 0;
252 if (percent > 100) percent = 100;
253 mProgressItem->setProgress(percent);
254}
255
256static bool messageIsDispositionNotificationReport( KMMessage *msg )
257{
258 if ( msg->type() == DwMime::kTypeMessage &&
259 msg->subtype() == DwMime::kSubtypeDispositionNotification )
260 return true;
261
262 if ( msg->type() != DwMime::kTypeMultipart ||
263 msg->subtype() != DwMime::kSubtypeReport )
264 return false;
265
266 DwMediaType& ct = msg->dwContentType();
267 DwParameter *param = ct.FirstParameter();
268 while( param ) {
269 if ( !tqstricmp( param->Attribute().c_str(), "report-type")
270 && !tqstricmp( param->Value().c_str(), "disposition-notification" ) )
271 return true;
272 else
273 param = param->Next();
274 }
275 return false;
276}
277
278//-----------------------------------------------------------------------------
279void KMSender::doSendMsg()
280{
281 if (!kmkernel) //To handle message sending in progress when kaplan is exited
282 return; //TODO: handle this case better
283
284 const bool someSent = mCurrentMsg;
285 if (someSent) {
286 mSentMessages++;
287 mSentBytes += mCurrentMsg->msgSize();
288 }
289
290 // Post-process sent message (filtering)
291 KMFolder *sentFolder = 0, *imapSentFolder = 0;
292 if (mCurrentMsg && kmkernel->filterMgr())
293 {
294 mCurrentMsg->setTransferInProgress( false );
295 if( mCurrentMsg->hasUnencryptedMsg() ) {
296 kdDebug(5006) << "KMSender::doSendMsg() post-processing: replace mCurrentMsg body by unencryptedMsg data" << endl;
297 // delete all current body parts
298 mCurrentMsg->deleteBodyParts();
299 // copy Content-[..] headers from unencrypted message to current one
300 KMMessage & newMsg( *mCurrentMsg->unencryptedMsg() );
301 mCurrentMsg->dwContentType() = newMsg.dwContentType();
302 mCurrentMsg->setContentTransferEncodingStr( newMsg.contentTransferEncodingStr() );
303 TQCString newDispo = newMsg.headerField("Content-Disposition").latin1();
304 if( newDispo.isEmpty() )
305 mCurrentMsg->removeHeaderField( "Content-Disposition" );
306 else
307 mCurrentMsg->setHeaderField( "Content-Disposition", newDispo );
308 // copy the body
309 mCurrentMsg->setBody( newMsg.body() );
310 // copy all the body parts
311 KMMessagePart msgPart;
312 for( int i = 0; i < newMsg.numBodyParts(); ++i ) {
313 newMsg.bodyPart( i, &msgPart );
314 mCurrentMsg->addBodyPart( &msgPart );
315 }
316 }
317 mCurrentMsg->setStatus(KMMsgStatusSent);
318 mCurrentMsg->setStatus(KMMsgStatusRead); // otherwise it defaults to new on imap
319 mCurrentMsg->updateAttachmentState();
320 mCurrentMsg->updateInvitationState();
321
322 const KPIM::Identity & id = kmkernel->identityManager()
323 ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
324 if ( !mCurrentMsg->fcc().isEmpty() )
325 {
326 sentFolder = kmkernel->folderMgr()->findIdString( mCurrentMsg->fcc() );
327 if ( sentFolder == 0 )
328 // This is *NOT* supposed to be imapSentFolder!
329 sentFolder =
330 kmkernel->dimapFolderMgr()->findIdString( mCurrentMsg->fcc() );
331 if ( sentFolder == 0 )
332 imapSentFolder =
333 kmkernel->imapFolderMgr()->findIdString( mCurrentMsg->fcc() );
334 }
335 // No, or no usable sentFolder, and no, or no usable imapSentFolder,
336 // let's try the on in the identity
337 if ( ( sentFolder == 0 || sentFolder->isReadOnly() )
338 && ( imapSentFolder == 0 || imapSentFolder->isReadOnly() )
339 && !id.fcc().isEmpty() )
340 {
341 sentFolder = kmkernel->folderMgr()->findIdString( id.fcc() );
342 if ( sentFolder == 0 )
343 // This is *NOT* supposed to be imapSentFolder!
344 sentFolder = kmkernel->dimapFolderMgr()->findIdString( id.fcc() );
345 if ( sentFolder == 0 )
346 imapSentFolder = kmkernel->imapFolderMgr()->findIdString( id.fcc() );
347 }
348 if (imapSentFolder
349 && ( imapSentFolder->noContent() || imapSentFolder->isReadOnly() ) )
350 imapSentFolder = 0;
351
352 if ( sentFolder == 0 || sentFolder->isReadOnly() )
353 sentFolder = kmkernel->sentFolder();
354
355 if ( sentFolder ) {
356 if ( const int err = sentFolder->open("sentFolder") ) {
357 Q_UNUSED( err );
358 cleanup();
359 return;
360 }
361 }
362
363 // Disable the emitting of msgAdded signal, because the message is taken out of the
364 // current folder (outbox) and re-added, to make filter actions changing the message
365 // work. We don't want that to screw up message counts.
366 if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( true );
367 const int processResult = kmkernel->filterMgr()->process(mCurrentMsg,KMFilterMgr::Outbound);
368 if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( false );
369
370 // 0==processed ok, 1==no filter matched, 2==critical error, abort!
371 switch (processResult) {
372 case 2:
373 perror("Critical error: Unable to process sent mail (out of space?)");
374 KMessageBox::information(0, i18n("Critical error: "
375 "Unable to process sent mail (out of space?)"
376 "Moving failing message to \"sent-mail\" folder."));
377 if ( sentFolder ) {
378 sentFolder->moveMsg(mCurrentMsg);
379 sentFolder->close("sentFolder");
380 }
381 cleanup();
382 return;
383 case 1:
384 if ( sentFolder && sentFolder->moveMsg(mCurrentMsg) != 0 )
385 {
386 KMessageBox::error(0, i18n("Moving the sent message \"%1\" from the "
387 "\"outbox\" to the \"sent-mail\" folder failed.\n"
388 "Possible reasons are lack of disk space or write permission. "
389 "Please try to fix the problem and move the message manually.")
390 .arg(mCurrentMsg->subject()));
391 cleanup();
392 return;
393 }
394 if (imapSentFolder) {
395 // Does proper folder refcounting and message locking
396 KMCommand *command = new KMMoveCommand( imapSentFolder, mCurrentMsg );
397 command->keepFolderOpen( sentFolder ); // will open it, and close it once done
398 command->start();
399 }
400 default:
401 break;
402 }
403 setStatusByLink( mCurrentMsg );
404 if (mCurrentMsg->parent() && !imapSentFolder) {
405 // for speed optimization, this code assumes that mCurrentMsg is the
406 // last one in it's parent folder; make sure that's really the case:
407 assert( mCurrentMsg->parent()->find( mCurrentMsg )
408 == mCurrentMsg->parent()->count() - 1 );
409 // unGet this message:
410 mCurrentMsg->parent()->unGetMsg( mCurrentMsg->parent()->count() -1 );
411 }
412
413 mCurrentMsg = 0;
414 }
415
416 // See if there is another queued message
417 mCurrentMsg = mOutboxFolder->getMsg(mFailedMessages);
418 if ( mCurrentMsg && !mCurrentMsg->transferInProgress() &&
419 mCurrentMsg->sender().isEmpty() ) {
420 // if we do not have a sender address then use the email address of the
421 // message's identity or of the default identity unless those two are also
422 // empty
423 const KPIM::Identity & id = kmkernel->identityManager()
424 ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
425 if ( !id.primaryEmailAddress().isEmpty() ) {
426 mCurrentMsg->setFrom( id.fullEmailAddr() );
427 }
428 else if ( !kmkernel->identityManager()->defaultIdentity().primaryEmailAddress().isEmpty() ) {
429 mCurrentMsg->setFrom( kmkernel->identityManager()->defaultIdentity().fullEmailAddr() );
430 }
431 else {
432 KMessageBox::sorry( 0, i18n( "It's not possible to send messages "
433 "without specifying a sender address.\n"
434 "Please set the email address of "
435 "identity '%1' in the Identities "
436 "section of the configuration dialog "
437 "and then try again." )
438 .arg( id.identityName() ) );
439 mOutboxFolder->unGetMsg( mFailedMessages );
440 mCurrentMsg = 0;
441 }
442 }
443 if (!mCurrentMsg || mCurrentMsg->transferInProgress())
444 {
445 // a message is locked finish the send
446 if (mCurrentMsg && mCurrentMsg->transferInProgress())
447 mCurrentMsg = 0;
448 // no more message: cleanup and done
449 if ( sentFolder != 0 )
450 sentFolder->close("sentFolder");
451 if ( someSent ) {
452 if ( mSentMessages == mTotalMessages ) {
453 setStatusMsg(i18n("%n queued message successfully sent.",
454 "%n queued messages successfully sent.",
455 mSentMessages));
456 } else {
457 setStatusMsg(i18n("%1 of %2 queued messages successfully sent.")
458 .arg(mSentMessages).arg( mTotalMessages ));
459 }
460 }
461 cleanup();
462 return;
463 }
464 mCurrentMsg->setTransferInProgress( true );
465
466 // start the sender process or initialize communication
467 if (!mSendInProgress)
468 {
469 Q_ASSERT( !mProgressItem );
470 mProgressItem = KPIM::ProgressManager::createProgressItem(
471 "Sender",
472 i18n( "Sending messages" ),
473 i18n("Initiating sender process..."),
474 true );
475 connect( mProgressItem, TQ_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
476 this, TQ_SLOT( slotAbortSend() ) );
477 tdeApp->ref();
478 mSendInProgress = true;
479 }
480
481 TQString msgTransport = mCustomTransport;
482 if ( msgTransport.isEmpty() ) {
483 msgTransport = mCurrentMsg->headerField("X-KMail-Transport");
484 }
485 if ( msgTransport.isEmpty() ) {
486 msgTransport = GlobalSettings::self()->defaultTransport();
487 }
488 if ( msgTransport.isEmpty() ) {
489 const TQStringList sl = KMTransportInfo::availableTransports();
490 if (!sl.empty()) msgTransport = sl.front();
491 }
492
493 if (!mSendProc || msgTransport != mMethodStr) {
494 if (mSendProcStarted && mSendProc) {
495 mSendProc->finish();
496 mSendProcStarted = false;
497 }
498
499 mSendProc = createSendProcFromString(msgTransport);
500 mMethodStr = msgTransport;
501
502 if( mTransportInfo->encryption == "TLS" || mTransportInfo->encryption == "SSL" ) {
503 mProgressItem->setUsesCrypto( true );
504 } else if ( !mCustomTransport.isEmpty() ) {
505 int result = KMessageBox::warningContinueCancel( 0,
506 i18n( "You have chosen to send all queued email using an unencrypted transport, do you want to continue? "),
507 i18n( "Security Warning" ),
508 i18n( "Send Unencrypted" ),
509 "useCustomTransportWithoutAsking", false);
510
511 if( result == KMessageBox::Cancel ) {
512 mProgressItem->cancel();
513 mProgressItem->setComplete();
514 slotAbortSend();
515 cleanup();
516 return;
517 }
518 }
519
520 if (!mSendProc)
521 sendProcStarted(false);
522 else {
523 connect(mSendProc, TQ_SIGNAL(idle()), TQ_SLOT(slotIdle()));
524 connect(mSendProc, TQ_SIGNAL(started(bool)), TQ_SLOT(sendProcStarted(bool)));
525
526 // Run the precommand if there is one
527 if ( !mTransportInfo->precommand.isEmpty() ) {
528 runPrecommand( mTransportInfo->precommand );
529 return;
530 }
531
532 mSendProc->start();
533 }
534 }
535 else if (!mSendProcStarted)
536 mSendProc->start();
537 else
538 doSendMsgAux();
539}
540
541bool KMSender::runPrecommand( const TQString & cmd ) {
542 setStatusMsg( i18n("Executing precommand %1").arg( cmd ) );
543 mPrecommand = new KMPrecommand( cmd );
544 connect( mPrecommand, TQ_SIGNAL(finished(bool)),
545 TQ_SLOT(slotPrecommandFinished(bool)) );
546 if ( !mPrecommand->start() ) {
547 delete mPrecommand; mPrecommand = 0;
548 return false;
549 }
550 return true;
551}
552
553//-----------------------------------------------------------------------------
554void KMSender::sendProcStarted(bool success)
555{
556 if (!success) {
557 if (mSendProc)
558 mSendProc->finish();
559 else
560 setStatusMsg(i18n("Unrecognized transport protocol. Unable to send message."));
561 mSendProc = 0;
562 mSendProcStarted = false;
563 cleanup();
564 return;
565 }
566 doSendMsgAux();
567}
568
569
570static TQStringList addrSpecListToStringList( const AddrSpecList & l, bool allowEmpty=false ) {
571 TQStringList result;
572 for ( AddrSpecList::const_iterator it = l.begin(), end = l.end() ; it != end ; ++it ) {
573 const TQString s = (*it).asString();
574 if ( allowEmpty || !s.isEmpty() )
575 result.push_back( s );
576 }
577 return result;
578}
579
580static void extractSenderToCCAndBcc( KMMessage * aMsg, TQString * sender, TQStringList * to, TQStringList * cc, TQStringList * bcc ) {
581 if ( sender ) *sender = aMsg->sender();
582 if( !aMsg->headerField("X-KMail-Recipients").isEmpty() ) {
583 // extended BCC handling to prevent TOs and CCs from seeing
584 // BBC information by looking at source of an OpenPGP encrypted mail
585 if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "X-KMail-Recipients" ) );
586 aMsg->removeHeaderField( "X-KMail-Recipients" );
587 } else {
588 if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "To" ) );
589 if ( cc ) *cc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Cc" ) );
590 if ( bcc ) *bcc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Bcc" ) );
591 }
592}
593
594//-----------------------------------------------------------------------------
595void KMSender::doSendMsgAux()
596{
597 mSendProcStarted = true;
598
599 // start sending the current message
600
601 setStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3")
602 .arg(mSentMessages+mFailedMessages+1).arg(mTotalMessages)
603 .arg(mCurrentMsg->subject()));
604 TQStringList to, cc, bcc;
605 TQString sender;
606 extractSenderToCCAndBcc( mCurrentMsg, &sender, &to, &cc, &bcc );
607
608 // MDNs are required to have an empty envelope from as per RFC2298.
609 if ( messageIsDispositionNotificationReport( mCurrentMsg ) && GlobalSettings::self()->sendMDNsWithEmptySender() )
610 sender = "<>";
611
612 const TQByteArray message = mCurrentMsg->asSendableString();
613 if ( sender.isEmpty() || !mSendProc->send( sender, to, cc, bcc, message ) ) {
614 if ( mCurrentMsg )
615 mCurrentMsg->setTransferInProgress( false );
616 if ( mOutboxFolder )
617 mOutboxFolder->unGetMsg( mFailedMessages );
618 mCurrentMsg = 0;
619 cleanup();
620 setStatusMsg(i18n("Failed to send (some) queued messages."));
621 return;
622 }
623 // Do *not* add code here, after send(). It can happen that this method
624 // is called recursively if send() emits the idle signal directly.
625}
626
627
628//-----------------------------------------------------------------------------
629void KMSender::cleanup(void)
630{
631 kdDebug(5006) << k_funcinfo << endl;
632 if (mSendProc && mSendProcStarted) mSendProc->finish();
633 mSendProc = 0;
634 mSendProcStarted = false;
635 if (mSendInProgress) tdeApp->deref();
636 mSendInProgress = false;
637 if (mCurrentMsg)
638 {
639 mCurrentMsg->setTransferInProgress( false );
640 mCurrentMsg = 0;
641 }
642 if ( mSentFolder ) {
643 mSentFolder->close("dosendsent");
644 mSentFolder = 0;
645 }
646 if ( mOutboxFolder ) {
647 disconnect( mOutboxFolder, TQ_SIGNAL(msgAdded(int)),
648 this, TQ_SLOT(outboxMsgAdded(int)) );
649 mOutboxFolder->close("dosendoutbox");
650 if ( mOutboxFolder->count( true ) == 0 ) {
651 mOutboxFolder->expunge();
652 }
653 else if ( mOutboxFolder->needsCompacting() ) {
654 mOutboxFolder->compact( KMFolder::CompactSilentlyNow );
655 }
656 mOutboxFolder = 0;
657 }
658
659 mSendAborted = false;
660 mSentMessages = 0;
661 mFailedMessages = 0;
662 mSentBytes = 0;
663 if ( mProgressItem )
664 mProgressItem->setComplete();
665 mProgressItem = 0;
666 kmkernel->filterMgr()->deref();
667}
668
669
670//-----------------------------------------------------------------------------
671void KMSender::slotAbortSend()
672{
673 mSendAborted = true;
674 delete mPrecommand;
675 mPrecommand = 0;
676 if (mSendProc) mSendProc->abort();
677}
678
679//-----------------------------------------------------------------------------
680void KMSender::slotIdle()
681{
682 assert(mSendProc != 0);
683
684 TQString msg;
685 TQString errString;
686 if (mSendProc)
687 errString = mSendProc->lastErrorMessage();
688
689 if (mSendAborted) {
690 // sending of message aborted
691 if ( mCurrentMsg ) {
692 mCurrentMsg->setTransferInProgress( false );
693 if ( mOutboxFolder )
694 mOutboxFolder->unGetMsg( mFailedMessages );
695 mCurrentMsg = 0;
696 }
697 msg = i18n("Sending aborted:\n%1\n"
698 "The message will stay in the 'outbox' folder until you either "
699 "fix the problem (e.g. a broken address) or remove the message "
700 "from the 'outbox' folder.\n"
701 "The following transport protocol was used:\n %2")
702 .arg(errString)
703 .arg(mMethodStr);
704 if (!errString.isEmpty()) KMessageBox::error(0,msg);
705 setStatusMsg( i18n( "Sending aborted." ) );
706 } else {
707 if (!mSendProc->sendOk()) {
708 if ( mCurrentMsg )
709 mCurrentMsg->setTransferInProgress( false );
710 if ( mOutboxFolder )
711 mOutboxFolder->unGetMsg( mFailedMessages );
712 mCurrentMsg = 0;
713 mFailedMessages++;
714 // reset cached password
715 TQMapIterator <TQString,TQString> pc;
716 if ( (pc = mPasswdCache.find( mMethodStr )) != mPasswdCache.end() ) {
717 mPasswdCache.erase(pc);
718 }
719 // Sending of message failed.
720 if (!errString.isEmpty()) {
721 int res = KMessageBox::Yes;
722 if (mSentMessages+mFailedMessages != mTotalMessages) {
723 msg = i18n("<p>Sending failed:</p>"
724 "<p>%1</p>"
725 "<p>The message will stay in the 'outbox' folder until you either "
726 "fix the problem (e.g. a broken address) or remove the message "
727 "from the 'outbox' folder.</p>"
728 "<p>The following transport protocol was used: %2</p>"
729 "<p>Do you want me to continue sending the remaining messages?</p>")
730 .arg(errString)
731 .arg(mMethodStr);
732 res = KMessageBox::warningYesNo( 0 , msg ,
733 i18n( "Continue Sending" ), i18n( "&Continue Sending" ),
734 i18n("&Abort Sending") );
735 } else {
736 msg = i18n("Sending failed:\n%1\n"
737 "The message will stay in the 'outbox' folder until you either "
738 "fix the problem (e.g. a broken address) or remove the message "
739 "from the 'outbox' folder.\n"
740 "The following transport protocol was used:\n %2")
741 .arg(errString)
742 .arg(mMethodStr);
743 KMessageBox::error(0,msg);
744 }
745 if (res == KMessageBox::Yes) {
746 // Try the next one.
747 doSendMsg();
748 return;
749 } else {
750 setStatusMsg( i18n( "Sending aborted." ) );
751 }
752 }
753 } else {
754 // Sending suceeded.
755 doSendMsg();
756 return;
757 }
758 }
759 mSendProc->finish();
760 mSendProc = 0;
761 mSendProcStarted = false;
762
763 cleanup();
764}
765
766
767//-----------------------------------------------------------------------------
768void KMSender::slotPrecommandFinished(bool normalExit)
769{
770 delete mPrecommand;
771 mPrecommand = 0;
772 if (normalExit) mSendProc->start();
773 else slotIdle();
774}
775
776
777//-----------------------------------------------------------------------------
778void KMSender::setSendImmediate(bool aSendImmediate)
779{
780 mSendImmediate = aSendImmediate;
781}
782
783
784//-----------------------------------------------------------------------------
785void KMSender::setSendQuotedPrintable(bool aSendQuotedPrintable)
786{
787 mSendQuotedPrintable = aSendQuotedPrintable;
788}
789
790
791//-----------------------------------------------------------------------------
792KMSendProc* KMSender::createSendProcFromString( const TQString & transport )
793{
794 mTransportInfo->type = TQString();
795 int nr = KMTransportInfo::findTransport(transport);
796 if (nr)
797 {
798 mTransportInfo->readConfig(nr);
799 } else {
800 if (transport.startsWith("smtp://")) // should probably use KURL and SMTP_PROTOCOL
801 {
802 mTransportInfo->type = "smtp";
803 mTransportInfo->auth = false;
804 mTransportInfo->encryption = "NONE";
805 TQString serverport = transport.mid(7);
806 int colon = serverport.find(':');
807 if (colon != -1) {
808 mTransportInfo->host = serverport.left(colon);
809 mTransportInfo->port = serverport.mid(colon + 1);
810 } else {
811 mTransportInfo->host = serverport;
812 mTransportInfo->port = "25";
813 }
814 } else
815 if (transport.startsWith("smtps://")) // should probably use KURL and SMTPS_PROTOCOL
816 {
817 mTransportInfo->type = "smtps";
818 mTransportInfo->auth = false;
819 mTransportInfo->encryption = "ssl";
820 TQString serverport = transport.mid(7);
821 int colon = serverport.find(':');
822 if (colon != -1) {
823 mTransportInfo->host = serverport.left(colon);
824 mTransportInfo->port = serverport.mid(colon + 1);
825 } else {
826 mTransportInfo->host = serverport;
827 mTransportInfo->port = "465";
828 }
829 }
830 else if (transport.startsWith("file://"))
831 {
832 mTransportInfo->type = "sendmail";
833 mTransportInfo->host = transport.mid(7);
834 }
835 }
836 // strip off a trailing "/"
837 while (mTransportInfo->host.endsWith("/")) {
838 mTransportInfo->host.truncate(mTransportInfo->host.length()-1);
839 }
840
841
842 if (mTransportInfo->type == "sendmail")
843 return new KMSendSendmail(this);
844 if (mTransportInfo->type == "smtp" || mTransportInfo->type == "smtps")
845 return new KMSendSMTP(this);
846
847 return 0L;
848}
849
850//-----------------------------------------------------------------------------
851void KMSender::setStatusByLink(const KMMessage *aMsg)
852{
853 int n = 0;
854 while (1) {
855 ulong msn;
856 KMMsgStatus status;
857 aMsg->getLink(n, &msn, &status);
858 if (!msn || !status)
859 break;
860 n++;
861
862 KMFolder *folder = 0;
863 int index = -1;
864 KMMsgDict::instance()->getLocation(msn, &folder, &index);
865 if (folder && index != -1) {
866 KMFolderOpener openFolder(folder, "setstatus");
867 if ( status == KMMsgStatusDeleted ) {
868 // Move the message to the trash folder
869 KMDeleteMsgCommand *cmd =
870 new KMDeleteMsgCommand( folder, folder->getMsg( index ) );
871 cmd->start();
872 } else {
873 folder->setStatus(index, status);
874 }
875 } else {
876 kdWarning(5006) << k_funcinfo << "Cannot update linked message, it could not be found!" << endl;
877 }
878 }
879}
880
881//=============================================================================
882//=============================================================================
883KMSendProc::KMSendProc( KMSender * sender )
884 : TQObject( 0 ),
885 mSender( sender ),
886 mLastErrorMessage(),
887 mSendOk( false ),
888 mSending( false )
889{
890}
891
892//-----------------------------------------------------------------------------
893void KMSendProc::reset()
894{
895 mSending = false;
896 mSendOk = false;
897 mLastErrorMessage = TQString();
898}
899
900//-----------------------------------------------------------------------------
901void KMSendProc::failed(const TQString &aMsg)
902{
903 mSending = false;
904 mSendOk = false;
905 mLastErrorMessage = aMsg;
906}
907
908//-----------------------------------------------------------------------------
909void KMSendProc::statusMsg(const TQString& aMsg)
910{
911 if (mSender) mSender->setStatusMsg(aMsg);
912}
913
914//=============================================================================
915//=============================================================================
916KMSendSendmail::KMSendSendmail( KMSender * sender )
917 : KMSendProc( sender ),
918 mMsgStr(),
919 mMsgPos( 0 ),
920 mMsgRest( 0 ),
921 mMailerProc( 0 )
922{
923
924}
925
926KMSendSendmail::~KMSendSendmail() {
927 delete mMailerProc; mMailerProc = 0;
928}
929
930bool KMSendSendmail::doStart() {
931
932 if (mSender->transportInfo()->host.isEmpty())
933 {
934 const TQString str = i18n("Please specify a mailer program in the settings.");
935 const TQString msg = i18n("Sending failed:\n%1\n"
936 "The message will stay in the 'outbox' folder and will be resent.\n"
937 "Please remove it from there if you do not want the message to "
938 "be resent.\n"
939 "The following transport protocol was used:\n %2")
940 .arg(str + "\n")
941 .arg("sendmail://");
942 KMessageBox::information(0,msg);
943 return false;
944 }
945
946 if (!mMailerProc)
947 {
948 mMailerProc = new TDEProcess;
949 assert(mMailerProc != 0);
950 connect(mMailerProc,TQ_SIGNAL(processExited(TDEProcess*)),
951 this, TQ_SLOT(sendmailExited(TDEProcess*)));
952 connect(mMailerProc,TQ_SIGNAL(wroteStdin(TDEProcess*)),
953 this, TQ_SLOT(wroteStdin(TDEProcess*)));
954 connect(mMailerProc,TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)),
955 this, TQ_SLOT(receivedStderr(TDEProcess*, char*, int)));
956 }
957 return true;
958}
959
960void KMSendSendmail::doFinish() {
961 delete mMailerProc;
962 mMailerProc = 0;
963}
964
965void KMSendSendmail::abort()
966{
967 delete mMailerProc;
968 mMailerProc = 0;
969 mSendOk = false;
970 mMsgStr = 0;
971 idle();
972}
973
974bool KMSendSendmail::doSend( const TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & message ) {
975 mMailerProc->clearArguments();
976 *mMailerProc << mSender->transportInfo()->host
977 << "-i" << "-f" << sender
978 << to << cc << bcc ;
979
980 mMsgStr = message;
981
982 if ( !mMailerProc->start( TDEProcess::NotifyOnExit, TDEProcess::All ) ) {
983 KMessageBox::information( 0, i18n("Failed to execute mailer program %1")
984 .arg( mSender->transportInfo()->host ) );
985 return false;
986 }
987 mMsgPos = mMsgStr.data();
988 mMsgRest = mMsgStr.size();
989 wroteStdin( mMailerProc );
990
991 return true;
992}
993
994
995void KMSendSendmail::wroteStdin(TDEProcess *proc)
996{
997 char* str;
998 int len;
999
1000 assert(proc!=0);
1001 Q_UNUSED( proc );
1002
1003 str = mMsgPos;
1004 len = (mMsgRest>1024 ? 1024 : mMsgRest);
1005
1006 if (len <= 0)
1007 {
1008 mMailerProc->closeStdin();
1009 }
1010 else
1011 {
1012 mMsgRest -= len;
1013 mMsgPos += len;
1014 mMailerProc->writeStdin(str,len);
1015 // if code is added after writeStdin() TDEProcess probably initiates
1016 // a race condition.
1017 }
1018}
1019
1020
1021void KMSendSendmail::receivedStderr(TDEProcess *proc, char *buffer, int buflen)
1022{
1023 assert(proc!=0);
1024 Q_UNUSED( proc );
1025 mLastErrorMessage.replace(mLastErrorMessage.length(), buflen, buffer);
1026}
1027
1028
1029void KMSendSendmail::sendmailExited(TDEProcess *proc)
1030{
1031 assert(proc!=0);
1032 mSendOk = (proc->normalExit() && proc->exitStatus()==0);
1033 if (!mSendOk) failed(i18n("Sendmail exited abnormally."));
1034 mMsgStr = 0;
1035 emit idle();
1036}
1037
1038
1039
1040//-----------------------------------------------------------------------------
1041//=============================================================================
1042//=============================================================================
1043KMSendSMTP::KMSendSMTP(KMSender *sender)
1044 : KMSendProc(sender),
1045 mInProcess(false),
1046 mJob(0),
1047 mSlave(0)
1048{
1049 TDEIO::Scheduler::connect(TQ_SIGNAL(slaveError(TDEIO::Slave *, int,
1050 const TQString &)), this, TQ_SLOT(slaveError(TDEIO::Slave *, int,
1051 const TQString &)));
1052}
1053
1054KMSendSMTP::~KMSendSMTP()
1055{
1056 if (mJob) mJob->kill();
1057}
1058
1059bool KMSendSMTP::doSend( const TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & message ) {
1060 TQString query = "headers=0&from=";
1061 query += KURL::encode_string( sender );
1062
1063 TQStringList::ConstIterator it;
1064
1065 for ( it = to.begin(); it != to.end(); ++it )
1066 query += "&to=" + KURL::encode_string(*it);
1067 for ( it = cc.begin(); it != cc.end(); ++it )
1068 query += "&cc=" + KURL::encode_string(*it);
1069 for ( it = bcc.begin(); it != bcc.end(); ++it )
1070 query += "&bcc=" + KURL::encode_string(*it);
1071
1072 KMTransportInfo * ti = mSender->transportInfo();
1073
1074 if ( ti->specifyHostname )
1075 query += "&hostname=" + KURL::encode_string( ti->localHostname );
1076
1077 if ( !kmkernel->msgSender()->sendQuotedPrintable() )
1078 query += "&body=8bit";
1079
1080 KURL destination;
1081
1082 destination.setProtocol((ti->encryption == "SSL") ? SMTPS_PROTOCOL : SMTP_PROTOCOL);
1083 destination.setHost(ti->host);
1084 destination.setPort(ti->port.toUShort());
1085
1086 if (ti->auth)
1087 {
1088 TQMapIterator<TQString,TQString> tpc = mSender->mPasswdCache.find( ti->name );
1089 TQString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):TQString();
1090
1091 if ( ti->passwd().isEmpty() )
1092 ti->setPasswd( tpwd );
1093
1094 if( (ti->user.isEmpty() || ti->passwd().isEmpty()) &&
1095 ti->authType != "GSSAPI" )
1096 {
1097 bool b = false;
1098 int result;
1099
1100 KCursorSaver idle(KBusyPtr::idle());
1101 TQString passwd = ti->passwd();
1102 result = TDEIO::PasswordDialog::getNameAndPassword(ti->user, passwd,
1103 &b, i18n("You need to supply a username and a password to use this "
1104 "SMTP server."), false, TQString(), ti->name, TQString());
1105
1106 if ( result != TQDialog::Accepted )
1107 {
1108 abort();
1109 return false;
1110 }
1111 if (int id = KMTransportInfo::findTransport(ti->name)) {
1112 ti->setPasswd( passwd );
1113 ti->writeConfig(id);
1114
1115 // save the password into the cache
1116 mSender->mPasswdCache[ti->name] = passwd;
1117 }
1118 }
1119 destination.setUser(ti->user);
1120 destination.setPass(ti->passwd());
1121 }
1122
1123 if (!mSlave || !mInProcess)
1124 {
1125 TDEIO::MetaData slaveConfig;
1126 slaveConfig.insert("tls", (ti->encryption == "TLS") ? "on" : "off");
1127 if (ti->auth) slaveConfig.insert("sasl", ti->authType);
1128 mSlave = TDEIO::Scheduler::getConnectedSlave(destination, slaveConfig);
1129 }
1130
1131 if (!mSlave)
1132 {
1133 abort();
1134 return false;
1135 }
1136
1137 // dotstuffing is now done by the slave (see setting of metadata)
1138 mMessage = message;
1139 mMessageLength = mMessage.size();
1140 mMessageOffset = 0;
1141
1142 if ( mMessageLength )
1143 // allow +5% for subsequent LF->CRLF and dotstuffing (an average
1144 // over 2G-lines gives an average line length of 42-43):
1145 query += "&size=" + TQString::number( tqRound( mMessageLength * 1.05 ) );
1146
1147 destination.setPath("/send");
1148 destination.setQuery( query );
1149
1150 mJob = TDEIO::put( destination, -1, false, false, false );
1151 if ( !mJob ) {
1152 abort();
1153 return false;
1154 }
1155 mJob->addMetaData( "lf2crlf+dotstuff", "slave" );
1156 TDEIO::Scheduler::assignJobToSlave(mSlave, mJob);
1157 connect(mJob, TQ_SIGNAL(result(TDEIO::Job *)), this, TQ_SLOT(result(TDEIO::Job *)));
1158 connect(mJob, TQ_SIGNAL(dataReq(TDEIO::Job *, TQByteArray &)),
1159 this, TQ_SLOT(dataReq(TDEIO::Job *, TQByteArray &)));
1160 mSendOk = true;
1161 mInProcess = true;
1162 return true;
1163}
1164
1165void KMSendSMTP::cleanup() {
1166 if(mJob)
1167 {
1168 mJob->kill(true);
1169 mJob = 0;
1170 mSlave = 0;
1171 }
1172
1173 if (mSlave)
1174 {
1175 TDEIO::Scheduler::disconnectSlave(mSlave);
1176 mSlave = 0;
1177 }
1178
1179 mInProcess = false;
1180}
1181
1182void KMSendSMTP::abort() {
1183 cleanup();
1184 emit idle();
1185}
1186
1187void KMSendSMTP::doFinish() {
1188 cleanup();
1189}
1190
1191void KMSendSMTP::dataReq(TDEIO::Job *, TQByteArray &array)
1192{
1193 // Send it by 32K chuncks
1194 const int chunkSize = TQMIN( mMessageLength - mMessageOffset, 32*1024 );
1195 if ( chunkSize > 0 ) {
1196 array.duplicate(mMessage.data() + mMessageOffset, chunkSize);
1197 mMessageOffset += chunkSize;
1198 } else
1199 {
1200 array.resize(0);
1201 mMessage.resize(0);
1202 }
1203 mSender->emitProgressInfo( mMessageOffset );
1204}
1205
1206void KMSendSMTP::result(TDEIO::Job *_job)
1207{
1208 if (!mJob) return;
1209 mJob = 0;
1210
1211 if(_job->error())
1212 {
1213 mSendOk = false;
1214 if (_job->error() == TDEIO::ERR_SLAVE_DIED) mSlave = 0;
1215 failed(_job->errorString());
1216 abort();
1217 } else {
1218 emit idle();
1219 }
1220}
1221
1222void KMSendSMTP::slaveError(TDEIO::Slave *aSlave, int error, const TQString &errorMsg)
1223{
1224 if (aSlave == mSlave)
1225 {
1226 if (error == TDEIO::ERR_SLAVE_DIED) mSlave = 0;
1227 mSendOk = false;
1228 mJob = 0;
1229 failed(TDEIO::buildErrorString(error, errorMsg));
1230 abort();
1231 }
1232}
1233
1234#include "kmsender.moc"
1235#include "kmsender_p.moc"
sets a cursor and makes sure it's restored on destruction Create a KCursorSaver object when you want ...
Definition: kcursorsaver.h:14
RAII for KMFolder::open() / close().
Definition: kmfolder.h:688
Mail folder.
Definition: kmfolder.h:69
int moveMsg(KMMessage *msg, int *index_return=0)
Detaches the given message from it's current folder and adds it to this folder.
Definition: kmfolder.cpp:425
KMMsgInfo * unGetMsg(int idx)
Replace KMMessage with KMMsgInfo and delete KMMessage
Definition: kmfolder.cpp:326
int addMsg(KMMessage *msg, int *index_return=0)
Add the given message to the folder.
Definition: kmfolder.cpp:390
void close(const char *owner, bool force=false)
Close folder.
Definition: kmfolder.cpp:489
KMMessage * getMsg(int idx)
Read message at given index.
Definition: kmfolder.cpp:321
void setStatus(int idx, KMMsgStatus status, bool toggle=false)
Set the status of the message at index idx to status.
Definition: kmfolder.cpp:831
int count(bool cache=false) const
Number of messages in this folder.
Definition: kmfolder.cpp:445
int open(const char *owner)
Open folder for access.
Definition: kmfolder.cpp:479
bool isReadOnly() const
Is the folder read-only?
Definition: kmfolder.cpp:561
This is a Mime Message.
Definition: kmmessage.h:68
static TQString generateMessageId(const TQString &addr)
Generates the Message-Id.
Definition: kmmessage.cpp:3364
void setTransferInProgress(bool value, bool force=false)
Set that the message shall not be deleted because it is still required.
Definition: kmmessage.cpp:243
TQString msgId() const
Get or set the 'Message-Id' header field.
Definition: kmmessage.cpp:2182
TQString sender() const
Definition: kmmessage.cpp:2039
void setStatus(const KMMsgStatus status, int idx=-1)
Set status and mark dirty.
Definition: kmmessage.cpp:4153
void setUnencryptedMsg(KMMessage *unencrypted)
Specifies an unencrypted copy of this message to be stored in a separate member variable to allow sav...
Definition: kmmessage.cpp:265
DwMediaType & dwContentType()
Return reference to Content-Type header for direct manipulation.
Definition: kmmessage.cpp:389
TQString to() const
Get or set the 'To' header field.
Definition: kmmessage.cpp:1894
void removeHeaderField(const TQCString &name)
Remove header field with given name.
Definition: kmmessage.cpp:2317
TQString headerField(const TQCString &name) const
Returns the value of a header field with the given name.
Definition: kmmessage.cpp:2289
KMMessage * unencryptedMsg() const
Returns an unencrypted copy of this message or 0 if none exists.
Definition: kmmessage.h:137
void getLink(int n, ulong *retMsgSerNum, KMMsgStatus *reStatus) const
Returns the information for the Nth link into retMsg and reStatus.
Definition: kmmessage.cpp:4216
void getLocation(unsigned long key, KMFolder **retFolder, int *retIndex) const
Returns the folder the message represented by the serial number key is in and the index in that folde...
Definition: kmmsgdict.cpp:319
static const KMMsgDict * instance()
Access the globally unique MessageDict.
Definition: kmmsgdict.cpp:167