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>
11 using 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 //-----------------------------------------------------------------------------
49 KMSender::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 //-----------------------------------------------------------------------------
70 KMSender::~KMSender()
71 {
72  writeConfig(false);
73  delete mSendProc;
74  delete mPrecommand;
75  delete mTransportInfo;
76 }
77 
78 //-----------------------------------------------------------------------------
79 void KMSender::setStatusMsg(const TQString &msg)
80 {
81  if ( mProgressItem )
82  mProgressItem->setStatus(msg);
83 }
84 
85 //-----------------------------------------------------------------------------
86 void 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 //-----------------------------------------------------------------------------
97 void 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 //-----------------------------------------------------------------------------
109 bool 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 
119 static 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 //-----------------------------------------------------------------------------
127 bool 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 //-----------------------------------------------------------------------------
199 void 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 //-----------------------------------------------------------------------------
210 bool 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 //-----------------------------------------------------------------------------
248 void 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 
255 static 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 //-----------------------------------------------------------------------------
278 void 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  kapp->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 
540 bool 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 //-----------------------------------------------------------------------------
553 void 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 
569 static 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 
579 static 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 //-----------------------------------------------------------------------------
594 void 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 //-----------------------------------------------------------------------------
628 void 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) kapp->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 //-----------------------------------------------------------------------------
670 void KMSender::slotAbortSend()
671 {
672  mSendAborted = true;
673  delete mPrecommand;
674  mPrecommand = 0;
675  if (mSendProc) mSendProc->abort();
676 }
677 
678 //-----------------------------------------------------------------------------
679 void 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 //-----------------------------------------------------------------------------
767 void KMSender::slotPrecommandFinished(bool normalExit)
768 {
769  delete mPrecommand;
770  mPrecommand = 0;
771  if (normalExit) mSendProc->start();
772  else slotIdle();
773 }
774 
775 
776 //-----------------------------------------------------------------------------
777 void KMSender::setSendImmediate(bool aSendImmediate)
778 {
779  mSendImmediate = aSendImmediate;
780 }
781 
782 
783 //-----------------------------------------------------------------------------
784 void KMSender::setSendQuotedPrintable(bool aSendQuotedPrintable)
785 {
786  mSendQuotedPrintable = aSendQuotedPrintable;
787 }
788 
789 
790 //-----------------------------------------------------------------------------
791 KMSendProc* 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 //-----------------------------------------------------------------------------
850 void 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 //=============================================================================
882 KMSendProc::KMSendProc( KMSender * sender )
883  : TQObject( 0 ),
884  mSender( sender ),
885  mLastErrorMessage(),
886  mSendOk( false ),
887  mSending( false )
888 {
889 }
890 
891 //-----------------------------------------------------------------------------
892 void KMSendProc::reset()
893 {
894  mSending = false;
895  mSendOk = false;
896  mLastErrorMessage = TQString();
897 }
898 
899 //-----------------------------------------------------------------------------
900 void KMSendProc::failed(const TQString &aMsg)
901 {
902  mSending = false;
903  mSendOk = false;
904  mLastErrorMessage = aMsg;
905 }
906 
907 //-----------------------------------------------------------------------------
908 void KMSendProc::statusMsg(const TQString& aMsg)
909 {
910  if (mSender) mSender->setStatusMsg(aMsg);
911 }
912 
913 //=============================================================================
914 //=============================================================================
915 KMSendSendmail::KMSendSendmail( KMSender * sender )
916  : KMSendProc( sender ),
917  mMsgStr(),
918  mMsgPos( 0 ),
919  mMsgRest( 0 ),
920  mMailerProc( 0 )
921 {
922 
923 }
924 
925 KMSendSendmail::~KMSendSendmail() {
926  delete mMailerProc; mMailerProc = 0;
927 }
928 
929 bool 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 
959 void KMSendSendmail::doFinish() {
960  delete mMailerProc;
961  mMailerProc = 0;
962 }
963 
964 void KMSendSendmail::abort()
965 {
966  delete mMailerProc;
967  mMailerProc = 0;
968  mSendOk = false;
969  mMsgStr = 0;
970  idle();
971 }
972 
973 bool 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 
994 void 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 
1020 void 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 
1028 void 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 //=============================================================================
1042 KMSendSMTP::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 
1053 KMSendSMTP::~KMSendSMTP()
1054 {
1055  if (mJob) mJob->kill();
1056 }
1057 
1058 bool 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 
1164 void 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 
1181 void KMSendSMTP::abort() {
1182  cleanup();
1183  emit idle();
1184 }
1185 
1186 void KMSendSMTP::doFinish() {
1187  cleanup();
1188 }
1189 
1190 void 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 
1205 void 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 
1221 void 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
KMMessage * unencryptedMsg() const
Returns an unencrypted copy of this message or 0 if none exists.
Definition: kmmessage.h:137
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
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