diff options
Diffstat (limited to 'kmail/messagecomposer.cpp')
-rw-r--r-- | kmail/messagecomposer.cpp | 2284 |
1 files changed, 2284 insertions, 0 deletions
diff --git a/kmail/messagecomposer.cpp b/kmail/messagecomposer.cpp new file mode 100644 index 000000000..2cf5b8138 --- /dev/null +++ b/kmail/messagecomposer.cpp @@ -0,0 +1,2284 @@ +/** + * messagecomposer.cpp + * + * Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of this program with any edition of + * the Qt library by Trolltech AS, Norway (or with modified versions + * of Qt that use the same license as Qt), and distribute linked + * combinations including the two. You must obey the GNU General + * Public License in all respects for all of the code used other than + * Qt. If you modify this file, you may extend this exception to + * your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from + * your version. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "messagecomposer.h" +#include "kmmsgpart.h" +#define REALLY_WANT_KMCOMPOSEWIN_H +#include "kmcomposewin.h" +#undef REALLY_WANT_KMCOMPOSEWIN_H +#include "klistboxdialog.h" +#include "kcursorsaver.h" +#include "messagesender.h" +#include "kmfolder.h" +#include "kmfoldercombobox.h" +#include "keyresolver.h" +#include "kleo_util.h" +#include "globalsettings.h" +#include "custommimeheader.h" +#include "kmedit.h" +#include "util.h" + +#include <libkpimidentities/identity.h> +#include <libkpimidentities/identitymanager.h> +#include <libemailfunctions/email.h> + +#include <ui/keyselectiondialog.h> +#include <ui/keyapprovaldialog.h> +#include <ui/messagebox.h> +#include <kleo/cryptobackendfactory.h> +#include <kleo/keylistjob.h> +#include <kleo/encryptjob.h> +#include <kleo/signencryptjob.h> +#include <kleo/signjob.h> +#include <kleo/specialjob.h> + +#include <kmime_util.h> +#include <kmime_codecs.h> +#include <kpgpblock.h> + +#include <mimelib/mimepp.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kinputdialog.h> +#include <kdebug.h> +#include <kaction.h> +#include <qfile.h> +#include <qtextcodec.h> +#include <qtextedit.h> +#include <qtimer.h> + +#include <gpgmepp/key.h> +#include <gpgmepp/keylistresult.h> +#include <gpgmepp/encryptionresult.h> +#include <gpgmepp/signingresult.h> +#include <gpgmepp/context.h> + +#include <algorithm> +#include <memory> + +// ## keep default values in sync with configuredialog.cpp, Security::CryptoTab::setup() +// This should be ported to a .kcfg one day I suppose (dfaure). + +static inline bool warnSendUnsigned() { + KConfigGroup group( KMKernel::config(), "Composer" ); + return group.readBoolEntry( "crypto-warning-unsigned", false ); +} +static inline bool warnSendUnencrypted() { + KConfigGroup group( KMKernel::config(), "Composer" ); + return group.readBoolEntry( "crypto-warning-unencrypted", false ); +} +static inline bool saveMessagesEncrypted() { + KConfigGroup group( KMKernel::config(), "Composer" ); + return group.readBoolEntry( "crypto-store-encrypted", true ); +} +static inline bool encryptToSelf() { + // return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf(); + KConfigGroup group( KMKernel::config(), "Composer" ); + return group.readBoolEntry( "crypto-encrypt-to-self", true ); +} +static inline bool showKeyApprovalDialog() { + KConfigGroup group( KMKernel::config(), "Composer" ); + return group.readBoolEntry( "crypto-show-keys-for-approval", true ); +} + +static inline int encryptKeyNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-encr-key-near-expire-int", 14 ); + return kMax( 1, num ); +} + +static inline int signingKeyNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-sign-key-near-expire-int", 14 ); + return kMax( 1, num ); +} + +static inline int encryptRootCertNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-encr-root-near-expire-int", 14 ); + return kMax( 1, num ); +} + +static inline int signingRootCertNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-sign-root-near-expire-int", 14 ); + return kMax( 1, num ); +} + +static inline int encryptChainCertNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-encr-chaincert-near-expire-int", 14 ); + return kMax( 1, num ); +} + +static inline int signingChainCertNearExpiryWarningThresholdInDays() { + const KConfigGroup composer( KMKernel::config(), "Composer" ); + if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) ) + return -1; + const int num = composer.readNumEntry( "crypto-warn-sign-chaincert-near-expire-int", 14 ); + return kMax( 1, num ); +} + +/* + Design of this: + + The idea is that the main run of applyChanges here makes two jobs: + the first sets the flags for encryption/signing or not, and the other + starts the encryption process. + + When a job is run, it has already been removed from the job queue. This + means if one of the current jobs needs to add new jobs, it can add them + to the front and that way control when new jobs are added. + + For example, the compose message job will add jobs that will do the + actual encryption and signing. + + There are two types of jobs: synchronous and asynchronous: + + A synchronous job simply implments the execute() method and performs + it's operation there and sets mComposer->mRc to false if the compose + queue should be canceled. + + An asynchronous job only sets up and starts it's operation. Before + returning, it connects to the result signals of the operation + (e.g. Kleo::Job's result(...) signal) and sets mComposer->mHoldJobs + to true. This makes the scheduler return to the event loop. The job + is now responsible for giving control back to the scheduler by + calling mComposer->doNextJob(). +*/ + +/* + Test plan: + + For each message format (e.g. openPGP/MIME) + 1. Body signed + 2. Body encrypted + 3. Body signed and encrypted + 4. Body encrypted, attachments encrypted (they must be encrypted together, mEarlyAddAttachments) + 5. Body encrypted, attachments not encrypted + 6. Body encrypted, attachment encrypted and signed (separately) + 7. Body not encrypted, one attachment encrypted+signed, one attachment encrypted only, one attachment signed only + (https://intevation.de/roundup/aegypten/issue295) + (this is the reason attachments can't be encrypted together) + 8. Body and attachments encrypted+signed (they must be encrypted+signed together, mEarlyAddAttachments) + 9. Body encrypted+signed, attachments encrypted + 10. Body encrypted+signed, one attachment signed, one attachment not encrypted nor signed + ... + + I recorded a KDExecutor script sending all of the above (David) + + Further tests (which test opportunistic encryption): + 1. Send a message to a person with valid key but without encryption preference + and answer the question whether the message should be encrypted with Yes. + 2. Send a message to a person with valid key but without encryption preference + and answer the question whether the message should be encrypted with No. + 3. Send a message to a person with valid key and with encryption preference + "Encrypt whenever possible" (aka opportunistic encryption). +*/ + +static QString mErrorProcessingStructuringInfo = +i18n("<qt><p>Structuring information returned by the Crypto plug-in " + "could not be processed correctly; the plug-in might be damaged.</p>" + "<p>Please contact your system administrator.</p></qt>"); +static QString mErrorNoCryptPlugAndNoBuildIn = +i18n("<p>No active Crypto Plug-In was found and the built-in OpenPGP code " + "did not run successfully.</p>" + "<p>You can do two things to change this:</p>" + "<ul><li><em>either</em> activate a Plug-In using the " + "Settings->Configure KMail->Plug-In dialog.</li>" + "<li><em>or</em> specify traditional OpenPGP settings on the same dialog's " + "Identity->Advanced tab.</li></ul>"); + + +class MessageComposerJob { +public: + MessageComposerJob( MessageComposer* composer ) : mComposer( composer ) {} + virtual ~MessageComposerJob() {} + + virtual void execute() = 0; + +protected: + // These are the methods that call the private MessageComposer methods + // Workaround for friend not being inherited + void adjustCryptFlags() { mComposer->adjustCryptFlags(); } + void composeMessage() { mComposer->composeMessage(); } + void continueComposeMessage( KMMessage& msg, bool doSign, bool doEncrypt, + Kleo::CryptoMessageFormat format ) + { + mComposer->continueComposeMessage( msg, doSign, doEncrypt, format ); + } + void chiasmusEncryptAllAttachments() { + mComposer->chiasmusEncryptAllAttachments(); + } + + MessageComposer* mComposer; +}; + +class ChiasmusBodyPartEncryptJob : public MessageComposerJob { +public: + ChiasmusBodyPartEncryptJob( MessageComposer * composer ) + : MessageComposerJob( composer ) {} + + void execute() { + chiasmusEncryptAllAttachments(); + } +}; + +class AdjustCryptFlagsJob : public MessageComposerJob { +public: + AdjustCryptFlagsJob( MessageComposer* composer ) + : MessageComposerJob( composer ) {} + + void execute() { + adjustCryptFlags(); + } +}; + +class ComposeMessageJob : public MessageComposerJob { +public: + ComposeMessageJob( MessageComposer* composer ) + : MessageComposerJob( composer ) {} + + void execute() { + composeMessage(); + } +}; + +MessageComposer::MessageComposer( KMComposeWin* win, const char* name ) + : QObject( win, name ), mComposeWin( win ), mCurrentJob( 0 ), + mReferenceMessage( 0 ), mKeyResolver( 0 ), + mUseOpportunisticEncryption( false ), + mSignBody( false ), mEncryptBody( false ), + mSigningRequested( false ), mEncryptionRequested( false ), + mDoSign( false ), mDoEncrypt( false ), + mAllowedCryptoMessageFormats( 0 ), + mDisableCrypto( false ), + mDisableBreaking( false ), + mDebugComposerCrypto( false ), + mAutoCharset( true ), + mIsRichText( false ), + mIdentityUid( 0 ), mRc( true ), + mHoldJobs( false ), + mNewBodyPart( 0 ), + mEarlyAddAttachments( false ), mAllAttachmentsAreInBody( false ), + mPreviousBoundaryLevel( 0 ), + mEncryptWithChiasmus( false ), + mPerformingSignOperation( false ) +{ +} + +MessageComposer::~MessageComposer() +{ + delete mKeyResolver; mKeyResolver = 0; + delete mNewBodyPart; mNewBodyPart = 0; +} + +void MessageComposer::applyChanges( bool disableCrypto ) +{ + // Do the initial setup + if( getenv("KMAIL_DEBUG_COMPOSER_CRYPTO") != 0 ) { + QCString cE = getenv("KMAIL_DEBUG_COMPOSER_CRYPTO"); + mDebugComposerCrypto = cE == "1" || cE.upper() == "ON" || cE.upper() == "TRUE"; + kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = TRUE" << endl; + } else { + mDebugComposerCrypto = false; + kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = FALSE" << endl; + } + + mHoldJobs = false; + mRc = true; + + mDisableCrypto = disableCrypto; + + // 1: Read everything from KMComposeWin and set all + // trivial parts of the message + readFromComposeWin(); + + // From now on, we're not supposed to read from the composer win + // TODO: Make it so ;-) + // 1.5: Replace all body parts with their chiasmus-encrypted equivalent + mJobs.push_back( new ChiasmusBodyPartEncryptJob( this ) ); + + // 2: Set encryption/signing options and resolve keys + mJobs.push_back( new AdjustCryptFlagsJob( this ) ); + + // 3: Build the message (makes the crypto jobs also) + mJobs.push_back( new ComposeMessageJob( this ) ); + + // Finally: Run the jobs + doNextJob(); +} + +void MessageComposer::doNextJob() +{ + delete mCurrentJob; mCurrentJob = 0; + + if( mJobs.isEmpty() ) { + // No more jobs. Signal that we're done + emitDone( mRc ); + return; + } + + if( !mRc ) { + // Something has gone wrong - stop the process and bail out + while( !mJobs.isEmpty() ) { + delete mJobs.front(); + mJobs.pop_front(); + } + emitDone( false ); + return; + } + + // We have more jobs to do, but allow others to come first + QTimer::singleShot( 0, this, SLOT( slotDoNextJob() ) ); +} + +void MessageComposer::emitDone( bool b ) +{ + // Save memory - before sending the mail + mEncodedBody = QByteArray(); + delete mNewBodyPart; mNewBodyPart = 0; + mOldBodyPart.clear(); + emit done( b ); +} + +void MessageComposer::slotDoNextJob() +{ + assert( !mCurrentJob ); + if( mHoldJobs ) + // Always make it run from now. If more than one job should be held, + // The individual jobs must do this. + mHoldJobs = false; + else { + assert( !mJobs.empty() ); + // Get the next job + mCurrentJob = mJobs.front(); + assert( mCurrentJob ); + mJobs.pop_front(); + + // Execute it + mCurrentJob->execute(); + } + + // Finally run the next job if necessary + if( !mHoldJobs ) + doNextJob(); +} + +void MessageComposer::readFromComposeWin() +{ + // Copy necessary attributes over + mDisableBreaking = false; + + mSignBody = mComposeWin->mSignAction->isChecked(); + mSigningRequested = mSignBody; // for now; will be adjusted depending on attachments + mEncryptBody = mComposeWin->mEncryptAction->isChecked(); + mEncryptionRequested = mEncryptBody; // for now; will be adjusted depending on attachments + + mAutoCharset = mComposeWin->mAutoCharset; + mCharset = mComposeWin->mCharset; + mReferenceMessage = mComposeWin->mMsg; + // if the user made any modifications to the message then the Content-Type + // of the message is no longer reliable (e. g. if he editted a draft/resent a + // message and then removed all attachments or changed from PGP/MIME signed + // to clearsigned); + // even if the user didn't make any modifications to the message the + // Content-Type of the message might be wrong, e.g. when inline-forwarding + // an mp/alt message then the Content-Type is set to mp/alt although it should + // be text/plain (cf. bug 127526); + // OTOH we must not reset the Content-Type of inline invitations; + // therefore we reset the Content-Type to text/plain whenever the current + // Content-Type is multipart/*. + if ( mReferenceMessage->type() == DwMime::kTypeMultipart ) + mReferenceMessage->setHeaderField( "Content-Type", "text/plain" ); + mUseOpportunisticEncryption = GlobalSettings::self()->pgpAutoEncrypt(); + mAllowedCryptoMessageFormats = mComposeWin->cryptoMessageFormat(); + + if( mAutoCharset ) { + QCString charset = KMMsgBase::autoDetectCharset( mCharset, KMMessage::preferredCharsets(), mComposeWin->mEditor->text() ); + if( charset.isEmpty() ) + { + KMessageBox::sorry( mComposeWin, + i18n( "No suitable encoding could be found for " + "your message.\nPlease set an encoding " + "using the 'Options' menu." ) ); + mRc = false; + return; + } + mCharset = charset; + // Also apply this to the composer window + mComposeWin->mCharset = charset; + } + mReferenceMessage->setCharset(mCharset); + + mReferenceMessage->setTo(mComposeWin->to()); + mReferenceMessage->setFrom(mComposeWin->from()); + mReferenceMessage->setCc(mComposeWin->cc()); + mReferenceMessage->setSubject(mComposeWin->subject()); + mReferenceMessage->setReplyTo(mComposeWin->replyTo()); + mReferenceMessage->setBcc(mComposeWin->bcc()); + + const KPIM::Identity & id = mComposeWin->identity(); + + KMFolder *f = mComposeWin->mFcc->getFolder(); + assert( f != 0 ); + if ( f->idString() == id.fcc() ) + mReferenceMessage->removeHeaderField("X-KMail-Fcc"); + else + mReferenceMessage->setFcc( f->idString() ); + + // set the correct drafts folder + mReferenceMessage->setDrafts( id.drafts() ); + + if (id.isDefault()) + mReferenceMessage->removeHeaderField("X-KMail-Identity"); + else mReferenceMessage->setHeaderField("X-KMail-Identity", QString::number( id.uoid() )); + + QString replyAddr; + if (!mComposeWin->replyTo().isEmpty()) replyAddr = mComposeWin->replyTo(); + else replyAddr = mComposeWin->from(); + + if (mComposeWin->mRequestMDNAction->isChecked()) + mReferenceMessage->setHeaderField("Disposition-Notification-To", replyAddr); + else + mReferenceMessage->removeHeaderField("Disposition-Notification-To"); + + if (mComposeWin->mUrgentAction->isChecked()) { + mReferenceMessage->setHeaderField("X-PRIORITY", "2 (High)"); + mReferenceMessage->setHeaderField("Priority", "urgent"); + } else { + mReferenceMessage->removeHeaderField("X-PRIORITY"); + mReferenceMessage->removeHeaderField("Priority"); + } + + int num = GlobalSettings::self()->custHeaderCount(); + for(int ix=0; ix<num; ix++) { + CustomMimeHeader customMimeHeader( QString::number(ix) ); + customMimeHeader.readConfig(); + mReferenceMessage->setHeaderField( + KMMsgBase::toUsAscii( customMimeHeader.custHeaderName() ), + customMimeHeader.custHeaderValue() ); + } + + + // we have to remember the Bcc because it might have been overwritten + // by a custom header (therefore we can't use bcc() later) and because + // mimelib removes addresses without domain part (therefore we can't use + // mReferenceMessage->bcc() later and also not now. So get the Bcc from + // the composer window.) + mBcc = mComposeWin->bcc(); + mTo = KPIM::splitEmailAddrList( mComposeWin->to().stripWhiteSpace() ); + mCc = KPIM::splitEmailAddrList( mComposeWin->cc().stripWhiteSpace() ); + mBccList = KPIM::splitEmailAddrList( mBcc.stripWhiteSpace() ); + + for ( unsigned int i = 0 ; i < mComposeWin->mAtmList.count() ; ++i ) + mAttachments.push_back( Attachment( mComposeWin->mAtmList.at(i), + mComposeWin->signFlagOfAttachment( i ), + mComposeWin->encryptFlagOfAttachment( i ) ) ); + + mEncryptWithChiasmus = mComposeWin->mEncryptWithChiasmus; + + mIsRichText = mComposeWin->mEditor->textFormat() == Qt::RichText; + mIdentityUid = mComposeWin->identityUid(); + mText = breakLinesAndApplyCodec(); + assert( mText.isEmpty() || mText[mText.size()-1] == '\n' ); + // Hopefully we can get rid of this eventually, it's needed to be able + // to break the plain/text version of a multipart/alternative (html) mail + // according to the line breaks of the richtext version. + mLineBreakColumn = mComposeWin->mEditor->lineBreakColumn(); +} + +static QCString escape_quoted_string( const QCString & str ) { + QCString result; + const unsigned int str_len = str.length(); + result.resize( 2*str_len + 1 ); + char * d = result.data(); + for ( unsigned int i = 0 ; i < str_len ; ++i ) + switch ( const char ch = str[i] ) { + case '\\': + case '"': + *d++ = '\\'; + default: // fall through: + *d++ = ch; + } + result.truncate( d - result.begin() ); + return result; +} + +bool MessageComposer::encryptWithChiasmus( const Kleo::CryptoBackend::Protocol * chiasmus, + const QByteArray& body, + QByteArray& resultData ) +{ + std::auto_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-encrypt", QMap<QString,QVariant>() ) ); + if ( !job.get() ) { + const QString msg = i18n( "Chiasmus backend does not offer the " + "\"x-encrypt\" function. Please report this bug." ); + KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) ); + return false; + } + if ( !job->setProperty( "key", GlobalSettings::chiasmusKey() ) || + !job->setProperty( "options", GlobalSettings::chiasmusOptions() ) || + !job->setProperty( "input", body ) ) { + const QString msg = i18n( "The \"x-encrypt\" function does not accept " + "the expected parameters. Please report this bug." ); + KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) ); + return false; + } + const GpgME::Error err = job->exec(); + if ( err.isCanceled() || err ) { + if ( err ) + job->showErrorDialog( mComposeWin, i18n( "Chiasmus Encryption Error" ) ); + return false; + } + const QVariant result = job->property( "result" ); + if ( result.type() != QVariant::ByteArray ) { + const QString msg = i18n( "Unexpected return value from Chiasmus backend: " + "The \"x-encrypt\" function did not return a " + "byte array. Please report this bug." ); + KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) ); + return false; + } + resultData = result.toByteArray(); + return true; +} + +void MessageComposer::chiasmusEncryptAllAttachments() { + if ( !mEncryptWithChiasmus ) + return; + assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure + if ( mAttachments.empty() ) + return; + const Kleo::CryptoBackend::Protocol * chiasmus + = Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ); + assert( chiasmus ); // kmcomposewin code should have made sure + + + for ( QValueVector<Attachment>::iterator it = mAttachments.begin(), end = mAttachments.end() ; it != end ; ++it ) { + KMMessagePart * part = it->part; + const QString filename = part->fileName(); + if ( filename.endsWith( ".xia", false ) ) + continue; // already encrypted + const QByteArray body = part->bodyDecodedBinary(); + QByteArray resultData; + if ( !encryptWithChiasmus( chiasmus, body, resultData ) ) { + mRc = false; + return; + } + // everything ok, so let's fill in the part again: + QValueList<int> dummy; + part->setBodyAndGuessCte( resultData, dummy ); + part->setTypeStr( "application" ); + part->setSubtypeStr( "vnd.de.bund.bsi.chiasmus" ); + part->setName( filename + ".xia" ); + // this is taken from kmmsgpartdlg.cpp: + QCString encoding = KMMsgBase::autoDetectCharset( part->charset(), KMMessage::preferredCharsets(), filename ); + if ( encoding.isEmpty() ) + encoding = "utf-8"; + const QCString enc_name = KMMsgBase::encodeRFC2231String( filename + ".xia", encoding ); + const QCString cDisp = "attachment;\n\tfilename" + + ( QString( enc_name ) != filename + ".xia" + ? "*=" + enc_name + : "=\"" + escape_quoted_string( enc_name ) + '\"' ); + part->setContentDisposition( cDisp ); + } +} + +void MessageComposer::adjustCryptFlags() +{ + if ( !mDisableCrypto && + mAllowedCryptoMessageFormats & Kleo::InlineOpenPGPFormat && + !mAttachments.empty() && + ( mSigningRequested || mEncryptionRequested ) ) + { + int ret; + if ( mAllowedCryptoMessageFormats == Kleo::InlineOpenPGPFormat ) { + ret = KMessageBox::warningYesNoCancel( mComposeWin, + i18n("The inline OpenPGP crypto message format " + "does not support encryption or signing " + "of attachments.\n" + "Really use deprecated inline OpenPGP?"), + i18n("Insecure Message Format"), + i18n("Use Inline OpenPGP"), + i18n("Use OpenPGP/MIME") ); + } + else { + // if other crypto message formats are allowed then simply don't use + // inline OpenPGP + ret = KMessageBox::No; + } + + if ( ret == KMessageBox::Cancel ) { + mRc = false; + return; + } else if ( ret == KMessageBox::No ) { + mAllowedCryptoMessageFormats &= ~Kleo::InlineOpenPGPFormat; + mAllowedCryptoMessageFormats |= Kleo::OpenPGPMIMEFormat; + if ( mSigningRequested ) { + // The composer window disabled signing on the attachments, re-enable it + for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx ) + mAttachments[idx].sign = true; + } + if ( mEncryptionRequested ) { + // The composer window disabled encrypting on the attachments, re-enable it + // We assume this is what the user wants - after all he chose OpenPGP/MIME for this. + for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx ) + mAttachments[idx].encrypt = true; + } + } + } + + mKeyResolver = + new Kleo::KeyResolver( encryptToSelf(), showKeyApprovalDialog(), + mUseOpportunisticEncryption, mAllowedCryptoMessageFormats, + encryptKeyNearExpiryWarningThresholdInDays(), + signingKeyNearExpiryWarningThresholdInDays(), + encryptRootCertNearExpiryWarningThresholdInDays(), + signingRootCertNearExpiryWarningThresholdInDays(), + encryptChainCertNearExpiryWarningThresholdInDays(), + signingChainCertNearExpiryWarningThresholdInDays() ); + + if ( !mDisableCrypto ) { + const KPIM::Identity & id = + kmkernel->identityManager()->identityForUoidOrDefault( mIdentityUid ); + + QStringList encryptToSelfKeys; + if ( !id.pgpEncryptionKey().isEmpty() ) + encryptToSelfKeys.push_back( id.pgpEncryptionKey() ); + if ( !id.smimeEncryptionKey().isEmpty() ) + encryptToSelfKeys.push_back( id.smimeEncryptionKey() ); + if ( mKeyResolver->setEncryptToSelfKeys( encryptToSelfKeys ) != Kpgp::Ok ) { + mRc = false; + return; + } + + QStringList signKeys; + if ( !id.pgpSigningKey().isEmpty() ) + signKeys.push_back( mPGPSigningKey = id.pgpSigningKey() ); + if ( !id.smimeSigningKey().isEmpty() ) + signKeys.push_back( mSMIMESigningKey = id.smimeSigningKey() ); + if ( mKeyResolver->setSigningKeys( signKeys ) != Kpgp::Ok ) { + mRc = false; + return; + } + } + + mKeyResolver->setPrimaryRecipients( mTo + mCc ); + mKeyResolver->setSecondaryRecipients( mBccList ); + + // check settings of composer buttons *and* attachment check boxes + bool doSignCompletely = mSigningRequested; + bool doEncryptCompletely = mEncryptionRequested; + for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx ) { + if ( mAttachments[idx].encrypt ) + mEncryptionRequested = true; + else + doEncryptCompletely = false; + if ( mAttachments[idx].sign ) + mSigningRequested = true; + else + doSignCompletely = false; + } + + mDoSign = !mDisableCrypto && determineWhetherToSign( doSignCompletely ); + + if ( !mRc ) + return; + + mDoEncrypt = !mDisableCrypto && determineWhetherToEncrypt( doEncryptCompletely ); + + if ( !mRc ) + return; + + // resolveAllKeys needs to run even if mDisableCrypto == true, since + // we depend on it collecting all recipients into one dummy + // SplitInfo to avoid special-casing all over the place: + if ( mKeyResolver->resolveAllKeys( mDoSign, mDoEncrypt ) != Kpgp::Ok ) + mRc = false; +} + +bool MessageComposer::determineWhetherToSign( bool doSignCompletely ) { + bool sign = false; + switch ( mKeyResolver->checkSigningPreferences( mSigningRequested ) ) { + case Kleo::DoIt: + if ( !mSigningRequested ) { + markAllAttachmentsForSigning( true ); + return true; + } + sign = true; + break; + case Kleo::DontDoIt: + sign = false; + break; + case Kleo::AskOpportunistic: + assert( 0 ); + case Kleo::Ask: + { + // the user wants to be asked or has to be asked + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = i18n("Examination of the recipient's signing preferences " + "yielded that you be asked whether or not to sign " + "this message.\n" + "Sign this message?"); + switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg, + i18n("Sign Message?"), + i18n("to sign","&Sign"), + i18n("Do &Not Sign") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForSigning( true ); + return true; + case KMessageBox::No: + markAllAttachmentsForSigning( false ); + return false; + } + } + break; + case Kleo::Conflict: + { + // warn the user that there are conflicting signing preferences + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = i18n("There are conflicting signing preferences " + "for these recipients.\n" + "Sign this message?"); + switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg, + i18n("Sign Message?"), + i18n("to sign","&Sign"), + i18n("Do &Not Sign") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForSigning( true ); + return true; + case KMessageBox::No: + markAllAttachmentsForSigning( false ); + return false; + } + } + break; + case Kleo::Impossible: + { + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = i18n("You have requested to sign this message, " + "but no valid signing keys have been configured " + "for this identity."); + if ( KMessageBox::warningContinueCancel( mComposeWin, msg, + i18n("Send Unsigned?"), + i18n("Send &Unsigned") ) + == KMessageBox::Cancel ) { + mRc = false; + return false; + } else { + markAllAttachmentsForSigning( false ); + return false; + } + } + } + + if ( !sign || !doSignCompletely ) { + if ( warnSendUnsigned() ) { + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = sign && !doSignCompletely + ? i18n("Some parts of this message will not be signed.\n" + "Sending only partially signed messages might violate site policy.\n" + "Sign all parts instead?") // oh, I hate this... + : i18n("This message will not be signed.\n" + "Sending unsigned message might violate site policy.\n" + "Sign message instead?") ; // oh, I hate this... + const QString buttonText = sign && !doSignCompletely + ? i18n("&Sign All Parts") : i18n("&Sign") ; + switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg, + i18n("Unsigned-Message Warning"), + buttonText, + i18n("Send &As Is") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForSigning( true ); + return true; + case KMessageBox::No: + return sign || doSignCompletely; + } + } + } + + return sign || doSignCompletely ; +} + +bool MessageComposer::determineWhetherToEncrypt( bool doEncryptCompletely ) { + bool encrypt = false; + bool opportunistic = false; + switch ( mKeyResolver->checkEncryptionPreferences( mEncryptionRequested ) ) { + case Kleo::DoIt: + if ( !mEncryptionRequested ) { + markAllAttachmentsForEncryption( true ); + return true; + } + encrypt = true; + break; + case Kleo::DontDoIt: + encrypt = false; + break; + case Kleo::AskOpportunistic: + opportunistic = true; + // fall through... + case Kleo::Ask: + { + // the user wants to be asked or has to be asked + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = opportunistic + ? i18n("Valid trusted encryption keys were found for all recipients.\n" + "Encrypt this message?") + : i18n("Examination of the recipient's encryption preferences " + "yielded that you be asked whether or not to encrypt " + "this message.\n" + "Encrypt this message?"); + switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg, + i18n("Encrypt Message?"), + mDoSign + ? i18n("Sign && &Encrypt") + : i18n("&Encrypt"), + mDoSign + ? i18n("&Sign Only") + : i18n("&Send As-Is") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForEncryption( true ); + return true; + case KMessageBox::No: + markAllAttachmentsForEncryption( false ); + return false; + } + } + break; + case Kleo::Conflict: + { + // warn the user that there are conflicting encryption preferences + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = i18n("There are conflicting encryption preferences " + "for these recipients.\n" + "Encrypt this message?"); + switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg, + i18n("Encrypt Message?"), + i18n("&Encrypt"), + i18n("Do &Not Encrypt") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForEncryption( true ); + return true; + case KMessageBox::No: + markAllAttachmentsForEncryption( false ); + return false; + } + } + break; + case Kleo::Impossible: + { + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = i18n("You have requested to encrypt this message, " + "and to encrypt a copy to yourself, " + "but no valid trusted encryption keys have been " + "configured for this identity."); + if ( KMessageBox::warningContinueCancel( mComposeWin, msg, + i18n("Send Unencrypted?"), + i18n("Send &Unencrypted") ) + == KMessageBox::Cancel ) { + mRc = false; + return false; + } else { + markAllAttachmentsForEncryption( false ); + return false; + } + } + } + + if ( !encrypt || !doEncryptCompletely ) { + if ( warnSendUnencrypted() ) { + const KCursorSaver idle( KBusyPtr::idle() ); + const QString msg = !doEncryptCompletely + ? i18n("Some parts of this message will not be encrypted.\n" + "Sending only partially encrypted messages might violate site policy " + "and/or leak sensitive information.\n" + "Encrypt all parts instead?") // oh, I hate this... + : i18n("This message will not be encrypted.\n" + "Sending unencrypted messages might violate site policy and/or " + "leak sensitive information.\n" + "Encrypt messages instead?") ; // oh, I hate this... + const QString buttonText = !doEncryptCompletely + ? i18n("&Encrypt All Parts") : i18n("&Encrypt") ; + switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg, + i18n("Unencrypted Message Warning"), + buttonText, + mDoSign + ? i18n("&Sign Only") + : i18n("&Send As-Is") ) ) { + case KMessageBox::Cancel: + mRc = false; + return false; + case KMessageBox::Yes: + markAllAttachmentsForEncryption( true ); + return true; + case KMessageBox::No: + return encrypt || doEncryptCompletely; + } + } + } + + return encrypt || doEncryptCompletely ; +} + +void MessageComposer::markAllAttachmentsForSigning( bool sign ) { + mSignBody = sign; + for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) + it->sign = sign; +} + +void MessageComposer::markAllAttachmentsForEncryption( bool enc ) { + mEncryptBody = enc; + for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) + it->encrypt = enc; +} + + +void MessageComposer::composeMessage() +{ + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + if ( mKeyResolver->encryptionItems( concreteCryptoMessageFormats[i] ).empty() ) + continue; + KMMessage * msg = new KMMessage( *mReferenceMessage ); + composeMessage( *msg, mDoSign, mDoEncrypt, concreteCryptoMessageFormats[i] ); + if ( !mRc ) + return; + } +} + +// +// These are replacements for StructuringInfo(Wrapper): +// + +// check whether to use multipart/{signed,encrypted} +static inline bool makeMultiMime( Kleo::CryptoMessageFormat f, bool sign ) { + switch ( f ) { + default: + case Kleo::InlineOpenPGPFormat: + case Kleo::SMIMEOpaqueFormat: return false; + case Kleo::OpenPGPMIMEFormat: return true; + case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME + } +} +static inline bool makeMultiPartSigned( Kleo::CryptoMessageFormat f ) { + return makeMultiMime( f, true ); +} +static inline bool makeMultiPartEncrypted( Kleo::CryptoMessageFormat f ) { + return makeMultiMime( f, false ); +} + +static inline bool makeMimeObject( Kleo::CryptoMessageFormat f, bool /*signing*/ ) { + return f != Kleo::InlineOpenPGPFormat; +} + +static inline const char * toplevelContentType( Kleo::CryptoMessageFormat f, bool signing ) { + switch ( f ) { + default: + case Kleo::InlineOpenPGPFormat: return 0; + case Kleo::OpenPGPMIMEFormat: + return signing ? + "multipart/signed;\n\t" + "boundary=\"%boundary\";\n\t" + "protocol=\"application/pgp-signature\";\n\t" + "micalg=pgp-sha1" // FIXME: obtain this parameter from gpgme! + : + "multipart/encrypted;\n\t" + "boundary=\"%boundary\";\n\t" + "protocol=\"application/pgp-encrypted\"" + ; + case Kleo::SMIMEFormat: + if ( signing ) + return + "multipart/signed;\n\t" + "boundary=\"%boundary\";\n\t" + "protocol=\"application/pkcs7-signature\";\n\t" + "micalg=sha1"; // FIXME: obtain this parameter from gpgme! + // fall through (for encryption, there's no difference between + // SMIME and SMIMEOpaque, since there is no mp/encrypted for + // S/MIME): + case Kleo::SMIMEOpaqueFormat: + return signing ? + "application/pkcs7-mime;\n\t" + "smime-type=signed-data;\n\t" + "name=\"smime.p7m\";\n\t" + : + "application/pkcs7-mime;\n\t" + "smime-type=enveloped-data;\n\t" + "name=\"smime.p7m\";\n\t" + ; + } +} + +static inline const char * toplevelContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) { + switch ( f ) { + default: + case Kleo::InlineOpenPGPFormat: + case Kleo::OpenPGPMIMEFormat: + return 0; + case Kleo::SMIMEFormat: + if ( signing ) + return 0; + case Kleo::SMIMEOpaqueFormat: + return "attachment; filename=\"smime.p7m\""; + } +} + +static inline bool includeCleartextWhenSigning( Kleo::CryptoMessageFormat f ) { + return makeMultiPartSigned( f ); +} + +static inline const char * nestedContentType( Kleo::CryptoMessageFormat f, bool signing ) { + switch ( f ) { + case Kleo::OpenPGPMIMEFormat: + return signing ? "application/pgp-signature; name=signature.asc \nContent-Description: This is a digitally signed message part." : "application/octet-stream" ; + case Kleo::SMIMEFormat: + if ( signing ) + return "application/pkcs7-signature; name=\"smime.p7s\""; + // fall through: + default: + case Kleo::InlineOpenPGPFormat: + case Kleo::SMIMEOpaqueFormat: + return 0; + } +} + +static inline const char * nestedContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) { + if ( !signing && f == Kleo::OpenPGPMIMEFormat ) + return "inline; filename=\"msg.asc\""; + if ( signing && f == Kleo::SMIMEFormat ) + return "attachment; filename=\"smime.p7s\""; + return 0; +} + +static inline bool binaryHint( Kleo::CryptoMessageFormat f ) { + switch ( f ) { + case Kleo::SMIMEFormat: + case Kleo::SMIMEOpaqueFormat: + return true; + default: + case Kleo::OpenPGPMIMEFormat: + case Kleo::InlineOpenPGPFormat: + return false; + } +} + +static inline bool armor( Kleo::CryptoMessageFormat f ) { + return !binaryHint( f ); +} + +static inline bool textMode( Kleo::CryptoMessageFormat f ) { + return f == Kleo::InlineOpenPGPFormat; +} + +static inline GpgME::Context::SignatureMode signingMode( Kleo::CryptoMessageFormat f ) { + switch ( f ) { + case Kleo::SMIMEOpaqueFormat: + return GpgME::Context::Normal; + case Kleo::InlineOpenPGPFormat: + return GpgME::Context::Clearsigned; + default: + case Kleo::SMIMEFormat: + case Kleo::OpenPGPMIMEFormat: + return GpgME::Context::Detached; + } +} + +// +// END replacements for StructuringInfo(Wrapper) +// + +class EncryptMessageJob : public MessageComposerJob { +public: + EncryptMessageJob( KMMessage* msg, const Kleo::KeyResolver::SplitInfo & si, + bool doSign, bool doEncrypt, const QByteArray& encodedBody, + int boundaryLevel, /*const KMMessagePart& oldBodyPart,*/ + KMMessagePart* newBodyPart, Kleo::CryptoMessageFormat format, + MessageComposer* composer ) + : MessageComposerJob( composer ), mMsg( msg ), mSplitInfo( si ), + mDoSign( doSign ), mDoEncrypt( doEncrypt ), mEncodedBody( encodedBody ), + mBoundaryLevel( boundaryLevel ), /*mOldBodyPart( oldBodyPart ),*/ + mNewBodyPart( newBodyPart ), mFormat( format ) {} + + void execute() { + KMMessagePart tmpNewBodyPart; + tmpNewBodyPart.duplicate( *mNewBodyPart ); // slow - we duplicate everything again + + // TODO: Async call + + mComposer->encryptMessage( mMsg, mSplitInfo, mDoSign, mDoEncrypt, + tmpNewBodyPart, mFormat ); + if ( !mComposer->mRc ) { + delete mMsg; mMsg = 0; + return; + } + mComposer->mMessageList.push_back( mMsg ); + } + +private: + KMMessage* mMsg; + Kleo::KeyResolver::SplitInfo mSplitInfo; + bool mDoSign, mDoEncrypt; + QByteArray mEncodedBody; + int mBoundaryLevel; + //KMMessagePart mOldBodyPart; + KMMessagePart* mNewBodyPart; + Kleo::CryptoMessageFormat mFormat; +}; + +class SetLastMessageAsUnencryptedVersionOfLastButOne : public MessageComposerJob { +public: + SetLastMessageAsUnencryptedVersionOfLastButOne( MessageComposer * composer ) + : MessageComposerJob( composer ) {} + + void execute() { + KMMessage * last = mComposer->mMessageList.back(); + mComposer->mMessageList.pop_back(); + mComposer->mMessageList.back()->setUnencryptedMsg( last ); + } +}; + +void MessageComposer::composeInlineOpenPGPMessage( KMMessage& theMessage, + bool doSign, bool doEncrypt ) +{ + // preprocess the body text + const QByteArray bodyData = mText; + if (bodyData.isNull()) { + mRc = false; + return; + } + + mNewBodyPart = 0; // unused + mEarlyAddAttachments = false; + mAllAttachmentsAreInBody = false; + + // set the main headers + theMessage.deleteBodyParts(); + QString oldContentType = theMessage.headerField( "Content-Type" ); + theMessage.removeHeaderField("Content-Type"); + theMessage.removeHeaderField("Content-Transfer-Encoding"); + + const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos + = mKeyResolver->encryptionItems( Kleo::InlineOpenPGPFormat ); + kdWarning( splitInfos.empty() ) + << "MessageComposer::continueComposeMessage(): splitInfos.empty() for InlineOpenPGPFormat" + << endl; + std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it; + for ( it = splitInfos.begin() ; it != splitInfos.end() ; ++it ) { + const Kleo::KeyResolver::SplitInfo& splitInfo = *it; + KMMessage* msg = new KMMessage( theMessage ); + if ( doEncrypt ) { + Kpgp::Result result; + QByteArray encryptedBody; + if ( doSign ) { // Sign and encrypt + const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( Kleo::InlineOpenPGPFormat ); + result = pgpSignedAndEncryptedMsg( encryptedBody, bodyData, signingKeys, + splitInfo.keys, Kleo::InlineOpenPGPFormat ); + } else { // Encrypt but don't sign + result = pgpEncryptedMsg( encryptedBody, bodyData, + splitInfo.keys, Kleo::InlineOpenPGPFormat ); + } + if ( result != Kpgp::Ok ) { + mRc = false; + return; + } + assert( !encryptedBody.isNull() ); // if you hit this, check gpg-agent is running, then blame gpgme. + mOldBodyPart.setBodyEncodedBinary( encryptedBody ); + } else { + if ( doSign ) { // Sign but don't encrypt + pgpSignedMsg( bodyData, Kleo::InlineOpenPGPFormat ); + if ( mSignature.isNull() ) { + mRc = false; + return; + } + mOldBodyPart.setBodyEncodedBinary( mSignature ); + } else { // don't sign nor encrypt -> nothing to do + assert( !bodyData.isNull() ); + mOldBodyPart.setBodyEncodedBinary( bodyData ); + } + } + mOldBodyPart.setContentDisposition( "inline" ); + mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() ); + mOldBodyPart.setCharset(mCharset); + addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat ); + mMessageList.push_back( msg ); + if ( it == splitInfos.begin() ) { + if ( doEncrypt && !saveMessagesEncrypted() ) { + mOldBodyPart.setBodyEncodedBinary( bodyData ); + KMMessage* msgUnenc = new KMMessage( theMessage ); + addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat ); + msg->setUnencryptedMsg( msgUnenc ); + } + } + } // end for +} + +// very much inspired by composeInlineOpenPGPMessage +void MessageComposer::composeChiasmusMessage( KMMessage& theMessage, Kleo::CryptoMessageFormat format ) +{ + assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure + const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance(); + assert( cpf ); + const Kleo::CryptoBackend::Protocol * chiasmus + = cpf->protocol( "Chiasmus" ); + assert( chiasmus ); // kmcomposewin code should have made sure + + // preprocess the body text + const QByteArray bodyData = mText; + if (bodyData.isNull()) { + mRc = false; + return; + } + + mNewBodyPart = 0; // unused + mEarlyAddAttachments = false; + mAllAttachmentsAreInBody = false; + + // set the main headers + theMessage.deleteBodyParts(); + QString oldContentType = theMessage.headerField( "Content-Type" ); + theMessage.removeHeaderField("Content-Type"); + theMessage.removeHeaderField("Content-Transfer-Encoding"); + + // This reads strange, but we know that AdjustCryptFlagsJob created a single splitinfo, + // under the given "format" (usually openpgp/mime; doesn't matter) + const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos + = mKeyResolver->encryptionItems( format ); + assert( splitInfos.size() == 1 ); + for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it ) + { + const Kleo::KeyResolver::SplitInfo& splitInfo = *it; + KMMessage* msg = new KMMessage( theMessage ); + QByteArray encryptedBody; + + if ( !encryptWithChiasmus( chiasmus, bodyData, encryptedBody ) ) { + mRc = false; + return; + } + assert( !encryptedBody.isNull() ); + // This leaves CTE==7-bit, no good + //mOldBodyPart.setBodyEncodedBinary( encryptedBody ); + + bool doSign = false; + QValueList<int> allowedCTEs; + mOldBodyPart.setBodyAndGuessCte( encryptedBody, allowedCTEs, + !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, + doSign ); + + + mOldBodyPart.setContentDisposition( "inline" ); + // Used in case of no attachments + mOldBodyPart.setOriginalContentTypeStr( "application/vnd.de.bund.bsi.chiasmus-text;chiasmus-charset=" + mCharset ); + // Used in case of attachments + mOldBodyPart.setTypeStr( "application" ); + mOldBodyPart.setSubtypeStr( "vnd.de.bund.bsi.chiasmus-text" ); + mOldBodyPart.setAdditionalCTypeParamStr( QCString( "chiasmus-charset=" + mCharset ) ); + addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat ); + mMessageList.push_back( msg ); + + if ( it == splitInfos.begin() && !saveMessagesEncrypted() ) { + mOldBodyPart.setBodyEncodedBinary( bodyData ); + KMMessage* msgUnenc = new KMMessage( theMessage ); + addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat ); + msg->setUnencryptedMsg( msgUnenc ); + } + } +} + +void MessageComposer::composeMessage( KMMessage& theMessage, + bool doSign, bool doEncrypt, + Kleo::CryptoMessageFormat format ) +{ +#ifdef DEBUG + kdDebug(5006) << "entering KMComposeWin::composeMessage" << endl; +#endif + if ( format == Kleo::InlineOpenPGPFormat ) { + composeInlineOpenPGPMessage( theMessage, doSign, doEncrypt ); + return; + } + + if ( mEncryptWithChiasmus ) + { + composeChiasmusMessage( theMessage, format ); + return; + } + + // create informative header for those that have no mime-capable + // email client + theMessage.setBody( "This message is in MIME format." ); + + // preprocess the body text + QByteArray bodyData = mText; + if (bodyData.isNull()) { + mRc = false; + return; + } + + // set the main headers + QString oldContentType = theMessage.headerField( "Content-Type" ); + theMessage.deleteBodyParts(); + theMessage.removeHeaderField("Content-Type"); + theMessage.removeHeaderField("Content-Transfer-Encoding"); + theMessage.setAutomaticFields(true); // == multipart/mixed + + // this is our *final* body part + mNewBodyPart = new KMMessagePart; + + // this is the boundary depth of the surrounding MIME part + mPreviousBoundaryLevel = 0; + + // whether the body must be signed/encrypted + const bool doEncryptBody = doEncrypt && mEncryptBody; + const bool doSignBody = doSign && mSignBody; + + // create temporary bodyPart for editor text + // (and for all attachments, if mail is to be signed and/or encrypted) + mEarlyAddAttachments = !mAttachments.empty() && ( doSignBody || doEncryptBody ); + + mAllAttachmentsAreInBody = mEarlyAddAttachments; + + // test whether there ARE attachments that can be included into the body + if( mEarlyAddAttachments ) { + bool someOk = false; + for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) { + if ( it->encrypt == doEncryptBody && it->sign == doSignBody ) + someOk = true; + else + mAllAttachmentsAreInBody = false; + } + if( !mAllAttachmentsAreInBody && !someOk ) + mEarlyAddAttachments = false; + } + + kdDebug(5006) << "mEarlyAddAttachments=" << mEarlyAddAttachments << " mAllAttachmentsAreInBody=" << mAllAttachmentsAreInBody << endl; + + // if an html message is to be generated, make a text/plain and text/html part + mMultipartMixedBoundary = ""; + if ( mEarlyAddAttachments ) { + mOldBodyPart.setTypeStr( "multipart" ); + mOldBodyPart.setSubtypeStr( "mixed" ); + // calculate a boundary string + DwMediaType tmpCT; + tmpCT.CreateBoundary( ++mPreviousBoundaryLevel ); + mMultipartMixedBoundary = tmpCT.Boundary().c_str(); + } + else if ( mIsRichText ) { + mOldBodyPart.setTypeStr( "multipart" ); + mOldBodyPart.setSubtypeStr( "alternative" ); + } + else + mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() ); + + mOldBodyPart.setContentDisposition( "inline" ); + + if ( mIsRichText ) { // create a multipart body + // calculate a boundary string + QCString boundaryCStr; // storing boundary string data + QCString newbody; + DwMediaType tmpCT; + tmpCT.CreateBoundary( ++mPreviousBoundaryLevel ); + boundaryCStr = KMail::Util::CString( tmpCT.Boundary() ); + QValueList<int> allowedCTEs; + + KMMessagePart textBodyPart; + textBodyPart.setTypeStr("text"); + textBodyPart.setSubtypeStr("plain"); + + QCString textbody = plainTextFromMarkup( mText /* converted to QString */ ); + + // the signed body must not be 8bit encoded + textBodyPart.setBodyAndGuessCte( textbody, allowedCTEs, + !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, + doSign ); + textBodyPart.setCharset( mCharset ); + textBodyPart.setBodyEncoded( textbody ); + DwBodyPart* textDwPart = theMessage.createDWBodyPart( &textBodyPart ); + textDwPart->Assemble(); + newbody += "--"; + newbody += boundaryCStr; + newbody += "\n"; + newbody += textDwPart->AsString().c_str(); + delete textDwPart; + textDwPart = 0; + + KMMessagePart htmlBodyPart; + htmlBodyPart.setTypeStr("text"); + htmlBodyPart.setSubtypeStr("html"); + QByteArray htmlbody = mText; + // the signed body must not be 8bit encoded + htmlBodyPart.setBodyAndGuessCte( htmlbody, allowedCTEs, + !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, + doSign ); + htmlBodyPart.setCharset( mCharset ); + htmlBodyPart.setBodyEncodedBinary( htmlbody ); + DwBodyPart* htmlDwPart = theMessage.createDWBodyPart( &htmlBodyPart ); + htmlDwPart->Assemble(); + newbody += "\n--"; + newbody += boundaryCStr; + newbody += "\n"; + newbody += htmlDwPart->AsString().c_str(); + delete htmlDwPart; + htmlDwPart = 0; + + newbody += "--"; + newbody += boundaryCStr; + newbody += "--\n"; + bodyData = KMail::Util::byteArrayFromQCStringNoDetach( newbody ); + mOldBodyPart.setBodyEncodedBinary( bodyData ); + + mSaveBoundary = tmpCT.Boundary(); + } + + // Prepare attachments that will be signed/encrypted + for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) { + // signed/encrypted body parts must be either QP or base64 encoded + // Why not 7 bit? Because the LF->CRLF canonicalization would render + // e.g. 7 bit encoded shell scripts unusable because of the CRs. + // + // (marc) this is a workaround for the KMail bug that doesn't + // respect the CRLF->LF de-canonicalisation. We should + // eventually get rid of this: + if( it->sign || it->encrypt ) { + QCString cte = it->part->cteStr().lower(); + if( ( "8bit" == cte && it->part->type() != DwMime::kTypeMessage ) + || ( ( it->part->type() == DwMime::kTypeText ) + && ( "7bit" == cte ) ) ) { + const QByteArray body = it->part->bodyDecodedBinary(); + QValueList<int> dummy; + it->part->setBodyAndGuessCte(body, dummy, false, it->sign); + kdDebug(5006) << "Changed encoding of message part from " + << cte << " to " << it->part->cteStr() << endl; + } + } + } + + if( mEarlyAddAttachments ) { + // add the normal body text + KMMessagePart innerBodyPart; + if ( mIsRichText ) { + innerBodyPart.setTypeStr( "multipart");//text" ); + innerBodyPart.setSubtypeStr("alternative");//html"); + } + else { + innerBodyPart.setOriginalContentTypeStr( oldContentType.utf8() ); + } + innerBodyPart.setContentDisposition( "inline" ); + QValueList<int> allowedCTEs; + // the signed body must not be 8bit encoded + innerBodyPart.setBodyAndGuessCte( bodyData, allowedCTEs, + !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, + doSign ); + if ( !mIsRichText ) + innerBodyPart.setCharset( mCharset ); + innerBodyPart.setBodyEncodedBinary( bodyData ); // do we need this, since setBodyAndGuessCte does this already? + DwBodyPart* innerDwPart = theMessage.createDWBodyPart( &innerBodyPart ); + innerDwPart->Assemble(); + QByteArray tmpbody = KMail::Util::ByteArray( innerDwPart->AsString() ); + if ( mIsRichText ) { // and add our mp/a boundary + int boundPos = tmpbody.find( '\n' ); + if( -1 < boundPos ) { + QCString bStr( ";\n boundary=\"" ); + bStr += mSaveBoundary.c_str(); + bStr += "\""; + bodyData = tmpbody; + KMail::Util::insert( bodyData, boundPos, bStr ); + KMail::Util::insert( bodyData, 0, "--" + mMultipartMixedBoundary + "\n" ); // prepend + } + } + else { + bodyData = tmpbody; + KMail::Util::insert( bodyData, 0, "--" + mMultipartMixedBoundary + "\n" ); // prepend + } + delete innerDwPart; + innerDwPart = 0; + // add all matching Attachments + // NOTE: This code will be changed when KMime is complete. + for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) { + if ( it->encrypt == doEncryptBody && it->sign == doSignBody ) { + innerDwPart = theMessage.createDWBodyPart( it->part ); + innerDwPart->Assemble(); + KMail::Util::append( bodyData, QCString( "\n--" + mMultipartMixedBoundary + "\n" ) ); + KMail::Util::append( bodyData, innerDwPart->AsString().c_str() ); + delete innerDwPart; + innerDwPart = 0; + } + } + KMail::Util::append( bodyData, QCString( "\n--" + mMultipartMixedBoundary + "--\n" ) ); + } else { // !earlyAddAttachments + QValueList<int> allowedCTEs; + // the signed body must not be 8bit encoded + mOldBodyPart.setBodyAndGuessCte(bodyData, allowedCTEs, !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, + doSign); + if ( !mIsRichText ) + mOldBodyPart.setCharset(mCharset); + } + // create S/MIME body part for signing and/or encrypting + mOldBodyPart.setBodyEncodedBinary( bodyData ); + + if( doSignBody || doEncryptBody ) { + // get string representation of body part (including the attachments) + + DwBodyPart* dwPart; + if ( mIsRichText && !mEarlyAddAttachments ) { + // if we are using richtext and not already have a mp/a body + // make the body a mp/a body + dwPart = theMessage.createDWBodyPart( &mOldBodyPart ); + DwHeaders& headers = dwPart->Headers(); + DwMediaType& ct = headers.ContentType(); + ct.SetBoundary(mSaveBoundary); + dwPart->Assemble(); + } + else { + dwPart = theMessage.createDWBodyPart( &mOldBodyPart ); + dwPart->Assemble(); + } + mEncodedBody = KMail::Util::ByteArray( dwPart->AsString() ); + delete dwPart; + dwPart = 0; + + // manually add a boundary definition to the Content-Type header + if( !mMultipartMixedBoundary.isEmpty() ) { + int boundPos = mEncodedBody.find( '\n' ); + if( -1 < boundPos ) { + // insert new "boundary" parameter + QCString bStr( ";\n boundary=\"" ); + bStr += mMultipartMixedBoundary; + bStr += "\""; + KMail::Util::insert( mEncodedBody, boundPos, bStr.data() ); + } + } + + // replace simple LFs by CRLFs for all MIME supporting CryptPlugs + // according to RfC 2633, 3.1.1 Canonicalization + //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; + mEncodedBody = KMail::Util::lf2crlf( mEncodedBody ); + } + + if ( doSignBody ) { + mPerformingSignOperation = true; // this lets the KMComposeWin know if it is safe to close the window. + pgpSignedMsg( mEncodedBody, format ); + mPerformingSignOperation = false; + + if ( mSignature.isEmpty() ) { + kdDebug() << "signature was empty" << endl; + mRc = false; + return; + } + mRc = processStructuringInfo( QString::null, + mOldBodyPart.contentDescription(), + mOldBodyPart.typeStr(), + mOldBodyPart.subtypeStr(), + mOldBodyPart.contentDisposition(), + mOldBodyPart.contentTransferEncodingStr(), + mEncodedBody, "signature", + mSignature, + *mNewBodyPart, true, format ); + if ( mRc ) { + if ( !makeMultiPartSigned( format ) ) { + mNewBodyPart->setCharset( mCharset ); + } + } else + KMessageBox::sorry( mComposeWin, + mErrorProcessingStructuringInfo ); + } + + if ( !mRc ) + return; + + continueComposeMessage( theMessage, doSign, doEncrypt, format ); +} + +// Do the encryption stuff +void MessageComposer::continueComposeMessage( KMMessage& theMessage, + bool doSign, bool doEncrypt, + Kleo::CryptoMessageFormat format ) +{ + + const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos + = mKeyResolver->encryptionItems( format ); + kdWarning( splitInfos.empty() ) + << "MessageComposer::continueComposeMessage(): splitInfos.empty() for " + << Kleo::cryptoMessageFormatToString( format ) << endl; + + if ( !splitInfos.empty() && doEncrypt && !saveMessagesEncrypted() ) { + mJobs.push_front( new SetLastMessageAsUnencryptedVersionOfLastButOne( this ) ); + mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ), + Kleo::KeyResolver::SplitInfo( splitInfos.front().recipients ), doSign, + false, mEncodedBody, + mPreviousBoundaryLevel, + /*mOldBodyPart,*/ mNewBodyPart, + format, this ) ); + } + + for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it ) + mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ), *it, doSign, + doEncrypt, mEncodedBody, + mPreviousBoundaryLevel, + /*mOldBodyPart,*/ mNewBodyPart, + format, this ) ); +} + +void MessageComposer::encryptMessage( KMMessage* msg, + const Kleo::KeyResolver::SplitInfo & splitInfo, + bool doSign, bool doEncrypt, + KMMessagePart newBodyPart, + Kleo::CryptoMessageFormat format ) +{ + if ( doEncrypt && splitInfo.keys.empty() ) { + // the user wants to send the message unencrypted + //mComposeWin->setEncryption( false, false ); + //FIXME why is this talkback needed? Till + doEncrypt = false; + } + + const bool doEncryptBody = doEncrypt && mEncryptBody; + const bool doSignBody = doSign && mSignBody; + + if ( doEncryptBody ) { + QByteArray innerContent; + if ( doSignBody ) { + // extract signed body from newBodyPart + DwBodyPart* dwPart = msg->createDWBodyPart( &newBodyPart ); + dwPart->Assemble(); + innerContent = KMail::Util::ByteArray( dwPart->AsString() ); + delete dwPart; + dwPart = 0; + } else { + innerContent = mEncodedBody; + } + + // now do the encrypting: + // replace simple LFs by CRLFs for all MIME supporting CryptPlugs + // according to RfC 2633, 3.1.1 Canonicalization + //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; + innerContent = KMail::Util::lf2crlf( innerContent ); + //kdDebug(5006) << " done." << endl; + + QByteArray encryptedBody; + Kpgp::Result result = pgpEncryptedMsg( encryptedBody, innerContent, + splitInfo.keys, format ); + if ( result != Kpgp::Ok ) { + mRc = false; + return; + } + mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/", + newBodyPart.contentDescription(), + newBodyPart.typeStr(), + newBodyPart.subtypeStr(), + newBodyPart.contentDisposition(), + newBodyPart.contentTransferEncodingStr(), + innerContent, + "encrypted data", + encryptedBody, + newBodyPart, false, format ); + if ( !mRc ) + KMessageBox::sorry(mComposeWin, mErrorProcessingStructuringInfo); + } + + // process the attachments that are not included into the body + if( mRc ) { + const bool useNewBodyPart = doSignBody || doEncryptBody; + addBodyAndAttachments( msg, splitInfo, doSign, doEncrypt, + useNewBodyPart ? newBodyPart : mOldBodyPart, format ); + } +} + +void MessageComposer::addBodyAndAttachments( KMMessage* msg, + const Kleo::KeyResolver::SplitInfo & splitInfo, + bool doSign, bool doEncrypt, + const KMMessagePart& ourFineBodyPart, + Kleo::CryptoMessageFormat format ) +{ + const bool doEncryptBody = doEncrypt && mEncryptBody; + const bool doSignBody = doSign && mSignBody; + + if( !mAttachments.empty() + && ( !mEarlyAddAttachments || !mAllAttachmentsAreInBody ) ) { + // set the content type header + msg->headers().ContentType().SetType( DwMime::kTypeMultipart ); + msg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed ); + msg->headers().ContentType().CreateBoundary( 0 ); + kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to Multipart/Mixed" << endl; + + // add our Body Part + DwBodyPart* tmpDwPart = msg->createDWBodyPart( &ourFineBodyPart ); + DwHeaders& headers = tmpDwPart->Headers(); + DwMediaType& ct = headers.ContentType(); + if ( !mSaveBoundary.empty() ) + ct.SetBoundary(mSaveBoundary); + tmpDwPart->Assemble(); + + //KMMessagePart newPart; + //newPart.setBody(tmpDwPart->AsString().c_str()); + msg->addDwBodyPart(tmpDwPart); // only this method doesn't add it as text/plain + + // add Attachments + // create additional bodyparts for the attachments (if any) + KMMessagePart newAttachPart; + for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) { + + const bool cryptFlagsDifferent = ( it->encrypt != doEncryptBody || it->sign != doSignBody ) ; + + if ( !cryptFlagsDifferent && mEarlyAddAttachments ) + continue; + + const bool encryptThisNow = doEncrypt && cryptFlagsDifferent && it->encrypt ; + const bool signThisNow = doSign && cryptFlagsDifferent && it->sign ; + + if ( !encryptThisNow && !signThisNow ) { + msg->addBodyPart( it->part ); + // Assemble the message. Not sure why, but this fixes the vanishing boundary parameter + (void)msg->asDwMessage(); + continue; + } + + KMMessagePart& rEncryptMessagePart( *it->part ); + + DwBodyPart* innerDwPart = msg->createDWBodyPart( it->part ); + innerDwPart->Assemble(); + QByteArray encodedAttachment = KMail::Util::ByteArray( innerDwPart->AsString() ); + delete innerDwPart; + innerDwPart = 0; + + // replace simple LFs by CRLFs for all MIME supporting CryptPlugs + // according to RfC 2633, 3.1.1 Canonicalization + //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; + encodedAttachment = KMail::Util::lf2crlf( encodedAttachment ); + + // sign this attachment + if( signThisNow ) { + pgpSignedMsg( encodedAttachment, format ); + mRc = !mSignature.isEmpty(); + if( mRc ) { + mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/", + it->part->contentDescription(), + it->part->typeStr(), + it->part->subtypeStr(), + it->part->contentDisposition(), + it->part->contentTransferEncodingStr(), + encodedAttachment, + "signature", + mSignature, + newAttachPart, true, format ); + if( mRc ) { + if( encryptThisNow ) { + rEncryptMessagePart = newAttachPart; + DwBodyPart* dwPart = msg->createDWBodyPart( &newAttachPart ); + dwPart->Assemble(); + encodedAttachment = KMail::Util::ByteArray( dwPart->AsString() ); + delete dwPart; + dwPart = 0; + } + } else + KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo ); + } else { + // quit the attachments' loop + break; + } + } + if( encryptThisNow ) { + QByteArray encryptedBody; + Kpgp::Result result = pgpEncryptedMsg( encryptedBody, + encodedAttachment, + splitInfo.keys, + format ); + + if( Kpgp::Ok == result ) { + mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/", + rEncryptMessagePart.contentDescription(), + rEncryptMessagePart.typeStr(), + rEncryptMessagePart.subtypeStr(), + rEncryptMessagePart.contentDisposition(), + rEncryptMessagePart.contentTransferEncodingStr(), + encodedAttachment, + "encrypted data", + encryptedBody, + newAttachPart, false, format ); + if ( !mRc ) + KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo ); + } else + mRc = false; + } + msg->addBodyPart( &newAttachPart ); + (void)msg->asDwMessage(); // Assemble the message. One gets a completely empty message otherwise :/ + } + } else { // no attachments in the final message + if( ourFineBodyPart.originalContentTypeStr() ) { + msg->headers().ContentType().FromString( ourFineBodyPart.originalContentTypeStr() ); + msg->headers().ContentType().Parse(); + kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type from originalContentTypeStr()=" << ourFineBodyPart.originalContentTypeStr() << endl; + } else { + QCString ct = ourFineBodyPart.typeStr() + "/" + ourFineBodyPart.subtypeStr(); + if ( ct == "multipart/mixed" ) + ct += ";\n\tboundary=\"" + mMultipartMixedBoundary + '"'; + else if ( ct == "multipart/alternative" ) + ct += ";\n\tboundary=\"" + QCString(mSaveBoundary.c_str()) + '"'; + msg->headers().ContentType().FromString( ct ); + msg->headers().ContentType().Parse(); + kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to " << ct << endl; + } + if ( !ourFineBodyPart.charset().isEmpty() ) + msg->setCharset( ourFineBodyPart.charset() ); + msg->setHeaderField( "Content-Transfer-Encoding", + ourFineBodyPart.contentTransferEncodingStr() ); + msg->setHeaderField( "Content-Description", + ourFineBodyPart.contentDescription() ); + msg->setHeaderField( "Content-Disposition", + ourFineBodyPart.contentDisposition() ); + + if ( mDebugComposerCrypto ) + kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : top level headers and body adjusted" << endl; + + // set body content + msg->setBody( ourFineBodyPart.dwBody() ); + + } + + msg->setHeaderField( "X-KMail-Recipients", + splitInfo.recipients.join(", "), KMMessage::Address ); + + if ( mDebugComposerCrypto ) { + kdDebug(5006) << "MessageComposer::addBodyAndAttachments():\n Final message:\n|||" << msg->asString() << "|||\n\n" << endl; + msg->headers().Assemble(); + kdDebug(5006) << "\n\n\nMessageComposer::addBodyAndAttachments():\n Final headers:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; + } +} + +//----------------------------------------------------------------------------- +// This method does not call any crypto ops, so it does not need to be async +bool MessageComposer::processStructuringInfo( const QString bugURL, + const QString contentDescClear, + const QCString contentTypeClear, + const QCString contentSubtypeClear, + const QCString contentDispClear, + const QCString contentTEncClear, + const QByteArray& clearCStr, + const QString /*contentDescCiph*/, + const QByteArray& ciphertext, + KMMessagePart& resultingPart, + bool signing, Kleo::CryptoMessageFormat format ) +{ + assert( clearCStr.isEmpty() || clearCStr[clearCStr.size()-1] != '\0' ); // I was called with a QCString !? + bool bOk = true; + + if ( makeMimeObject( format, signing ) ) { + QCString mainHeader = "Content-Type: "; + const char * toplevelCT = toplevelContentType( format, signing ); + if ( toplevelCT ) + mainHeader += toplevelCT; + else { + if( makeMultiMime( format, signing ) ) + mainHeader += "text/plain"; + else + mainHeader += contentTypeClear + '/' + contentSubtypeClear; + } + + const QCString boundaryCStr = KMime::multiPartBoundary(); + // add "boundary" parameter + if ( makeMultiMime( format, signing ) ) + mainHeader.replace( "%boundary", boundaryCStr ); + + if ( toplevelCT ) { + if ( const char * str = toplevelContentDisposition( format, signing ) ) { + mainHeader += "\nContent-Disposition: "; + mainHeader += str; + } + if ( !makeMultiMime( format, signing ) && + binaryHint( format ) ) + mainHeader += "\nContent-Transfer-Encoding: base64"; + } else { + if( 0 < contentDispClear.length() ) { + mainHeader += "\nContent-Disposition: "; + mainHeader += contentDispClear; + } + if( 0 < contentTEncClear.length() ) { + mainHeader += "\nContent-Transfer-Encoding: "; + mainHeader += contentTEncClear; + } + } + + //kdDebug(5006) << "processStructuringInfo: mainHeader=" << mainHeader << endl; + + DwString mainDwStr; + mainDwStr = mainHeader + "\n\n"; + DwBodyPart mainDwPa( mainDwStr, 0 ); + mainDwPa.Parse(); + KMMessage::bodyPart( &mainDwPa, &resultingPart ); + if( !makeMultiMime( format, signing ) ) { + if ( signing && includeCleartextWhenSigning( format ) ) { + QByteArray bodyText( clearCStr ); + KMail::Util::append( bodyText, "\n" ); + KMail::Util::append( bodyText, ciphertext ); + resultingPart.setBodyEncodedBinary( bodyText ); + } else { + resultingPart.setBodyEncodedBinary( ciphertext ); + } + } else { + // Build the encapsulated MIME parts. + // Build a MIME part holding the version information + // taking the body contents returned in + // structuring.data.bodyTextVersion. + QCString versCStr, codeCStr; + if ( !signing && format == Kleo::OpenPGPMIMEFormat ) + versCStr = + "Content-Type: application/pgp-encrypted\n" + "Content-Disposition: attachment\n" + "\n" + "Version: 1"; + + // Build a MIME part holding the code information + // taking the body contents returned in ciphertext. + const char * nestedCT = nestedContentType( format, signing ); + assert( nestedCT ); + codeCStr = "Content-Type: "; + codeCStr += nestedCT; + codeCStr += '\n'; + if ( const char * str = nestedContentDisposition( format, signing ) ) { + codeCStr += "Content-Disposition: "; + codeCStr += str; + codeCStr += '\n'; + } + if ( binaryHint( format ) ) { + codeCStr += "Content-Transfer-Encoding: base64\n\n"; + codeCStr += KMime::Codec::codecForName( "base64" )->encodeToQCString( ciphertext ); + } else + codeCStr += '\n' + QCString( ciphertext.data(), ciphertext.size() + 1 ); + + + QByteArray mainStr; + KMail::Util::append( mainStr, "--" ); + KMail::Util::append( mainStr, boundaryCStr ); + if ( signing && includeCleartextWhenSigning( format ) && + !clearCStr.isEmpty() ) { + KMail::Util::append( mainStr, "\n" ); + // clearCStr is the one that can be very big for large attachments, don't merge with the other lines + KMail::Util::append( mainStr, clearCStr ); + KMail::Util::append( mainStr, "\n--" + boundaryCStr ); + } + if ( !versCStr.isEmpty() ) + KMail::Util::append( mainStr, "\n" + versCStr + "\n--" + boundaryCStr ); + if( !codeCStr.isEmpty() ) + KMail::Util::append( mainStr, "\n" + codeCStr + "\n--" + boundaryCStr ); + KMail::Util::append( mainStr, "--\n" ); + + //kdDebug(5006) << "processStructuringInfo: mainStr=" << mainStr << endl; + resultingPart.setBodyEncodedBinary( mainStr ); + } + + } else { // not making a mime object, build a plain message body. + + resultingPart.setContentDescription( contentDescClear ); + resultingPart.setTypeStr( contentTypeClear ); + resultingPart.setSubtypeStr( contentSubtypeClear ); + resultingPart.setContentDisposition( contentDispClear ); + resultingPart.setContentTransferEncodingStr( contentTEncClear ); + QByteArray resultingBody; + + if ( signing && includeCleartextWhenSigning( format ) ) { + if( !clearCStr.isEmpty() ) + KMail::Util::append( resultingBody, clearCStr ); + } + if ( !ciphertext.isEmpty() ) + KMail::Util::append( resultingBody, ciphertext ); + else { + // Plugin error! + KMessageBox::sorry( mComposeWin, + i18n( "<qt><p>Error: The backend did not return " + "any encoded data.</p>" + "<p>Please report this bug:<br>%2</p></qt>" ) + .arg( bugURL ) ); + bOk = false; + } + resultingPart.setBodyEncodedBinary( resultingBody ); + } + + return bOk; +} + +//----------------------------------------------------------------------------- +QCString MessageComposer::plainTextFromMarkup( const QString& markupText ) +{ + QTextEdit *hackConspiratorTextEdit = new QTextEdit( markupText ); + hackConspiratorTextEdit->setTextFormat(Qt::PlainText); + if ( !mDisableBreaking ) { + hackConspiratorTextEdit->setWordWrap( QTextEdit::FixedColumnWidth ); + hackConspiratorTextEdit->setWrapColumnOrWidth( mLineBreakColumn ); + } + QString text = hackConspiratorTextEdit->text(); + QCString textbody; + + const QTextCodec *codec = KMMsgBase::codecForName( mCharset ); + if( mCharset == "us-ascii" ) { + textbody = KMMsgBase::toUsAscii( text ); + } else if( codec == 0 ) { + kdDebug(5006) << "Something is wrong and I can not get a codec." << endl; + textbody = text.local8Bit(); + } else { + text = codec->toUnicode( text.latin1(), text.length() ); + textbody = codec->fromUnicode( text ); + } + if (textbody.isNull()) textbody = ""; + + delete hackConspiratorTextEdit; + return textbody; +} + +//----------------------------------------------------------------------------- +QByteArray MessageComposer::breakLinesAndApplyCodec() +{ + QString text; + QCString cText; + + if( mDisableBreaking || mIsRichText || !GlobalSettings::self()->wordWrap() ) + text = mComposeWin->mEditor->text(); + else + text = mComposeWin->mEditor->brokenText(); + text.truncate( text.length() ); // to ensure text.size()==text.length()+1 + + QString newText; + const QTextCodec *codec = KMMsgBase::codecForName( mCharset ); + + if( mCharset == "us-ascii" ) { + cText = KMMsgBase::toUsAscii( text ); + newText = QString::fromLatin1( cText ); + } else if( codec == 0 ) { + kdDebug(5006) << "Something is wrong and I can not get a codec." << endl; + cText = text.local8Bit(); + newText = QString::fromLocal8Bit( cText ); + } else { + cText = codec->fromUnicode( text ); + newText = codec->toUnicode( cText ); + } + if (cText.isNull()) cText = ""; + + if( !text.isEmpty() && (newText != text) ) { + QString oldText = mComposeWin->mEditor->text(); + mComposeWin->mEditor->setText( newText ); + KCursorSaver idle( KBusyPtr::idle() ); + bool anyway = ( KMessageBox::warningYesNo( mComposeWin, + i18n("<qt>Not all characters fit into the chosen" + " encoding.<br><br>Send the message anyway?</qt>"), + i18n("Some Characters Will Be Lost"), + i18n("Lose Characters"), i18n("Change Encoding") ) == KMessageBox::Yes ); + if( !anyway ) { + mComposeWin->mEditor->setText(oldText); + return QByteArray(); + } + } + + // From RFC 3156: + // Note: The accepted OpenPGP convention is for signed data to end + // with a <CR><LF> sequence. Note that the <CR><LF> sequence + // immediately preceding a MIME boundary delimiter line is considered + // to be part of the delimiter in [3], 5.1. Thus, it is not part of + // the signed data preceding the delimiter line. An implementation + // which elects to adhere to the OpenPGP convention has to make sure + // it inserts a <CR><LF> pair on the last line of the data to be + // signed and transmitted (signed message and transmitted message + // MUST be identical). + // So make sure that the body ends with a <LF>. + if( cText.isEmpty() || cText[cText.length()-1] != '\n' ) { + kdDebug(5006) << "Added an <LF> on the last line" << endl; + cText += "\n"; + } + return KMail::Util::byteArrayFromQCStringNoDetach( cText ); +} + + +//----------------------------------------------------------------------------- +void MessageComposer::pgpSignedMsg( const QByteArray& cText, Kleo::CryptoMessageFormat format ) { + + assert( cText.isEmpty() || cText[cText.size()-1] != '\0' ); // I was called with a QCString !? + mSignature = QByteArray(); + + const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( format ); + + assert( !signingKeys.empty() ); + + // TODO: ASync call? Likely, yes :-) + const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance(); + assert( cpf ); + const Kleo::CryptoBackend::Protocol * proto + = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ; + assert( proto ); /// hmmm.....? + + std::auto_ptr<Kleo::SignJob> job( proto->signJob( armor( format ), + textMode( format ) ) ); + + if ( !job.get() ) { + KMessageBox::sorry( mComposeWin, + i18n("This message could not be signed, " + "since the chosen backend does not seem to support " + "signing; this should actually never happen, " + "please report this bug.") ); + return; + } + + QByteArray signature; + const GpgME::SigningResult res = + job->exec( signingKeys, cText, signingMode( format ), signature ); + if ( res.error().isCanceled() ) { + kdDebug() << "signing was canceled by user" << endl; + return; + } + if ( res.error() ) { + kdDebug() << "signing failed: " << res.error().asString() << endl; + job->showErrorDialog( mComposeWin ); + return; + } + + if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Signing Operation") ); + + mSignature = signature; + if ( mSignature.isEmpty() ) { + KMessageBox::sorry( mComposeWin, + i18n( "The signing operation failed. " + "Please make sure that the gpg-agent program " + "is running." ) ); + } +} + +//----------------------------------------------------------------------------- +Kpgp::Result MessageComposer::pgpEncryptedMsg( QByteArray & encryptedBody, + const QByteArray& cText, + const std::vector<GpgME::Key> & encryptionKeys, + Kleo::CryptoMessageFormat format ) +{ + // TODO: ASync call? Likely, yes :-) + const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance(); + assert( cpf ); + const Kleo::CryptoBackend::Protocol * proto + = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ; + assert( proto ); // hmmmm....? + + std::auto_ptr<Kleo::EncryptJob> job( proto->encryptJob( armor( format ), + textMode( format ) ) ); + if ( !job.get() ) { + KMessageBox::sorry( mComposeWin, + i18n("This message could not be encrypted, " + "since the chosen backend does not seem to support " + "encryption; this should actually never happen, " + "please report this bug.") ); + return Kpgp::Failure; + } + + const GpgME::EncryptionResult res = + job->exec( encryptionKeys, cText, true /* we do ownertrust ourselves */, encryptedBody ); + if ( res.error().isCanceled() ) { + kdDebug() << "encryption was canceled by user" << endl; + return Kpgp::Canceled; + } + if ( res.error() ) { + kdDebug() << "encryption failed: " << res.error().asString() << endl; + job->showErrorDialog( mComposeWin ); + return Kpgp::Failure; + } + + if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") ); + + return Kpgp::Ok; +} + +Kpgp::Result MessageComposer::pgpSignedAndEncryptedMsg( QByteArray & encryptedBody, + const QByteArray& cText, + const std::vector<GpgME::Key> & signingKeys, + const std::vector<GpgME::Key> & encryptionKeys, + Kleo::CryptoMessageFormat format ) +{ + // TODO: ASync call? Likely, yes :-) + const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance(); + assert( cpf ); + const Kleo::CryptoBackend::Protocol * proto + = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ; + assert( proto ); // hmmmm....? + + std::auto_ptr<Kleo::SignEncryptJob> job( proto->signEncryptJob( armor( format ), + textMode( format ) ) ); + if ( !job.get() ) { + KMessageBox::sorry( mComposeWin, + i18n("This message could not be signed and encrypted, " + "since the chosen backend does not seem to support " + "combined signing and encryption; this should actually never happen, " + "please report this bug.") ); + return Kpgp::Failure; + } + + const std::pair<GpgME::SigningResult,GpgME::EncryptionResult> res = + job->exec( signingKeys, encryptionKeys, cText, false, encryptedBody ); + if ( res.first.error().isCanceled() || res.second.error().isCanceled() ) { + kdDebug() << "encrypt/sign was canceled by user" << endl; + return Kpgp::Canceled; + } + if ( res.first.error() || res.second.error() ) { + if ( res.first.error() ) + kdDebug() << "signing failed: " << res.first.error().asString() << endl; + else + kdDebug() << "encryption failed: " << res.second.error().asString() << endl; + job->showErrorDialog( mComposeWin ); + return Kpgp::Failure; + } + + if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") ); + + return Kpgp::Ok; +} + + +#include "messagecomposer.moc" |