You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2439 lines
100 KiB
2439 lines
100 KiB
/** |
|
* messagecomposer.cpp |
|
* |
|
* Copyright (c) 2004 Bo Thorsen <bo@klaralvdalens-datakonsult.se> |
|
* |
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" |
|
#include "kmcomposewin.h" |
|
#include "kmmessage.h" |
|
#include "klistboxdialog.h" |
|
#include "kcursorsaver.h" |
|
#include "kmkernel.h" |
|
#include "kmsender.h" |
|
#include "kmfolder.h" |
|
#include "kmfoldercombobox.h" |
|
#include <libkdepim/identity.h> |
|
#include <libkdepim/identitymanager.h> |
|
#include <libkdepim/identitycombo.h> |
|
#include <libkdepim/email.h> |
|
|
|
#include <kpgpblock.h> |
|
#include <mimelib/mimepp.h> |
|
#include <cryptplugwrapper.h> |
|
|
|
#include <kmessagebox.h> |
|
#include <klocale.h> |
|
#include <kinputdialog.h> |
|
#include <kdebug.h> |
|
#include <kaction.h> |
|
#include <qfile.h> |
|
#include <qtextcodec.h> |
|
#include <qtimer.h> |
|
|
|
|
|
/* |
|
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 implements the execute() method and performs |
|
it's operation there. When execute returns, this returns to the event |
|
queue and returns to executes the next job. |
|
|
|
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(). |
|
|
|
If a problem occurs, set mComposer->mRc to false, and the job |
|
queue be stopped. |
|
|
|
If mRc is set to false in an asynchronous job, you must still call |
|
doNextJob(). This will do the deletion of the command queue. |
|
*/ |
|
|
|
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 composeMessage2() { mComposer->composeMessage2(); } |
|
void composeMessage3() { mComposer->composeMessage3(); } |
|
void continueComposeMessage( KMMessage& msg, bool doSign, bool doEncrypt, |
|
bool ignoreBcc ) |
|
{ |
|
mComposer->continueComposeMessage( msg, doSign, doEncrypt, ignoreBcc ); |
|
} |
|
|
|
MessageComposer* mComposer; |
|
}; |
|
|
|
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(); |
|
} |
|
}; |
|
|
|
class ComposeMessage2Job : public MessageComposerJob { |
|
public: |
|
ComposeMessage2Job( MessageComposer* composer ) |
|
: MessageComposerJob( composer ) {} |
|
|
|
void execute() { |
|
composeMessage2(); |
|
} |
|
}; |
|
|
|
class ComposeMessage3Job : public MessageComposerJob { |
|
public: |
|
ComposeMessage3Job( MessageComposer* composer ) |
|
: MessageComposerJob( composer ) {} |
|
|
|
void execute() { |
|
composeMessage3(); |
|
} |
|
}; |
|
|
|
class ContinueComposeMessageJob : public MessageComposerJob { |
|
public: |
|
ContinueComposeMessageJob( MessageComposer* composer, KMMessage& msg, |
|
bool doSign, bool doEncrypt, bool ignoreBcc ) |
|
: MessageComposerJob( composer ), mMsg( msg ), mDoSign( doSign ), |
|
mDoEncrypt( doEncrypt ), mIgnoreBcc( ignoreBcc ) {} |
|
|
|
void execute() { |
|
continueComposeMessage( mMsg, mDoSign, mDoEncrypt, mIgnoreBcc ); |
|
} |
|
|
|
private: |
|
KMMessage& mMsg; |
|
bool mDoSign, mDoEncrypt, mIgnoreBcc; |
|
}; |
|
|
|
|
|
MessageComposer::MessageComposer( KMComposeWin* win, const char* name ) |
|
: QObject( win, name ), mComposeWin( win ), mCurrentJob( 0 ) |
|
{ |
|
} |
|
|
|
MessageComposer::~MessageComposer() |
|
{ |
|
} |
|
|
|
void MessageComposer::applyChanges( bool dontSign, bool dontEncrypt ) |
|
{ |
|
// Do the initial setup |
|
mDisableBreaking = false; |
|
|
|
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; |
|
} |
|
|
|
mDontSign = !mComposeWin->mSignAction->isChecked() || |
|
mComposeWin->mNeverSign || dontSign; |
|
mDontEncrypt = !mComposeWin->mEncryptAction->isChecked() || |
|
mComposeWin->mNeverEncrypt || dontEncrypt; |
|
|
|
mHoldJobs = false; |
|
mRc = true; |
|
|
|
// 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 ;-) |
|
|
|
// 2: Set encryption/signing options |
|
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() ) { |
|
// Unlock the GUI again |
|
// TODO: This should be back in the KMComposeWin |
|
mComposeWin->setEnabled( true ); |
|
|
|
// No more jobs. Signal that we're done |
|
emit done( mRc ); |
|
return; |
|
} |
|
|
|
if( !mRc ) { |
|
// Something has gone wrong - stop the process and bail out |
|
while( !mJobs.isEmpty() ) { |
|
delete mJobs.front(); |
|
mJobs.pop_front(); |
|
} |
|
|
|
// Unlock the GUI again |
|
// TODO: This should be back in the KMComposeWin |
|
mComposeWin->setEnabled( true ); |
|
|
|
emit done( false ); |
|
return; |
|
} |
|
|
|
// We have more jobs to do, but allow others to come first |
|
QTimer::singleShot( 0, this, SLOT( slotDoNextJob() ) ); |
|
} |
|
|
|
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 |
|
mSelectedCryptPlug = mComposeWin->mSelectedCryptPlug; |
|
mAutoCharset = mComposeWin->mAutoCharset; |
|
mCharset = mComposeWin->mCharset; |
|
mMsg = mComposeWin->mMsg; |
|
mAutoPgpEncrypt = mComposeWin->mAutoPgpEncrypt; |
|
mPgpIdentity = mComposeWin->pgpIdentity(); |
|
|
|
mComposeWin->mBccMsgList.clear(); |
|
|
|
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; |
|
} |
|
mMsg->setCharset(mCharset); |
|
|
|
mMsg->setTo(mComposeWin->to()); |
|
mMsg->setFrom(mComposeWin->from()); |
|
mMsg->setCc(mComposeWin->cc()); |
|
mMsg->setSubject(mComposeWin->subject()); |
|
mMsg->setReplyTo(mComposeWin->replyTo()); |
|
mMsg->setBcc(mComposeWin->bcc()); |
|
|
|
const KPIM::Identity & id |
|
= kmkernel->identityManager()->identityForUoid( mComposeWin->mIdentity->currentIdentity() ); |
|
|
|
KMFolder *f = mComposeWin->mFcc->getFolder(); |
|
assert( f != 0 ); |
|
if ( f->idString() == id.fcc() ) |
|
mMsg->removeHeaderField("X-KMail-Fcc"); |
|
else |
|
mMsg->setFcc( f->idString() ); |
|
|
|
// set the correct drafts folder |
|
mMsg->setDrafts( id.drafts() ); |
|
|
|
if (id.isDefault()) |
|
mMsg->removeHeaderField("X-KMail-Identity"); |
|
else mMsg->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()) |
|
mMsg->setHeaderField("Disposition-Notification-To", replyAddr); |
|
else |
|
mMsg->removeHeaderField("Disposition-Notification-To"); |
|
|
|
if (mComposeWin->mUrgentAction->isChecked()) { |
|
mMsg->setHeaderField("X-PRIORITY", "2 (High)"); |
|
mMsg->setHeaderField("Priority", "urgent"); |
|
} else { |
|
mMsg->removeHeaderField("X-PRIORITY"); |
|
mMsg->removeHeaderField("Priority"); |
|
} |
|
|
|
_StringPair *pCH; |
|
for (pCH = mComposeWin->mCustHeaders.first(); |
|
pCH != 0; |
|
pCH = mComposeWin->mCustHeaders.next()) { |
|
mMsg->setHeaderField(KMMsgBase::toUsAscii(pCH->name), pCH->value); |
|
} |
|
|
|
// 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 |
|
// mMsg->bcc() later) |
|
mBcc = mMsg->bcc(); |
|
} |
|
|
|
void MessageComposer::adjustCryptFlags() |
|
{ |
|
// Apply the overriding settings |
|
// And apply the users choice |
|
bool doSign = !mDontSign; |
|
bool doEncrypt = !mDontEncrypt; |
|
|
|
// check settings of composer buttons *and* attachment check boxes |
|
bool doSignCompletely = doSign; |
|
bool doEncryptCompletely = doEncrypt; |
|
bool doEncryptPartially = doEncrypt; |
|
if( mSelectedCryptPlug && ( !mComposeWin->mAtmList.isEmpty() ) ) { |
|
int idx=0; |
|
KMMessagePart *attachPart; |
|
for( attachPart = mComposeWin->mAtmList.first(); |
|
attachPart; |
|
attachPart=mComposeWin->mAtmList.next(), ++idx ) { |
|
if( mComposeWin->encryptFlagOfAttachment( idx ) ) { |
|
doEncryptPartially = true; |
|
} |
|
else { |
|
doEncryptCompletely = false; |
|
} |
|
if( !mComposeWin->signFlagOfAttachment( idx ) ) |
|
doSignCompletely = false; |
|
} |
|
} |
|
|
|
if( !doSignCompletely ) { |
|
if( mSelectedCryptPlug ) { |
|
// note: only ask for signing if "Warn me" flag is set! (khz) |
|
if( mSelectedCryptPlug->warnSendUnsigned() && !mDontSign ) { |
|
int ret = |
|
KMessageBox::warningYesNoCancel( mComposeWin, |
|
QString( "<qt><b>" |
|
+ i18n("Warning:") |
|
+ "</b><br>" |
|
+ ((doSign && !doSignCompletely) |
|
? i18n("You specified to not sign some parts of this message, but" |
|
" you wanted to be warned not to send unsigned messages.") |
|
: i18n("You specified to not sign this message, but" |
|
" you wanted to be warned not to send unsigned messages.") ) |
|
+ "<br> <br><b>" |
|
+ i18n("Sign all parts of this message?") |
|
+ "</b></qt>" ), |
|
i18n("Signature Warning"), |
|
KGuiItem( i18n("&Sign All Parts") ), |
|
KGuiItem( i18n("Send &as is") ) ); |
|
if( ret == KMessageBox::Cancel ) { |
|
mRc = false; |
|
return; |
|
} else if( ret == KMessageBox::Yes ) { |
|
doSign = true; |
|
doSignCompletely = true; |
|
} |
|
} |
|
} else { |
|
// ask if the message should be encrypted via old build-in pgp code |
|
// pending (who ever wants to implement it) |
|
} |
|
} |
|
|
|
if( !mDontEncrypt ) { |
|
// check whether all encrypted messages should be encrypted to self |
|
bool bEncryptToSelf = mSelectedCryptPlug |
|
? mSelectedCryptPlug->alwaysEncryptToSelf() |
|
: Kpgp::Module::getKpgp()->encryptToSelf(); |
|
// check whether we have the user's key if necessary |
|
bool bEncryptionPossible = !bEncryptToSelf || !mPgpIdentity.isEmpty(); |
|
// check whether we are using OpenPGP (built-in or plug-in) |
|
bool bUsingOpenPgp = !mSelectedCryptPlug || |
|
( mSelectedCryptPlug && |
|
( -1 != mSelectedCryptPlug->libName().find( "openpgp" ) ) ); |
|
// only try automatic encryption if all of the following conditions hold |
|
// a) the user enabled automatic encryption |
|
// b) we have the user's key if he wants to encrypt to himself |
|
// c) we are using OpenPGP |
|
// d) no message part is marked for encryption |
|
if( mAutoPgpEncrypt && bEncryptionPossible && bUsingOpenPgp && |
|
!doEncryptPartially ) { |
|
// check if encryption is possible and if yes suggest encryption |
|
// first determine the complete list of recipients |
|
QString _to = mComposeWin->to().simplifyWhiteSpace(); |
|
if( !mComposeWin->cc().isEmpty() ) { |
|
if( !_to.isEmpty() && !_to.endsWith(",") ) |
|
_to += ","; |
|
_to += mComposeWin->cc().simplifyWhiteSpace(); |
|
} |
|
if( !mBcc.isEmpty() ) { |
|
if( !_to.isEmpty() && !_to.endsWith(",") ) |
|
_to += ","; |
|
_to += mBcc.simplifyWhiteSpace(); |
|
} |
|
QStringList allRecipients = KPIM::splitEmailAddrList(_to); |
|
// now check if encrypting to these recipients is possible and desired |
|
Kpgp::Module *pgp = Kpgp::Module::getKpgp(); |
|
int status = pgp->encryptionPossible( allRecipients ); |
|
if( 1 == status ) { |
|
// encrypt all message parts |
|
doEncrypt = true; |
|
doEncryptCompletely = true; |
|
} else if( 2 == status ) { |
|
// the user wants to be asked or has to be asked |
|
KCursorSaver idle(KBusyPtr::idle()); |
|
int ret; |
|
if( doSign ) |
|
ret = KMessageBox::questionYesNoCancel( mComposeWin, |
|
i18n("<qt><p>You have a trusted OpenPGP key for every " |
|
"recipient of this message and the message will " |
|
"be signed.</p>" |
|
"<p>Should this message also be " |
|
"encrypted?</p></qt>"), |
|
i18n("Encrypt Message?"), |
|
KGuiItem( i18n("Sign && &Encrypt") ), |
|
KGuiItem( i18n("&Sign Only") ) ); |
|
else |
|
ret = KMessageBox::questionYesNoCancel( mComposeWin, |
|
i18n("<qt><p>You have a trusted OpenPGP key for every " |
|
"recipient of this message;</p>" |
|
"<p>should this message be encrypted?</p></qt>"), |
|
i18n("Encrypt Message?"), |
|
KGuiItem( i18n("&Encrypt") ), |
|
KGuiItem( i18n("&Do not Encrypt") ) ); |
|
if( KMessageBox::Cancel == ret ) { |
|
mRc = false; |
|
return; |
|
} else if( KMessageBox::Yes == ret ) { |
|
// encrypt all message parts |
|
doEncrypt = true; |
|
doEncryptCompletely = true; |
|
} |
|
} else if( status == -1 ) { |
|
// warn the user that there are conflicting encryption preferences |
|
KCursorSaver idle(KBusyPtr::idle()); |
|
int ret = |
|
KMessageBox::warningYesNoCancel( mComposeWin, |
|
i18n("<qt><p>There are conflicting encryption " |
|
"preferences;</p>" |
|
"<p>should this message be encrypted?</p></qt>"), |
|
i18n("Encrypt Message?"), |
|
KGuiItem( i18n("&Encrypt") ), |
|
KGuiItem( i18n("&Do not Encrypt") ) ); |
|
if( KMessageBox::Cancel == ret ) { |
|
mRc = false; |
|
return; |
|
} else if( KMessageBox::Yes == ret ) { |
|
// encrypt all message parts |
|
doEncrypt = true; |
|
doEncryptCompletely = true; |
|
} |
|
} |
|
} else if( !doEncryptCompletely && mSelectedCryptPlug ) { |
|
// note: only ask for encrypting if "Warn me" flag is set! (khz) |
|
if( mSelectedCryptPlug->warnSendUnencrypted() ) { |
|
int ret = |
|
KMessageBox::warningYesNoCancel( mComposeWin, |
|
QString( "<qt><b>" |
|
+ i18n("Warning:") |
|
+ "</b><br>" |
|
+ ((doEncrypt && !doEncryptCompletely) |
|
? i18n("You specified to not encrypt some parts of this message, but" |
|
" you wanted to be warned not to send unencrypted messages.") |
|
: i18n("You specified to not encrypt this message, but" |
|
" you wanted to be warned not to send unencrypted messages.") ) |
|
+ "<br> <br><b>" |
|
+ i18n("Encrypt all parts of this message?") |
|
+ "</b></qt>" ), |
|
i18n("Encryption Warning"), |
|
KGuiItem( i18n("&Encrypt All Parts") ), |
|
KGuiItem( i18n("Send &as is") ) ); |
|
if( ret == KMessageBox::Cancel ) { |
|
mRc = false; |
|
return; |
|
} else if( ret == KMessageBox::Yes ) { |
|
doEncrypt = true; |
|
doEncryptCompletely = true; |
|
} |
|
} |
|
|
|
/* |
|
note: Processing the mSelectedCryptPlug->encryptEmail() flag here would |
|
be absolutely wrong: this is used for specifying |
|
if messages should be encrypted 'in general'. |
|
--> This sets the initial state of a freshly started Composer. |
|
--> This does *not* mean overriding user setting made while |
|
editing in that composer window! (khz, 2002/06/26) |
|
*/ |
|
} |
|
} |
|
|
|
// if necessary mark all attachments for signing/encryption |
|
if( mSelectedCryptPlug && ( !mComposeWin->mAtmList.isEmpty() ) && |
|
( doSignCompletely || doEncryptCompletely ) ) { |
|
for( KMAtmListViewItem* lvi = (KMAtmListViewItem*)mComposeWin->mAtmItemList.first(); |
|
lvi; lvi = (KMAtmListViewItem*)mComposeWin->mAtmItemList.next() ) { |
|
if( doSignCompletely ) |
|
lvi->setSign( true ); |
|
if( doEncryptCompletely ) |
|
lvi->setEncrypt( true ); |
|
} |
|
} |
|
} |
|
|
|
void MessageComposer::composeMessage() |
|
{ |
|
mExtraMessage = new KMMessage( *mMsg ); |
|
mJobs.push_front( new ComposeMessage2Job( this ) ); |
|
composeMessage( *mMsg, !mDontSign, !mDontEncrypt, false ); |
|
} |
|
|
|
void MessageComposer::composeMessage2() |
|
{ |
|
bool saveMessagesEncrypted = mSelectedCryptPlug ? |
|
mSelectedCryptPlug->saveMessagesEncrypted() : true; |
|
|
|
// note: The following define is specified on top of this file. To compile |
|
// a less strict version of KMail just comment it out there above. |
|
#ifdef STRICT_RULES_OF_GERMAN_GOVERNMENT_01 |
|
// Hack to make sure the S/MIME CryptPlugs follows the strict requirement |
|
// of german government: |
|
// --> Encrypted messages *must* be stored in unencrypted form after sending. |
|
// ( "Abspeichern ausgegangener Nachrichten in entschluesselter Form" ) |
|
// --> Signed messages *must* be stored including the signature after sending. |
|
// ( "Aufpraegen der Signatur" ) |
|
// So we provide the user with a non-deactivateble warning and let her/him |
|
// choose to obey the rules or to ignore them explicitly. |
|
if( mSelectedCryptPlug |
|
&& ( 0 <= mSelectedCryptPlug->libName().find( "smime", 0, false ) ) |
|
&& ( doEncrypt && saveMessagesEncrypted ) ) { |
|
QString headTxt = i18n("Warning: Your S/MIME Plug-in configuration is unsafe."); |
|
QString encrTxt = i18n("Encrypted messages should be stored in unencrypted form; saving locally in encrypted form is not allowed."); |
|
QString footTxt = i18n("Please correct the wrong settings in KMail's Plug-in configuration pages as soon as possible."); |
|
QString question = i18n("Store message in the recommended way?"); |
|
if( KMessageBox::Yes == KMessageBox::warningYesNo( mComposeWin, |
|
"<qt><p><b>" + headTxt + "</b><br>" + encrTxt + "</p><p>" |
|
+ footTxt + "</p><p><b>" + question + "</b></p></qt>", |
|
i18n("Unsafe S/MIME Configuration"), |
|
KGuiItem( i18n("Save &Unencrypted") ), |
|
KGuiItem( i18n("Save &Encrypted") ) ) ) |
|
saveMessagesEncrypted = false; |
|
} |
|
kdDebug(5006) << "MessageComposer::applyChanges(void) - Send encrypted=" << doEncrypt << " Store encrypted=" << saveMessagesEncrypted << endl; |
|
#endif |
|
if( !mDontEncrypt && !saveMessagesEncrypted ) { |
|
if( mSelectedCryptPlug ) { |
|
for( KMAtmListViewItem* entry = (KMAtmListViewItem*)mComposeWin->mAtmItemList.first(); |
|
entry; |
|
entry = (KMAtmListViewItem*)mComposeWin->mAtmItemList.next() ) |
|
entry->setEncrypt( false ); |
|
} |
|
mJobs.push_front( new ComposeMessage3Job( this ) ); |
|
composeMessage( *mExtraMessage, !mDontSign, false, true ); |
|
} else |
|
delete mExtraMessage; |
|
} |
|
|
|
void MessageComposer::composeMessage3() |
|
{ |
|
mExtraMessage->cleanupHeader(); |
|
mMsg->setUnencryptedMsg( mExtraMessage ); |
|
mExtraMessage = 0; |
|
} |
|
|
|
|
|
class SignSMimeJob : public MessageComposerJob { |
|
public: |
|
SignSMimeJob( CryptPlugWrapper* selectedCryptPlug, QCString& encodedBody, |
|
uint boundaryLevel, KMMessagePart& oldBodyPart, |
|
const QCString& charset, KMMessagePart *newBodyPart, |
|
MessageComposer* composer ) |
|
: MessageComposerJob( composer ), mSelectedCryptPlug( selectedCryptPlug ), |
|
mEncodedBody( encodedBody ), mBoundaryLevel( boundaryLevel ), |
|
mOldBodyPart( oldBodyPart ), mCharset( charset ), |
|
mNewBodyPart( newBodyPart ), mStructuring( selectedCryptPlug ) |
|
{ |
|
mBugUrl = QString::fromUtf8( mSelectedCryptPlug->bugURL() ); |
|
} |
|
|
|
void execute() { |
|
kdDebug(5006) << "************** mEncodedBody:\n" << mEncodedBody << endl; |
|
|
|
if( !mComposer->mSignature.isEmpty() ) { |
|
mComposer->mRc = mComposer-> |
|
processStructuringInfo( mBugUrl, mBoundaryLevel, |
|
mOldBodyPart.contentDescription(), |
|
mOldBodyPart.typeStr(), |
|
mOldBodyPart.subtypeStr(), |
|
mOldBodyPart.contentDisposition(), |
|
mOldBodyPart.contentTransferEncodingStr(), |
|
mEncodedBody, "signature", |
|
mComposer->mSignature, mStructuring, |
|
*mNewBodyPart ); |
|
if( mComposer->mRc ) { |
|
if( !mStructuring.data.makeMultiMime ) { |
|
mNewBodyPart->setCharset( mCharset ); |
|
} |
|
} else |
|
KMessageBox::sorry( mComposer->mComposeWin, |
|
mErrorProcessingStructuringInfo ); |
|
} else |
|
mComposer->mRc = false; |
|
} |
|
|
|
StructuringInfoWrapper& structuringInfo() { return mStructuring; } |
|
|
|
private: |
|
CryptPlugWrapper* mSelectedCryptPlug; |
|
QString mBugUrl; |
|
QCString &mEncodedBody; |
|
uint mBoundaryLevel; |
|
KMMessagePart& mOldBodyPart; |
|
QCString mCharset; |
|
KMMessagePart* mNewBodyPart; |
|
StructuringInfoWrapper mStructuring; |
|
}; |
|
|
|
#if 0 |
|
// This class is not yet implemented. Should get the last part of the |
|
// gpg signing done |
|
class SignOldGPGJob : public MessageComposerJob { |
|
public: |
|
SignJob( CryptPlugWrapper* selectedCryptPlug, const QCString& encodedBody, |
|
uint boundaryLevel, const KMMessagePart& oldBodyPart, |
|
const QCString& charset, const QCString& pgpIdentity, |
|
KMMessagePart *newBodyPart, MessageComposer* composer ) |
|
: MessageComposerJob( composer ), mSelectedCryptPlug( selectedCryptPlug ), |
|
mEncodedBody( encodedBody ), mBoundaryLevel( boundaryLevel ), |
|
mOldBodyPart( oldBodyPart ), mCharset( charset ), |
|
mPgpIdentity( pgpIdentity ), mNewBodyPart( newBodyPart ) {} |
|
|
|
void execute() { |
|
adjustCryptFlags(); |
|
} |
|
|
|
private: |
|
CryptPlugWrapper* mSelectedCryptPlug; |
|
QCString mEncodedBody; |
|
uint mBoundaryLevel; |
|
KMMessagePart mOldBodyPart; |
|
QCString mCharset; |
|
QCString mPgpIdentity; |
|
KMMessagePart *mNewBodyPart; |
|
}; |
|
#endif |
|
|
|
class EncryptMessageJob : public MessageComposerJob { |
|
public: |
|
EncryptMessageJob( const KMMessage& msg, const QStringList& recipients, |
|
bool doSign, bool doEncrypt, const QCString& encodedBody, |
|
int boundaryLevel, const KMMessagePart& oldBodyPart, |
|
KMMessagePart* newBodyPart, MessageComposer* composer ) |
|
: MessageComposerJob( composer ), mRecipients( recipients ), |
|
mDoSign( doSign ), mDoEncrypt( doEncrypt ), mEncodedBody( encodedBody ), |
|
mBoundaryLevel( boundaryLevel ), mOldBodyPart( oldBodyPart ), |
|
mBCCMessage( true ), mNewBodyPart( newBodyPart ) |
|
{ |
|
mMsg = new KMMessage( msg ); |
|
} |
|
|
|
EncryptMessageJob( KMMessage* msg, const QStringList& recipients, |
|
bool doSign, bool doEncrypt, const QCString& encodedBody, |
|
int boundaryLevel, const KMMessagePart& oldBodyPart, |
|
KMMessagePart* newBodyPart, MessageComposer* composer ) |
|
: MessageComposerJob( composer ), mMsg( msg ), mRecipients( recipients ), |
|
mDoSign( doSign ), mDoEncrypt( doEncrypt ), mEncodedBody( encodedBody ), |
|
mBoundaryLevel( boundaryLevel ), mOldBodyPart( oldBodyPart ), |
|
mBCCMessage( false ), mNewBodyPart( newBodyPart ) {} |
|
|
|
void execute() { |
|
KMMessagePart tmpNewBodyPart = *mNewBodyPart; |
|
|
|
// TODO: Async call |
|
|
|
mComposer->encryptMessage( mMsg, mRecipients, mDoSign, mDoEncrypt, |
|
mBoundaryLevel, tmpNewBodyPart ); |
|
if( mComposer->mRc && mBCCMessage ) { |
|
mMsg->setHeaderField( "X-KMail-Recipients", mRecipients.last() ); |
|
mComposer->handleBCCMessage( mMsg ); |
|
} |
|
} |
|
|
|
private: |
|
KMMessage* mMsg; |
|
QStringList mRecipients; |
|
bool mDoSign, mDoEncrypt; |
|
QCString mEncodedBody; |
|
int mBoundaryLevel; |
|
KMMessagePart mOldBodyPart; |
|
bool mBCCMessage; |
|
KMMessagePart* mNewBodyPart; |
|
}; |
|
|
|
void MessageComposer::handleBCCMessage( KMMessage* msg ) |
|
{ |
|
mComposeWin->mBccMsgList.append( msg ); |
|
} |
|
|
|
void MessageComposer::composeMessage( KMMessage& theMessage, |
|
bool doSign, bool doEncrypt, |
|
bool ignoreBcc ) |
|
{ |
|
#ifdef DEBUG |
|
kdDebug(5006) << "entering KMComposeWin::composeMessage" << endl; |
|
#endif |
|
|
|
// create informative header for those that have no mime-capable |
|
// email client |
|
theMessage.setBody( "This message is in MIME format." ); |
|
|
|
// preprocess the body text |
|
QCString body = breakLinesAndApplyCodec(); |
|
|
|
if (body.isNull()) { |
|
mRc = false; |
|
return; |
|
} |
|
|
|
if (body.isEmpty()) body = "\n"; // don't crash |
|
|
|
// 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( body[body.length()-1] != '\n' ) { |
|
kdDebug(5006) << "Added an <LF> on the last line" << endl; |
|
body += "\n"; |
|
} |
|
|
|
// set the main headers |
|
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; |
|
|
|
// create temporary bodyPart for editor text |
|
// (and for all attachments, if mail is to be singed and/or encrypted) |
|
mEarlyAddAttachments = mSelectedCryptPlug && |
|
( !mComposeWin->mAtmList.isEmpty() ) && ( doSign || doEncrypt ); |
|
|
|
mAllAttachmentsAreInBody = mEarlyAddAttachments; |
|
|
|
// test whether there ARE attachments that can be included into the body |
|
if( mEarlyAddAttachments ) { |
|
bool someOk = false; |
|
int idx; |
|
KMMessagePart *attachPart; |
|
for( idx=0, attachPart = mComposeWin->mAtmList.first(); |
|
attachPart; attachPart=mComposeWin->mAtmList.next(), ++idx ) |
|
if( doEncrypt == mComposeWin->encryptFlagOfAttachment( idx ) |
|
&& doSign == mComposeWin->signFlagOfAttachment( idx ) ) |
|
someOk = true; |
|
else |
|
mAllAttachmentsAreInBody = false; |
|
if( !mAllAttachmentsAreInBody && !someOk ) |
|
mEarlyAddAttachments = false; |
|
} |
|
|
|
// if an html message is to be generated, make a text/plain and text/html part |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText ) { |
|
mOldBodyPart.setTypeStr( "multipart"); |
|
mOldBodyPart.setSubtypeStr(mEarlyAddAttachments ? "mixed" : "alternative"); |
|
} |
|
else { |
|
mOldBodyPart.setTypeStr( mEarlyAddAttachments ? "multipart" : "text" ); |
|
mOldBodyPart.setSubtypeStr(mEarlyAddAttachments ? "mixed" : "plain"); |
|
} |
|
|
|
mOldBodyPart.setContentDisposition( "inline" ); |
|
|
|
QCString boundaryCStr; |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText ) { // create a multipart body |
|
// calculate a boundary string |
|
QCString boundaryCStr; // storing boundary string data |
|
QCString newbody=""; |
|
DwMediaType tmpCT; |
|
tmpCT.CreateBoundary( mPreviousBoundaryLevel++ ); // was 0 |
|
boundaryCStr = tmpCT.Boundary().c_str(); |
|
QValueList<int> allowedCTEs; |
|
|
|
KMMessagePart textBodyPart; |
|
textBodyPart.setTypeStr("text"); |
|
textBodyPart.setSubtypeStr("plain"); |
|
mComposeWin->mEditor->setTextFormat(Qt::PlainText); |
|
QCString textbody = breakLinesAndApplyCodec(); |
|
mComposeWin->mEditor->setTextFormat(Qt::RichText); |
|
textBodyPart.setBodyAndGuessCte(textbody, allowedCTEs, !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, |
|
doSign); |
|
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"); |
|
// the signed body must not be 8bit encoded |
|
QCString htmlbody = breakLinesAndApplyCodec(); |
|
htmlBodyPart.setBodyAndGuessCte(htmlbody, allowedCTEs, !!kmkernel->msgSender()->sendQuotedPrintable() && !doSign, |
|
doSign); |
|
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"; |
|
body = newbody; |
|
mOldBodyPart.setBodyEncoded( newbody ); |
|
|
|
mSaveBoundary = tmpCT.Boundary(); |
|
} |
|
|
|
|
|
if( mEarlyAddAttachments ) { |
|
// calculate a boundary string |
|
++mPreviousBoundaryLevel; |
|
DwMediaType tmpCT; |
|
tmpCT.CreateBoundary( mPreviousBoundaryLevel ); |
|
boundaryCStr = tmpCT.Boundary().c_str(); |
|
// add the normal body text |
|
KMMessagePart innerBodyPart; |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText ) { |
|
innerBodyPart.setTypeStr( "multipart");//text" ); |
|
innerBodyPart.setSubtypeStr("alternative");//html"); |
|
} |
|
else { |
|
innerBodyPart.setTypeStr( "text" ); |
|
innerBodyPart.setSubtypeStr("plain"); |
|
} |
|
innerBodyPart.setContentDisposition( "inline" ); |
|
QValueList<int> allowedCTEs; |
|
// the signed body must not be 8bit encoded |
|
innerBodyPart.setBodyAndGuessCte( body, allowedCTEs, |
|
!kmkernel->msgSender()->sendQuotedPrintable() && !doSign, |
|
doSign ); |
|
innerBodyPart.setCharset( mCharset ); |
|
innerBodyPart.setBodyEncoded( body ); // do we need this, since setBodyAndGuessCte does this already? |
|
DwBodyPart* innerDwPart = theMessage.createDWBodyPart( &innerBodyPart ); |
|
innerDwPart->Assemble(); |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText ) { // and add our mp/a boundary |
|
QCString tmpbody = innerDwPart->AsString().c_str(); |
|
int boundPos = tmpbody.find( '\n' ); |
|
if( -1 < boundPos ) { |
|
QCString bStr( ";\n boundary=\"" ); |
|
bStr += mSaveBoundary.c_str(); |
|
bStr += "\""; |
|
body = innerDwPart->AsString().c_str(); |
|
body.insert( boundPos, bStr ); |
|
body = "--" + boundaryCStr + "\n" + body; |
|
} |
|
} |
|
else |
|
body = "--" + boundaryCStr + "\n" + innerDwPart->AsString().c_str(); |
|
delete innerDwPart; |
|
innerDwPart = 0; |
|
// add all matching Attachments |
|
// NOTE: This code will be changed when KMime is complete. |
|
int idx = 0; |
|
KMMessagePart* attachPart = mComposeWin->mAtmList.first(); |
|
for( ; attachPart; attachPart=mComposeWin->mAtmList.next(), ++idx ) { |
|
bool bEncrypt = mComposeWin->encryptFlagOfAttachment( idx ); |
|
bool bSign = mComposeWin->signFlagOfAttachment( idx ); |
|
if( !mSelectedCryptPlug || ( ( doEncrypt == bEncrypt ) |
|
&& ( doSign == bSign ) ) ) { |
|
// 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. |
|
if( bSign || bEncrypt ) { |
|
QCString cte = attachPart->cteStr().lower(); |
|
if( ( "8bit" == cte ) |
|
|| ( ( attachPart->type() == DwMime::kTypeText ) |
|
&& ( "7bit" == cte ) ) ) { |
|
QByteArray body = attachPart->bodyDecodedBinary(); |
|
QValueList<int> dummy; |
|
attachPart->setBodyAndGuessCte(body, dummy, false, bSign); |
|
kdDebug(5006) << "Changed encoding of message part from " |
|
<< cte << " to " << attachPart->cteStr() << endl; |
|
} |
|
} |
|
innerDwPart = theMessage.createDWBodyPart( attachPart ); |
|
innerDwPart->Assemble(); |
|
body += "\n--" + boundaryCStr + "\n" + innerDwPart->AsString().c_str(); |
|
delete innerDwPart; |
|
innerDwPart = 0; |
|
} |
|
} |
|
body += "\n--" + boundaryCStr + "--\n"; |
|
} else { // !earlyAddAttachments |
|
QValueList<int> allowedCTEs; |
|
// the signed body must not be 8bit encoded |
|
mOldBodyPart.setBodyAndGuessCte(body, allowedCTEs, !kmkernel->msgSender()->sendQuotedPrintable() && !doSign, |
|
doSign); |
|
mOldBodyPart.setCharset(mCharset); |
|
} |
|
// create S/MIME body part for signing and/or encrypting |
|
mOldBodyPart.setBodyEncoded( body ); |
|
|
|
if( doSign || doEncrypt ) { |
|
if( mSelectedCryptPlug ) { |
|
// get string representation of body part (including the attachments) |
|
|
|
DwBodyPart* dwPart; |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText && !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(); |
|
mEncodedBody = dwPart->AsString().c_str(); |
|
} |
|
else { |
|
dwPart = theMessage.createDWBodyPart( &mOldBodyPart ); |
|
dwPart->Assemble(); |
|
mEncodedBody = dwPart->AsString().c_str(); |
|
} |
|
delete dwPart; |
|
dwPart = 0; |
|
|
|
// manually add a boundary definition to the Content-Type header |
|
if( !boundaryCStr.isEmpty() ) { |
|
int boundPos = mEncodedBody.find( '\n' ); |
|
if( -1 < boundPos ) { |
|
// insert new "boundary" parameter |
|
QCString bStr( ";\n boundary=\"" ); |
|
bStr += boundaryCStr; |
|
bStr += "\""; |
|
mEncodedBody.insert( boundPos, bStr ); |
|
} |
|
} |
|
|
|
// 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 = KMMessage::lf2crlf( mEncodedBody ); |
|
} else { |
|
mEncodedBody = body; |
|
} |
|
} |
|
|
|
MessageComposerJob* signJob = 0; |
|
if( doSign ) { |
|
if( mSelectedCryptPlug ) { |
|
// TODO: Async call |
|
SignSMimeJob* job = new SignSMimeJob( mSelectedCryptPlug, mEncodedBody, |
|
mPreviousBoundaryLevel + doEncrypt ? 3 : 2, |
|
mOldBodyPart, mCharset, |
|
mNewBodyPart, this ); |
|
signJob = job; |
|
pgpSignedMsg( mEncodedBody, job->structuringInfo() ); |
|
} else if( !doEncrypt ) { |
|
// we try calling the *old* build-in code for OpenPGP clearsigning |
|
Kpgp::Block block; |
|
block.setText( mEncodedBody ); |
|
|
|
// clearsign the message |
|
|
|
// TODO: Async call |
|
|
|
Kpgp::Result result = block.clearsign( mPgpIdentity, mCharset ); |
|
|
|
if( result == Kpgp::Ok ) { |
|
mNewBodyPart->setType( mOldBodyPart.type() ); |
|
mNewBodyPart->setSubtype( mOldBodyPart.subtype() ); |
|
mNewBodyPart->setCharset( mOldBodyPart.charset() ); |
|
mNewBodyPart->setContentTransferEncodingStr( mOldBodyPart.contentTransferEncodingStr() ); |
|
mNewBodyPart->setContentDescription( mOldBodyPart.contentDescription() ); |
|
mNewBodyPart->setContentDisposition( mOldBodyPart.contentDisposition() ); |
|
mNewBodyPart->setBodyEncoded( block.text() ); |
|
} else { |
|
mRc = false; |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt><p>This message could not be signed.</p>%1</qt>") |
|
.arg( mErrorNoCryptPlugAndNoBuildIn ) ); |
|
} |
|
} |
|
} |
|
|
|
if( signJob ) { |
|
mJobs.push_front( new ContinueComposeMessageJob( this, theMessage, doSign, |
|
doEncrypt, ignoreBcc ) ); |
|
mJobs.push_front( signJob ); |
|
} else |
|
// No signing going on, so just continue |
|
continueComposeMessage( theMessage, doSign, doEncrypt, ignoreBcc ); |
|
} |
|
|
|
// Do the encryption stuff |
|
void MessageComposer::continueComposeMessage( KMMessage& theMessage, |
|
bool doSign, bool doEncrypt, |
|
bool ignoreBcc ) |
|
{ |
|
// determine the list of public recipients |
|
QString _to = mComposeWin->to().simplifyWhiteSpace(); |
|
if( !mComposeWin->cc().isEmpty() ) { |
|
if( !_to.isEmpty() && !_to.endsWith(",") ) |
|
_to += ","; |
|
_to += mComposeWin->cc().simplifyWhiteSpace(); |
|
} |
|
QStringList recipientsWithoutBcc = KPIM::splitEmailAddrList(_to); |
|
|
|
// run encrypting(s) for Bcc recipient(s) |
|
if( doEncrypt && !ignoreBcc && !theMessage.bcc().isEmpty() ) { |
|
QStringList bccRecips = KPIM::splitEmailAddrList( theMessage.bcc() ); |
|
for( QStringList::ConstIterator it = bccRecips.begin(); |
|
it != bccRecips.end(); ++it ) { |
|
QStringList tmpRecips( recipientsWithoutBcc ); |
|
tmpRecips << *it; |
|
// Note: This will do the encryptions in reverse order, compared |
|
// to the old synchronous code. Hopefully, this doesn't matter |
|
mJobs.push_front( new EncryptMessageJob( theMessage, tmpRecips, doSign, |
|
doEncrypt, mEncodedBody, |
|
mPreviousBoundaryLevel, |
|
mOldBodyPart, mNewBodyPart, |
|
this ) ); |
|
} |
|
theMessage.setHeaderField( "X-KMail-Recipients", |
|
recipientsWithoutBcc.join(",") ); |
|
} |
|
|
|
// Run encrypting for public recipient(s) |
|
mJobs.push_front( new EncryptMessageJob( mMsg, recipientsWithoutBcc, doSign, |
|
doEncrypt, mEncodedBody, |
|
mPreviousBoundaryLevel, |
|
mOldBodyPart, mNewBodyPart, |
|
this ) ); |
|
} |
|
|
|
Kpgp::Result MessageComposer::getEncryptionCertificates( const QStringList& recipients, |
|
QCString& encryptionCertificates ) |
|
{ |
|
Kpgp::Result result = Kpgp::Ok; |
|
|
|
// find out whether we are dealing with the OpenPGP or the S/MIME plugin |
|
if ( -1 != mSelectedCryptPlug->libName().find( "openpgp" ) ) { |
|
// We are dealing with the OpenPGP plugin. Use Kpgp to determine |
|
// the encryption keys. |
|
// get the OpenPGP key ID for the chosen identity |
|
Kpgp::Module *pgp = Kpgp::Module::getKpgp(); |
|
Kpgp::KeyIDList encryptionKeyIds; |
|
|
|
// temporarily set encrypt_to_self to the value specified in the |
|
// plugin configuration. this value is used implicitely by the |
|
// function which determines the encryption keys. |
|
const bool bEncryptToSelf_Old = pgp->encryptToSelf(); |
|
pgp->setEncryptToSelf( mSelectedCryptPlug->alwaysEncryptToSelf() ); |
|
result = pgp->getEncryptionKeys( encryptionKeyIds, recipients, mPgpIdentity ); |
|
// reset encrypt_to_self to the old value |
|
pgp->setEncryptToSelf( bEncryptToSelf_Old ); |
|
|
|
if ( result == Kpgp::Ok && !encryptionKeyIds.isEmpty() ) { |
|
// loop over all key IDs |
|
for ( Kpgp::KeyIDList::ConstIterator it = encryptionKeyIds.begin(); |
|
it != encryptionKeyIds.end(); ++it ) { |
|
const Kpgp::Key* key = pgp->publicKey( *it ); |
|
if ( key ) { |
|
QCString certFingerprint = key->primaryFingerprint(); |
|
kdDebug(5006) << "Fingerprint of encryption key: " |
|
<< certFingerprint << endl; |
|
// add this key to the list of encryption keys |
|
if( !encryptionCertificates.isEmpty() ) |
|
encryptionCertificates += '\1'; |
|
encryptionCertificates += certFingerprint; |
|
} |
|
} |
|
} |
|
} else { |
|
// S/MIME |
|
QStringList allRecipients = recipients; |
|
if ( mSelectedCryptPlug->alwaysEncryptToSelf() ) |
|
allRecipients << mComposeWin->from(); |
|
for ( QStringList::ConstIterator it = allRecipients.begin(); |
|
it != allRecipients.end(); |
|
++it ) { |
|
QCString certFingerprint = getEncryptionCertificate( *it ); |
|
|
|
if ( certFingerprint.isEmpty() ) { |
|
// most likely the user canceled the certificate selection |
|
encryptionCertificates.truncate( 0 ); |
|
return Kpgp::Canceled; |
|
} |
|
|
|
certFingerprint.remove( 0, certFingerprint.findRev( '(' ) + 1 ); |
|
certFingerprint.truncate( certFingerprint.length() - 1 ); |
|
kdDebug(5006) << "\n\n Recipient: " << *it |
|
<< "\nFingerprint of encryption key: " |
|
<< certFingerprint << "\n\n" << endl; |
|
|
|
const bool certOkay = |
|
checkForEncryptCertificateExpiry( *it, certFingerprint ); |
|
if( certOkay ) { |
|
if( !encryptionCertificates.isEmpty() ) |
|
encryptionCertificates += '\1'; |
|
encryptionCertificates += certFingerprint; |
|
} |
|
else { |
|
// ###### This needs to be improved: Tell the user that the certificate |
|
// ###### expired and let him choose a different one. |
|
encryptionCertificates.truncate( 0 ); |
|
return Kpgp::Failure; |
|
} |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
void MessageComposer::encryptMessage( KMMessage* msg, |
|
const QStringList& recipients, |
|
bool doSign, bool doEncrypt, |
|
int previousBoundaryLevel, |
|
KMMessagePart newBodyPart ) |
|
{ |
|
// This c-string (init empty here) is set by *first* testing of expiring |
|
// encryption certificate: stops us from repeatedly asking same questions. |
|
QCString encryptCertFingerprints; |
|
|
|
// determine the encryption certificates in case we need them |
|
if ( mSelectedCryptPlug ) { |
|
bool encrypt = doEncrypt; |
|
if( !encrypt ) { |
|
// check whether at least one attachment is marked for encryption |
|
for ( KMAtmListViewItem* atmlvi = |
|
static_cast<KMAtmListViewItem*>( mComposeWin->mAtmItemList.first() ); |
|
atmlvi; |
|
atmlvi = static_cast<KMAtmListViewItem*>( mComposeWin->mAtmItemList.next() ) ) { |
|
if ( atmlvi->isEncrypt() ) { |
|
encrypt = true; |
|
break; |
|
} |
|
} |
|
} |
|
if ( encrypt ) { |
|
Kpgp::Result result = |
|
getEncryptionCertificates( recipients, encryptCertFingerprints ); |
|
if ( result != Kpgp::Ok ) { |
|
mRc = false; |
|
return; |
|
} |
|
if ( encryptCertFingerprints.isEmpty() ) { |
|
// the user wants to send the message unencrypted |
|
mComposeWin->setEncryption( false, false ); |
|
doEncrypt = false; |
|
} |
|
} |
|
} |
|
|
|
// encrypt message |
|
if( doEncrypt ) { |
|
QCString innerContent; |
|
if( doSign && mSelectedCryptPlug ) { |
|
DwBodyPart* dwPart = msg->createDWBodyPart( &newBodyPart ); |
|
dwPart->Assemble(); |
|
innerContent = dwPart->AsString().c_str(); |
|
delete dwPart; |
|
dwPart = 0; |
|
} else |
|
innerContent = mEncodedBody; |
|
|
|
// now do the encrypting: |
|
{ |
|
if( mSelectedCryptPlug ) { |
|
// 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 = KMMessage::lf2crlf( innerContent ); |
|
kdDebug(5006) << " done." << endl; |
|
|
|
StructuringInfoWrapper structuring( mSelectedCryptPlug ); |
|
|
|
QByteArray encryptedBody; |
|
Kpgp::Result result = pgpEncryptedMsg( encryptedBody, innerContent, |
|
structuring, |
|
encryptCertFingerprints ); |
|
|
|
if( Kpgp::Ok == result ) { |
|
mRc = processStructuringInfo( QString::fromUtf8( mSelectedCryptPlug->bugURL() ), |
|
previousBoundaryLevel + doEncrypt ? 2 : 1, |
|
newBodyPart.contentDescription(), |
|
newBodyPart.typeStr(), |
|
newBodyPart.subtypeStr(), |
|
newBodyPart.contentDisposition(), |
|
newBodyPart.contentTransferEncodingStr(), |
|
innerContent, |
|
"encrypted data", |
|
encryptedBody, |
|
structuring, |
|
newBodyPart ); |
|
if ( !mRc ) |
|
KMessageBox::sorry(mComposeWin, mErrorProcessingStructuringInfo); |
|
} else { |
|
mRc = false; |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt><p><b>This message could not be encrypted:</b></p>" |
|
"<p>The Crypto Plug-in '%1' did not return an encoded text " |
|
"block;</p>" |
|
"<p>probably, a recipient's public key was not found or is " |
|
"untrusted.</p></qt>" ) |
|
.arg( mSelectedCryptPlug->libName() ) ); |
|
} |
|
} else { |
|
// we try calling the *old* build-in code for OpenPGP encrypting |
|
Kpgp::Block block; |
|
block.setText( innerContent ); |
|
|
|
// encrypt the message |
|
Kpgp::Result result = |
|
block.encrypt( recipients, mPgpIdentity, doSign, mCharset ); |
|
|
|
if( Kpgp::Ok == result ) { |
|
newBodyPart.setBodyEncodedBinary( block.text() ); |
|
newBodyPart.setCharset( mOldBodyPart.charset() ); |
|
} else { |
|
mRc = false; |
|
KMessageBox::sorry(mComposeWin, |
|
i18n("<qt><p>This message could not be encrypted.</p>%1</qt>") |
|
.arg( mErrorNoCryptPlugAndNoBuildIn )); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// process the attachments that are not included into the body |
|
if( mRc ) { |
|
const KMMessagePart& ourFineBodyPart( (doSign || doEncrypt) |
|
? newBodyPart : mOldBodyPart ); |
|
if( !mComposeWin->mAtmList.isEmpty() |
|
&& ( !mEarlyAddAttachments || !mAllAttachmentsAreInBody ) ) { |
|
// set the content type header |
|
msg->headers().ContentType().FromString( "Multipart/Mixed" ); |
|
kdDebug(5006) << "MessageComposer::encryptMessage() : set top level Content-Type to Multipart/Mixed" << endl; |
|
// msg->setBody( "This message is in MIME format.\n" |
|
// "Since your mail reader does not understand this format,\n" |
|
// "some or all parts of this message may not be legible." ); |
|
// add our Body Part |
|
DwBodyPart* tmpDwPart = msg->createDWBodyPart( &ourFineBodyPart ); |
|
DwHeaders& headers = tmpDwPart->Headers(); |
|
DwMediaType& ct = headers.ContentType(); |
|
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) |
|
int idx; |
|
KMMessagePart newAttachPart; |
|
KMMessagePart *attachPart; |
|
for( idx=0, attachPart = mComposeWin->mAtmList.first(); |
|
attachPart; |
|
attachPart = mComposeWin->mAtmList.next(), ++idx ) { |
|
kdDebug(5006) << " processing " << idx << ". attachment" << endl; |
|
|
|
const bool cryptFlagsDifferent = mSelectedCryptPlug |
|
? ((mComposeWin->encryptFlagOfAttachment( idx ) != doEncrypt) |
|
|| (mComposeWin->signFlagOfAttachment( idx ) != doSign) ) |
|
: false; |
|
// CONTROVERSIAL |
|
const bool encryptThisNow = doEncrypt && ( cryptFlagsDifferent ? mComposeWin->encryptFlagOfAttachment( idx ) : false ); |
|
const bool signThisNow = doSign && ( cryptFlagsDifferent ? mComposeWin->signFlagOfAttachment( idx ) : false ); |
|
|
|
if( cryptFlagsDifferent || !mEarlyAddAttachments ) { |
|
|
|
if( encryptThisNow || signThisNow ) { |
|
|
|
KMMessagePart& rEncryptMessagePart( *attachPart ); |
|
|
|
// prepare the attachment's content |
|
// signed/encrypted body parts must be either QP or base64 encoded |
|
QCString cte = attachPart->cteStr().lower(); |
|
if( ( "8bit" == cte ) |
|
|| ( ( attachPart->type() == DwMime::kTypeText ) |
|
&& ( "7bit" == cte ) ) ) { |
|
QByteArray body = attachPart->bodyDecodedBinary(); |
|
QValueList<int> dummy; |
|
attachPart->setBodyAndGuessCte(body, dummy, false, true); |
|
kdDebug(5006) << "Changed encoding of message part from " |
|
<< cte << " to " << attachPart->cteStr() << endl; |
|
} |
|
DwBodyPart* innerDwPart = msg->createDWBodyPart( attachPart ); |
|
innerDwPart->Assemble(); |
|
QCString encodedAttachment = innerDwPart->AsString().c_str(); |
|
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 = KMMessage::lf2crlf( encodedAttachment ); |
|
kdDebug(5006) << " done." << endl; |
|
|
|
// sign this attachment |
|
if( signThisNow ) { |
|
kdDebug(5006) << " sign " << idx << ". attachment separately" << endl; |
|
StructuringInfoWrapper structuring( mSelectedCryptPlug ); |
|
|
|
pgpSignedMsg( encodedAttachment, structuring ); |
|
QByteArray signature = mSignature; |
|
mRc = !signature.isEmpty(); |
|
if( mRc ) { |
|
mRc = processStructuringInfo( QString::fromUtf8( mSelectedCryptPlug->bugURL() ), |
|
previousBoundaryLevel + 10 + idx, |
|
attachPart->contentDescription(), |
|
attachPart->typeStr(), |
|
attachPart->subtypeStr(), |
|
attachPart->contentDisposition(), |
|
attachPart->contentTransferEncodingStr(), |
|
encodedAttachment, |
|
"signature", |
|
signature, |
|
structuring, |
|
newAttachPart ); |
|
if( mRc ) { |
|
if( encryptThisNow ) { |
|
rEncryptMessagePart = newAttachPart; |
|
DwBodyPart* dwPart = msg->createDWBodyPart( &newAttachPart ); |
|
dwPart->Assemble(); |
|
encodedAttachment = dwPart->AsString().c_str(); |
|
delete dwPart; |
|
dwPart = 0; |
|
} |
|
} else |
|
KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo ); |
|
} else { |
|
// quit the attachments' loop |
|
break; |
|
} |
|
} |
|
if( encryptThisNow ) { |
|
kdDebug(5006) << " encrypt " << idx << ". attachment separately" << endl; |
|
StructuringInfoWrapper structuring( mSelectedCryptPlug ); |
|
QByteArray encryptedBody; |
|
Kpgp::Result result = pgpEncryptedMsg( encryptedBody, |
|
encodedAttachment, |
|
structuring, |
|
encryptCertFingerprints ); |
|
|
|
if( Kpgp::Ok == result ) { |
|
mRc = processStructuringInfo( QString::fromUtf8( mSelectedCryptPlug->bugURL() ), |
|
previousBoundaryLevel + 11 + idx, |
|
rEncryptMessagePart.contentDescription(), |
|
rEncryptMessagePart.typeStr(), |
|
rEncryptMessagePart.subtypeStr(), |
|
rEncryptMessagePart.contentDisposition(), |
|
rEncryptMessagePart.contentTransferEncodingStr(), |
|
encodedAttachment, |
|
"encrypted data", |
|
encryptedBody, |
|
structuring, |
|
newAttachPart ); |
|
if ( !mRc ) |
|
KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo ); |
|
} else |
|
mRc = false; |
|
} |
|
msg->addBodyPart( &newAttachPart ); |
|
} else |
|
msg->addBodyPart( attachPart ); |
|
|
|
kdDebug(5006) << " added " << idx << ". attachment to this Multipart/Mixed" << endl; |
|
} else { |
|
kdDebug(5006) << " " << idx << ". attachment was part of the BODY already" << endl; |
|
} |
|
} |
|
} else { |
|
if( ourFineBodyPart.originalContentTypeStr() ) { |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n A.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
msg->headers().ContentType().FromString( ourFineBodyPart.originalContentTypeStr() ); |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n B.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
msg->headers().ContentType().Parse(); |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n C.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
kdDebug(5006) << "MessageComposer::encryptMessage() : set top level Content-Type from originalContentTypeStr()" << endl; |
|
} else { |
|
msg->headers().ContentType().FromString( ourFineBodyPart.typeStr() + "/" + ourFineBodyPart.subtypeStr() ); |
|
kdDebug(5006) << "MessageComposer::encryptMessage() : set top level Content-Type from typeStr()/subtypeStr()" << endl; |
|
} |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n D.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
if ( !ourFineBodyPart.charset().isEmpty() ) |
|
msg->setCharset( ourFineBodyPart.charset() ); |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n E.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
msg->setHeaderField( "Content-Transfer-Encoding", |
|
ourFineBodyPart.contentTransferEncodingStr() ); |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n F.:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl; |
|
msg->setHeaderField( "Content-Description", |
|
ourFineBodyPart.contentDescription() ); |
|
msg->setHeaderField( "Content-Disposition", |
|
ourFineBodyPart.contentDisposition() ); |
|
|
|
kdDebug(5006) << "MessageComposer::encryptMessage() : top level headers and body adjusted" << endl; |
|
|
|
// set body content |
|
// msg->setBody( ourFineBodyPart.body() ); |
|
// msg->setMultiPartBody( ourFineBodyPart.body() ); |
|
//kdDebug(5006) << "MessageComposer::encryptMessage():\n 98.:\n|||" << msg->asString() << "|||\n\n" << endl; |
|
//kdDebug(5006) << "MessageComposer::encryptMessage():\n 98a.:\n|||" << ourFineBodyPart.body() << "|||\n\n" << endl; |
|
if ( mComposeWin->mEditor->textFormat() == Qt::RichText && !(doSign || doEncrypt) ) { // add the boundary to the header |
|
DwMediaType & contentType = msg->dwContentType(); |
|
contentType.SetBoundary(mSaveBoundary); |
|
} |
|
msg->setBody(ourFineBodyPart.body() ); |
|
|
|
//kdDebug(5006) << "MessageComposer::encryptMessage():\n 99.:\n|||" << msg->asString() << "|||\n\n" << endl; |
|
//msg->headers().Assemble(); |
|
//kdDebug(5006) << "\n\n\nMessageComposer::messagecomposer():\n Z.:\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, |
|
uint boundaryLevel, |
|
const QString contentDescClear, |
|
const QCString contentTypeClear, |
|
const QCString contentSubtypeClear, |
|
const QCString contentDispClear, |
|
const QCString contentTEncClear, |
|
const QCString& clearCStr, |
|
const QString /*contentDescCiph*/, |
|
const QByteArray& ciphertext, |
|
const StructuringInfoWrapper& structuring, |
|
KMMessagePart& resultingPart ) |
|
{ |
|
bool bOk = true; |
|
|
|
if( structuring.data.makeMimeObject ) { |
|
QCString mainHeader = "Content-Type: "; |
|
if( structuring.data.contentTypeMain |
|
&& 0 < strlen( structuring.data.contentTypeMain ) ) |
|
mainHeader += structuring.data.contentTypeMain; |
|
else { |
|
if( structuring.data.makeMultiMime ) |
|
mainHeader += "text/plain"; |
|
else |
|
mainHeader += contentTypeClear + '/' + contentSubtypeClear; |
|
} |
|
|
|
QCString boundaryCStr; // storing boundary string data |
|
// add "boundary" parameter |
|
if( structuring.data.makeMultiMime ) { |
|
// calculate boundary value |
|
DwMediaType tmpCT; |
|
tmpCT.CreateBoundary( boundaryLevel ); |
|
boundaryCStr = tmpCT.Boundary().c_str(); |
|
// remove old "boundary" parameter |
|
int boundA = mainHeader.find( "boundary=", 0, false ); |
|
int boundZ; |
|
if( -1 < boundA ) { |
|
// take into account a leading "; " string |
|
while( 0 < boundA && ' ' == mainHeader[ boundA-1 ] ) |
|
--boundA; |
|
if( 0 < boundA && ';' == mainHeader[ boundA-1 ] ) |
|
--boundA; |
|
boundZ = mainHeader.find( ';', boundA+1 ); |
|
if( -1 == boundZ ) |
|
mainHeader.truncate( boundA ); |
|
else |
|
mainHeader.remove( boundA, (1 + boundZ - boundA) ); |
|
} |
|
// insert new "boundary" parameter |
|
QCString bStr( "; boundary=\"" ); |
|
bStr += boundaryCStr; |
|
bStr += "\""; |
|
mainHeader += bStr; |
|
} |
|
|
|
if( structuring.data.contentTypeMain |
|
&& 0 < strlen( structuring.data.contentTypeMain ) ) { |
|
if( structuring.data.contentDispMain |
|
&& 0 < strlen( structuring.data.contentDispMain ) ) { |
|
mainHeader += "\nContent-Disposition: "; |
|
mainHeader += structuring.data.contentDispMain; |
|
} |
|
if( structuring.data.contentTEncMain |
|
&& 0 < strlen( structuring.data.contentTEncMain ) ) { |
|
mainHeader += "\nContent-Transfer-Encoding: "; |
|
mainHeader += structuring.data.contentTEncMain; |
|
} |
|
} else { |
|
if( 0 < contentDispClear.length() ) { |
|
mainHeader += "\nContent-Disposition: "; |
|
mainHeader += contentDispClear; |
|
} |
|
if( 0 < contentTEncClear.length() ) { |
|
mainHeader += "\nContent-Transfer-Encoding: "; |
|
mainHeader += contentTEncClear; |
|
} |
|
} |
|
|
|
DwString mainDwStr; |
|
mainDwStr = mainHeader + "\n\n"; |
|
DwBodyPart mainDwPa( mainDwStr, 0 ); |
|
mainDwPa.Parse(); |
|
KMMessage::bodyPart( &mainDwPa, &resultingPart ); |
|
if( ! structuring.data.makeMultiMime ) { |
|
if( structuring.data.includeCleartext ) { |
|
QCString bodyText( clearCStr ); |
|
bodyText += '\n'; |
|
bodyText += ciphertext; |
|
resultingPart.setBodyEncoded( bodyText ); |
|
} else |
|
resultingPart.setBodyEncodedBinary( ciphertext ); |
|
} else { // OF if( ! structuring.data.makeMultiMime ) |
|
// 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( structuring.data.contentTypeVersion |
|
&& 0 < strlen( structuring.data.contentTypeVersion ) ) { |
|
DwString versStr( "Content-Type: " ); |
|
versStr += structuring.data.contentTypeVersion; |
|
versStr += "\nContent-Description: version code"; |
|
|
|
if( structuring.data.contentDispVersion |
|
&& 0 < strlen( structuring.data.contentDispVersion ) ) { |
|
versStr += "\nContent-Disposition: "; |
|
versStr += structuring.data.contentDispVersion; |
|
} |
|
if( structuring.data.contentTEncVersion |
|
&& 0 < strlen( structuring.data.contentTEncVersion ) ) { |
|
versStr += "\nContent-Transfer-Encoding: "; |
|
versStr += structuring.data.contentTEncVersion; |
|
} |
|
|
|
DwBodyPart versDwPa( versStr + "\n\n", 0 ); |
|
versDwPa.Parse(); |
|
KMMessagePart versKmPa; |
|
KMMessage::bodyPart(&versDwPa, &versKmPa); |
|
versKmPa.setBodyEncoded( structuring.data.bodyTextVersion ); |
|
// store string representation of the cleartext headers |
|
versCStr = versDwPa.Headers().AsString().c_str(); |
|
// store string representation of encoded cleartext |
|
versCStr += "\n"; |
|
versCStr += versKmPa.body(); |
|
} |
|
|
|
// Build a MIME part holding the code information |
|
// taking the body contents returned in ciphertext. |
|
if( structuring.data.contentTypeCode |
|
&& 0 < strlen( structuring.data.contentTypeCode ) ) { |
|
DwString codeStr( "Content-Type: " ); |
|
codeStr += structuring.data.contentTypeCode; |
|
if( structuring.data.contentTEncCode |
|
&& 0 < strlen( structuring.data.contentTEncCode ) ) { |
|
codeStr += "\nContent-Transfer-Encoding: "; |
|
codeStr += structuring.data.contentTEncCode; |
|
//} else { |
|
// codeStr += "\nContent-Transfer-Encoding: "; |
|
// codeStr += "base64"; |
|
} |
|
if( structuring.data.contentDispCode |
|
&& 0 < strlen( structuring.data.contentDispCode ) ) { |
|
codeStr += "\nContent-Disposition: "; |
|
codeStr += structuring.data.contentDispCode; |
|
} |
|
|
|
DwBodyPart codeDwPa( codeStr + "\n\n", 0 ); |
|
codeDwPa.Parse(); |
|
KMMessagePart codeKmPa; |
|
KMMessage::bodyPart(&codeDwPa, &codeKmPa); |
|
//if( structuring.data.contentTEncCode |
|
// && 0 < strlen( structuring.data.contentTEncCode ) ) { |
|
// codeKmPa.setCteStr( structuring.data.contentTEncCode ); |
|
//} else { |
|
// codeKmPa.setCteStr("base64"); |
|
//} |
|
codeKmPa.setBodyEncodedBinary( ciphertext ); |
|
// store string representation of the cleartext headers |
|
codeCStr = codeDwPa.Headers().AsString().c_str(); |
|
// store string representation of encoded cleartext |
|
codeCStr += "\n"; |
|
codeCStr += codeKmPa.body(); |
|
} else { |
|
// Plugin error! |
|
KMessageBox::sorry( mComposeWin, |
|
i18n("<qt><p>Error: The Crypto Plug-in '%1' returned<br>" |
|
" \" structuring.makeMultiMime \"<br>" |
|
"but did <b>not</b> specify a Content-Type header " |
|
"for the ciphertext that was generated.</p>" |
|
"<p>Please report this bug:<br>%2</p></qt>") |
|
.arg(mSelectedCryptPlug->libName()) |
|
.arg(bugURL) ); |
|
bOk = false; |
|
} |
|
|
|
QCString mainStr = "--" + boundaryCStr; |
|
if( structuring.data.includeCleartext && (0 < clearCStr.length()) ) |
|
mainStr += "\n" + clearCStr + "\n--" + boundaryCStr; |
|
if( 0 < versCStr.length() ) |
|
mainStr += "\n" + versCStr + "\n\n--" + boundaryCStr; |
|
if( 0 < codeCStr.length() ) |
|
mainStr += "\n" + codeCStr + "\n--" + boundaryCStr; |
|
mainStr += "--\n"; |
|
|
|
resultingPart.setBodyEncoded( mainStr ); |
|
} // OF if( ! structuring.data.makeMultiMime ) .. else |
|
} else { // OF if( structuring.data.makeMimeObject ) |
|
// Build a plain message body |
|
// based on the values returned in structInf. |
|
// Note: We do _not_ insert line breaks between the parts since |
|
// it is the plugin job to provide us with ready-to-use |
|
// texts containing all necessary line breaks. |
|
resultingPart.setContentDescription( contentDescClear ); |
|
resultingPart.setTypeStr( contentTypeClear ); |
|
resultingPart.setSubtypeStr( contentSubtypeClear ); |
|
resultingPart.setContentDisposition( contentDispClear ); |
|
resultingPart.setContentTransferEncodingStr( contentTEncClear ); |
|
QCString resultingBody; |
|
|
|
if( structuring.data.flatTextPrefix |
|
&& strlen( structuring.data.flatTextPrefix ) ) |
|
resultingBody += structuring.data.flatTextPrefix; |
|
if( structuring.data.includeCleartext ) { |
|
if( !clearCStr.isEmpty() ) |
|
resultingBody += clearCStr; |
|
if( structuring.data.flatTextSeparator |
|
&& strlen( structuring.data.flatTextSeparator ) ) |
|
resultingBody += structuring.data.flatTextSeparator; |
|
} |
|
if( ciphertext && strlen( ciphertext ) ) |
|
resultingBody += *ciphertext; |
|
else { |
|
// Plugin error! |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt><p>Error: The Crypto Plug-in '%1' did not return " |
|
"any encoded data.</p>" |
|
"<p>Please report this bug:<br>%2</p></qt>" ) |
|
.arg( mSelectedCryptPlug->libName() ) |
|
.arg( bugURL ) ); |
|
bOk = false; |
|
} |
|
if( structuring.data.flatTextPostfix |
|
&& strlen( structuring.data.flatTextPostfix ) ) |
|
resultingBody += structuring.data.flatTextPostfix; |
|
|
|
resultingPart.setBodyEncoded( resultingBody ); |
|
} // OF if( structuring.data.makeMimeObject ) .. else |
|
|
|
// No need to free the memory that was allocated for the ciphertext |
|
// since this memory is freed by it's QCString destructor. |
|
|
|
// Neither do we free the memory that was allocated |
|
// for our structuring info data's char* members since we are using |
|
// not the pure cryptplug's StructuringInfo struct |
|
// but the convenient CryptPlugWrapper's StructuringInfoWrapper class. |
|
|
|
return bOk; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QCString MessageComposer::breakLinesAndApplyCodec() |
|
{ |
|
QString text; |
|
QCString cText; |
|
|
|
if( mDisableBreaking || mComposeWin->mEditor->textFormat() == Qt::RichText) |
|
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 QCString(); |
|
} |
|
} |
|
|
|
return cText; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void MessageComposer::pgpSignedMsg( QCString cText, |
|
StructuringInfoWrapper& structuring ) |
|
{ |
|
if( !mSelectedCryptPlug ) { |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt>No active Crypto Plug-In could be found.<br><br>" |
|
"Please activate a Plug-In in the configuration dialog.</qt>" ) ); |
|
mRc = false; |
|
return; |
|
} |
|
|
|
QByteArray signature; |
|
|
|
kdDebug(5006) << "\nMessageComposer::pgpSignedMsg calling CRYPTPLUG " |
|
<< mSelectedCryptPlug->libName() << endl; |
|
|
|
bool bSign = true; |
|
|
|
if( mSignCertFingerprint.isEmpty() ) { |
|
// find out whether we are dealing with the OpenPGP or the S/MIME plugin |
|
if( mSelectedCryptPlug->protocol() == "openpgp" ) { |
|
// We are dealing with the OpenPGP plugin. Use Kpgp to determine |
|
// the signing key. |
|
if( !mPgpIdentity.isEmpty() ) { |
|
Kpgp::Module *pgp = Kpgp::Module::getKpgp(); |
|
|
|
// TODO: ASync call? |
|
|
|
Kpgp::Key* key = pgp->publicKey( mPgpIdentity ); |
|
if( key ) { |
|
|
|
// TODO: ASync call? |
|
|
|
mSignCertFingerprint = key->primaryFingerprint(); |
|
// kdDebug(5006) << "Signer: " << mComposeWin->from() |
|
// << "\nFingerprint of signature key: " |
|
// << QString( mSignCertFingerprint ) << endl; |
|
} else { |
|
KMessageBox::sorry( mComposeWin, |
|
i18n("<qt>This message could not be signed " |
|
"because the OpenPGP key which should be " |
|
"used for signing messages with this " |
|
"identity could not be found in your " |
|
"keyring.<br><br>" |
|
"You can change which OpenPGP key " |
|
"should be used with the current " |
|
"identity in the identity configuration.</qt>"), |
|
i18n("Missing Signing Key") ); |
|
bSign = false; |
|
} |
|
} else { |
|
KMessageBox::sorry( mComposeWin, |
|
i18n("<qt>This message could not be signed " |
|
"because you did not define the OpenPGP " |
|
"key which should be used for signing " |
|
"messages with this identity.<br><br>" |
|
"You can define which OpenPGP key " |
|
"should be used with the current " |
|
"identity in the identity configuration.</qt>"), |
|
i18n("Undefined Signing Key") ); |
|
bSign = false; |
|
} |
|
} else { // S/MIME |
|
|
|
// TODO (Bo): Why doesn't this use the getEncryptionCertificate |
|
// method below? |
|
|
|
int certSize = 0; |
|
QByteArray certificate; |
|
QString selectedCert; |
|
KListBoxDialog dialog( selectedCert, "", |
|
i18n( "&Select certificate:") ); |
|
dialog.resize( 700, 200 ); |
|
|
|
QCString signer = mComposeWin->from().utf8(); |
|
signer.replace('\x001', ' '); |
|
|
|
kdDebug(5006) << "\n\nRetrieving keys for: " << mComposeWin->from() |
|
<< endl; |
|
char* certificatePtr = 0; |
|
|
|
// TODO: ASync call? |
|
|
|
bool findCertsOk = |
|
mSelectedCryptPlug->findCertificates( &(*signer), &certificatePtr, |
|
&certSize, true ) |
|
&& (0 < certSize); |
|
kdDebug(5006) << "keys retrieved ok: " << findCertsOk << endl; |
|
|
|
bool useDialog = false; |
|
if( findCertsOk ) { |
|
kdDebug(5006) << "findCertificates() returned " << certificatePtr |
|
<< endl; |
|
certificate.assign( certificatePtr, certSize ); |
|
|
|
// fill selection dialog listbox |
|
dialog.entriesLB->clear(); |
|
int iA = 0, iZ = 0; |
|
while( iZ < certSize ) { |
|
if( (certificate[iZ] == '\1') || (certificate[iZ] == '\0') ) { |
|
char c = certificate[iZ]; |
|
if( (c == '\1') && !useDialog ) { |
|
// set up selection dialog |
|
useDialog = true; |
|
dialog.setCaption( i18n("Select Certificate [%1]") |
|
.arg( mComposeWin->from() ) ); |
|
} |
|
certificate[iZ] = '\0'; |
|
QString s = QString::fromUtf8( &certificate[iA] ); |
|
certificate[iZ] = c; |
|
if( useDialog ) |
|
dialog.entriesLB->insertItem( s ); |
|
else |
|
selectedCert = s; |
|
++iZ; |
|
iA = iZ; |
|
} |
|
++iZ; |
|
} |
|
|
|
// run selection dialog and retrieve user choice |
|
// OR take the single entry (if only one was found) |
|
if( useDialog ) { |
|
dialog.entriesLB->setFocus(); |
|
dialog.entriesLB->setSelected( 0, true ); |
|
bSign = (dialog.exec() == QDialog::Accepted); |
|
} |
|
|
|
if (bSign) { |
|
mSignCertFingerprint = selectedCert.utf8(); |
|
mSignCertFingerprint.remove( 0, mSignCertFingerprint.findRev( '(' )+1 ); |
|
mSignCertFingerprint.truncate( mSignCertFingerprint.length()-1 ); |
|
kdDebug(5006) << "Signer: " << mComposeWin->from() |
|
<< "\nFingerprint of signature key: " |
|
<< QString( mSignCertFingerprint ) << "\n\n\n"; |
|
if( mSignCertFingerprint.isEmpty() ) |
|
bSign = false; |
|
} |
|
} |
|
} |
|
|
|
// Check for expiry of the signer, CA, and Root certificate. |
|
// Only do the expiry check if the plugin has this feature |
|
// and if there in *no* fingerprint in mSignCertFingerprint already. |
|
if( mSelectedCryptPlug->hasFeature( Feature_WarnSignCertificateExpiry ) ){ |
|
|
|
// TODO: ASync call? |
|
|
|
int sigDaysLeft = mSelectedCryptPlug->signatureCertificateDaysLeftToExpiry( mSignCertFingerprint ); |
|
|
|
// TODO: ASync calls? |
|
|
|
if( mSelectedCryptPlug->signatureCertificateExpiryNearWarning() && |
|
sigDaysLeft < |
|
mSelectedCryptPlug->signatureCertificateExpiryNearInterval() ) { |
|
QString txt1; |
|
if( 0 < sigDaysLeft ) |
|
txt1 = i18n( "The certificate you want to use for signing expires in %1 days.<br>This means that after this period, the recipients will not be able to check your signature any longer." ).arg( sigDaysLeft ); |
|
else if( 0 > sigDaysLeft ) |
|
txt1 = i18n( "The certificate you want to use for signing expired %1 days ago.<br>This means that the recipients will not be able to check your signature." ).arg( -sigDaysLeft ); |
|
else |
|
txt1 = i18n( "The certificate you want to use for signing expires today.<br>This means that, starting from tomorrow, the recipients will not be able to check your signature any longer." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use this " |
|
"certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
i18n( "Certificate Warning" ), |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bSign = false; |
|
} |
|
|
|
if( bSign && ( 0 <= mSelectedCryptPlug->libName().find( "smime", 0, false ) ) ) { |
|
|
|
// TODO: ASync call? |
|
|
|
int rootDaysLeft = mSelectedCryptPlug->rootCertificateDaysLeftToExpiry( mSignCertFingerprint ); |
|
|
|
// TODO: ASync calls? |
|
|
|
if( mSelectedCryptPlug->rootCertificateExpiryNearWarning() && |
|
rootDaysLeft < |
|
mSelectedCryptPlug->rootCertificateExpiryNearInterval() ) { |
|
QString txt1; |
|
if( 0 < rootDaysLeft ) |
|
txt1 = i18n( "The root certificate of the certificate you want to use for signing expires in %1 days.<br>This means that after this period, the recipients will not be able to check your signature any longer." ).arg( rootDaysLeft ); |
|
else if( 0 > rootDaysLeft ) |
|
txt1 = i18n( "The root certificate of the certificate you want to use for signing expired %1 days ago.<br>This means that the recipients will not be able to check your signature." ).arg( -rootDaysLeft ); |
|
else |
|
txt1 = i18n( "The root certificate of the certificate you want to use for signing expires today.<br>This means that beginning from tomorrow, the recipients will not be able to check your signature any longer." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use this " |
|
"certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
i18n( "Certificate Warning" ), |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bSign = false; |
|
} |
|
} |
|
|
|
if( bSign && ( 0 <= mSelectedCryptPlug->libName().find( "smime", 0, false ) ) ) { |
|
|
|
// TODO: ASync call? |
|
|
|
int caDaysLeft = mSelectedCryptPlug->caCertificateDaysLeftToExpiry( mSignCertFingerprint ); |
|
|
|
// TODO: ASync calls? |
|
|
|
if( mSelectedCryptPlug->caCertificateExpiryNearWarning() && |
|
caDaysLeft < |
|
mSelectedCryptPlug->caCertificateExpiryNearInterval() ) { |
|
QString txt1; |
|
if( 0 < caDaysLeft ) |
|
txt1 = i18n( "The CA certificate of the certificate you want to use for signing expires in %1 days.<br>This means that after this period, the recipients will not be able to check your signature any longer." ).arg( caDaysLeft ); |
|
else if( 0 > caDaysLeft ) |
|
txt1 = i18n( "The CA certificate of the certificate you want to use for signing expired %1 days ago.<br>This means that the recipients will not be able to check your signature." ).arg( -caDaysLeft ); |
|
else |
|
txt1 = i18n( "The CA certificate of the certificate you want to use for signing expires today.<br>This means that beginning from tomorrow, the recipients will not be able to check your signature any longer." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use this " |
|
"certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
i18n( "Certificate Warning" ), |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bSign = false; |
|
} |
|
} |
|
} |
|
// Check whether the sender address of the signer is contained in |
|
// the certificate, but only do this if the plugin has this feature. |
|
if( mSelectedCryptPlug->hasFeature( Feature_WarnSignEmailNotInCertificate ) ) { |
|
|
|
// TODO: ASync calls? |
|
|
|
if( bSign && mSelectedCryptPlug->warnNoCertificate() && |
|
!mSelectedCryptPlug->isEmailInCertificate( QString( KPIM::getEmailAddr( mComposeWin->from() ) ).utf8(), mSignCertFingerprint ) ) { |
|
QString txt1 = i18n( "The certificate you want to use for signing does not contain your sender email address.<br>This means that it is not possible for the recipients to check whether the email really came from you." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use this " |
|
"certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
i18n( "Certificate Warning" ), |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bSign = false; |
|
} |
|
} |
|
} // if( mSignCertFingerprint.isEmpty() ) |
|
|
|
// Finally sign the message, but only if the plugin has this feature. |
|
// TODO (Bo): If this isn't the case, why go through this method at all? |
|
// Why not just exit at the beginning? |
|
if( mSelectedCryptPlug->hasFeature( Feature_SignMessages ) ) { |
|
size_t cipherLen; |
|
|
|
const char* cleartext = cText; |
|
char* ciphertext = 0; |
|
|
|
if( mDebugComposerCrypto ) { |
|
QFile fileS( "dat_11_sign.input" ); |
|
if( fileS.open( IO_WriteOnly ) ) { |
|
QDataStream ds( &fileS ); |
|
ds.writeRawBytes( cleartext, strlen( cleartext ) ); |
|
fileS.close(); |
|
} |
|
} |
|
|
|
if( bSign ) { |
|
int errId = 0; |
|
char* errTxt = 0; |
|
|
|
// TODO: ASync call? Likely, yes :-) |
|
|
|
if( mSelectedCryptPlug->signMessage( cleartext, |
|
&ciphertext, &cipherLen, |
|
mSignCertFingerprint, |
|
structuring, &errId, &errTxt ) ) { |
|
if( mDebugComposerCrypto ) { |
|
QFile fileD( "dat_12_sign.output" ); |
|
if( fileD.open( IO_WriteOnly ) ) { |
|
QDataStream ds( &fileD ); |
|
ds.writeRawBytes( ciphertext, cipherLen ); |
|
fileD.close(); |
|
} |
|
} |
|
signature.assign( ciphertext, cipherLen ); |
|
kdDebug(5006) << "Assigned signature\n"; |
|
} else if ( errId == /*GPGME_Canceled*/20 ) { |
|
mRc = false; |
|
return; |
|
} else { |
|
QString error = "#" + QString::number( errId ) + " : "; |
|
if( errTxt ) |
|
error += errTxt; |
|
else |
|
error += i18n("[unknown error]"); |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt><p><b>This message could not be signed;</b></p>" |
|
"<p>The Crypto Plug-In '%1' reported the following " |
|
"details:</p>" |
|
"<p><i>%2</i></p>" |
|
"<p>Your configuration might be invalid or the Plug-In " |
|
"damaged;</p>" |
|
"<p><b>Please contact your system " |
|
"administrator.</b></p></qt>" ) |
|
.arg( mSelectedCryptPlug->libName() ) |
|
.arg( error ) ); |
|
} |
|
// we do NOT call a "delete ciphertext" ! |
|
// since "signature" will take care for it (is a QByteArray) |
|
delete errTxt; |
|
errTxt = 0; |
|
} |
|
} |
|
|
|
// PENDING(khz,kalle) Warn if there was no signature? (because of |
|
// a problem or because the plugin does not allow signing? |
|
|
|
/* ----------------------------- */ |
|
kdDebug(5006) << "\nMessageComposer::pgpSignedMsg returning from CRYPTPLUG.\n" << endl; |
|
|
|
mSignature = signature; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
Kpgp::Result MessageComposer::pgpEncryptedMsg( QByteArray & encryptedBody, |
|
QCString cText, |
|
StructuringInfoWrapper& structuring, |
|
QCString& encryptCertFingerprints ) |
|
{ |
|
if( !mSelectedCryptPlug ) { |
|
KMessageBox::sorry( mComposeWin, |
|
i18n( "<qt>No active Crypto Plug-In could be found.<br><br>" |
|
"Please activate a Plug-In in the configuration dialog.</qt>" ) ); |
|
|
|
// TODO (Bo): Sure about this? (Will stop all further jobs) |
|
mRc = false; |
|
|
|
// TODO (Bo): The method always returns Ok, so better do it here also. |
|
// TODO (Bo): Figure out if this should really be the case |
|
return Kpgp::Ok; |
|
} |
|
|
|
Kpgp::Result result = Kpgp::Ok; |
|
|
|
// we call the cryptplug |
|
kdDebug(5006) << "\nMessageComposer::pgpEncryptedMsg: going to call CRYPTPLUG " |
|
<< mSelectedCryptPlug->libName() << endl; |
|
|
|
// PENDING(khz,kalle) Warn if no encryption? |
|
|
|
const char* cleartext = cText; |
|
const char* ciphertext = 0; |
|
|
|
// Actually do the encryption, if the plugin supports this |
|
size_t cipherLen; |
|
|
|
int errId = 0; |
|
char* errTxt = 0; |
|
if( mSelectedCryptPlug->hasFeature( Feature_EncryptMessages ) && |
|
|
|
// TODO: ASync call? Likely, yes :-) |
|
|
|
mSelectedCryptPlug->encryptMessage( cleartext, |
|
&ciphertext, &cipherLen, |
|
encryptCertFingerprints, |
|
structuring, &errId, &errTxt ) |
|
&& ciphertext ) |
|
encryptedBody.assign( ciphertext, cipherLen ); |
|
else { |
|
QString error = "#" + QString::number( errId ) + " : "; |
|
if( errTxt ) |
|
error += errTxt; |
|
else |
|
error += i18n("[unknown error]"); |
|
KMessageBox::sorry(mComposeWin, |
|
i18n("<qt><p><b>This message could not be encrypted;</b></p>" |
|
"<p>The Crypto Plug-In '%1' reported the following " |
|
"details:</p>" |
|
"<p><i>%2</i></p>" |
|
"<p>Your configuration might be invalid or the Plug-In " |
|
"damaged;</p>" |
|
"<p><b>Please contact your system " |
|
"administrator.</b></p></qt>") |
|
.arg(mSelectedCryptPlug->libName()) |
|
.arg( error ) ); |
|
} |
|
delete errTxt; |
|
errTxt = 0; |
|
|
|
// we do NOT delete the "ciphertext" ! |
|
// bacause "encoding" will take care for it (is a QByteArray) |
|
kdDebug(5006) << "\nMessageComposer::pgpEncryptedMsg: returning from CRYPTPLUG.\n" << endl; |
|
|
|
// TODO (Bo): Why does this always return success? |
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QCString MessageComposer::getEncryptionCertificate( const QString& recipient ) |
|
{ |
|
bool bEncrypt = true; |
|
|
|
QCString addressee = recipient.utf8(); |
|
addressee.replace('\x001', ' '); |
|
kdDebug(5006) << "\n\n1st try: Retrieving keys for: " << recipient << endl; |
|
|
|
QString selectedCert; |
|
KListBoxDialog dialog( selectedCert, "", i18n( "&Select certificate:" ) ); |
|
dialog.resize( 700, 200 ); |
|
bool useDialog; |
|
int certSize = 0; |
|
QByteArray certificateList; |
|
|
|
bool askForDifferentSearchString = false; |
|
do { |
|
certSize = 0; |
|
char* certificatePtr = 0; |
|
bool findCertsOk; |
|
if( askForDifferentSearchString ) |
|
findCertsOk = false; |
|
else { |
|
|
|
// TODO: ASync call? |
|
|
|
findCertsOk = mSelectedCryptPlug->findCertificates( &(*addressee), |
|
&certificatePtr, |
|
&certSize, |
|
false ) |
|
&& (0 < certSize); |
|
kdDebug(5006) << "keys retrieved successfully: " << findCertsOk << "\n" << endl; |
|
kdDebug(5006) << "findCertificates() 1st try returned " << certificatePtr << endl; |
|
if( findCertsOk ) |
|
certificateList.assign( certificatePtr, certSize ); |
|
} |
|
while( !findCertsOk ) { |
|
bool bOk = false; |
|
addressee = KInputDialog::getText( askForDifferentSearchString |
|
? i18n("Look for Other Certificates") |
|
: i18n("No Certificate Found"), |
|
i18n("Enter different address for recipient %1 " |
|
"or enter \" * \" to see all certificates:") |
|
.arg(recipient), |
|
addressee, &bOk, mComposeWin ) |
|
.stripWhiteSpace().utf8(); |
|
askForDifferentSearchString = false; |
|
if( bOk ) { |
|
addressee = addressee.simplifyWhiteSpace(); |
|
if( ("\"*\"" == addressee) || |
|
("\" *\"" == addressee) || |
|
("\"* \"" == addressee) || |
|
("\" * \"" == addressee)) // You never know what users type. :-) |
|
addressee = "*"; |
|
kdDebug(5006) << "\n\nnext try: Retrieving keys for: " << addressee << endl; |
|
certSize = 0; |
|
char* certificatePtr = 0; |
|
|
|
// TODO: ASync call? |
|
|
|
findCertsOk = mSelectedCryptPlug->findCertificates( &(*addressee), |
|
&certificatePtr, |
|
&certSize, |
|
false ) |
|
&& (0 < certSize); |
|
kdDebug(5006) << "keys retrieved successfully: " << findCertsOk << "\n" << endl; |
|
kdDebug(5006) << "findCertificates() 2nd try returned " << certificatePtr << endl; |
|
if( findCertsOk ) |
|
certificateList.assign( certificatePtr, certSize ); |
|
} else { |
|
bEncrypt = false; |
|
break; |
|
} |
|
} |
|
if( bEncrypt && findCertsOk ) { |
|
// fill selection dialog listbox |
|
dialog.entriesLB->clear(); |
|
// show dialog even if only one entry to allow specifying of |
|
// another search string _instead_of_ the recipients address |
|
bool bAlwaysShowDialog = true; |
|
|
|
useDialog = false; |
|
int iA = 0, iZ = 0; |
|
while( iZ < certSize ) { |
|
if( (certificateList.at(iZ) == '\1') || (certificateList.at(iZ) == '\0') ) { |
|
kdDebug(5006) << "iA=" << iA << " iZ=" << iZ << endl; |
|
char c = certificateList.at(iZ); |
|
if( (bAlwaysShowDialog || (c == '\1')) && !useDialog ) { |
|
// set up selection dialog |
|
useDialog = true; |
|
dialog.setCaption( i18n( "Select Certificate for Encryption [%1]" ) |
|
.arg( recipient ) ); |
|
dialog.setLabelAbove( |
|
i18n( "&Select certificate for recipient %1:" ) |
|
.arg( recipient ) ); |
|
} |
|
certificateList.at(iZ) = '\0'; |
|
QString s = QString::fromUtf8( &certificateList.at(iA) ); |
|
certificateList.at(iZ) = c; |
|
if( useDialog ) |
|
dialog.entriesLB->insertItem( s ); |
|
else |
|
selectedCert = s; |
|
++iZ; |
|
iA = iZ; |
|
} |
|
++iZ; |
|
} |
|
// run selection dialog and retrieve user choice |
|
// OR take the single entry (if only one was found) |
|
if( useDialog ) { |
|
dialog.setCommentBelow( |
|
i18n("(Certificates matching address \"%1\", " |
|
"press [Cancel] to use different address for recipient %2.)") |
|
.arg( addressee ) |
|
.arg( recipient ) ); |
|
dialog.entriesLB->setFocus(); |
|
dialog.entriesLB->setSelected( 0, true ); |
|
askForDifferentSearchString = (dialog.exec() != QDialog::Accepted); |
|
} |
|
} |
|
} while ( askForDifferentSearchString ); |
|
|
|
if( bEncrypt ) |
|
return selectedCert.utf8(); |
|
else |
|
return QCString(); |
|
} |
|
|
|
// TODO: Does this have any crypto stuff that needs to be async? |
|
bool MessageComposer::checkForEncryptCertificateExpiry( const QString& recipient, |
|
const QCString& certFingerprint ) |
|
{ |
|
bool bEncrypt = true; |
|
|
|
// Check for expiry of various certificates, but only if the |
|
// plugin supports this. |
|
if( mSelectedCryptPlug->hasFeature( Feature_WarnEncryptCertificateExpiry ) ) { |
|
QString captionWarn = i18n( "Certificate Warning [%1]" ).arg( recipient ); |
|
|
|
|
|
// TODO: ASync call? |
|
|
|
int encRecvDaysLeft = |
|
mSelectedCryptPlug->receiverCertificateDaysLeftToExpiry( certFingerprint ); |
|
if( mSelectedCryptPlug->receiverCertificateExpiryNearWarning() && |
|
encRecvDaysLeft < |
|
mSelectedCryptPlug->receiverCertificateExpiryNearWarningInterval() ) { |
|
QString txt1; |
|
if( 0 < encRecvDaysLeft ) |
|
txt1 = i18n( "The certificate of the recipient you want to send this " |
|
"message to expires in %1 days.<br>This means that after " |
|
"this period, the recipient will not be able to read " |
|
"your message any longer." ) |
|
.arg( encRecvDaysLeft ); |
|
else if( 0 > encRecvDaysLeft ) |
|
txt1 = i18n( "The certificate of the recipient you want to send this " |
|
"message to expired %1 days ago.<br>This means that the " |
|
"recipient will not be able to read your message." ) |
|
.arg( -encRecvDaysLeft ); |
|
else |
|
txt1 = i18n( "The certificate of the recipient you want to send this " |
|
"message to expires today.<br>This means that beginning " |
|
"from tomorrow, the recipient will not be able to read " |
|
"your message any longer." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use " |
|
"this certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
captionWarn, |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bEncrypt = false; |
|
} |
|
|
|
if( bEncrypt ) { |
|
int certInChainDaysLeft = |
|
mSelectedCryptPlug->certificateInChainDaysLeftToExpiry( certFingerprint ); |
|
if( mSelectedCryptPlug->certificateInChainExpiryNearWarning() && |
|
certInChainDaysLeft < |
|
mSelectedCryptPlug->certificateInChainExpiryNearWarningInterval() ) { |
|
QString txt1; |
|
if( 0 < certInChainDaysLeft ) |
|
txt1 = i18n( "One of the certificates in the chain of the " |
|
"certificate of the recipient you want to send this " |
|
"message to expires in %1 days.<br>" |
|
"This means that after this period, the recipient " |
|
"might not be able to read your message any longer." ) |
|
.arg( certInChainDaysLeft ); |
|
else if( 0 > certInChainDaysLeft ) |
|
txt1 = i18n( "One of the certificates in the chain of the " |
|
"certificate of the recipient you want to send this " |
|
"message to expired %1 days ago.<br>" |
|
"This means that the recipient might not be able to " |
|
"read your message." ) |
|
.arg( -certInChainDaysLeft ); |
|
else |
|
txt1 = i18n( "One of the certificates in the chain of the " |
|
"certificate of the recipient you want to send this " |
|
"message to expires today.<br>This means that " |
|
"beginning from tomorrow, the recipient might not be " |
|
"able to read your message any longer." ); |
|
int ret = KMessageBox::warningYesNo( mComposeWin, |
|
i18n( "<qt><p>%1</p>" |
|
"<p>Do you still want to use this " |
|
"certificate?</p></qt>" ) |
|
.arg( txt1 ), |
|
captionWarn, |
|
KGuiItem( i18n("&Use Certificate") ), |
|
KGuiItem( i18n("&Do Not Use Certificate") ) ); |
|
if( ret == KMessageBox::No ) |
|
bEncrypt = false; |
|
} |
|
} |
|
} |
|
|
|
return bEncrypt; |
|
} |
|
|
|
|
|
#include "messagecomposer.moc"
|
|
|