kmail

messagecomposer.cpp

00001 
00031 #ifdef HAVE_CONFIG_H
00032 #include <config.h>
00033 #endif
00034 
00035 #include "messagecomposer.h"
00036 #include "kmmsgpart.h"
00037 #define REALLY_WANT_KMCOMPOSEWIN_H
00038 #include "kmcomposewin.h"
00039 #undef REALLY_WANT_KMCOMPOSEWIN_H
00040 #include "klistboxdialog.h"
00041 #include "kcursorsaver.h"
00042 #include "messagesender.h"
00043 #include "kmfolder.h"
00044 #include "kmfoldercombobox.h"
00045 #include "keyresolver.h"
00046 #include "kleo_util.h"
00047 #include "globalsettings.h"
00048 #include "custommimeheader.h"
00049 #include "kmedit.h"
00050 #include "util.h"
00051 
00052 #include <libkpimidentities/identity.h>
00053 #include <libkpimidentities/identitymanager.h>
00054 #include <libemailfunctions/email.h>
00055 
00056 #include <ui/keyselectiondialog.h>
00057 #include <ui/keyapprovaldialog.h>
00058 #include <kleo/cryptobackendfactory.h>
00059 #include <kleo/keylistjob.h>
00060 #include <kleo/encryptjob.h>
00061 #include <kleo/signencryptjob.h>
00062 #include <kleo/signjob.h>
00063 #include <kleo/specialjob.h>
00064 
00065 #include <kmime_util.h>
00066 #include <kmime_codecs.h>
00067 #include <kpgpblock.h>
00068 
00069 #include <mimelib/mimepp.h>
00070 
00071 #include <kmessagebox.h>
00072 #include <klocale.h>
00073 #include <kinputdialog.h>
00074 #include <kdebug.h>
00075 #include <kaction.h>
00076 #include <qfile.h>
00077 #include <qtextcodec.h>
00078 #include <qtextedit.h>
00079 #include <qtimer.h>
00080 
00081 #include <gpgmepp/key.h>
00082 #include <gpgmepp/keylistresult.h>
00083 #include <gpgmepp/encryptionresult.h>
00084 #include <gpgmepp/signingresult.h>
00085 #include <gpgmepp/context.h>
00086 
00087 #include <algorithm>
00088 #include <memory>
00089 
00090 // ## keep default values in sync with configuredialog.cpp, Security::CryptoTab::setup()
00091 // This should be ported to a .kcfg one day I suppose (dfaure).
00092 
00093 static inline bool warnSendUnsigned() {
00094     KConfigGroup group( KMKernel::config(), "Composer" );
00095     return group.readBoolEntry( "crypto-warning-unsigned", false );
00096 }
00097 static inline bool warnSendUnencrypted() {
00098     KConfigGroup group( KMKernel::config(), "Composer" );
00099     return group.readBoolEntry( "crypto-warning-unencrypted", false );
00100 }
00101 static inline bool saveMessagesEncrypted() {
00102     KConfigGroup group( KMKernel::config(), "Composer" );
00103     return group.readBoolEntry( "crypto-store-encrypted", true );
00104 }
00105 static inline bool encryptToSelf() {
00106     // return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf();
00107     KConfigGroup group( KMKernel::config(), "Composer" );
00108     return group.readBoolEntry( "crypto-encrypt-to-self", true );
00109 }
00110 static inline bool showKeyApprovalDialog() {
00111     KConfigGroup group( KMKernel::config(), "Composer" );
00112     return group.readBoolEntry( "crypto-show-keys-for-approval", true );
00113 }
00114 
00115 static inline int encryptKeyNearExpiryWarningThresholdInDays() {
00116   const KConfigGroup composer( KMKernel::config(), "Composer" );
00117   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00118     return -1;
00119   const int num = composer.readNumEntry( "crypto-warn-encr-key-near-expire-int", 14 );
00120   return kMax( 1, num );
00121 }
00122 
00123 static inline int signingKeyNearExpiryWarningThresholdInDays() {
00124   const KConfigGroup composer( KMKernel::config(), "Composer" );
00125   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00126     return -1;
00127   const int num = composer.readNumEntry( "crypto-warn-sign-key-near-expire-int", 14 );
00128   return kMax( 1, num );
00129 }
00130 
00131 static inline int encryptRootCertNearExpiryWarningThresholdInDays() {
00132   const KConfigGroup composer( KMKernel::config(), "Composer" );
00133   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00134     return -1;
00135   const int num = composer.readNumEntry( "crypto-warn-encr-root-near-expire-int", 14 );
00136   return kMax( 1, num );
00137 }
00138 
00139 static inline int signingRootCertNearExpiryWarningThresholdInDays() {
00140   const KConfigGroup composer( KMKernel::config(), "Composer" );
00141   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00142     return -1;
00143   const int num = composer.readNumEntry( "crypto-warn-sign-root-near-expire-int", 14 );
00144   return kMax( 1, num );
00145 }
00146 
00147 static inline int encryptChainCertNearExpiryWarningThresholdInDays() {
00148   const KConfigGroup composer( KMKernel::config(), "Composer" );
00149   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00150     return -1;
00151   const int num = composer.readNumEntry( "crypto-warn-encr-chaincert-near-expire-int", 14 );
00152   return kMax( 1, num );
00153 }
00154 
00155 static inline int signingChainCertNearExpiryWarningThresholdInDays() {
00156   const KConfigGroup composer( KMKernel::config(), "Composer" );
00157   if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
00158     return -1;
00159   const int num = composer.readNumEntry( "crypto-warn-sign-chaincert-near-expire-int", 14 );
00160   return kMax( 1, num );
00161 }
00162 
00163 /*
00164   Design of this:
00165 
00166   The idea is that the main run of applyChanges here makes two jobs:
00167   the first sets the flags for encryption/signing or not, and the other
00168   starts the encryption process.
00169 
00170   When a job is run, it has already been removed from the job queue. This
00171   means if one of the current jobs needs to add new jobs, it can add them
00172   to the front and that way control when new jobs are added.
00173 
00174   For example, the compose message job will add jobs that will do the
00175   actual encryption and signing.
00176 
00177   There are two types of jobs: synchronous and asynchronous:
00178 
00179   A synchronous job simply implments the execute() method and performs
00180   it's operation there and sets mComposer->mRc to false if the compose
00181   queue should be canceled.
00182 
00183   An asynchronous job only sets up and starts it's operation. Before
00184   returning, it connects to the result signals of the operation
00185   (e.g. Kleo::Job's result(...) signal) and sets mComposer->mHoldJobs
00186   to true. This makes the scheduler return to the event loop. The job
00187   is now responsible for giving control back to the scheduler by
00188   calling mComposer->doNextJob().
00189 */
00190 
00191 /*
00192  Test plan:
00193 
00194  For each message format (e.g. openPGP/MIME)
00195  1. Body signed
00196  2. Body encrypted
00197  3. Body signed and encrypted
00198  4. Body encrypted, attachments encrypted  (they must be encrypted together, mEarlyAddAttachments)
00199  5. Body encrypted, attachments not encrypted
00200  6. Body encrypted, attachment encrypted and signed (separately)
00201  7. Body not encrypted, one attachment encrypted+signed, one attachment encrypted only, one attachment signed only
00202        (https://intevation.de/roundup/aegypten/issue295)
00203        (this is the reason attachments can't be encrypted together)
00204  8. Body and attachments encrypted+signed (they must be encrypted+signed together, mEarlyAddAttachments)
00205  9. Body encrypted+signed, attachments encrypted
00206  10. Body encrypted+signed, one attachment signed, one attachment not encrypted nor signed
00207  ...
00208 
00209  I recorded a KDExecutor script sending all of the above (David)
00210 
00211  Further tests (which test opportunistic encryption):
00212  1. Send a message to a person with valid key but without encryption preference
00213     and answer the question whether the message should be encrypted with Yes.
00214  2. Send a message to a person with valid key but without encryption preference
00215     and answer the question whether the message should be encrypted with No.
00216  3. Send a message to a person with valid key and with encryption preference
00217     "Encrypt whenever possible" (aka opportunistic encryption).
00218 */
00219 
00220 static QString mErrorProcessingStructuringInfo =
00221 i18n("<qt><p>Structuring information returned by the Crypto plug-in "
00222      "could not be processed correctly; the plug-in might be damaged.</p>"
00223      "<p>Please contact your system administrator.</p></qt>");
00224 static QString mErrorNoCryptPlugAndNoBuildIn =
00225 i18n("<p>No active Crypto Plug-In was found and the built-in OpenPGP code "
00226      "did not run successfully.</p>"
00227      "<p>You can do two things to change this:</p>"
00228      "<ul><li><em>either</em> activate a Plug-In using the "
00229      "Settings->Configure KMail->Plug-In dialog.</li>"
00230      "<li><em>or</em> specify traditional OpenPGP settings on the same dialog's "
00231      "Identity->Advanced tab.</li></ul>");
00232 
00233 
00234 class MessageComposerJob {
00235 public:
00236   MessageComposerJob( MessageComposer* composer ) : mComposer( composer ) {}
00237   virtual ~MessageComposerJob() {}
00238 
00239   virtual void execute() = 0;
00240 
00241 protected:
00242   // These are the methods that call the private MessageComposer methods
00243   // Workaround for friend not being inherited
00244   void adjustCryptFlags() { mComposer->adjustCryptFlags(); }
00245   void composeMessage() { mComposer->composeMessage(); }
00246   void continueComposeMessage( KMMessage& msg, bool doSign, bool doEncrypt,
00247                                Kleo::CryptoMessageFormat format )
00248   {
00249     mComposer->continueComposeMessage( msg, doSign, doEncrypt, format );
00250   }
00251   void chiasmusEncryptAllAttachments() {
00252     mComposer->chiasmusEncryptAllAttachments();
00253   }
00254 
00255   MessageComposer* mComposer;
00256 };
00257 
00258 class ChiasmusBodyPartEncryptJob : public MessageComposerJob {
00259 public:
00260   ChiasmusBodyPartEncryptJob( MessageComposer * composer )
00261     : MessageComposerJob( composer ) {}
00262 
00263   void execute() {
00264     chiasmusEncryptAllAttachments();
00265   }
00266 };
00267 
00268 class AdjustCryptFlagsJob : public MessageComposerJob {
00269 public:
00270   AdjustCryptFlagsJob( MessageComposer* composer )
00271     : MessageComposerJob( composer ) {}
00272 
00273   void execute() {
00274     adjustCryptFlags();
00275   }
00276 };
00277 
00278 class ComposeMessageJob : public MessageComposerJob {
00279 public:
00280   ComposeMessageJob( MessageComposer* composer )
00281     : MessageComposerJob( composer ) {}
00282 
00283   void execute() {
00284     composeMessage();
00285   }
00286 };
00287 
00288 MessageComposer::MessageComposer( KMComposeWin* win, const char* name )
00289   : QObject( win, name ), mComposeWin( win ), mCurrentJob( 0 ),
00290     mKeyResolver( 0 ), mIdentityUid( 0 )
00291 {
00292 }
00293 
00294 MessageComposer::~MessageComposer()
00295 {
00296   delete mKeyResolver; mKeyResolver = 0;
00297 }
00298 
00299 void MessageComposer::applyChanges( bool disableCrypto )
00300 {
00301   // Do the initial setup
00302   if( getenv("KMAIL_DEBUG_COMPOSER_CRYPTO") != 0 ) {
00303     QCString cE = getenv("KMAIL_DEBUG_COMPOSER_CRYPTO");
00304     mDebugComposerCrypto = cE == "1" || cE.upper() == "ON" || cE.upper() == "TRUE";
00305     kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = TRUE" << endl;
00306   } else {
00307     mDebugComposerCrypto = false;
00308     kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = FALSE" << endl;
00309   }
00310 
00311   mHoldJobs = false;
00312   mRc = true;
00313 
00314   mDisableCrypto = disableCrypto;
00315 
00316   // 1: Read everything from KMComposeWin and set all
00317   //    trivial parts of the message
00318   readFromComposeWin();
00319 
00320   // From now on, we're not supposed to read from the composer win
00321   // TODO: Make it so ;-)
00322   // 1.5: Replace all body parts with their chiasmus-encrypted equivalent
00323   mJobs.push_back( new ChiasmusBodyPartEncryptJob( this ) );
00324 
00325   // 2: Set encryption/signing options and resolve keys
00326   mJobs.push_back( new AdjustCryptFlagsJob( this ) );
00327 
00328   // 3: Build the message (makes the crypto jobs also)
00329   mJobs.push_back( new ComposeMessageJob( this ) );
00330 
00331   // Finally: Run the jobs
00332   doNextJob();
00333 }
00334 
00335 void MessageComposer::doNextJob()
00336 {
00337   delete mCurrentJob; mCurrentJob = 0;
00338 
00339   if( mJobs.isEmpty() ) {
00340     // No more jobs. Signal that we're done
00341     emit done( mRc );
00342     return;
00343   }
00344 
00345   if( !mRc ) {
00346     // Something has gone wrong - stop the process and bail out
00347     while( !mJobs.isEmpty() ) {
00348       delete mJobs.front();
00349       mJobs.pop_front();
00350     }
00351     emit done( false );
00352     return;
00353   }
00354 
00355   // We have more jobs to do, but allow others to come first
00356   QTimer::singleShot( 0, this, SLOT( slotDoNextJob() ) );
00357 }
00358 
00359 void MessageComposer::slotDoNextJob()
00360 {
00361   assert( !mCurrentJob );
00362   if( mHoldJobs )
00363     // Always make it run from now. If more than one job should be held,
00364     // The individual jobs must do this.
00365     mHoldJobs = false;
00366   else {
00367     assert( !mJobs.empty() );
00368     // Get the next job
00369     mCurrentJob = mJobs.front();
00370     assert( mCurrentJob );
00371     mJobs.pop_front();
00372 
00373     // Execute it
00374     mCurrentJob->execute();
00375   }
00376 
00377   // Finally run the next job if necessary
00378   if( !mHoldJobs )
00379     doNextJob();
00380 }
00381 
00382 void MessageComposer::readFromComposeWin()
00383 {
00384   // Copy necessary attributes over
00385   mDisableBreaking = false;
00386 
00387   mSignBody = mComposeWin->mSignAction->isChecked();
00388   mSigningRequested = mSignBody; // for now; will be adjusted depending on attachments
00389   mEncryptBody = mComposeWin->mEncryptAction->isChecked();
00390   mEncryptionRequested = mEncryptBody; // for now; will be adjusted depending on attachments
00391 
00392   mAutoCharset = mComposeWin->mAutoCharset;
00393   mCharset = mComposeWin->mCharset;
00394   mReferenceMessage = mComposeWin->mMsg;
00395   // if the user made any modifications to the message then the Content-Type
00396   // of the message is no longer reliable (e. g. if he editted a draft/resent a
00397   // message and then removed all attachments or changed from PGP/MIME signed
00398   // to clearsigned); therefore we reset the Content-Type to text/plain.
00399   if ( mComposeWin->isModified() )
00400     mReferenceMessage->setHeaderField( "Content-Type", "text/plain" );
00401   mUseOpportunisticEncryption = GlobalSettings::self()->pgpAutoEncrypt();
00402   mAllowedCryptoMessageFormats = mComposeWin->cryptoMessageFormat();
00403 
00404   if( mAutoCharset ) {
00405     QCString charset = KMMsgBase::autoDetectCharset( mCharset, KMMessage::preferredCharsets(), mComposeWin->mEditor->text() );
00406     if( charset.isEmpty() )
00407     {
00408       KMessageBox::sorry( mComposeWin,
00409                           i18n( "No suitable encoding could be found for "
00410                                 "your message.\nPlease set an encoding "
00411                                 "using the 'Options' menu." ) );
00412       mRc = false;
00413       return;
00414     }
00415     mCharset = charset;
00416     // Also apply this to the composer window
00417     mComposeWin->mCharset = charset;
00418   }
00419   mReferenceMessage->setCharset(mCharset);
00420 
00421   mReferenceMessage->setTo(mComposeWin->to());
00422   mReferenceMessage->setFrom(mComposeWin->from());
00423   mReferenceMessage->setCc(mComposeWin->cc());
00424   mReferenceMessage->setSubject(mComposeWin->subject());
00425   mReferenceMessage->setReplyTo(mComposeWin->replyTo());
00426   mReferenceMessage->setBcc(mComposeWin->bcc());
00427 
00428   const KPIM::Identity & id = mComposeWin->identity();
00429 
00430   KMFolder *f = mComposeWin->mFcc->getFolder();
00431   assert( f != 0 );
00432   if ( f->idString() == id.fcc() )
00433     mReferenceMessage->removeHeaderField("X-KMail-Fcc");
00434   else
00435     mReferenceMessage->setFcc( f->idString() );
00436 
00437   // set the correct drafts folder
00438   mReferenceMessage->setDrafts( id.drafts() );
00439 
00440   if (id.isDefault())
00441     mReferenceMessage->removeHeaderField("X-KMail-Identity");
00442   else mReferenceMessage->setHeaderField("X-KMail-Identity", QString::number( id.uoid() ));
00443 
00444   QString replyAddr;
00445   if (!mComposeWin->replyTo().isEmpty()) replyAddr = mComposeWin->replyTo();
00446   else replyAddr = mComposeWin->from();
00447 
00448   if (mComposeWin->mRequestMDNAction->isChecked())
00449     mReferenceMessage->setHeaderField("Disposition-Notification-To", replyAddr);
00450   else
00451     mReferenceMessage->removeHeaderField("Disposition-Notification-To");
00452 
00453   if (mComposeWin->mUrgentAction->isChecked()) {
00454     mReferenceMessage->setHeaderField("X-PRIORITY", "2 (High)");
00455     mReferenceMessage->setHeaderField("Priority", "urgent");
00456   } else {
00457     mReferenceMessage->removeHeaderField("X-PRIORITY");
00458     mReferenceMessage->removeHeaderField("Priority");
00459   }
00460 
00461   int num = GlobalSettings::self()->custHeaderCount();
00462   for(int ix=0; ix<num; ix++) {
00463     CustomMimeHeader customMimeHeader( QString::number(ix) );
00464     customMimeHeader.readConfig();
00465     mReferenceMessage->setHeaderField(
00466         KMMsgBase::toUsAscii( customMimeHeader.custHeaderName() ),
00467         customMimeHeader.custHeaderValue() );
00468   }
00469 
00470 
00471   // we have to remember the Bcc because it might have been overwritten
00472   // by a custom header (therefore we can't use bcc() later) and because
00473   // mimelib removes addresses without domain part (therefore we can't use
00474   // mReferenceMessage->bcc() later and also not now. So get the Bcc from
00475   // the composer window.)
00476   mBcc = mComposeWin->bcc();
00477   mTo = KPIM::splitEmailAddrList( mComposeWin->to().stripWhiteSpace() );
00478   mCc = KPIM::splitEmailAddrList( mComposeWin->cc().stripWhiteSpace() );
00479   mBccList = KPIM::splitEmailAddrList( mBcc.stripWhiteSpace() );
00480 
00481   for ( unsigned int i = 0 ; i < mComposeWin->mAtmList.count() ; ++i )
00482     mAttachments.push_back( Attachment( mComposeWin->mAtmList.at(i),
00483                     mComposeWin->signFlagOfAttachment( i ),
00484                     mComposeWin->encryptFlagOfAttachment( i ) ) );
00485 
00486   mEncryptWithChiasmus = mComposeWin->mEncryptWithChiasmus;
00487 
00488   mIsRichText = mComposeWin->mEditor->textFormat() == Qt::RichText;
00489   mIdentityUid = mComposeWin->identityUid();
00490   mText = breakLinesAndApplyCodec();
00491   // Hopefully we can get rid of this eventually, it's needed to be able
00492   // to break the plain/text version of a multipart/alternative (html) mail
00493   // according to the line breaks of the richtext version.
00494   mLineBreakColumn = mComposeWin->mEditor->lineBreakColumn();
00495 }
00496 static QCString escape_quoted_string( const QCString & str ) {
00497   QCString result;
00498   const unsigned int str_len = str.length();
00499   result.resize( 2*str_len + 1 );
00500   char * d = result.data();
00501   for ( unsigned int i = 0 ; i < str_len ; ++i )
00502     switch ( const char ch = str[i] ) {
00503     case '\\':
00504     case '"':
00505       *d++ = '\\';
00506     default: // fall through:
00507       *d++ = ch;
00508     }
00509   result.truncate( d - result.begin() );
00510   return result;
00511 }
00512 
00513 bool MessageComposer::encryptWithChiasmus( const Kleo::CryptoBackend::Protocol * chiasmus,
00514                                            const QByteArray& body,
00515                                            QByteArray& resultData )
00516 {
00517   std::auto_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-encrypt", QMap<QString,QVariant>() ) );
00518   if ( !job.get() ) {
00519     const QString msg = i18n( "Chiasmus backend does not offer the "
00520                               "\"x-encrypt\" function. Please report this bug." );
00521     KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
00522     return false;
00523   }
00524   if ( !job->setProperty( "key", GlobalSettings::chiasmusKey() ) ||
00525        !job->setProperty( "options", GlobalSettings::chiasmusOptions() ) ||
00526        !job->setProperty( "input", body ) ) {
00527     const QString msg = i18n( "The \"x-encrypt\" function does not accept "
00528                               "the expected parameters. Please report this bug." );
00529     KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
00530     return false;
00531   }
00532   const GpgME::Error err = job->exec();
00533   if ( err.isCanceled() || err ) {
00534     if ( err )
00535       job->showErrorDialog( mComposeWin, i18n( "Chiasmus Encryption Error" ) );
00536     return false;
00537   }
00538   const QVariant result = job->property( "result" );
00539   if ( result.type() != QVariant::ByteArray ) {
00540     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
00541                               "The \"x-encrypt\" function did not return a "
00542                               "byte array. Please report this bug." );
00543     KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
00544     return false;
00545   }
00546   resultData = result.toByteArray();
00547   return true;
00548 }
00549 
00550 void MessageComposer::chiasmusEncryptAllAttachments() {
00551   if ( !mEncryptWithChiasmus )
00552     return;
00553   assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure
00554   if ( mAttachments.empty() )
00555     return;
00556   const Kleo::CryptoBackend::Protocol * chiasmus
00557     = Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
00558   assert( chiasmus ); // kmcomposewin code should have made sure
00559 
00560 
00561   for ( QValueVector<Attachment>::iterator it = mAttachments.begin(), end = mAttachments.end() ; it != end ; ++it ) {
00562     KMMessagePart * part = it->part;
00563     const QString filename = part->fileName();
00564     if ( filename.endsWith( ".xia", false ) )
00565       continue; // already encrypted
00566     const QByteArray body = part->bodyDecodedBinary();
00567     QByteArray resultData;
00568     if ( !encryptWithChiasmus( chiasmus, body, resultData ) ) {
00569       mRc = false;
00570       return;
00571     }
00572     // everything ok, so let's fill in the part again:
00573     QValueList<int> dummy;
00574     part->setBodyAndGuessCte( resultData, dummy );
00575     part->setTypeStr( "application" );
00576     part->setSubtypeStr( "vnd.de.bund.bsi.chiasmus" );
00577     part->setName( filename + ".xia" );
00578     // this is taken from kmmsgpartdlg.cpp:
00579     QCString encoding = KMMsgBase::autoDetectCharset( part->charset(), KMMessage::preferredCharsets(), filename );
00580     if ( encoding.isEmpty() )
00581       encoding = "utf-8";
00582     const QCString enc_name = KMMsgBase::encodeRFC2231String( filename + ".xia", encoding );
00583     const QCString cDisp = "attachment;\n\tfilename"
00584                            + ( QString( enc_name ) != filename + ".xia"
00585                                ? "*=" + enc_name
00586                                : "=\"" + escape_quoted_string( enc_name ) + '\"' );
00587     part->setContentDisposition( cDisp );
00588   }
00589 }
00590 
00591 void MessageComposer::adjustCryptFlags()
00592 {
00593   if ( !mDisableCrypto &&
00594        mAllowedCryptoMessageFormats & Kleo::InlineOpenPGPFormat &&
00595        !mAttachments.empty() &&
00596        ( mSigningRequested || mEncryptionRequested ) )
00597   {
00598     int ret;
00599     if ( mAllowedCryptoMessageFormats == Kleo::InlineOpenPGPFormat ) {
00600       ret = KMessageBox::warningYesNoCancel( mComposeWin,
00601                                              i18n("The inline OpenPGP crypto message format "
00602                                                   "does not support encryption or signing "
00603                                                   "of attachments.\n"
00604                                                   "Really use deprecated inline OpenPGP?"),
00605                                              i18n("Insecure Message Format"),
00606                                              i18n("Use Inline OpenPGP"),
00607                                              i18n("Use OpenPGP/MIME") );
00608     }
00609     else {
00610       // if other crypto message formats are allowed then simply don't use
00611       // inline OpenPGP
00612       ret = KMessageBox::No;
00613     }
00614 
00615     if ( ret == KMessageBox::Cancel ) {
00616       mRc = false;
00617       return;
00618     } else if ( ret == KMessageBox::No ) {
00619       mAllowedCryptoMessageFormats &= ~Kleo::InlineOpenPGPFormat;
00620       mAllowedCryptoMessageFormats |= Kleo::OpenPGPMIMEFormat;
00621       if ( mSigningRequested ) {
00622         // The composer window disabled signing on the attachments, re-enable it
00623         for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx )
00624           mAttachments[idx].sign = true;
00625       }
00626       if ( mEncryptionRequested ) {
00627         // The composer window disabled encrypting on the attachments, re-enable it
00628         // We assume this is what the user wants - after all he chose OpenPGP/MIME for this.
00629         for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx )
00630           mAttachments[idx].encrypt = true;
00631       }
00632     }
00633   }
00634 
00635   mKeyResolver =
00636     new Kleo::KeyResolver( encryptToSelf(), showKeyApprovalDialog(),
00637                mUseOpportunisticEncryption, mAllowedCryptoMessageFormats,
00638                encryptKeyNearExpiryWarningThresholdInDays(),
00639                signingKeyNearExpiryWarningThresholdInDays(),
00640                encryptRootCertNearExpiryWarningThresholdInDays(),
00641                signingRootCertNearExpiryWarningThresholdInDays(),
00642                encryptChainCertNearExpiryWarningThresholdInDays(),
00643                signingChainCertNearExpiryWarningThresholdInDays() );
00644 
00645   if ( !mDisableCrypto ) {
00646     const KPIM::Identity & id =
00647       kmkernel->identityManager()->identityForUoidOrDefault( mIdentityUid );
00648 
00649     QStringList encryptToSelfKeys;
00650     if ( !id.pgpEncryptionKey().isEmpty() )
00651       encryptToSelfKeys.push_back( id.pgpEncryptionKey() );
00652     if ( !id.smimeEncryptionKey().isEmpty() )
00653       encryptToSelfKeys.push_back( id.smimeEncryptionKey() );
00654     if ( mKeyResolver->setEncryptToSelfKeys( encryptToSelfKeys ) != Kpgp::Ok ) {
00655       mRc = false;
00656       return;
00657     }
00658 
00659     QStringList signKeys;
00660     if ( !id.pgpSigningKey().isEmpty() )
00661       signKeys.push_back( mPGPSigningKey = id.pgpSigningKey() );
00662     if ( !id.smimeSigningKey().isEmpty() )
00663       signKeys.push_back( mSMIMESigningKey = id.smimeSigningKey() );
00664     if ( mKeyResolver->setSigningKeys( signKeys ) != Kpgp::Ok ) {
00665       mRc = false;
00666       return;
00667     }
00668   }
00669 
00670   mKeyResolver->setPrimaryRecipients( mTo + mCc );
00671   mKeyResolver->setSecondaryRecipients( mBccList );
00672 
00673   // check settings of composer buttons *and* attachment check boxes
00674   bool doSignCompletely    = mSigningRequested;
00675   bool doEncryptCompletely = mEncryptionRequested;
00676   for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx ) {
00677     if ( mAttachments[idx].encrypt )
00678       mEncryptionRequested = true;
00679     else
00680       doEncryptCompletely = false;
00681     if ( mAttachments[idx].sign )
00682       mSigningRequested = true;
00683     else
00684       doSignCompletely = false;
00685   }
00686 
00687   mDoSign = !mDisableCrypto && determineWhetherToSign( doSignCompletely );
00688 
00689   if ( !mRc )
00690     return;
00691 
00692   mDoEncrypt = !mDisableCrypto && determineWhetherToEncrypt( doEncryptCompletely );
00693 
00694   if ( !mRc )
00695     return;
00696 
00697   // resolveAllKeys needs to run even if mDisableCrypto == true, since
00698   // we depend on it collecting all recipients into one dummy
00699   // SplitInfo to avoid special-casing all over the place:
00700   if ( mKeyResolver->resolveAllKeys( mDoSign, mDoEncrypt ) != Kpgp::Ok )
00701     mRc = false;
00702 }
00703 
00704 bool MessageComposer::determineWhetherToSign( bool doSignCompletely ) {
00705   bool sign = false;
00706   switch ( mKeyResolver->checkSigningPreferences( mSigningRequested ) ) {
00707   case Kleo::DoIt:
00708     if ( !mSigningRequested ) {
00709       markAllAttachmentsForSigning( true );
00710       return true;
00711     }
00712     sign = true;
00713     break;
00714   case Kleo::DontDoIt:
00715     sign = false;
00716     break;
00717   case Kleo::AskOpportunistic:
00718     assert( 0 );
00719   case Kleo::Ask:
00720     {
00721       // the user wants to be asked or has to be asked
00722       const KCursorSaver idle( KBusyPtr::idle() );
00723       const QString msg = i18n("Examination of the recipient's signing preferences "
00724                    "yielded that you be asked whether or not to sign "
00725                    "this message.\n"
00726                    "Sign this message?");
00727       switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg,
00728                          i18n("Sign Message?"),
00729                          i18n("to sign","&Sign"),
00730                          i18n("Do &Not Sign") ) ) {
00731       case KMessageBox::Cancel:
00732     mRc = false;
00733     return false;
00734       case KMessageBox::Yes:
00735     markAllAttachmentsForSigning( true );
00736     return true;
00737       case KMessageBox::No:
00738     markAllAttachmentsForSigning( false );
00739     return false;
00740       }
00741     }
00742     break;
00743   case Kleo::Conflict:
00744     {
00745       // warn the user that there are conflicting signing preferences
00746       const KCursorSaver idle( KBusyPtr::idle() );
00747       const QString msg = i18n("There are conflicting signing preferences "
00748                    "for these recipients.\n"
00749                    "Sign this message?");
00750       switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
00751                         i18n("Sign Message?"),
00752                         i18n("to sign","&Sign"),
00753                         i18n("Do &Not Sign") ) ) {
00754       case KMessageBox::Cancel:
00755     mRc = false;
00756     return false;
00757       case KMessageBox::Yes:
00758     markAllAttachmentsForSigning( true );
00759     return true;
00760       case KMessageBox::No:
00761     markAllAttachmentsForSigning( false );
00762     return false;
00763       }
00764     }
00765     break;
00766   case Kleo::Impossible:
00767     {
00768       const KCursorSaver idle( KBusyPtr::idle() );
00769       const QString msg = i18n("You have requested to sign this message, "
00770                    "but no valid signing keys have been configured "
00771                    "for this identity.");
00772       if ( KMessageBox::warningContinueCancel( mComposeWin, msg,
00773                            i18n("Send Unsigned?"),
00774                                                i18n("Send &Unsigned") )
00775        == KMessageBox::Cancel ) {
00776     mRc = false;
00777     return false;
00778       } else {
00779     markAllAttachmentsForSigning( false );
00780     return false;
00781       }
00782     }
00783   }
00784 
00785   if ( !sign || !doSignCompletely ) {
00786     if ( warnSendUnsigned() ) {
00787       const KCursorSaver idle( KBusyPtr::idle() );
00788       const QString msg = sign && !doSignCompletely
00789     ? i18n("Some parts of this message will not be signed.\n"
00790            "Sending only partially signed messages might violate site policy.\n"
00791            "Sign all parts instead?") // oh, I hate this...
00792     : i18n("This message will not be signed.\n"
00793            "Sending unsigned message might violate site policy.\n"
00794            "Sign message instead?") ; // oh, I hate this...
00795       const QString buttonText = sign && !doSignCompletely
00796     ? i18n("&Sign All Parts") : i18n("&Sign") ;
00797       switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
00798                         i18n("Unsigned-Message Warning"),
00799                         buttonText,
00800                         i18n("Send &As Is") ) ) {
00801       case KMessageBox::Cancel:
00802     mRc = false;
00803     return false;
00804       case KMessageBox::Yes:
00805     markAllAttachmentsForSigning( true );
00806     return true;
00807       case KMessageBox::No:
00808     return sign || doSignCompletely;
00809       }
00810     }
00811   }
00812 
00813   return sign || doSignCompletely ;
00814 }
00815 
00816 bool MessageComposer::determineWhetherToEncrypt( bool doEncryptCompletely ) {
00817   bool encrypt = false;
00818   bool opportunistic = false;
00819   switch ( mKeyResolver->checkEncryptionPreferences( mEncryptionRequested ) ) {
00820   case Kleo::DoIt:
00821     if ( !mEncryptionRequested ) {
00822       markAllAttachmentsForEncryption( true );
00823       return true;
00824     }
00825     encrypt = true;
00826     break;
00827   case Kleo::DontDoIt:
00828     encrypt = false;
00829     break;
00830   case Kleo::AskOpportunistic:
00831     opportunistic = true;
00832     // fall through...
00833   case Kleo::Ask:
00834     {
00835       // the user wants to be asked or has to be asked
00836       const KCursorSaver idle( KBusyPtr::idle() );
00837       const QString msg = opportunistic
00838     ? i18n("Valid trusted encryption keys were found for all recipients.\n"
00839            "Encrypt this message?")
00840     : i18n("Examination of the recipient's encryption preferences "
00841            "yielded that you be asked whether or not to encrypt "
00842            "this message.\n"
00843            "Encrypt this message?");
00844       switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg,
00845                          i18n("Encrypt Message?"),
00846                          mDoSign
00847                          ? i18n("Sign && &Encrypt")
00848                          : i18n("&Encrypt"),
00849                          mDoSign
00850                          ? i18n("&Sign Only")
00851                          : i18n("&Send As-Is") ) ) {
00852       case KMessageBox::Cancel:
00853     mRc = false;
00854     return false;
00855       case KMessageBox::Yes:
00856     markAllAttachmentsForEncryption( true );
00857     return true;
00858       case KMessageBox::No:
00859     markAllAttachmentsForEncryption( false );
00860     return false;
00861       }
00862     }
00863     break;
00864   case Kleo::Conflict:
00865     {
00866       // warn the user that there are conflicting encryption preferences
00867       const KCursorSaver idle( KBusyPtr::idle() );
00868       const QString msg = i18n("There are conflicting encryption preferences "
00869                    "for these recipients.\n"
00870                    "Encrypt this message?");
00871       switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
00872                         i18n("Encrypt Message?"),
00873                         i18n("&Encrypt"),
00874                         i18n("Do &Not Encrypt") ) ) {
00875       case KMessageBox::Cancel:
00876     mRc = false;
00877     return false;
00878       case KMessageBox::Yes:
00879     markAllAttachmentsForEncryption( true );
00880     return true;
00881       case KMessageBox::No:
00882     markAllAttachmentsForEncryption( false );
00883     return false;
00884       }
00885     }
00886     break;
00887   case Kleo::Impossible:
00888     {
00889       const KCursorSaver idle( KBusyPtr::idle() );
00890       const QString msg = i18n("You have requested to encrypt this message, "
00891                    "and to encrypt a copy to yourself, "
00892                    "but no valid trusted encryption keys have been "
00893                    "configured for this identity.");
00894       if ( KMessageBox::warningContinueCancel( mComposeWin, msg,
00895                            i18n("Send Unencrypted?"),
00896                                                i18n("Send &Unencrypted") )
00897        == KMessageBox::Cancel ) {
00898     mRc = false;
00899     return false;
00900       } else {
00901     markAllAttachmentsForEncryption( false );
00902     return false;
00903       }
00904     }
00905   }
00906 
00907   if ( !encrypt || !doEncryptCompletely ) {
00908     if ( warnSendUnencrypted() ) {
00909       const KCursorSaver idle( KBusyPtr::idle() );
00910       const QString msg = !doEncryptCompletely
00911     ? i18n("Some parts of this message will not be encrypted.\n"
00912            "Sending only partially encrypted messages might violate site policy "
00913            "and/or leak sensitive information.\n"
00914            "Encrypt all parts instead?") // oh, I hate this...
00915     : i18n("This message will not be encrypted.\n"
00916            "Sending unencrypted messages might violate site policy and/or "
00917            "leak sensitive information.\n"
00918            "Encrypt messages instead?") ; // oh, I hate this...
00919       const QString buttonText = !doEncryptCompletely
00920     ? i18n("&Encrypt All Parts") : i18n("&Encrypt") ;
00921       switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
00922                         i18n("Unencrypted Message Warning"),
00923                         buttonText,
00924                         mDoSign
00925                         ? i18n("&Sign Only")
00926                         : i18n("&Send As-Is") ) ) {
00927       case KMessageBox::Cancel:
00928     mRc = false;
00929     return false;
00930       case KMessageBox::Yes:
00931     markAllAttachmentsForEncryption( true );
00932     return true;
00933       case KMessageBox::No:
00934     return encrypt || doEncryptCompletely;
00935       }
00936     }
00937   }
00938 
00939   return encrypt || doEncryptCompletely ;
00940 }
00941 
00942 void MessageComposer::markAllAttachmentsForSigning( bool sign ) {
00943   mSignBody = sign;
00944   for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it )
00945     it->sign = sign;
00946 }
00947 
00948 void MessageComposer::markAllAttachmentsForEncryption( bool enc ) {
00949   mEncryptBody = enc;
00950   for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it )
00951     it->encrypt = enc;
00952 }
00953 
00954 
00955 void MessageComposer::composeMessage()
00956 {
00957   for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
00958     if ( mKeyResolver->encryptionItems( concreteCryptoMessageFormats[i] ).empty() )
00959       continue;
00960     KMMessage * msg = new KMMessage( *mReferenceMessage );
00961     composeMessage( *msg, mDoSign, mDoEncrypt, concreteCryptoMessageFormats[i] );
00962     if ( !mRc )
00963       return;
00964   }
00965 }
00966 
00967 //
00968 // These are replacements for StructuringInfo(Wrapper):
00969 //
00970 
00971 // check whether to use multipart/{signed,encrypted}
00972 static inline bool makeMultiMime( Kleo::CryptoMessageFormat f, bool sign ) {
00973   switch ( f ) {
00974   default:
00975   case Kleo::InlineOpenPGPFormat:
00976   case Kleo::SMIMEOpaqueFormat:   return false;
00977   case Kleo::OpenPGPMIMEFormat:   return true;
00978   case Kleo::SMIMEFormat:         return sign; // only on sign - there's no mp/encrypted for S/MIME
00979   }
00980 }
00981 static inline bool makeMultiPartSigned( Kleo::CryptoMessageFormat f ) {
00982   return makeMultiMime( f, true );
00983 }
00984 static inline bool makeMultiPartEncrypted( Kleo::CryptoMessageFormat f ) {
00985   return makeMultiMime( f, false );
00986 }
00987 
00988 static inline bool makeMimeObject( Kleo::CryptoMessageFormat f, bool /*signing*/ ) {
00989   return f != Kleo::InlineOpenPGPFormat;
00990 }
00991 
00992 static inline const char * toplevelContentType( Kleo::CryptoMessageFormat f, bool signing ) {
00993   switch ( f ) {
00994   default:
00995   case Kleo::InlineOpenPGPFormat: return 0;
00996   case Kleo::OpenPGPMIMEFormat:
00997     return signing ?
00998       "multipart/signed;\n\t"
00999       "boundary=\"%boundary\";\n\t"
01000       "protocol=\"application/pgp-signature\";\n\t"
01001       "micalg=pgp-sha1" // FIXME: obtain this parameter from gpgme!
01002       :
01003       "multipart/encrypted;\n\t"
01004       "boundary=\"%boundary\";\n\t"
01005       "protocol=\"application/pgp-encrypted\""
01006       ;
01007   case Kleo::SMIMEFormat:
01008     if ( signing )
01009       return
01010     "multipart/signed;\n\t"
01011     "boundary=\"%boundary\";\n\t"
01012     "protocol=\"application/pkcs7-signature\";\n\t"
01013     "micalg=sha1"; // FIXME: obtain this parameter from gpgme!
01014     // fall through (for encryption, there's no difference between
01015     // SMIME and SMIMEOpaque, since there is no mp/encrypted for
01016     // S/MIME):
01017   case Kleo::SMIMEOpaqueFormat:
01018     return signing ?
01019       "application/pkcs7-mime;\n\t"
01020       "smime-type=signed-data;\n\t"
01021       "name=\"smime.p7m\";\n\t"
01022       :
01023       "application/pkcs7-mime;\n\t"
01024       "smime-type=enveloped-data;\n\t"
01025       "name=\"smime.p7m\";\n\t"
01026       ;
01027   }
01028 }
01029 
01030 static inline const char * toplevelContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) {
01031   switch ( f ) {
01032   default:
01033   case Kleo::InlineOpenPGPFormat:
01034   case Kleo::OpenPGPMIMEFormat:
01035     return 0;
01036   case Kleo::SMIMEFormat:
01037     if ( signing )
01038       return 0;
01039   case Kleo::SMIMEOpaqueFormat:
01040     return "attachment; filename=\"smime.p7m\"";
01041   }
01042 }
01043 
01044 static inline bool includeCleartextWhenSigning( Kleo::CryptoMessageFormat f ) {
01045   return makeMultiPartSigned( f );
01046 }
01047 
01048 static inline const char * nestedContentType( Kleo::CryptoMessageFormat f, bool signing ) {
01049   switch ( f ) {
01050   case Kleo::OpenPGPMIMEFormat:
01051     return signing ? "application/pgp-signature" : "application/octet-stream" ;
01052   case Kleo::SMIMEFormat:
01053     if ( signing )
01054       return "application/pkcs7-signature; name=\"smime.p7s\"";
01055     // fall through:
01056   default:
01057   case Kleo::InlineOpenPGPFormat:
01058   case Kleo::SMIMEOpaqueFormat:
01059     return 0;
01060   }
01061 }
01062 
01063 static inline const char * nestedContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) {
01064   if ( !signing && f == Kleo::OpenPGPMIMEFormat )
01065     return "inline; filename=\"msg.asc\"";
01066   if ( signing && f == Kleo::SMIMEFormat )
01067     return "attachment; filename=\"smime.p7s\"";
01068   return 0;
01069 }
01070 
01071 static inline bool binaryHint( Kleo::CryptoMessageFormat f ) {
01072   switch ( f ) {
01073   case Kleo::SMIMEFormat:
01074   case Kleo::SMIMEOpaqueFormat:
01075     return true;
01076   default:
01077   case Kleo::OpenPGPMIMEFormat:
01078   case Kleo::InlineOpenPGPFormat:
01079     return false;
01080   }
01081 }
01082 
01083 static inline bool armor( Kleo::CryptoMessageFormat f ) {
01084   return !binaryHint( f );
01085 }
01086 
01087 static inline bool textMode( Kleo::CryptoMessageFormat f ) {
01088   return f == Kleo::InlineOpenPGPFormat;
01089 }
01090 
01091 static inline GpgME::Context::SignatureMode signingMode( Kleo::CryptoMessageFormat f ) {
01092   switch ( f ) {
01093   case Kleo::SMIMEOpaqueFormat:
01094     return GpgME::Context::Normal;
01095   case Kleo::InlineOpenPGPFormat:
01096     return GpgME::Context::Clearsigned;
01097   default:
01098   case Kleo::SMIMEFormat:
01099   case Kleo::OpenPGPMIMEFormat:
01100     return GpgME::Context::Detached;
01101   }
01102 }
01103 
01104 //
01105 // END replacements for StructuringInfo(Wrapper)
01106 //
01107 
01108 class EncryptMessageJob : public MessageComposerJob {
01109 public:
01110   EncryptMessageJob( KMMessage* msg, const Kleo::KeyResolver::SplitInfo & si,
01111                      bool doSign, bool doEncrypt, const QCString& encodedBody,
01112                      int boundaryLevel, const KMMessagePart& oldBodyPart,
01113                      KMMessagePart* newBodyPart, Kleo::CryptoMessageFormat format,
01114              MessageComposer* composer )
01115     : MessageComposerJob( composer ), mMsg( msg ), mSplitInfo( si ),
01116       mDoSign( doSign ), mDoEncrypt( doEncrypt ), mEncodedBody( encodedBody ),
01117       mBoundaryLevel( boundaryLevel ), mOldBodyPart( oldBodyPart ),
01118       mNewBodyPart( newBodyPart ), mFormat( format ) {}
01119 
01120   void execute() {
01121     KMMessagePart tmpNewBodyPart;
01122     tmpNewBodyPart.duplicate( *mNewBodyPart );
01123 
01124     // TODO: Async call
01125 
01126     mComposer->encryptMessage( mMsg, mSplitInfo, mDoSign, mDoEncrypt,
01127                                tmpNewBodyPart, mFormat );
01128     if ( !mComposer->mRc ) {
01129       delete mMsg; mMsg = 0;
01130       return;
01131     }
01132     mComposer->mMessageList.push_back( mMsg );
01133   }
01134 
01135 private:
01136   KMMessage* mMsg;
01137   Kleo::KeyResolver::SplitInfo mSplitInfo;
01138   bool mDoSign, mDoEncrypt;
01139   QCString mEncodedBody;
01140   int mBoundaryLevel;
01141   KMMessagePart mOldBodyPart;
01142   KMMessagePart* mNewBodyPart;
01143   Kleo::CryptoMessageFormat mFormat;
01144 };
01145 
01146 class SetLastMessageAsUnencryptedVersionOfLastButOne : public MessageComposerJob {
01147 public:
01148   SetLastMessageAsUnencryptedVersionOfLastButOne( MessageComposer * composer )
01149     : MessageComposerJob( composer ) {}
01150 
01151   void execute() {
01152     KMMessage * last = mComposer->mMessageList.back();
01153     mComposer->mMessageList.pop_back();
01154     mComposer->mMessageList.back()->setUnencryptedMsg( last );
01155   }
01156 };
01157 
01158 QCString MessageComposer::bodyText()
01159 {
01160   QCString body = mText;
01161 
01162   if (body.isNull()) return body;
01163 
01164   if (body.isEmpty()) body = "\n"; // don't crash
01165 
01166   // From RFC 3156:
01167   //  Note: The accepted OpenPGP convention is for signed data to end
01168   //  with a <CR><LF> sequence.  Note that the <CR><LF> sequence
01169   //  immediately preceding a MIME boundary delimiter line is considered
01170   //  to be part of the delimiter in [3], 5.1.  Thus, it is not part of
01171   //  the signed data preceding the delimiter line.  An implementation
01172   //  which elects to adhere to the OpenPGP convention has to make sure
01173   //  it inserts a <CR><LF> pair on the last line of the data to be
01174   //  signed and transmitted (signed message and transmitted message
01175   //  MUST be identical).
01176   // So make sure that the body ends with a <LF>.
01177   if( body[body.length()-1] != '\n' ) {
01178     kdDebug(5006) << "Added an <LF> on the last line" << endl;
01179     body += "\n";
01180   }
01181   return body;
01182 }
01183 
01184 void MessageComposer::composeInlineOpenPGPMessage( KMMessage& theMessage,
01185                                                    bool doSign, bool doEncrypt )
01186 {
01187   // preprocess the body text
01188   QCString body = bodyText();
01189   if (body.isNull()) {
01190     mRc = false;
01191     return;
01192   }
01193 
01194   mNewBodyPart = 0; // unused
01195   mEarlyAddAttachments = false;
01196   mAllAttachmentsAreInBody = false;
01197 
01198   // set the main headers
01199   theMessage.deleteBodyParts();
01200   QString oldContentType = theMessage.headerField( "Content-Type" );
01201   theMessage.removeHeaderField("Content-Type");
01202   theMessage.removeHeaderField("Content-Transfer-Encoding");
01203 
01204   const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
01205     = mKeyResolver->encryptionItems( Kleo::InlineOpenPGPFormat );
01206   kdWarning( splitInfos.empty() )
01207     << "MessageComposer::continueComposeMessage(): splitInfos.empty() for InlineOpenPGPFormat"
01208     << endl;
01209   std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it;
01210   for ( it = splitInfos.begin() ; it != splitInfos.end() ; ++it ) {
01211     const Kleo::KeyResolver::SplitInfo& splitInfo = *it;
01212     KMMessage* msg = new KMMessage( theMessage );
01213     if ( doEncrypt ) {
01214       Kpgp::Result result;
01215       QByteArray encryptedBody;
01216       if ( doSign ) {  // Sign and encrypt
01217         const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( Kleo::InlineOpenPGPFormat );
01218         result = pgpSignedAndEncryptedMsg( encryptedBody, body, signingKeys,
01219                                            splitInfo.keys, Kleo::InlineOpenPGPFormat );
01220       } else { // Encrypt but don't sign
01221         result = pgpEncryptedMsg( encryptedBody, body,
01222                                   splitInfo.keys, Kleo::InlineOpenPGPFormat );
01223       }
01224       if ( result != Kpgp::Ok ) {
01225         mRc = false;
01226         return;
01227       }
01228       assert( !encryptedBody.isNull() ); // if you hit this, check gpg-agent is running, then blame gpgme.
01229       mOldBodyPart.setBodyEncodedBinary( encryptedBody );
01230     } else {
01231       if ( doSign ) { // Sign but don't encrypt
01232         pgpSignedMsg( body, Kleo::InlineOpenPGPFormat );
01233         if ( mSignature.isNull() ) {
01234           mRc = false;
01235           return;
01236         }
01237         mOldBodyPart.setBodyEncodedBinary( mSignature );
01238       } else { // don't sign nor encrypt -> nothing to do
01239         assert( !body.isNull() );
01240         mOldBodyPart.setBodyEncoded( body );
01241       }
01242     }
01243     mOldBodyPart.setContentDisposition( "inline" );
01244     mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
01245     mOldBodyPart.setCharset(mCharset);
01246     addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
01247     mMessageList.push_back( msg );
01248     if ( it == splitInfos.begin() ) {
01249       if ( doEncrypt && !saveMessagesEncrypted() ) {
01250         mOldBodyPart.setBodyEncoded( body );
01251         KMMessage* msgUnenc = new KMMessage( theMessage );
01252         addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
01253         msg->setUnencryptedMsg( msgUnenc );
01254       }
01255     }
01256   } // end for
01257 }
01258 
01259 // very much inspired by composeInlineOpenPGPMessage
01260 void MessageComposer::composeChiasmusMessage( KMMessage& theMessage, Kleo::CryptoMessageFormat format )
01261 {
01262   assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure
01263   const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
01264   assert( cpf );
01265   const Kleo::CryptoBackend::Protocol * chiasmus
01266     = cpf->protocol( "Chiasmus" );
01267   assert( chiasmus ); // kmcomposewin code should have made sure
01268 
01269   // preprocess the body text
01270   QCString body = bodyText();
01271   if (body.isNull()) {
01272     mRc = false;
01273     return;
01274   }
01275 
01276   mNewBodyPart = 0; // unused
01277   mEarlyAddAttachments = false;
01278   mAllAttachmentsAreInBody = false;
01279 
01280   // set the main headers
01281   theMessage.deleteBodyParts();
01282   QString oldContentType = theMessage.headerField( "Content-Type" );
01283   theMessage.removeHeaderField("Content-Type");
01284   theMessage.removeHeaderField("Content-Transfer-Encoding");
01285 
01286   QByteArray plainText;
01287   plainText.duplicate( body.data(), body.length() ); // hrmpf...
01288 
01289   // This reads strange, but we know that AdjustCryptFlagsJob created a single splitinfo,
01290   // under the given "format" (usually openpgp/mime; doesn't matter)
01291   const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
01292     = mKeyResolver->encryptionItems( format );
01293   assert( splitInfos.size() == 1 );
01294   for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it )
01295   {
01296     const Kleo::KeyResolver::SplitInfo& splitInfo = *it;
01297     KMMessage* msg = new KMMessage( theMessage );
01298     QByteArray encryptedBody;
01299 
01300     if ( !encryptWithChiasmus( chiasmus, plainText, encryptedBody ) ) {
01301       mRc = false;
01302       return;
01303     }
01304     assert( !encryptedBody.isNull() );
01305     // This leaves CTE==7-bit, no good
01306     //mOldBodyPart.setBodyEncodedBinary( encryptedBody );
01307 
01308     bool doSign = false;
01309     QValueList<int> allowedCTEs;
01310     mOldBodyPart.setBodyAndGuessCte( encryptedBody, allowedCTEs,
01311                                      !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
01312                      doSign );
01313 
01314 
01315     mOldBodyPart.setContentDisposition( "inline" );
01316     // Used in case of no attachments
01317     mOldBodyPart.setOriginalContentTypeStr( "application/vnd.de.bund.bsi.chiasmus-text;chiasmus-charset=" + mCharset );
01318     // Used in case of attachments
01319     mOldBodyPart.setTypeStr( "application" );
01320     mOldBodyPart.setSubtypeStr( "vnd.de.bund.bsi.chiasmus-text" );
01321     mOldBodyPart.setAdditionalCTypeParamStr( QCString( "chiasmus-charset=" + mCharset ) );
01322     addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
01323     mMessageList.push_back( msg );
01324 
01325     if ( it == splitInfos.begin() && !saveMessagesEncrypted() ) {
01326       mOldBodyPart.setBodyEncoded( body );
01327       KMMessage* msgUnenc = new KMMessage( theMessage );
01328       addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
01329       msg->setUnencryptedMsg( msgUnenc );
01330     }
01331   }
01332 }
01333 
01334 void MessageComposer::composeMessage( KMMessage& theMessage,
01335                                       bool doSign, bool doEncrypt,
01336                                       Kleo::CryptoMessageFormat format )
01337 {
01338 #ifdef DEBUG
01339   kdDebug(5006) << "entering KMComposeWin::composeMessage" << endl;
01340 #endif
01341   if ( format == Kleo::InlineOpenPGPFormat ) {
01342     composeInlineOpenPGPMessage( theMessage, doSign, doEncrypt );
01343     return;
01344   }
01345 
01346   if ( mEncryptWithChiasmus )
01347   {
01348     composeChiasmusMessage( theMessage, format );
01349     return;
01350   }
01351 
01352   // create informative header for those that have no mime-capable
01353   // email client
01354   theMessage.setBody( "This message is in MIME format." );
01355 
01356   // preprocess the body text
01357   QCString body = bodyText();
01358   if (body.isNull()) {
01359     mRc = false;
01360     return;
01361   }
01362 
01363   // set the main headers
01364   QString oldContentType = theMessage.headerField( "Content-Type" );
01365   theMessage.deleteBodyParts();
01366   theMessage.removeHeaderField("Content-Type");
01367   theMessage.removeHeaderField("Content-Transfer-Encoding");
01368   theMessage.setAutomaticFields(TRUE); // == multipart/mixed
01369 
01370   // this is our *final* body part
01371   mNewBodyPart = new KMMessagePart;
01372 
01373   // this is the boundary depth of the surrounding MIME part
01374   mPreviousBoundaryLevel = 0;
01375 
01376   // whether the body must be signed/encrypted
01377   const bool doEncryptBody = doEncrypt && mEncryptBody;
01378   const bool doSignBody = doSign && mSignBody;
01379 
01380   // create temporary bodyPart for editor text
01381   // (and for all attachments, if mail is to be signed and/or encrypted)
01382   mEarlyAddAttachments = !mAttachments.empty() && ( doSignBody || doEncryptBody );
01383 
01384   mAllAttachmentsAreInBody = mEarlyAddAttachments;
01385 
01386   // test whether there ARE attachments that can be included into the body
01387   if( mEarlyAddAttachments ) {
01388     bool someOk = false;
01389     for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
01390       if ( it->encrypt == doEncryptBody && it->sign == doSignBody )
01391         someOk = true;
01392       else
01393         mAllAttachmentsAreInBody = false;
01394     }
01395     if( !mAllAttachmentsAreInBody && !someOk )
01396       mEarlyAddAttachments = false;
01397   }
01398 
01399   kdDebug(5006) << "mEarlyAddAttachments=" << mEarlyAddAttachments << " mAllAttachmentsAreInBody=" << mAllAttachmentsAreInBody << endl;
01400 
01401   // if an html message is to be generated, make a text/plain and text/html part
01402   if ( mIsRichText ) {
01403     mOldBodyPart.setTypeStr(   "multipart");
01404     mOldBodyPart.setSubtypeStr(mEarlyAddAttachments ? "mixed"     : "alternative");
01405   }
01406   else if( mEarlyAddAttachments ) {
01407     mOldBodyPart.setTypeStr( "multipart" );
01408     mOldBodyPart.setSubtypeStr( "mixed" );
01409   } else
01410     mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
01411 
01412   mOldBodyPart.setContentDisposition( "inline" );
01413 
01414   QCString boundaryCStr;
01415   if ( mIsRichText ) { // create a multipart body
01416     // calculate a boundary string
01417     QCString boundaryCStr;  // storing boundary string data
01418     QCString newbody;
01419     DwMediaType tmpCT;
01420     tmpCT.CreateBoundary( mPreviousBoundaryLevel++ ); // was 0
01421     boundaryCStr = tmpCT.Boundary().c_str();
01422     QValueList<int> allowedCTEs;
01423 
01424     KMMessagePart textBodyPart;
01425     textBodyPart.setTypeStr("text");
01426     textBodyPart.setSubtypeStr("plain");
01427 
01428     QCString textbody = plainTextFromMarkup( mText );
01429 
01430     // the signed body must not be 8bit encoded
01431     textBodyPart.setBodyAndGuessCte( textbody, allowedCTEs,
01432                                      !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
01433                      doSign );
01434     textBodyPart.setCharset( mCharset );
01435     textBodyPart.setBodyEncoded( textbody );
01436     DwBodyPart* textDwPart = theMessage.createDWBodyPart( &textBodyPart );
01437     textDwPart->Assemble();
01438     newbody += "--";
01439     newbody +=     boundaryCStr;
01440     newbody +=                 "\n";
01441     newbody += textDwPart->AsString().c_str();
01442     delete textDwPart;
01443     textDwPart = 0;
01444 
01445     KMMessagePart htmlBodyPart;
01446     htmlBodyPart.setTypeStr("text");
01447     htmlBodyPart.setSubtypeStr("html");
01448     QCString htmlbody = mText;
01449     // the signed body must not be 8bit encoded
01450     htmlBodyPart.setBodyAndGuessCte( htmlbody, allowedCTEs,
01451                                      !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
01452                      doSign );
01453     htmlBodyPart.setCharset( mCharset );
01454     htmlBodyPart.setBodyEncoded( htmlbody );
01455     DwBodyPart* htmlDwPart = theMessage.createDWBodyPart( &htmlBodyPart );
01456     htmlDwPart->Assemble();
01457     newbody += "\n--";
01458     newbody +=     boundaryCStr;
01459     newbody +=                 "\n";
01460     newbody += htmlDwPart->AsString().c_str();
01461     delete htmlDwPart;
01462     htmlDwPart = 0;
01463 
01464     newbody += "--";
01465     newbody +=     boundaryCStr;
01466     newbody +=                 "--\n";
01467     body = newbody;
01468     mOldBodyPart.setBodyEncoded( newbody );
01469 
01470     mSaveBoundary = tmpCT.Boundary();
01471   }
01472 
01473   // Prepare attachments that will be signed/encrypted
01474   for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
01475     // signed/encrypted body parts must be either QP or base64 encoded
01476     // Why not 7 bit? Because the LF->CRLF canonicalization would render
01477     // e.g. 7 bit encoded shell scripts unusable because of the CRs.
01478     //
01479     // (marc) this is a workaround for the KMail bug that doesn't
01480     // respect the CRLF->LF de-canonicalisation. We should
01481     // eventually get rid of this:
01482     if( it->sign || it->encrypt ) {
01483       QCString cte = it->part->cteStr().lower();
01484       if( ( "8bit" == cte )
01485           || ( ( it->part->type() == DwMime::kTypeText )
01486                && ( "7bit" == cte ) ) ) {
01487         const QByteArray body = it->part->bodyDecodedBinary();
01488         QValueList<int> dummy;
01489         it->part->setBodyAndGuessCte(body, dummy, false, it->sign);
01490         kdDebug(5006) << "Changed encoding of message part from "
01491                       << cte << " to " << it->part->cteStr() << endl;
01492       }
01493     }
01494   }
01495 
01496   if( mEarlyAddAttachments ) {
01497     // calculate a boundary string
01498     ++mPreviousBoundaryLevel;
01499     DwMediaType tmpCT;
01500     tmpCT.CreateBoundary( mPreviousBoundaryLevel );
01501     boundaryCStr = tmpCT.Boundary().c_str();
01502     // add the normal body text
01503     KMMessagePart innerBodyPart;
01504     if ( mIsRichText ) {
01505       innerBodyPart.setTypeStr(   "multipart");//text" );
01506       innerBodyPart.setSubtypeStr("alternative");//html");
01507     }
01508     else {
01509       innerBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
01510     }
01511     innerBodyPart.setContentDisposition( "inline" );
01512     QValueList<int> allowedCTEs;
01513     // the signed body must not be 8bit encoded
01514     innerBodyPart.setBodyAndGuessCte( body, allowedCTEs,
01515                                       !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
01516                                       doSign );
01517     if ( !mIsRichText )
01518       innerBodyPart.setCharset( mCharset );
01519     innerBodyPart.setBodyEncoded( body ); // do we need this, since setBodyAndGuessCte does this already?
01520     DwBodyPart* innerDwPart = theMessage.createDWBodyPart( &innerBodyPart );
01521     innerDwPart->Assemble();
01522     if ( mIsRichText ) { // and add our mp/a boundary
01523         QCString tmpbody = innerDwPart->AsString().c_str();
01524         int boundPos = tmpbody.find( '\n' );
01525         if( -1 < boundPos ) {
01526           QCString bStr( ";\n  boundary=\"" );
01527           bStr += mSaveBoundary.c_str();
01528           bStr += "\"";
01529           body = innerDwPart->AsString().c_str();
01530           body.insert( boundPos, bStr );
01531           body = "--" + boundaryCStr + "\n" + body;
01532         }
01533     }
01534     else
01535       body = "--" + boundaryCStr + "\n" + innerDwPart->AsString().c_str();
01536     delete innerDwPart;
01537     innerDwPart = 0;
01538     // add all matching Attachments
01539     // NOTE: This code will be changed when KMime is complete.
01540     for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
01541       if ( it->encrypt == doEncryptBody && it->sign == doSignBody ) {
01542         innerDwPart = theMessage.createDWBodyPart( it->part );
01543         innerDwPart->Assemble();
01544         body += "\n--" + boundaryCStr + "\n" + innerDwPart->AsString().c_str();
01545         delete innerDwPart;
01546         innerDwPart = 0;
01547       }
01548     }
01549     body += "\n--" + boundaryCStr + "--\n";
01550   } else { // !earlyAddAttachments
01551     QValueList<int> allowedCTEs;
01552     // the signed body must not be 8bit encoded
01553     mOldBodyPart.setBodyAndGuessCte(body, allowedCTEs, !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
01554                                    doSign);
01555     if ( !mIsRichText )
01556       mOldBodyPart.setCharset(mCharset);
01557   }
01558   // create S/MIME body part for signing and/or encrypting
01559   mOldBodyPart.setBodyEncoded( body );
01560 
01561   if( doSignBody || doEncryptBody ) {
01562     // get string representation of body part (including the attachments)
01563 
01564     DwBodyPart* dwPart;
01565     if ( mIsRichText && !mEarlyAddAttachments ) {
01566       // if we are using richtext and not already have a mp/a body
01567       // make the body a mp/a body
01568       dwPart = theMessage.createDWBodyPart( &mOldBodyPart );
01569       DwHeaders& headers = dwPart->Headers();
01570       DwMediaType& ct = headers.ContentType();
01571       ct.SetBoundary(mSaveBoundary);
01572       dwPart->Assemble();
01573       mEncodedBody = dwPart->AsString().c_str();
01574     }
01575     else {
01576       dwPart = theMessage.createDWBodyPart( &mOldBodyPart );
01577       dwPart->Assemble();
01578       mEncodedBody = dwPart->AsString().c_str();
01579     }
01580     delete dwPart;
01581     dwPart = 0;
01582 
01583     // manually add a boundary definition to the Content-Type header
01584     if( !boundaryCStr.isEmpty() ) {
01585       int boundPos = mEncodedBody.find( '\n' );
01586       if( -1 < boundPos ) {
01587         // insert new "boundary" parameter
01588         QCString bStr( ";\n  boundary=\"" );
01589         bStr += boundaryCStr;
01590         bStr += "\"";
01591         mEncodedBody.insert( boundPos, bStr );
01592       }
01593     }
01594 
01595     // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
01596     // according to RfC 2633, 3.1.1 Canonicalization
01597     //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
01598     mEncodedBody = KMail::Util::lf2crlf( mEncodedBody );
01599   }
01600 
01601   if ( doSignBody ) {
01602     pgpSignedMsg( mEncodedBody, format );
01603 
01604     if ( mSignature.isEmpty() ) {
01605       kdDebug() << "signature was empty" << endl;
01606       mRc = false;
01607       return;
01608     }
01609     mRc = processStructuringInfo( QString::null,
01610                   mOldBodyPart.contentDescription(),
01611                   mOldBodyPart.typeStr(),
01612                   mOldBodyPart.subtypeStr(),
01613                   mOldBodyPart.contentDisposition(),
01614                   mOldBodyPart.contentTransferEncodingStr(),
01615                   mEncodedBody, "signature",
01616                   mSignature,
01617                   *mNewBodyPart, true, format );
01618     if ( mRc ) {
01619       if ( !makeMultiPartSigned( format ) ) {
01620     mNewBodyPart->setCharset( mCharset );
01621       }
01622     } else
01623       KMessageBox::sorry( mComposeWin,
01624               mErrorProcessingStructuringInfo );
01625   }
01626 
01627   if ( !mRc )
01628     return;
01629 
01630   continueComposeMessage( theMessage, doSign, doEncrypt, format );
01631 }
01632 
01633 // Do the encryption stuff
01634 void MessageComposer::continueComposeMessage( KMMessage& theMessage,
01635                                               bool doSign, bool doEncrypt,
01636                                               Kleo::CryptoMessageFormat format )
01637 {
01638 
01639   const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
01640     = mKeyResolver->encryptionItems( format );
01641   kdWarning( splitInfos.empty() )
01642     << "MessageComposer::continueComposeMessage(): splitInfos.empty() for "
01643     << Kleo::cryptoMessageFormatToString( format ) << endl;
01644 
01645   if ( !splitInfos.empty() && doEncrypt && !saveMessagesEncrypted() ) {
01646     mJobs.push_front( new SetLastMessageAsUnencryptedVersionOfLastButOne( this ) );
01647     mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ),
01648                          Kleo::KeyResolver::SplitInfo( splitInfos.front().recipients ), doSign,
01649                          false, mEncodedBody,
01650                          mPreviousBoundaryLevel,
01651                          mOldBodyPart, mNewBodyPart,
01652                          format, this ) );
01653   }
01654 
01655   for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it )
01656     mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ), *it, doSign,
01657                          doEncrypt, mEncodedBody,
01658                          mPreviousBoundaryLevel,
01659                          mOldBodyPart, mNewBodyPart,
01660                          format, this ) );
01661 }
01662 
01663 void MessageComposer::encryptMessage( KMMessage* msg,
01664                       const Kleo::KeyResolver::SplitInfo & splitInfo,
01665                                       bool doSign, bool doEncrypt,
01666                                       KMMessagePart newBodyPart,
01667                       Kleo::CryptoMessageFormat format )
01668 {
01669   if ( doEncrypt && splitInfo.keys.empty() ) {
01670     // the user wants to send the message unencrypted
01671     //mComposeWin->setEncryption( false, false );
01672     //FIXME why is this talkback needed? Till
01673     doEncrypt = false;
01674   }
01675 
01676   const bool doEncryptBody = doEncrypt && mEncryptBody;
01677   const bool doSignBody = doSign && mSignBody;
01678 
01679   if ( doEncryptBody ) {
01680     QCString innerContent;
01681     if ( doSignBody ) {
01682       // extract signed body from newBodyPart
01683       DwBodyPart* dwPart = msg->createDWBodyPart( &newBodyPart );
01684       dwPart->Assemble();
01685       innerContent = dwPart->AsString().c_str();
01686       delete dwPart;
01687       dwPart = 0;
01688     } else
01689       innerContent = mEncodedBody;
01690 
01691     // now do the encrypting:
01692     // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
01693     // according to RfC 2633, 3.1.1 Canonicalization
01694     //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
01695     innerContent = KMail::Util::lf2crlf( innerContent );
01696     //kdDebug(5006) << "                                                       done." << endl;
01697 
01698     QByteArray encryptedBody;
01699     Kpgp::Result result = pgpEncryptedMsg( encryptedBody, innerContent,
01700                                            splitInfo.keys, format );
01701     if ( result != Kpgp::Ok ) {
01702       mRc = false;
01703       return;
01704     }
01705     mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
01706                   newBodyPart.contentDescription(),
01707                   newBodyPart.typeStr(),
01708                   newBodyPart.subtypeStr(),
01709                   newBodyPart.contentDisposition(),
01710                   newBodyPart.contentTransferEncodingStr(),
01711                   innerContent,
01712                   "encrypted data",
01713                   encryptedBody,
01714                   newBodyPart, false, format );
01715     if ( !mRc )
01716       KMessageBox::sorry(mComposeWin, mErrorProcessingStructuringInfo);
01717   }
01718 
01719   // process the attachments that are not included into the body
01720   if( mRc ) {
01721     const bool useNewBodyPart = doSignBody || doEncryptBody;
01722     addBodyAndAttachments( msg, splitInfo, doSign, doEncrypt,
01723       useNewBodyPart ? newBodyPart : mOldBodyPart, format );
01724   }
01725 }
01726 
01727 void MessageComposer::addBodyAndAttachments( KMMessage* msg,
01728                                              const Kleo::KeyResolver::SplitInfo & splitInfo,
01729                                              bool doSign, bool doEncrypt,
01730                                              const KMMessagePart& ourFineBodyPart,
01731                                              Kleo::CryptoMessageFormat format )
01732 {
01733   const bool doEncryptBody = doEncrypt && mEncryptBody;
01734   const bool doSignBody = doSign && mSignBody;
01735 
01736   if( !mAttachments.empty()
01737       && ( !mEarlyAddAttachments || !mAllAttachmentsAreInBody ) ) {
01738     // set the content type header
01739     msg->headers().ContentType().SetType( DwMime::kTypeMultipart );
01740     msg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed );
01741     msg->headers().ContentType().CreateBoundary( 0 );
01742     kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to Multipart/Mixed" << endl;
01743 
01744     // add our Body Part
01745     DwBodyPart* tmpDwPart = msg->createDWBodyPart( &ourFineBodyPart );
01746     DwHeaders& headers = tmpDwPart->Headers();
01747     DwMediaType& ct = headers.ContentType();
01748     if ( !mSaveBoundary.empty() )
01749       ct.SetBoundary(mSaveBoundary);
01750     tmpDwPart->Assemble();
01751 
01752     //KMMessagePart newPart;
01753     //newPart.setBody(tmpDwPart->AsString().c_str());
01754     msg->addDwBodyPart(tmpDwPart); // only this method doesn't add it as text/plain
01755 
01756     // add Attachments
01757     // create additional bodyparts for the attachments (if any)
01758     KMMessagePart newAttachPart;
01759     for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
01760 
01761       const bool cryptFlagsDifferent = ( it->encrypt != doEncryptBody || it->sign != doSignBody ) ;
01762 
01763       if ( !cryptFlagsDifferent && mEarlyAddAttachments )
01764         continue;
01765 
01766       const bool encryptThisNow = doEncrypt && cryptFlagsDifferent && it->encrypt ;
01767       const bool signThisNow = doSign && cryptFlagsDifferent && it->sign ;
01768 
01769       if ( !encryptThisNow && !signThisNow ) {
01770         msg->addBodyPart( it->part );
01771         // Assemble the message. Not sure why, but this fixes the vanishing boundary parameter
01772         (void)msg->asDwMessage();
01773         continue;
01774       }
01775 
01776       KMMessagePart& rEncryptMessagePart( *it->part );
01777 
01778       DwBodyPart* innerDwPart = msg->createDWBodyPart( it->part );
01779       innerDwPart->Assemble();
01780       QCString encodedAttachment = innerDwPart->AsString().c_str();
01781       delete innerDwPart;
01782       innerDwPart = 0;
01783 
01784       // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
01785       // according to RfC 2633, 3.1.1 Canonicalization
01786       //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
01787       encodedAttachment = KMail::Util::lf2crlf( encodedAttachment );
01788 
01789       // sign this attachment
01790       if( signThisNow ) {
01791 
01792         pgpSignedMsg( encodedAttachment, format );
01793         QByteArray signature = mSignature;
01794         mRc = !signature.isEmpty();
01795         if( mRc ) {
01796           mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
01797                                         it->part->contentDescription(),
01798                                         it->part->typeStr(),
01799                                         it->part->subtypeStr(),
01800                                         it->part->contentDisposition(),
01801                                         it->part->contentTransferEncodingStr(),
01802                                         encodedAttachment,
01803                                         "signature",
01804                                         signature,
01805                                         newAttachPart, true, format );
01806           if( mRc ) {
01807             if( encryptThisNow ) {
01808               rEncryptMessagePart = newAttachPart;
01809               DwBodyPart* dwPart = msg->createDWBodyPart( &newAttachPart );
01810               dwPart->Assemble();
01811               encodedAttachment = dwPart->AsString().c_str();
01812               delete dwPart;
01813               dwPart = 0;
01814             }
01815           } else
01816             KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo );
01817         } else {
01818           // quit the attachments' loop
01819           break;
01820         }
01821       }
01822       if( encryptThisNow ) {
01823         QByteArray encryptedBody;
01824         Kpgp::Result result = pgpEncryptedMsg( encryptedBody,
01825                                                encodedAttachment,
01826                                                splitInfo.keys,
01827                                                format );
01828 
01829         if( Kpgp::Ok == result ) {
01830           mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
01831                                         rEncryptMessagePart.contentDescription(),
01832                                         rEncryptMessagePart.typeStr(),
01833                                         rEncryptMessagePart.subtypeStr(),
01834                                         rEncryptMessagePart.contentDisposition(),
01835                                         rEncryptMessagePart.contentTransferEncodingStr(),
01836                                         encodedAttachment,
01837                                         "encrypted data",
01838                                         encryptedBody,
01839                                         newAttachPart, false, format );
01840           if ( !mRc )
01841             KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo );
01842         } else
01843           mRc = false;
01844       }
01845       msg->addBodyPart( &newAttachPart );
01846       (void)msg->asDwMessage(); // Assemble the message. One gets a completely empty message otherwise :/
01847     }
01848   } else { // no attachments in the final message
01849     if( ourFineBodyPart.originalContentTypeStr() ) {
01850       msg->headers().ContentType().FromString( ourFineBodyPart.originalContentTypeStr() );
01851       msg->headers().ContentType().Parse();
01852       kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type from originalContentTypeStr()=" << ourFineBodyPart.originalContentTypeStr() << endl;
01853     } else {
01854       msg->headers().ContentType().FromString( ourFineBodyPart.typeStr() + "/" + ourFineBodyPart.subtypeStr() );
01855       kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to " << ourFineBodyPart.typeStr() << "/" << ourFineBodyPart.subtypeStr() << endl;
01856     }
01857     if ( !ourFineBodyPart.charset().isEmpty() )
01858       msg->setCharset( ourFineBodyPart.charset() );
01859     msg->setHeaderField( "Content-Transfer-Encoding",
01860                          ourFineBodyPart.contentTransferEncodingStr() );
01861     msg->setHeaderField( "Content-Description",
01862                          ourFineBodyPart.contentDescription() );
01863     msg->setHeaderField( "Content-Disposition",
01864                          ourFineBodyPart.contentDisposition() );
01865 
01866     if ( mDebugComposerCrypto )
01867       kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : top level headers and body adjusted" << endl;
01868 
01869     // set body content
01870     if ( mIsRichText && !(doSign || doEncrypt) ) { // add the boundary to the header
01871       msg->headers().ContentType().SetBoundary( mSaveBoundary );
01872       msg->headers().ContentType().Assemble();
01873     }
01874     msg->setBody(ourFineBodyPart.body() );
01875 
01876   }
01877 
01878   msg->setHeaderField( "X-KMail-Recipients",
01879                        splitInfo.recipients.join(", "), KMMessage::Address );
01880 
01881   if ( mDebugComposerCrypto ) {
01882     kdDebug(5006) << "MessageComposer::addBodyAndAttachments():\n      Final message:\n|||" << msg->asString() << "|||\n\n" << endl;
01883     msg->headers().Assemble();
01884     kdDebug(5006) << "\n\n\nMessageComposer::addBodyAndAttachments():\n      Final headers:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl;
01885   }
01886 }
01887 
01888 //-----------------------------------------------------------------------------
01889 // This method does not call any crypto ops, so it does not need to be async
01890 bool MessageComposer::processStructuringInfo( const QString bugURL,
01891                                               const QString contentDescClear,
01892                                               const QCString contentTypeClear,
01893                                               const QCString contentSubtypeClear,
01894                                               const QCString contentDispClear,
01895                                               const QCString contentTEncClear,
01896                                               const QCString& clearCStr,
01897                                               const QString /*contentDescCiph*/,
01898                                               const QByteArray& ciphertext,
01899                                               KMMessagePart& resultingPart,
01900                           bool signing, Kleo::CryptoMessageFormat format )
01901 {
01902   bool bOk = true;
01903 
01904   if ( makeMimeObject( format, signing ) ) {
01905     QCString mainHeader = "Content-Type: ";
01906     const char * toplevelCT = toplevelContentType( format, signing );
01907     if ( toplevelCT )
01908       mainHeader += toplevelCT;
01909     else {
01910       if( makeMultiMime( format, signing ) )
01911         mainHeader += "text/plain";
01912       else
01913         mainHeader += contentTypeClear + '/' + contentSubtypeClear;
01914     }
01915 
01916     const QCString boundaryCStr = KMime::multiPartBoundary();
01917     // add "boundary" parameter
01918     if ( makeMultiMime( format, signing ) )
01919       mainHeader.replace( "%boundary", boundaryCStr );
01920 
01921     if ( toplevelCT ) {
01922       if ( const char * str = toplevelContentDisposition( format, signing ) ) {
01923         mainHeader += "\nContent-Disposition: ";
01924         mainHeader += str;
01925       }
01926       if ( !makeMultiMime( format, signing ) &&
01927        binaryHint( format ) )
01928         mainHeader += "\nContent-Transfer-Encoding: base64";
01929     } else {
01930       if( 0 < contentDispClear.length() ) {
01931         mainHeader += "\nContent-Disposition: ";
01932         mainHeader += contentDispClear;
01933       }
01934       if( 0 < contentTEncClear.length() ) {
01935         mainHeader += "\nContent-Transfer-Encoding: ";
01936         mainHeader += contentTEncClear;
01937       }
01938     }
01939 
01940     //kdDebug(5006) << "processStructuringInfo: mainHeader=" << mainHeader << endl;
01941 
01942     DwString mainDwStr;
01943     mainDwStr = mainHeader + "\n\n";
01944     DwBodyPart mainDwPa( mainDwStr, 0 );
01945     mainDwPa.Parse();
01946     KMMessage::bodyPart( &mainDwPa, &resultingPart );
01947     if( !makeMultiMime( format, signing ) ) {
01948       if ( signing && includeCleartextWhenSigning( format ) ) {
01949         QCString bodyText( clearCStr );
01950         bodyText += '\n';
01951         bodyText += QCString( ciphertext.data(), ciphertext.size() + 1 );
01952         resultingPart.setBodyEncoded( bodyText );
01953       } else
01954         resultingPart.setBodyEncodedBinary( ciphertext );
01955     } else {
01956       // Build the encapsulated MIME parts.
01957       // Build a MIME part holding the version information
01958       // taking the body contents returned in
01959       // structuring.data.bodyTextVersion.
01960       QCString versCStr, codeCStr;
01961       if ( !signing && format == Kleo::OpenPGPMIMEFormat )
01962         versCStr =
01963       "Content-Type: application/pgp-encrypted\n"
01964       "Content-Disposition: attachment\n"
01965       "\n"
01966       "Version: 1";
01967 
01968       // Build a MIME part holding the code information
01969       // taking the body contents returned in ciphertext.
01970       const char * nestedCT = nestedContentType( format, signing );
01971       assert( nestedCT );
01972       codeCStr = "Content-Type: ";
01973       codeCStr += nestedCT;
01974       codeCStr += '\n';
01975       if ( const char * str = nestedContentDisposition( format, signing ) ) {
01976     codeCStr += "Content-Disposition: ";
01977     codeCStr += str;
01978     codeCStr += '\n';
01979       }
01980       if ( binaryHint( format ) ) {
01981     codeCStr += "Content-Transfer-Encoding: base64\n\n";
01982     codeCStr += KMime::Codec::codecForName( "base64" )->encodeToQCString( ciphertext );
01983       } else
01984     codeCStr += '\n' + QCString( ciphertext.data(), ciphertext.size() + 1 );
01985 
01986 
01987       QCString mainStr = "--" + boundaryCStr;
01988       if ( signing && includeCleartextWhenSigning( format ) &&
01989        !clearCStr.isEmpty() )
01990         mainStr += "\n" + clearCStr + "\n--" + boundaryCStr;
01991       if ( !versCStr.isEmpty() )
01992         mainStr += "\n" + versCStr + "\n--" + boundaryCStr;
01993       if( !codeCStr.isEmpty() )
01994         mainStr += "\n" + codeCStr + "\n--" + boundaryCStr;
01995       mainStr += "--\n";
01996 
01997       //kdDebug(5006) << "processStructuringInfo: mainStr=" << mainStr << endl;
01998       resultingPart.setBodyEncoded( mainStr );
01999     }
02000 
02001   } else { //  not making a mime object, build a plain message body.
02002 
02003     resultingPart.setContentDescription( contentDescClear );
02004     resultingPart.setTypeStr( contentTypeClear );
02005     resultingPart.setSubtypeStr( contentSubtypeClear );
02006     resultingPart.setContentDisposition( contentDispClear );
02007     resultingPart.setContentTransferEncodingStr( contentTEncClear );
02008     QCString resultingBody;
02009 
02010     if ( signing && includeCleartextWhenSigning( format ) ) {
02011       if( !clearCStr.isEmpty() )
02012         resultingBody += clearCStr;
02013     }
02014     if ( !ciphertext.isEmpty() )
02015       resultingBody += QCString( ciphertext.data(), ciphertext.size() + 1 ); // null-terminate
02016     else {
02017       // Plugin error!
02018       KMessageBox::sorry( mComposeWin,
02019                           i18n( "<qt><p>Error: The backend did not return "
02020                                 "any encoded data.</p>"
02021                                 "<p>Please report this bug:<br>%2</p></qt>" )
02022                           .arg( bugURL ) );
02023       bOk = false;
02024     }
02025     resultingPart.setBodyEncoded( resultingBody );
02026   }
02027 
02028   return bOk;
02029 }
02030 
02031 //-----------------------------------------------------------------------------
02032 QCString MessageComposer::plainTextFromMarkup( const QString& markupText )
02033 {
02034   QTextEdit *hackConspiratorTextEdit = new QTextEdit( markupText );
02035   hackConspiratorTextEdit->setTextFormat(Qt::PlainText);
02036   if ( !mDisableBreaking ) {
02037     hackConspiratorTextEdit->setWordWrap( QTextEdit::FixedColumnWidth );
02038     hackConspiratorTextEdit->setWrapColumnOrWidth( mLineBreakColumn );
02039   }
02040   QString text = hackConspiratorTextEdit->text();
02041   QCString textbody;
02042 
02043   const QTextCodec *codec = KMMsgBase::codecForName( mCharset );
02044   if( mCharset == "us-ascii" ) {
02045     textbody = KMMsgBase::toUsAscii( text );
02046   } else if( codec == 0 ) {
02047     kdDebug(5006) << "Something is wrong and I can not get a codec." << endl;
02048     textbody = text.local8Bit();
02049   } else {
02050     textbody = codec->fromUnicode( text );
02051   }
02052   if (textbody.isNull()) textbody = "";
02053 
02054   delete hackConspiratorTextEdit;
02055   return textbody;
02056 }
02057 
02058 //-----------------------------------------------------------------------------
02059 QCString MessageComposer::breakLinesAndApplyCodec()
02060 {
02061   QString text;
02062   QCString cText;
02063 
02064   if( mDisableBreaking || mIsRichText )
02065     text = mComposeWin->mEditor->text();
02066   else
02067     text = mComposeWin->mEditor->brokenText();
02068   text.truncate( text.length() ); // to ensure text.size()==text.length()+1
02069 
02070   QString newText;
02071   const QTextCodec *codec = KMMsgBase::codecForName( mCharset );
02072 
02073   if( mCharset == "us-ascii" ) {
02074     cText = KMMsgBase::toUsAscii( text );
02075     newText = QString::fromLatin1( cText );
02076   } else if( codec == 0 ) {
02077     kdDebug(5006) << "Something is wrong and I can not get a codec." << endl;
02078     cText = text.local8Bit();
02079     newText = QString::fromLocal8Bit( cText );
02080   } else {
02081     cText = codec->fromUnicode( text );
02082     newText = codec->toUnicode( cText );
02083   }
02084   if (cText.isNull()) cText = "";
02085 
02086   if( !text.isEmpty() && (newText != text) ) {
02087     QString oldText = mComposeWin->mEditor->text();
02088     mComposeWin->mEditor->setText( newText );
02089     KCursorSaver idle( KBusyPtr::idle() );
02090     bool anyway = ( KMessageBox::warningYesNo( mComposeWin,
02091                                                i18n("<qt>Not all characters fit into the chosen"
02092                                                     " encoding.<br><br>Send the message anyway?</qt>"),
02093                                                i18n("Some Characters Will Be Lost"),
02094                                                i18n("Lose Characters"), i18n("Change Encoding") ) == KMessageBox::Yes );
02095     if( !anyway ) {
02096       mComposeWin->mEditor->setText(oldText);
02097       return QCString();
02098     }
02099   }
02100 
02101   return cText;
02102 }
02103 
02104 
02105 //-----------------------------------------------------------------------------
02106 void MessageComposer::pgpSignedMsg( const QCString & cText, Kleo::CryptoMessageFormat format ) {
02107 
02108   mSignature = QByteArray();
02109 
02110   const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( format );
02111 
02112   assert( !signingKeys.empty() );
02113 
02114   // TODO: ASync call? Likely, yes :-)
02115   const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
02116   assert( cpf );
02117   const Kleo::CryptoBackend::Protocol * proto
02118     = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
02119   assert( proto ); 
02120 
02121   std::auto_ptr<Kleo::SignJob> job( proto->signJob( armor( format ),
02122                             textMode( format ) ) );
02123 
02124   if ( !job.get() ) {
02125     KMessageBox::sorry( mComposeWin,
02126             i18n("This message could not be signed, "
02127                  "since the chosen backend does not seem to support "
02128                  "signing; this should actually never happen, "
02129                  "please report this bug.") );
02130     return;
02131   }
02132 
02133   QByteArray plainText;
02134   plainText.duplicate( cText.data(), cText.length() ); // hrmpf...
02135   QByteArray signature;
02136   const GpgME::SigningResult res =
02137     job->exec( signingKeys, plainText, signingMode( format ), signature );
02138   if ( res.error().isCanceled() ) {
02139     kdDebug() << "signing was canceled by user" << endl;
02140     return;
02141   }
02142   if ( res.error() ) {
02143     kdDebug() << "signing failed: " << res.error().asString() << endl;
02144     job->showErrorDialog( mComposeWin );
02145     return;
02146   }
02147 
02148   mSignature = signature;
02149   Q_ASSERT( !mSignature.isNull() ); // if you hit this, check gpg-agent is running, then blame gpgme.
02150   if ( mSignature.isNull() ) {
02151     KMessageBox::error( mComposeWin, i18n( "The signing operation failed for an unknown reason." ) );
02152   }
02153 }
02154 
02155 //-----------------------------------------------------------------------------
02156 Kpgp::Result MessageComposer::pgpEncryptedMsg( QByteArray & encryptedBody,
02157                                                const QCString & cText,
02158                                                const std::vector<GpgME::Key> & encryptionKeys,
02159                            Kleo::CryptoMessageFormat format )
02160 {
02161   // TODO: ASync call? Likely, yes :-)
02162   const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
02163   assert( cpf );
02164   const Kleo::CryptoBackend::Protocol * proto
02165     = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
02166   assert( proto ); // hmmmm....?
02167 
02168   std::auto_ptr<Kleo::EncryptJob> job( proto->encryptJob( armor( format ),
02169                               textMode( format ) ) );
02170   if ( !job.get() ) {
02171     KMessageBox::sorry( mComposeWin,
02172             i18n("This message could not be encrypted, "
02173                  "since the chosen backend does not seem to support "
02174                  "encryption; this should actually never happen, "
02175                  "please report this bug.") );
02176     return Kpgp::Failure;
02177   }
02178 
02179   QByteArray plainText;
02180   plainText.duplicate( cText.data(), cText.length() ); // hrmpf...
02181 
02182   const GpgME::EncryptionResult res =
02183     job->exec( encryptionKeys, plainText, false, encryptedBody );
02184   if ( res.error().isCanceled() ) {
02185     kdDebug() << "encryption was canceled by user" << endl;
02186     return Kpgp::Canceled;
02187   }
02188   if ( res.error() ) {
02189     kdDebug() << "encryption failed: " << res.error().asString() << endl;
02190     job->showErrorDialog( mComposeWin );
02191     return Kpgp::Failure;
02192   }
02193   return Kpgp::Ok;
02194 }
02195 
02196 Kpgp::Result MessageComposer::pgpSignedAndEncryptedMsg( QByteArray & encryptedBody,
02197                             const QCString & cText,
02198                             const std::vector<GpgME::Key> & signingKeys,
02199                             const std::vector<GpgME::Key> & encryptionKeys,
02200                             Kleo::CryptoMessageFormat format )
02201 {
02202   // TODO: ASync call? Likely, yes :-)
02203   const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
02204   assert( cpf );
02205   const Kleo::CryptoBackend::Protocol * proto
02206     = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
02207   assert( proto ); // hmmmm....?
02208 
02209   std::auto_ptr<Kleo::SignEncryptJob> job( proto->signEncryptJob( armor( format ),
02210                                   textMode( format ) ) );
02211   if ( !job.get() ) {
02212     KMessageBox::sorry( mComposeWin,
02213             i18n("This message could not be signed and encrypted, "
02214                  "since the chosen backend does not seem to support "
02215                  "combined signing and encryption; this should actually never happen, "
02216                  "please report this bug.") );
02217     return Kpgp::Failure;
02218   }
02219 
02220   QByteArray plainText;
02221   plainText.duplicate( cText.data(), cText.length() ); // hrmpf...
02222 
02223   const std::pair<GpgME::SigningResult,GpgME::EncryptionResult> res =
02224     job->exec( signingKeys, encryptionKeys, plainText, false, encryptedBody );
02225   if ( res.first.error().isCanceled() || res.second.error().isCanceled() ) {
02226     kdDebug() << "encrypt/sign was canceled by user" << endl;
02227     return Kpgp::Canceled;
02228   }
02229   if ( res.first.error() || res.second.error() ) {
02230     if ( res.first.error() )
02231       kdDebug() << "signing failed: " << res.first.error().asString() << endl;
02232     else
02233       kdDebug() << "encryption failed: " << res.second.error().asString() << endl;
02234     job->showErrorDialog( mComposeWin );
02235     return Kpgp::Failure;
02236   }
02237   return Kpgp::Ok;
02238 }
02239 
02240 
02241 #include "messagecomposer.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys