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