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 <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 //-----------------------------------------------------------------------------
50 KMSender::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 //-----------------------------------------------------------------------------
71 KMSender::~KMSender()
72 {
73  writeConfig(false);
74  delete mSendProc;
75  delete mPrecommand;
76  delete mTransportInfo;
77 }
78 
79 //-----------------------------------------------------------------------------
80 void KMSender::setStatusMsg(const TQString &msg)
81 {
82  if ( mProgressItem )
83  mProgressItem->setStatus(msg);
84 }
85 
86 //-----------------------------------------------------------------------------
87 void 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 //-----------------------------------------------------------------------------
98 void 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 //-----------------------------------------------------------------------------
110 bool 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 
120 static 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 //-----------------------------------------------------------------------------
128 bool 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 //-----------------------------------------------------------------------------
200 void 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 //-----------------------------------------------------------------------------
211 bool 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, TQT_SIGNAL(msgAdded(int)),
235  this, TQT_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 //-----------------------------------------------------------------------------
249 void 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 
256 static 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 //-----------------------------------------------------------------------------
279 void 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, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
476  this, TQT_SLOT( slotAbortSend() ) );
477  kapp->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  const TQStringList sl = KMTransportInfo::availableTransports();
487  if (!sl.empty()) msgTransport = sl.front();
488  }
489 
490  if (!mSendProc || msgTransport != mMethodStr) {
491  if (mSendProcStarted && mSendProc) {
492  mSendProc->finish();
493  mSendProcStarted = false;
494  }
495 
496  mSendProc = createSendProcFromString(msgTransport);
497  mMethodStr = msgTransport;
498 
499  if( mTransportInfo->encryption == "TLS" || mTransportInfo->encryption == "SSL" ) {
500  mProgressItem->setUsesCrypto( true );
501  } else if ( !mCustomTransport.isEmpty() ) {
502  int result = KMessageBox::warningContinueCancel( 0,
503  i18n( "You have chosen to send all queued email using an unencrypted transport, do you want to continue? "),
504  i18n( "Security Warning" ),
505  i18n( "Send Unencrypted" ),
506  "useCustomTransportWithoutAsking", false);
507 
508  if( result == KMessageBox::Cancel ) {
509  mProgressItem->cancel();
510  mProgressItem->setComplete();
511  slotAbortSend();
512  cleanup();
513  return;
514  }
515  }
516 
517  if (!mSendProc)
518  sendProcStarted(false);
519  else {
520  connect(mSendProc, TQT_SIGNAL(idle()), TQT_SLOT(slotIdle()));
521  connect(mSendProc, TQT_SIGNAL(started(bool)), TQT_SLOT(sendProcStarted(bool)));
522 
523  // Run the precommand if there is one
524  if ( !mTransportInfo->precommand.isEmpty() ) {
525  runPrecommand( mTransportInfo->precommand );
526  return;
527  }
528 
529  mSendProc->start();
530  }
531  }
532  else if (!mSendProcStarted)
533  mSendProc->start();
534  else
535  doSendMsgAux();
536 }
537 
538 bool KMSender::runPrecommand( const TQString & cmd ) {
539  setStatusMsg( i18n("Executing precommand %1").arg( cmd ) );
540  mPrecommand = new KMPrecommand( cmd );
541  connect( mPrecommand, TQT_SIGNAL(finished(bool)),
542  TQT_SLOT(slotPrecommandFinished(bool)) );
543  if ( !mPrecommand->start() ) {
544  delete mPrecommand; mPrecommand = 0;
545  return false;
546  }
547  return true;
548 }
549 
550 //-----------------------------------------------------------------------------
551 void KMSender::sendProcStarted(bool success)
552 {
553  if (!success) {
554  if (mSendProc)
555  mSendProc->finish();
556  else
557  setStatusMsg(i18n("Unrecognized transport protocol. Unable to send message."));
558  mSendProc = 0;
559  mSendProcStarted = false;
560  cleanup();
561  return;
562  }
563  doSendMsgAux();
564 }
565 
566 
567 static TQStringList addrSpecListToStringList( const AddrSpecList & l, bool allowEmpty=false ) {
568  TQStringList result;
569  for ( AddrSpecList::const_iterator it = l.begin(), end = l.end() ; it != end ; ++it ) {
570  const TQString s = (*it).asString();
571  if ( allowEmpty || !s.isEmpty() )
572  result.push_back( s );
573  }
574  return result;
575 }
576 
577 static void extractSenderToCCAndBcc( KMMessage * aMsg, TQString * sender, TQStringList * to, TQStringList * cc, TQStringList * bcc ) {
578  if ( sender ) *sender = aMsg->sender();
579  if( !aMsg->headerField("X-KMail-Recipients").isEmpty() ) {
580  // extended BCC handling to prevent TOs and CCs from seeing
581  // BBC information by looking at source of an OpenPGP encrypted mail
582  if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "X-KMail-Recipients" ) );
583  aMsg->removeHeaderField( "X-KMail-Recipients" );
584  } else {
585  if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "To" ) );
586  if ( cc ) *cc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Cc" ) );
587  if ( bcc ) *bcc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Bcc" ) );
588  }
589 }
590 
591 //-----------------------------------------------------------------------------
592 void KMSender::doSendMsgAux()
593 {
594  mSendProcStarted = true;
595 
596  // start sending the current message
597 
598  setStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3")
599  .arg(mSentMessages+mFailedMessages+1).arg(mTotalMessages)
600  .arg(mCurrentMsg->subject()));
601  TQStringList to, cc, bcc;
602  TQString sender;
603  extractSenderToCCAndBcc( mCurrentMsg, &sender, &to, &cc, &bcc );
604 
605  // MDNs are required to have an empty envelope from as per RFC2298.
606  if ( messageIsDispositionNotificationReport( mCurrentMsg ) && GlobalSettings::self()->sendMDNsWithEmptySender() )
607  sender = "<>";
608 
609  const TQByteArray message = mCurrentMsg->asSendableString();
610  if ( sender.isEmpty() || !mSendProc->send( sender, to, cc, bcc, message ) ) {
611  if ( mCurrentMsg )
612  mCurrentMsg->setTransferInProgress( false );
613  if ( mOutboxFolder )
614  mOutboxFolder->unGetMsg( mFailedMessages );
615  mCurrentMsg = 0;
616  cleanup();
617  setStatusMsg(i18n("Failed to send (some) queued messages."));
618  return;
619  }
620  // Do *not* add code here, after send(). It can happen that this method
621  // is called recursively if send() emits the idle signal directly.
622 }
623 
624 
625 //-----------------------------------------------------------------------------
626 void KMSender::cleanup(void)
627 {
628  kdDebug(5006) << k_funcinfo << endl;
629  if (mSendProc && mSendProcStarted) mSendProc->finish();
630  mSendProc = 0;
631  mSendProcStarted = false;
632  if (mSendInProgress) kapp->deref();
633  mSendInProgress = false;
634  if (mCurrentMsg)
635  {
636  mCurrentMsg->setTransferInProgress( false );
637  mCurrentMsg = 0;
638  }
639  if ( mSentFolder ) {
640  mSentFolder->close("dosendsent");
641  mSentFolder = 0;
642  }
643  if ( mOutboxFolder ) {
644  disconnect( mOutboxFolder, TQT_SIGNAL(msgAdded(int)),
645  this, TQT_SLOT(outboxMsgAdded(int)) );
646  mOutboxFolder->close("dosendoutbox");
647  if ( mOutboxFolder->count( true ) == 0 ) {
648  mOutboxFolder->expunge();
649  }
650  else if ( mOutboxFolder->needsCompacting() ) {
651  mOutboxFolder->compact( KMFolder::CompactSilentlyNow );
652  }
653  mOutboxFolder = 0;
654  }
655 
656  mSendAborted = false;
657  mSentMessages = 0;
658  mFailedMessages = 0;
659  mSentBytes = 0;
660  if ( mProgressItem )
661  mProgressItem->setComplete();
662  mProgressItem = 0;
663  kmkernel->filterMgr()->deref();
664 }
665 
666 
667 //-----------------------------------------------------------------------------
668 void KMSender::slotAbortSend()
669 {
670  mSendAborted = true;
671  delete mPrecommand;
672  mPrecommand = 0;
673  if (mSendProc) mSendProc->abort();
674 }
675 
676 //-----------------------------------------------------------------------------
677 void KMSender::slotIdle()
678 {
679  assert(mSendProc != 0);
680 
681  TQString msg;
682  TQString errString;
683  if (mSendProc)
684  errString = mSendProc->lastErrorMessage();
685 
686  if (mSendAborted) {
687  // sending of message aborted
688  if ( mCurrentMsg ) {
689  mCurrentMsg->setTransferInProgress( false );
690  if ( mOutboxFolder )
691  mOutboxFolder->unGetMsg( mFailedMessages );
692  mCurrentMsg = 0;
693  }
694  msg = i18n("Sending aborted:\n%1\n"
695  "The message will stay in the 'outbox' folder until you either "
696  "fix the problem (e.g. a broken address) or remove the message "
697  "from the 'outbox' folder.\n"
698  "The following transport protocol was used:\n %2")
699  .arg(errString)
700  .arg(mMethodStr);
701  if (!errString.isEmpty()) KMessageBox::error(0,msg);
702  setStatusMsg( i18n( "Sending aborted." ) );
703  } else {
704  if (!mSendProc->sendOk()) {
705  if ( mCurrentMsg )
706  mCurrentMsg->setTransferInProgress( false );
707  if ( mOutboxFolder )
708  mOutboxFolder->unGetMsg( mFailedMessages );
709  mCurrentMsg = 0;
710  mFailedMessages++;
711  // reset cached password
712  TQMapIterator <TQString,TQString> pc;
713  if ( (pc = mPasswdCache.find( mMethodStr )) != mPasswdCache.end() ) {
714  mPasswdCache.erase(pc);
715  }
716  // Sending of message failed.
717  if (!errString.isEmpty()) {
718  int res = KMessageBox::Yes;
719  if (mSentMessages+mFailedMessages != mTotalMessages) {
720  msg = i18n("<p>Sending failed:</p>"
721  "<p>%1</p>"
722  "<p>The message will stay in the 'outbox' folder until you either "
723  "fix the problem (e.g. a broken address) or remove the message "
724  "from the 'outbox' folder.</p>"
725  "<p>The following transport protocol was used: %2</p>"
726  "<p>Do you want me to continue sending the remaining messages?</p>")
727  .arg(errString)
728  .arg(mMethodStr);
729  res = KMessageBox::warningYesNo( 0 , msg ,
730  i18n( "Continue Sending" ), i18n( "&Continue Sending" ),
731  i18n("&Abort Sending") );
732  } else {
733  msg = i18n("Sending failed:\n%1\n"
734  "The message will stay in the 'outbox' folder until you either "
735  "fix the problem (e.g. a broken address) or remove the message "
736  "from the 'outbox' folder.\n"
737  "The following transport protocol was used:\n %2")
738  .arg(errString)
739  .arg(mMethodStr);
740  KMessageBox::error(0,msg);
741  }
742  if (res == KMessageBox::Yes) {
743  // Try the next one.
744  doSendMsg();
745  return;
746  } else {
747  setStatusMsg( i18n( "Sending aborted." ) );
748  }
749  }
750  } else {
751  // Sending suceeded.
752  doSendMsg();
753  return;
754  }
755  }
756  mSendProc->finish();
757  mSendProc = 0;
758  mSendProcStarted = false;
759 
760  cleanup();
761 }
762 
763 
764 //-----------------------------------------------------------------------------
765 void KMSender::slotPrecommandFinished(bool normalExit)
766 {
767  delete mPrecommand;
768  mPrecommand = 0;
769  if (normalExit) mSendProc->start();
770  else slotIdle();
771 }
772 
773 
774 //-----------------------------------------------------------------------------
775 void KMSender::setSendImmediate(bool aSendImmediate)
776 {
777  mSendImmediate = aSendImmediate;
778 }
779 
780 
781 //-----------------------------------------------------------------------------
782 void KMSender::setSendQuotedPrintable(bool aSendQuotedPrintable)
783 {
784  mSendQuotedPrintable = aSendQuotedPrintable;
785 }
786 
787 
788 //-----------------------------------------------------------------------------
789 KMSendProc* KMSender::createSendProcFromString( const TQString & transport )
790 {
791  mTransportInfo->type = TQString();
792  int nr = KMTransportInfo::findTransport(transport);
793  if (nr)
794  {
795  mTransportInfo->readConfig(nr);
796  } else {
797  if (transport.startsWith("smtp://")) // should probably use KURL and SMTP_PROTOCOL
798  {
799  mTransportInfo->type = "smtp";
800  mTransportInfo->auth = false;
801  mTransportInfo->encryption = "NONE";
802  TQString serverport = transport.mid(7);
803  int colon = serverport.find(':');
804  if (colon != -1) {
805  mTransportInfo->host = serverport.left(colon);
806  mTransportInfo->port = serverport.mid(colon + 1);
807  } else {
808  mTransportInfo->host = serverport;
809  mTransportInfo->port = "25";
810  }
811  } else
812  if (transport.startsWith("smtps://")) // should probably use KURL and SMTPS_PROTOCOL
813  {
814  mTransportInfo->type = "smtps";
815  mTransportInfo->auth = false;
816  mTransportInfo->encryption = "ssl";
817  TQString serverport = transport.mid(7);
818  int colon = serverport.find(':');
819  if (colon != -1) {
820  mTransportInfo->host = serverport.left(colon);
821  mTransportInfo->port = serverport.mid(colon + 1);
822  } else {
823  mTransportInfo->host = serverport;
824  mTransportInfo->port = "465";
825  }
826  }
827  else if (transport.startsWith("file://"))
828  {
829  mTransportInfo->type = "sendmail";
830  mTransportInfo->host = transport.mid(7);
831  }
832  }
833  // strip off a trailing "/"
834  while (mTransportInfo->host.endsWith("/")) {
835  mTransportInfo->host.truncate(mTransportInfo->host.length()-1);
836  }
837 
838 
839  if (mTransportInfo->type == "sendmail")
840  return new KMSendSendmail(this);
841  if (mTransportInfo->type == "smtp" || mTransportInfo->type == "smtps")
842  return new KMSendSMTP(this);
843 
844  return 0L;
845 }
846 
847 //-----------------------------------------------------------------------------
848 void KMSender::setStatusByLink(const KMMessage *aMsg)
849 {
850  int n = 0;
851  while (1) {
852  ulong msn;
853  KMMsgStatus status;
854  aMsg->getLink(n, &msn, &status);
855  if (!msn || !status)
856  break;
857  n++;
858 
859  KMFolder *folder = 0;
860  int index = -1;
861  KMMsgDict::instance()->getLocation(msn, &folder, &index);
862  if (folder && index != -1) {
863  KMFolderOpener openFolder(folder, "setstatus");
864  if ( status == KMMsgStatusDeleted ) {
865  // Move the message to the trash folder
866  KMDeleteMsgCommand *cmd =
867  new KMDeleteMsgCommand( folder, folder->getMsg( index ) );
868  cmd->start();
869  } else {
870  folder->setStatus(index, status);
871  }
872  } else {
873  kdWarning(5006) << k_funcinfo << "Cannot update linked message, it could not be found!" << endl;
874  }
875  }
876 }
877 
878 //=============================================================================
879 //=============================================================================
880 KMSendProc::KMSendProc( KMSender * sender )
881  : TQObject( 0 ),
882  mSender( sender ),
883  mLastErrorMessage(),
884  mSendOk( false ),
885  mSending( false )
886 {
887 }
888 
889 //-----------------------------------------------------------------------------
890 void KMSendProc::reset()
891 {
892  mSending = false;
893  mSendOk = false;
894  mLastErrorMessage = TQString();
895 }
896 
897 //-----------------------------------------------------------------------------
898 void KMSendProc::failed(const TQString &aMsg)
899 {
900  mSending = false;
901  mSendOk = false;
902  mLastErrorMessage = aMsg;
903 }
904 
905 //-----------------------------------------------------------------------------
906 void KMSendProc::statusMsg(const TQString& aMsg)
907 {
908  if (mSender) mSender->setStatusMsg(aMsg);
909 }
910 
911 //=============================================================================
912 //=============================================================================
913 KMSendSendmail::KMSendSendmail( KMSender * sender )
914  : KMSendProc( sender ),
915  mMsgStr(),
916  mMsgPos( 0 ),
917  mMsgRest( 0 ),
918  mMailerProc( 0 )
919 {
920 
921 }
922 
923 KMSendSendmail::~KMSendSendmail() {
924  delete mMailerProc; mMailerProc = 0;
925 }
926 
927 bool KMSendSendmail::doStart() {
928 
929  if (mSender->transportInfo()->host.isEmpty())
930  {
931  const TQString str = i18n("Please specify a mailer program in the settings.");
932  const TQString msg = i18n("Sending failed:\n%1\n"
933  "The message will stay in the 'outbox' folder and will be resent.\n"
934  "Please remove it from there if you do not want the message to "
935  "be resent.\n"
936  "The following transport protocol was used:\n %2")
937  .arg(str + "\n")
938  .arg("sendmail://");
939  KMessageBox::information(0,msg);
940  return false;
941  }
942 
943  if (!mMailerProc)
944  {
945  mMailerProc = new TDEProcess;
946  assert(mMailerProc != 0);
947  connect(mMailerProc,TQT_SIGNAL(processExited(TDEProcess*)),
948  this, TQT_SLOT(sendmailExited(TDEProcess*)));
949  connect(mMailerProc,TQT_SIGNAL(wroteStdin(TDEProcess*)),
950  this, TQT_SLOT(wroteStdin(TDEProcess*)));
951  connect(mMailerProc,TQT_SIGNAL(receivedStderr(TDEProcess*,char*,int)),
952  this, TQT_SLOT(receivedStderr(TDEProcess*, char*, int)));
953  }
954  return true;
955 }
956 
957 void KMSendSendmail::doFinish() {
958  delete mMailerProc;
959  mMailerProc = 0;
960 }
961 
962 void KMSendSendmail::abort()
963 {
964  delete mMailerProc;
965  mMailerProc = 0;
966  mSendOk = false;
967  mMsgStr = 0;
968  idle();
969 }
970 
971 bool KMSendSendmail::doSend( const TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & message ) {
972  mMailerProc->clearArguments();
973  *mMailerProc << mSender->transportInfo()->host
974  << "-i" << "-f" << sender
975  << to << cc << bcc ;
976 
977  mMsgStr = message;
978 
979  if ( !mMailerProc->start( TDEProcess::NotifyOnExit, TDEProcess::All ) ) {
980  KMessageBox::information( 0, i18n("Failed to execute mailer program %1")
981  .arg( mSender->transportInfo()->host ) );
982  return false;
983  }
984  mMsgPos = mMsgStr.data();
985  mMsgRest = mMsgStr.size();
986  wroteStdin( mMailerProc );
987 
988  return true;
989 }
990 
991 
992 void KMSendSendmail::wroteStdin(TDEProcess *proc)
993 {
994  char* str;
995  int len;
996 
997  assert(proc!=0);
998  Q_UNUSED( proc );
999 
1000  str = mMsgPos;
1001  len = (mMsgRest>1024 ? 1024 : mMsgRest);
1002 
1003  if (len <= 0)
1004  {
1005  mMailerProc->closeStdin();
1006  }
1007  else
1008  {
1009  mMsgRest -= len;
1010  mMsgPos += len;
1011  mMailerProc->writeStdin(str,len);
1012  // if code is added after writeStdin() TDEProcess probably initiates
1013  // a race condition.
1014  }
1015 }
1016 
1017 
1018 void KMSendSendmail::receivedStderr(TDEProcess *proc, char *buffer, int buflen)
1019 {
1020  assert(proc!=0);
1021  Q_UNUSED( proc );
1022  mLastErrorMessage.replace(mLastErrorMessage.length(), buflen, buffer);
1023 }
1024 
1025 
1026 void KMSendSendmail::sendmailExited(TDEProcess *proc)
1027 {
1028  assert(proc!=0);
1029  mSendOk = (proc->normalExit() && proc->exitStatus()==0);
1030  if (!mSendOk) failed(i18n("Sendmail exited abnormally."));
1031  mMsgStr = 0;
1032  emit idle();
1033 }
1034 
1035 
1036 
1037 //-----------------------------------------------------------------------------
1038 //=============================================================================
1039 //=============================================================================
1040 KMSendSMTP::KMSendSMTP(KMSender *sender)
1041  : KMSendProc(sender),
1042  mInProcess(false),
1043  mJob(0),
1044  mSlave(0)
1045 {
1046  TDEIO::Scheduler::connect(TQT_SIGNAL(slaveError(TDEIO::Slave *, int,
1047  const TQString &)), this, TQT_SLOT(slaveError(TDEIO::Slave *, int,
1048  const TQString &)));
1049 }
1050 
1051 KMSendSMTP::~KMSendSMTP()
1052 {
1053  if (mJob) mJob->kill();
1054 }
1055 
1056 bool KMSendSMTP::doSend( const TQString & sender, const TQStringList & to, const TQStringList & cc, const TQStringList & bcc, const TQByteArray & message ) {
1057  TQString query = "headers=0&from=";
1058  query += KURL::encode_string( sender );
1059 
1060  TQStringList::ConstIterator it;
1061 
1062  for ( it = to.begin(); it != to.end(); ++it )
1063  query += "&to=" + KURL::encode_string(*it);
1064  for ( it = cc.begin(); it != cc.end(); ++it )
1065  query += "&cc=" + KURL::encode_string(*it);
1066  for ( it = bcc.begin(); it != bcc.end(); ++it )
1067  query += "&bcc=" + KURL::encode_string(*it);
1068 
1069  KMTransportInfo * ti = mSender->transportInfo();
1070 
1071  if ( ti->specifyHostname )
1072  query += "&hostname=" + KURL::encode_string( ti->localHostname );
1073 
1074  if ( !kmkernel->msgSender()->sendQuotedPrintable() )
1075  query += "&body=8bit";
1076 
1077  KURL destination;
1078 
1079  destination.setProtocol((ti->encryption == "SSL") ? SMTPS_PROTOCOL : SMTP_PROTOCOL);
1080  destination.setHost(ti->host);
1081  destination.setPort(ti->port.toUShort());
1082 
1083  if (ti->auth)
1084  {
1085  TQMapIterator<TQString,TQString> tpc = mSender->mPasswdCache.find( ti->name );
1086  TQString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):TQString();
1087 
1088  if ( ti->passwd().isEmpty() )
1089  ti->setPasswd( tpwd );
1090 
1091  if( (ti->user.isEmpty() || ti->passwd().isEmpty()) &&
1092  ti->authType != "GSSAPI" )
1093  {
1094  bool b = false;
1095  int result;
1096 
1097  KCursorSaver idle(KBusyPtr::idle());
1098  TQString passwd = ti->passwd();
1099  result = TDEIO::PasswordDialog::getNameAndPassword(ti->user, passwd,
1100  &b, i18n("You need to supply a username and a password to use this "
1101  "SMTP server."), false, TQString(), ti->name, TQString());
1102 
1103  if ( result != TQDialog::Accepted )
1104  {
1105  abort();
1106  return false;
1107  }
1108  if (int id = KMTransportInfo::findTransport(ti->name)) {
1109  ti->setPasswd( passwd );
1110  ti->writeConfig(id);
1111 
1112  // save the password into the cache
1113  mSender->mPasswdCache[ti->name] = passwd;
1114  }
1115  }
1116  destination.setUser(ti->user);
1117  destination.setPass(ti->passwd());
1118  }
1119 
1120  if (!mSlave || !mInProcess)
1121  {
1122  TDEIO::MetaData slaveConfig;
1123  slaveConfig.insert("tls", (ti->encryption == "TLS") ? "on" : "off");
1124  if (ti->auth) slaveConfig.insert("sasl", ti->authType);
1125  mSlave = TDEIO::Scheduler::getConnectedSlave(destination, slaveConfig);
1126  }
1127 
1128  if (!mSlave)
1129  {
1130  abort();
1131  return false;
1132  }
1133 
1134  // dotstuffing is now done by the slave (see setting of metadata)
1135  mMessage = message;
1136  mMessageLength = mMessage.size();
1137  mMessageOffset = 0;
1138 
1139  if ( mMessageLength )
1140  // allow +5% for subsequent LF->CRLF and dotstuffing (an average
1141  // over 2G-lines gives an average line length of 42-43):
1142  query += "&size=" + TQString::number( tqRound( mMessageLength * 1.05 ) );
1143 
1144  destination.setPath("/send");
1145  destination.setQuery( query );
1146 
1147  mJob = TDEIO::put( destination, -1, false, false, false );
1148  if ( !mJob ) {
1149  abort();
1150  return false;
1151  }
1152  mJob->addMetaData( "lf2crlf+dotstuff", "slave" );
1153  TDEIO::Scheduler::assignJobToSlave(mSlave, mJob);
1154  connect(mJob, TQT_SIGNAL(result(TDEIO::Job *)), this, TQT_SLOT(result(TDEIO::Job *)));
1155  connect(mJob, TQT_SIGNAL(dataReq(TDEIO::Job *, TQByteArray &)),
1156  this, TQT_SLOT(dataReq(TDEIO::Job *, TQByteArray &)));
1157  mSendOk = true;
1158  mInProcess = true;
1159  return true;
1160 }
1161 
1162 void KMSendSMTP::cleanup() {
1163  if(mJob)
1164  {
1165  mJob->kill(true);
1166  mJob = 0;
1167  mSlave = 0;
1168  }
1169 
1170  if (mSlave)
1171  {
1172  TDEIO::Scheduler::disconnectSlave(mSlave);
1173  mSlave = 0;
1174  }
1175 
1176  mInProcess = false;
1177 }
1178 
1179 void KMSendSMTP::abort() {
1180  cleanup();
1181  emit idle();
1182 }
1183 
1184 void KMSendSMTP::doFinish() {
1185  cleanup();
1186 }
1187 
1188 void KMSendSMTP::dataReq(TDEIO::Job *, TQByteArray &array)
1189 {
1190  // Send it by 32K chuncks
1191  const int chunkSize = TQMIN( mMessageLength - mMessageOffset, 32*1024 );
1192  if ( chunkSize > 0 ) {
1193  array.duplicate(mMessage.data() + mMessageOffset, chunkSize);
1194  mMessageOffset += chunkSize;
1195  } else
1196  {
1197  array.resize(0);
1198  mMessage.resize(0);
1199  }
1200  mSender->emitProgressInfo( mMessageOffset );
1201 }
1202 
1203 void KMSendSMTP::result(TDEIO::Job *_job)
1204 {
1205  if (!mJob) return;
1206  mJob = 0;
1207 
1208  if(_job->error())
1209  {
1210  mSendOk = false;
1211  if (_job->error() == TDEIO::ERR_SLAVE_DIED) mSlave = 0;
1212  failed(_job->errorString());
1213  abort();
1214  } else {
1215  emit idle();
1216  }
1217 }
1218 
1219 void KMSendSMTP::slaveError(TDEIO::Slave *aSlave, int error, const TQString &errorMsg)
1220 {
1221  if (aSlave == mSlave)
1222  {
1223  if (error == TDEIO::ERR_SLAVE_DIED) mSlave = 0;
1224  mSendOk = false;
1225  mJob = 0;
1226  failed(TDEIO::buildErrorString(error, errorMsg));
1227  abort();
1228  }
1229 }
1230 
1231 #include "kmsender.moc"
1232 #include "kmsender_p.moc"