diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /libkpgp | |
download | tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkpgp')
-rw-r--r-- | libkpgp/AUTHORS.kpgp | 13 | ||||
-rw-r--r-- | libkpgp/Makefile.am | 30 | ||||
-rw-r--r-- | libkpgp/README | 3 | ||||
-rwxr-xr-x | libkpgp/kpgp-3.1-upgrade-address-data.pl | 61 | ||||
-rw-r--r-- | libkpgp/kpgp.cpp | 1811 | ||||
-rw-r--r-- | libkpgp/kpgp.h | 469 | ||||
-rw-r--r-- | libkpgp/kpgp.upd | 9 | ||||
-rw-r--r-- | libkpgp/kpgpbase.cpp | 684 | ||||
-rw-r--r-- | libkpgp/kpgpbase.h | 239 | ||||
-rw-r--r-- | libkpgp/kpgpbase2.cpp | 1105 | ||||
-rw-r--r-- | libkpgp/kpgpbase5.cpp | 828 | ||||
-rw-r--r-- | libkpgp/kpgpbase6.cpp | 840 | ||||
-rw-r--r-- | libkpgp/kpgpbaseG.cpp | 855 | ||||
-rw-r--r-- | libkpgp/kpgpblock.cpp | 136 | ||||
-rw-r--r-- | libkpgp/kpgpblock.h | 356 | ||||
-rw-r--r-- | libkpgp/kpgpkey.cpp | 260 | ||||
-rw-r--r-- | libkpgp/kpgpkey.h | 780 | ||||
-rw-r--r-- | libkpgp/kpgpui.cpp | 1694 | ||||
-rw-r--r-- | libkpgp/kpgpui.h | 343 | ||||
-rw-r--r-- | libkpgp/pics/Makefile.am | 9 | ||||
-rw-r--r-- | libkpgp/pics/key.png | bin | 0 -> 311 bytes | |||
-rw-r--r-- | libkpgp/pics/key_bad.png | bin | 0 -> 339 bytes | |||
-rw-r--r-- | libkpgp/pics/key_ok.png | bin | 0 -> 360 bytes | |||
-rw-r--r-- | libkpgp/pics/key_unknown.png | bin | 0 -> 360 bytes |
24 files changed, 10525 insertions, 0 deletions
diff --git a/libkpgp/AUTHORS.kpgp b/libkpgp/AUTHORS.kpgp new file mode 100644 index 000000000..4fba20ca5 --- /dev/null +++ b/libkpgp/AUTHORS.kpgp @@ -0,0 +1,13 @@ +Program: kpgp + +Lars Knoll <knoll@mpi-hd.mpg.de> + - Original version, PGP2 and PGP5 support + +"J. Nick Koston" <bdraco@the.system.is.halted.net> + - GnuPG support + +Andreas Gungl <a.gungl@gmx.de> + - PGP6 and other enhancements + +Ingo Klöcker <ingo.kloecker@epost.de> + - KpgpKey and other enhancements diff --git a/libkpgp/Makefile.am b/libkpgp/Makefile.am new file mode 100644 index 000000000..3b99ef8b4 --- /dev/null +++ b/libkpgp/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = $(all_includes) +SUBDIRS = pics + +lib_LTLIBRARIES = libkpgp.la + +libkpgp_la_SOURCES = \ + kpgpblock.cpp \ + kpgpui.cpp \ + kpgpkey.cpp \ + kpgp.cpp \ + kpgpbase.cpp \ + kpgpbaseG.cpp \ + kpgpbase2.cpp \ + kpgpbase5.cpp \ + kpgpbase6.cpp + +libkpgp_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 4:0:2 + +libkpgp_la_LIBADD = $(LIB_KDEUI) $(LIB_POLL) + +METASOURCES = AUTO + +updatedir = $(kde_datadir)/kconf_update +update_DATA = kpgp.upd +update_SCRIPTS = kpgp-3.1-upgrade-address-data.pl + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/libkpgp.pot + +include $(top_srcdir)/admin/Doxyfile.am diff --git a/libkpgp/README b/libkpgp/README new file mode 100644 index 000000000..4c62d0fb8 --- /dev/null +++ b/libkpgp/README @@ -0,0 +1,3 @@ +pgp abstraction layer +Maintainer: Ingo Klöcker <ingo.kloecker@epost.de> +License: GPL diff --git a/libkpgp/kpgp-3.1-upgrade-address-data.pl b/libkpgp/kpgp-3.1-upgrade-address-data.pl new file mode 100755 index 000000000..46c416178 --- /dev/null +++ b/libkpgp/kpgp-3.1-upgrade-address-data.pl @@ -0,0 +1,61 @@ +#!/usr/bin/perl + +# Read all "AddressKeyEntry #" groups and the "EncryptionPreferences" group +# and store the information together in new "Address #" groups + +my %data; + +$currentGroup = ""; +$address = ""; + +while(<>) { + next if /^$/; + # filter out groups: + if( /^\[(.+)\]$/ ) { + $currentGroup = $1; + # delete the obsolete groups + if( ( $currentGroup =~ /^AddressKeyEntry \d*/ ) || + ( $currentGroup eq "EncryptionPreferences" ) ) { + print "# DELETEGROUP [$currentGroup]\n"; + } + next; + } + # store the values of the old groups and delete them + if( $currentGroup =~ /^AddressKeyEntry \d*/ ) { + if( /^Address=(.*)$/ ) { + $address = $1; + } + elsif( /^Key ID=(.*)$/ ) { + $data{$address}{"keyid"} = $1; + } + } + elsif( $currentGroup eq "EncryptionPreferences" ) { + ($address,$encrpref) = split /=/; + chomp $encrpref; + $data{$address}{"encrpref"} = $encrpref; + } +} + +# write the new address groups +$n = 1; +foreach $address ( keys %data ) { + %addressData = %{$data{$address}}; + print "[Address #$n]\n"; + print "Address=$address\n"; + if( exists $addressData{"encrpref"} ) { + $encrpref = $addressData{"encrpref"}; + print "EncryptionPreference=$encrpref\n"; + } + if( exists $addressData{"keyid"} ) { + $keyid = $addressData{"keyid"}; + print "Key IDs=$keyid\n"; + } + $n++; +} +$n--; + +# write the number of address groups +print "# DELETE [General]addressKeyEntries\n"; +print "# DELETE [General]addressEntries\n"; +print "[General]\naddressEntries=$n\n"; + diff --git a/libkpgp/kpgp.cpp b/libkpgp/kpgp.cpp new file mode 100644 index 000000000..d478d1999 --- /dev/null +++ b/libkpgp/kpgp.cpp @@ -0,0 +1,1811 @@ +/* -*- mode: C++; c-file-style: "gnu" -*- + kpgp.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#include <stdio.h> +#include <time.h> +#include <stdlib.h> +#include <assert.h> +#include <stdarg.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> + +#include <qlabel.h> +#include <qcursor.h> +#include <qapplication.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kconfigbase.h> +#include <kconfig.h> +#include <kstaticdeleter.h> + +#include "kpgpbase.h" +#include "kpgpui.h" +#include "kpgp.h" + +namespace Kpgp { + +Module *Module::kpgpObject = 0L; +static KStaticDeleter<Module> kpgpod; + +Module::Module() + : mPublicKeys(), + mPublicKeysCached(false), + mSecretKeys(), + mSecretKeysCached(false), + passphrase(0), passphrase_buffer_len(0), havePassPhrase(false) +{ + if (!kpgpObject) { + kdDebug(5100) << "creating new pgp object" << endl; + } + kpgpObject=kpgpod.setObject(Module::kpgpObject, this); + pgp = 0; + + config = new KConfig("kpgprc"); + + init(); +} + +Module::~Module() +{ + writeAddressData(); + + if (kpgpObject == this) kpgpObject = kpgpod.setObject( Module::kpgpObject, 0, false ); + clear(TRUE); + delete config; + delete pgp; +} + +// ----------------- public methods ------------------------- + +void +Module::init() +{ + wipePassPhrase(); + + // read kpgp config file entries + readConfig(); + + // read the email address -> { encryption keys, encryption preference } + // associations + readAddressData(); + + // do we have a pgp executable + checkForPGP(); + + // create the Base object later when it is + // needed to avoid the costly check done for + // the autodetection of PGP 2/6 + //assignPGPBase(); + delete pgp; + pgp=0; +} + + +void +Module::readConfig() +{ + storePass = config->readBoolEntry("storePass", false); + showEncryptionResult = config->readBoolEntry("showEncryptionResult", true); + mShowKeyApprovalDlg = config->readBoolEntry( "showKeysForApproval", true ); + // We have no config GUI for this key anymore, and the KPGP backend isn't ported, + // so let's just use Auto all the time. See #92619. + ///pgpType = (Module::PGPType) config->readNumEntry("pgpType", tAuto); + pgpType = tAuto; + flagEncryptToSelf = config->readBoolEntry("encryptToSelf", true); +} + +void +Module::writeConfig(bool sync) +{ + config->writeEntry("storePass", storePass); + config->writeEntry("showEncryptionResult", showEncryptionResult); + config->writeEntry( "showKeysForApproval", mShowKeyApprovalDlg ); + //config->writeEntry("pgpType", (int) pgpType); + config->writeEntry("encryptToSelf", flagEncryptToSelf); + + if(sync) + config->sync(); + + /// ### Why is the pgp object deleted? This is only necessary if the + /// PGP type was changed in the config dialog. + delete pgp; + pgp = 0; +} + + +void +Module::setUser(const KeyID& keyID) +{ + if (pgpUser != keyID) { + pgpUser = keyID; + wipePassPhrase(); + } +} + +const KeyID +Module::user(void) const +{ + return pgpUser; +} + + +void +Module::setEncryptToSelf(bool flag) +{ + flagEncryptToSelf = flag; +} + +bool +Module::encryptToSelf(void) const +{ + return flagEncryptToSelf; +} + + +void +Module::setStorePassPhrase(bool flag) +{ + storePass = flag; +} + +bool +Module::storePassPhrase(void) const +{ + return storePass; +} + +int +Module::prepare( bool needPassPhrase, Block* block ) +{ + if (0 == pgp) assignPGPBase(); + + if(!havePgp) + { + errMsg = i18n("Could not find PGP executable.\n" + "Please check your PATH is set correctly."); + return 0; + } + + if( block && ( block->status() & NO_SEC_KEY ) ) + return 0; + + if(needPassPhrase && !havePassPhrase) { + if( ( tGPG == pgpType ) && ( 0 != getenv("GPG_AGENT_INFO") ) ) { + // the user uses gpg-agent which asks itself for the passphrase + kdDebug(5100) << "user uses gpg-agent -> don't ask for passphrase\n"; + // set dummy passphrase (because else signing doesn't work -> FIXME) + setPassPhrase( "dummy" ); + } + else { + QString ID; + if( block ) + ID = block->requiredUserId(); + PassphraseDialog passdlg(0, i18n("OpenPGP Security Check"), true, ID); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int passdlgResult = passdlg.exec(); + QApplication::restoreOverrideCursor(); + if (passdlgResult == QDialog::Accepted) { + if (!setPassPhrase(passdlg.passphrase())) { + if (strlen(passdlg.passphrase()) >= 1024) + errMsg = i18n("Passphrase is too long, it must contain fewer than 1024 characters."); + else + errMsg = i18n("Out of memory."); + return 0; + } + } else { + wipePassPhrase(); + return -1; + } + } + } + return 1; +} + +void +Module::wipePassPhrase(bool freeMem) +{ + if ( passphrase ) { + if ( passphrase_buffer_len ) + memset( passphrase, 0x00, passphrase_buffer_len ); + else { + kdDebug(5100) << "wipePassPhrase: passphrase && !passphrase_buffer_len ???" << endl; + passphrase = 0; + } + } + if ( freeMem && passphrase ) { + free( passphrase ); + passphrase = 0; + passphrase_buffer_len = 0; + } + havePassPhrase = false; +} + +bool +Module::verify( Block& block ) +{ + int retval; + + if (0 == pgp) assignPGPBase(); + + // everything ready + if( !prepare( false, &block ) ) + return false; + // ok now try to verify the message. + retval = pgp->verify( block ); + + if(retval & ERROR) + { + errMsg = pgp->lastErrorMessage(); + return false; + } + return true; +} + +bool +Module::decrypt( Block& block ) +{ + int retval; + + if (0 == pgp) assignPGPBase(); + + do { + // loop as long as the user enters a wrong passphrase and doesn't abort + // everything ready + if( prepare( true, &block ) != 1 ) + return FALSE; + // ok now try to decrypt the message. + retval = pgp->decrypt( block, passphrase ); + // loop on bad passphrase + if( retval & BADPHRASE ) { + wipePassPhrase(); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningContinueCancel(0, + i18n("You just entered an invalid passphrase.\n" + "Do you want to try again, or " + "cancel and view the message undecrypted?"), + i18n("PGP Warning"), i18n("&Retry")); + QApplication::restoreOverrideCursor(); + if ( ret == KMessageBox::Cancel ) break; + } else + break; + } while ( true ); + + // erase the passphrase if we do not want to keep it + cleanupPass(); + + if(retval & ERROR) + { + errMsg = pgp->lastErrorMessage(); + return false; + } + return true; +} + +Kpgp::Result +Module::clearsign( Block& block, + const KeyID& keyId, const QCString& charset ) +{ + return encrypt( block, QStringList(), keyId, true, charset ); +} + +Kpgp::Result +Module::encrypt( Block& block, + const QStringList& receivers, const KeyID& keyId, + bool sign, const QCString& charset ) +{ + KeyIDList encryptionKeyIds; // list of keys which are used for encryption + int status = 0; + errMsg = ""; + + if( 0 == pgp ) assignPGPBase(); + + setUser( keyId ); + + if( !receivers.empty() ) { + Kpgp::Result result = getEncryptionKeys( encryptionKeyIds, receivers, + keyId ); + if( Kpgp::Ok != result ) { + return result; + } + } + + status = doEncSign( block, encryptionKeyIds, sign ); + + if( status & CANCEL ) + return Kpgp::Canceled; + + // check for bad passphrase + while( status & BADPHRASE ) { + wipePassPhrase(); + QString str = i18n("You entered an invalid passphrase.\n" + "Do you want to try again, continue and leave the " + "message unsigned, or cancel sending the message?"); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningYesNoCancel( 0, str, + i18n("PGP Warning"), + i18n("&Retry"), + i18n("Send &Unsigned") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + if( ret == KMessageBox::No ) { + // the user selected "Send unsigned" + if( encryptionKeyIds.isEmpty() ) { + block.reset(); + return Kpgp::Ok; + } + else { + sign = false; + } + } + // ok let's try once again... + status = doEncSign( block, encryptionKeyIds, sign ); + } + + // did signing fail? + if( status & ERR_SIGNING ) { + QString str = i18n("%1 = 'signing failed' error message", + "%1\nDo you want to send the message unsigned, " + "or cancel sending the message?") + .arg( pgp->lastErrorMessage() ); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningContinueCancel( 0, str, + i18n("PGP Warning"), + i18n("Send &Unsigned") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + sign = false; + status = doEncSign( block, encryptionKeyIds, sign ); + } + + // check for bad keys + if( status & BADKEYS ) { + QString str = i18n("%1 = 'bad keys' error message", + "%1\nDo you want to encrypt anyway, leave the " + "message as-is, or cancel sending the message?") + .arg( pgp->lastErrorMessage() ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningYesNoCancel( 0, str, + i18n("PGP Warning"), + i18n("Send &Encrypted"), + i18n("Send &Unencrypted") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + if( ret == KMessageBox::No ) { + // the user selected "Send unencrypted" + if( sign ) { + doEncSign( block, KeyIDList(), sign ); + } + else { + block.reset(); + } + return Kpgp::Ok; + } + } + + if( status & MISSINGKEY ) { + QString str = i18n("%1 = 'missing keys' error message", + "%1\nDo you want to leave the message as-is, " + "or cancel sending the message?") + .arg( pgp->lastErrorMessage() ); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningContinueCancel( 0, str, + i18n("PGP Warning"), + i18n("&Send As-Is") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + block.reset(); + return Kpgp::Ok; + } + + if( status & ERROR ) { + // show error dialog + errMsg = i18n( "The following error occurred:\n%1" ) + .arg( pgp->lastErrorMessage() ); + QString details = i18n( "This is the error message of %1:\n%2" ) + .arg( ( pgpType == tGPG ) ? "GnuPG" : "PGP" ) + .arg( block.error().data() ); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + KMessageBox::detailedSorry( 0, errMsg, details ); + QApplication::restoreOverrideCursor(); + return Kpgp::Failure; + } + + if( showCipherText() ) { + // show cipher text dialog + CipherTextDialog *cipherTextDlg = new CipherTextDialog( block.text(), charset ); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + bool result = ( cipherTextDlg->exec() == QDialog::Accepted ); + QApplication::restoreOverrideCursor(); + delete cipherTextDlg; + return result == QDialog::Accepted ? Kpgp::Ok : Kpgp::Canceled; + } + return Kpgp::Ok; +} + +int +Module::doEncSign( Block& block, + const KeyIDList& recipientKeyIds, bool sign ) +{ + int retval = 0; + + if( 0 == pgp ) assignPGPBase(); + + // to avoid error messages in case pgp is not installed + if( !havePgp ) return OK; + + if( sign ) { + int result = prepare( true, &block ); + switch( result ) { + case -1: + return CANCEL; + case 0: + return ERROR; + } + retval = pgp->encsign( block, recipientKeyIds, passphrase ); + } + else { + if( !prepare( false, &block ) ) return ERROR; + retval = pgp->encrypt( block, recipientKeyIds ); + } + // erase the passphrase if we do not want to keep it + cleanupPass(); + + return retval; +} + +Kpgp::Result +Module::getEncryptionKeys( KeyIDList& encryptionKeyIds, + const QStringList& recipients, + const KeyID& keyId ) +{ + if( recipients.empty() ) { + encryptionKeyIds.clear(); + return Kpgp::Ok; + } + + // list of lists of encryption keys (one list per recipient + one list + // for the sender) + QValueVector<KeyIDList> recipientKeyIds( recipients.count() + 1 ); + // add the sender's encryption key(s) to the list of recipient key IDs + if( encryptToSelf() ) { + recipientKeyIds[0] = KeyIDList( keyId ); + } + else { + recipientKeyIds[0] = KeyIDList(); + } + bool showKeysForApproval = false; + int i = 1; + for( QStringList::ConstIterator it = recipients.begin(); + it != recipients.end(); ++it, ++i ) { + EncryptPref encrPref = encryptionPreference( *it ); + if( ( encrPref == UnknownEncryptPref ) || ( encrPref == NeverEncrypt ) ) + showKeysForApproval = true; + + KeyIDList keyIds = getEncryptionKeys( *it ); + if( keyIds.isEmpty() ) { + showKeysForApproval = true; + } + recipientKeyIds[i] = keyIds; + } + + kdDebug(5100) << "recipientKeyIds = (\n"; + QValueVector<KeyIDList>::const_iterator kit; + for( kit = recipientKeyIds.begin(); kit != recipientKeyIds.end(); ++kit ) { + kdDebug(5100) << "( 0x" << (*kit).toStringList().join( ", 0x" ) + << " ),\n"; + } + kdDebug(5100) << ")\n"; + + if( showKeysForApproval || mShowKeyApprovalDlg ) { + // #### FIXME: Until we support encryption with untrusted keys only + // #### trusted keys are allowed + unsigned int allowedKeys = PublicKeys | EncryptionKeys | ValidKeys | TrustedKeys; +#if 0 + // ### reenable this code when we support encryption with untrusted keys + if( pgpType != tGPG ) { + // usage of untrusted keys is only possible with GnuPG + allowedKeys |= TrustedKeys; + } +#endif + // show the recipients <-> key relation + KeyApprovalDialog dlg( recipients, recipientKeyIds, allowedKeys ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = dlg.exec(); + + if( ret == QDialog::Rejected ) { + QApplication::restoreOverrideCursor(); + return Kpgp::Canceled; + } + + recipientKeyIds = dlg.keys(); + QApplication::restoreOverrideCursor(); + } + + // flatten the list of lists of key IDs and count empty key ID lists + unsigned int emptyListCount = 0; + for( QValueVector<KeyIDList>::const_iterator it = recipientKeyIds.begin(); + it != recipientKeyIds.end(); ++it ) { + if( (*it).isEmpty() ) { + // only count empty key ID lists for the recipients + if( it != recipientKeyIds.begin() ) { + emptyListCount++; + } + } + else { + for( KeyIDList::ConstIterator kit = (*it).begin(); + kit != (*it).end(); kit++ ) { + encryptionKeyIds.append( *kit ); + } + } + } + + // FIXME-AFTER-KDE-3.1: Show warning if message won't be encrypted to self + + // show a warning if the user didn't select an encryption key for + // some of the recipients + if( recipientKeyIds.size() == emptyListCount + 1 ) { // (+1 because of the sender's key) + QString str = ( recipients.count() == 1 ) + ? i18n("You did not select an encryption key for the " + "recipient of this message; therefore, the message " + "will not be encrypted.") + : i18n("You did not select an encryption key for any of the " + "recipients of this message; therefore, the message " + "will not be encrypted."); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningContinueCancel( 0, str, + i18n("PGP Warning"), + i18n("Send &Unencrypted") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + else + encryptionKeyIds.clear(); + } + else if( emptyListCount > 0 ) { + QString str = ( emptyListCount == 1 ) + ? i18n("You did not select an encryption key for one of " + "the recipients; this person will not be able to " + "decrypt the message if you encrypt it.") + : i18n("You did not select encryption keys for some of " + "the recipients; these persons will not be able to " + "decrypt the message if you encrypt it." ); + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + int ret = KMessageBox::warningYesNoCancel( 0, str, + i18n("PGP Warning"), + i18n("Send &Encrypted"), + i18n("Send &Unencrypted") ); + QApplication::restoreOverrideCursor(); + if( ret == KMessageBox::Cancel ) { + return Kpgp::Canceled; + } + else if( ret == KMessageBox::No ) { + // the user selected "Send unencrypted" + encryptionKeyIds.clear(); + } + } + + return Kpgp::Ok; +} + +int +Module::encryptionPossible( const QStringList& recipients ) +{ + if( 0 == pgp ) assignPGPBase(); + + if( !usePGP() ) + return 0; + + if( recipients.empty() ) + return 0; + + int noKey = 0, never = 0, unknown = 0, always = 0, aip = 0, ask = 0, + askwp = 0; + for( QStringList::ConstIterator it = recipients.begin(); + it != recipients.end(); ++it) { + if( haveTrustedEncryptionKey( *it ) ) { + EncryptPref encrPref = encryptionPreference( *it ); + switch( encrPref ) { + case NeverEncrypt: + never++; + break; + case UnknownEncryptPref: + unknown++; + break; + case AlwaysEncrypt: + always++; + break; + case AlwaysEncryptIfPossible: + aip++; + break; + case AlwaysAskForEncryption: + ask++; + break; + case AskWheneverPossible: + askwp++; + break; + } + } + else { + noKey++; + } + } + + if( ( always+aip > 0 ) && ( never+unknown+ask+askwp+noKey == 0 ) ) { + return 1; // encryption possible and desired + } + + if( ( unknown+ask+askwp > 0 ) && ( never+noKey == 0 ) ) { + return 2; // encryption possible, but user has to be asked + } + + if( ( never+noKey > 0 ) && ( always+ask == 0 ) ) { + return 0; // encryption isn't possible or desired + } + + return -1; // we can't decide it automatically +} + +bool +Module::signKey(const KeyID& keyId) +{ + if (0 == pgp) assignPGPBase(); + + if( prepare( true ) != 1 ) + return FALSE; + if(pgp->signKey(keyId, passphrase) & ERROR) + { + errMsg = pgp->lastErrorMessage(); + return false; + } + return true; +} + + +const KeyList +Module::publicKeys() +{ + if (0 == pgp) assignPGPBase(); + + if (!prepare()) return KeyList(); + + if( !mPublicKeysCached ) { + readPublicKeys(); + } + + return mPublicKeys; +} + + +const KeyList +Module::secretKeys() +{ + if (0 == pgp) assignPGPBase(); + + if (!prepare()) return KeyList(); + + if( !mSecretKeysCached ) { + readSecretKeys(); + } + + return mSecretKeys; +} + + +Key* +Module::publicKey(const KeyID& keyID) +{ + readPublicKeys(); + + for( KeyListIterator it( mPublicKeys ); (*it); ++it ) + if( keyID == (*it)->primaryKeyID() || + keyID == (*it)->primaryFingerprint() ) + return (*it); + + return 0; +} + +Key* +Module::publicKey( const QString& userID ) +{ + readPublicKeys(); + + for( KeyListIterator it( mPublicKeys ); (*it); ++it ) + if( (*it)->matchesUserID( userID ) ) + return (*it); + + return 0; +} + +Key* +Module::secretKey(const KeyID& keyID) +{ + readSecretKeys(); + + for( KeyListIterator it( mSecretKeys ); (*it); ++it ) + if( keyID == (*it)->primaryKeyID() || + keyID == (*it)->primaryFingerprint() ) + return (*it); + + return 0; +} + +Validity +Module::keyTrust( const KeyID& keyID ) +{ + Key *key = publicKey( keyID ); + + if( ( 0 == key ) || ( key->keyTrust() == KPGP_VALIDITY_UNKNOWN ) ) + { // (re)check the key if it's unknown or if its trust is unknown + key = rereadKey( keyID, true ); + if( key == 0 ) + return KPGP_VALIDITY_UNKNOWN; + } + + return key->keyTrust(); +} + +Validity +Module::keyTrust( const QString& userID ) +{ + Key *key = publicKey( userID ); + + if( key == 0 ) + return KPGP_VALIDITY_UNKNOWN; + + if( key->keyTrust() == KPGP_VALIDITY_UNKNOWN ) + { + key = rereadKey( key->primaryKeyID(), true ); + if( key == 0 ) + return KPGP_VALIDITY_UNKNOWN; + } + + return key->keyTrust(); +} + +bool +Module::isTrusted( const KeyID& keyID ) +{ + return ( keyTrust( keyID ) >= KPGP_VALIDITY_MARGINAL ); +} + +Key* +Module::rereadKey( const KeyID& keyID, const bool readTrust /* = true */ ) +{ + if( 0 == pgp ) assignPGPBase(); + + // search the old key data in the key list + Key* oldKey = publicKey( keyID ); + + Key* newKey = pgp->readPublicKey( keyID, readTrust, oldKey ); + + if( ( 0 == oldKey ) && ( 0 != newKey ) ) + { + mPublicKeys.inSort( newKey ); + kdDebug(5100) << "New public key 0x" << newKey->primaryKeyID() << " (" + << newKey->primaryUserID() << ").\n"; + } + else if( ( 0 != oldKey ) && ( 0 == newKey ) ) + { // the key has been deleted in the meantime + kdDebug(5100) << "Public key 0x" << oldKey->primaryKeyID() << " (" + << oldKey->primaryUserID() << ") will be removed.\n"; + mPublicKeys.removeRef( oldKey ); + } + + return newKey; +} + +QCString +Module::getAsciiPublicKey(const KeyID& keyID) +{ + if (0 == pgp) assignPGPBase(); + + return pgp->getAsciiPublicKey(keyID); +} + + +bool Module::setPassPhrase(const char * aPass) +{ + // null out old buffer before we touch the new string. So in case + // aPass isn't properly null-terminated, we don't leak secret data. + wipePassPhrase(); + + if (aPass) + { + size_t newlen = strlen( aPass ); + if ( newlen >= 1024 ) { + // rediculously long passphrase. + // Maybe someone wants to trick us in malloc()'ing + // huge buffers... + return false; + } + if ( passphrase_buffer_len < newlen + 1 ) { + // too little space in current buffer: + // allocate a larger one. + if ( passphrase ) + free( passphrase ); + passphrase_buffer_len = (newlen + 1 + 15) & ~0xF; // make it a multiple of 16. + passphrase = (char*)malloc( passphrase_buffer_len ); + if (!passphrase) { + passphrase_buffer_len = 0; + return false; + } + } + memcpy( passphrase, aPass, newlen + 1 ); + havePassPhrase = true; + } + return true; +} + +bool +Module::changePassPhrase() +{ + //FIXME... + KMessageBox::information(0,i18n("This feature is\nstill missing")); + return FALSE; +} + +void +Module::clear(const bool erasePassPhrase) +{ + if(erasePassPhrase) + wipePassPhrase(true); +} + +const QString +Module::lastErrorMsg(void) const +{ + return errMsg; +} + +bool +Module::havePGP(void) const +{ + return havePgp; +} + +void +Module::setShowCipherText(const bool flag) +{ + showEncryptionResult = flag; +} + +bool +Module::showCipherText(void) const +{ + return showEncryptionResult; +} + +KeyID +Module::selectSecretKey( const QString& title, + const QString& text, + const KeyID& keyId ) +{ + if( 0 == pgp ) { + assignPGPBase(); + } + + if( usePGP() ) { + return selectKey( secretKeys(), title, text, keyId, SecretKeys ); + } + else { + KMessageBox::sorry( 0, i18n("You either do not have GnuPG/PGP installed " + "or you chose not to use GnuPG/PGP.") ); + return KeyID(); + } +} + +KeyID +Module::selectPublicKey( const QString& title, + const QString& text /* = QString::null */, + const KeyID& oldKeyId /* = KeyID() */, + const QString& address /* = QString::null */, + const unsigned int allowedKeys /* = AllKeys */ ) +{ + if( 0 == pgp ) { + assignPGPBase(); + } + + if( usePGP() ) { + KeyID keyId; + + if( address.isEmpty() ) { + keyId = selectKey( publicKeys(), title, text, oldKeyId, allowedKeys ); + } + else { + bool rememberChoice; + keyId = selectKey( rememberChoice, publicKeys(), title, text, oldKeyId, + allowedKeys ); + if( !keyId.isEmpty() && rememberChoice ) { + setKeysForAddress( address, KeyIDList( keyId ) ); + } + } + + return keyId; + } + else { + KMessageBox::sorry( 0, i18n("You either do not have GnuPG/PGP installed " + "or you chose not to use GnuPG/PGP.") ); + return KeyID(); + } +} + + +KeyIDList +Module::selectPublicKeys( const QString& title, + const QString& text /* = QString::null */, + const KeyIDList& oldKeyIds /* = KeyIDList() */, + const QString& address /* = QString::null */, + const unsigned int allowedKeys /* = AllKeys */ ) +{ + if( 0 == pgp ) { + assignPGPBase(); + } + + if( usePGP() ) { + KeyIDList keyIds; + + if( address.isEmpty() ) { + keyIds = selectKeys( publicKeys(), title, text, oldKeyIds, allowedKeys ); + } + else { + bool rememberChoice; + keyIds = selectKeys( rememberChoice, publicKeys(), title, text, + oldKeyIds, allowedKeys ); + if( !keyIds.isEmpty() && rememberChoice ) { + setKeysForAddress( address, keyIds ); + } + } + + return keyIds; + } + else { + KMessageBox::sorry( 0, i18n("You either do not have GnuPG/PGP installed " + "or you chose not to use GnuPG/PGP.") ); + return KeyIDList(); + } +} + + +// -- static member functions ---------------------------------------------- + +Module * +Module::getKpgp() +{ + if (!kpgpObject) + { + kdError(5100) << "there is no instance of kpgp available" << endl; + } + return kpgpObject; +} + + +KConfig * +Module::getConfig() +{ + return getKpgp()->config; +} + + +bool +Module::prepareMessageForDecryption( const QCString& msg, + QPtrList<Block>& pgpBlocks, + QStrList& nonPgpBlocks ) +{ + BlockType pgpBlock = NoPgpBlock; + int start = -1; // start of the current PGP block + int lastEnd = -1; // end of the last PGP block + + pgpBlocks.setAutoDelete( true ); + pgpBlocks.clear(); + nonPgpBlocks.setAutoDelete( true ); + nonPgpBlocks.clear(); + + if( msg.isEmpty() ) + { + nonPgpBlocks.append( "" ); + return false; + } + + if( !strncmp( msg.data(), "-----BEGIN PGP ", 15 ) ) + start = 0; + else + { + start = msg.find( "\n-----BEGIN PGP" ) + 1; + if( start == 0 ) + { + nonPgpBlocks.append( msg ); + return false; // message doesn't contain an OpenPGP block + } + } + + while( start != -1 ) + { + int nextEnd, nextStart; + + // is the PGP block a clearsigned block? + if( !strncmp( msg.data() + start + 15, "SIGNED", 6 ) ) + pgpBlock = ClearsignedBlock; + else + pgpBlock = UnknownBlock; + + nextEnd = msg.find( "\n-----END PGP", start + 15 ); + if( nextEnd == -1 ) + { + nonPgpBlocks.append( msg.mid( lastEnd+1 ) ); + break; + } + nextStart = msg.find( "\n-----BEGIN PGP", start + 15 ); + + if( ( nextStart == -1 ) || ( nextEnd < nextStart ) || + ( pgpBlock == ClearsignedBlock ) ) + { // most likely we found a PGP block (but we don't check if it's valid) + // store the preceding non-PGP block + nonPgpBlocks.append( msg.mid( lastEnd+1, start-lastEnd-1 ) ); + lastEnd = msg.find( "\n", nextEnd + 14 ); + if( lastEnd == -1 ) + { + pgpBlocks.append( new Block( msg.mid( start ) ) ); + nonPgpBlocks.append( "" ); + break; + } + else + { + pgpBlocks.append( new Block( msg.mid( start, lastEnd+1-start ) ) ); + if( ( nextStart != -1 ) && ( nextEnd > nextStart ) ) + nextStart = msg.find( "\n-----BEGIN PGP", lastEnd+1 ); + } + } + + start = nextStart; + if( start == -1 ) + nonPgpBlocks.append( msg.mid( lastEnd+1 ) ); + else + start++; // move start behind the '\n' + } + + return ( !pgpBlocks.isEmpty() ); +} + + +// --------------------- private functions ------------------- + +bool +Module::haveTrustedEncryptionKey( const QString& person ) +{ + if( 0 == pgp ) assignPGPBase(); + + if( !usePGP() ) return false; + + readPublicKeys(); + + QString address = canonicalAddress( person ).lower(); + + // First look for this person's address in the address data dictionary + KeyIDList keyIds = keysForAddress( address ); + if( !keyIds.isEmpty() ) { + // Check if at least one of the keys is a trusted and valid encryption key + for( KeyIDList::ConstIterator it = keyIds.begin(); + it != keyIds.end(); ++it ) { + keyTrust( *it ); // this is called to make sure that the trust info + // for this key is read + Key *key = publicKey( *it ); + if( key && ( key->isValidEncryptionKey() ) && + ( key->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) + return true; + } + } + + // Now search the public keys for matching keys + KeyListIterator it( mPublicKeys ); + + // search a key which matches the complete address + for( it.toFirst(); (*it); ++it ) { + // search case insensitively in the list of userIDs of this key + if( (*it)->matchesUserID( person, false ) ) { + keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that + // the trust info for this key is read + if( ( (*it)->isValidEncryptionKey() ) && + ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) { + return true; + } + } + } + + // if no key matches the complete address look for a key which matches + // the canonical mail address + for( it.toFirst(); (*it); ++it ) { + // search case insensitively in the list of userIDs of this key + if( (*it)->matchesUserID( address, false ) ) { + keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that + // the trust info for this key is read + if( ( (*it)->isValidEncryptionKey() ) && + ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) { + return true; + } + } + } + + // no trusted encryption key was found for the given person + return false; +} + +KeyIDList +Module::getEncryptionKeys( const QString& person ) +{ + if( 0 == pgp ) assignPGPBase(); + + if( !usePGP() ) return KeyIDList(); + + readPublicKeys(); + + QString address = canonicalAddress( person ).lower(); + + // #### FIXME: Until we support encryption with untrusted keys only + // #### trusted keys are allowed + unsigned int allowedKeys = PublicKeys | EncryptionKeys | ValidKeys | TrustedKeys; +#if 0 + // ### reenable this code when we support encryption with untrusted keys + if( pgpType != tGPG ) { + // usage of untrusted keys is only possible with GnuPG + allowedKeys |= TrustedKeys; + } +#endif + + // First look for this person's address in the address->key dictionary + KeyIDList keyIds = keysForAddress( address ); + if( !keyIds.isEmpty() ) { + kdDebug(5100) << "Using encryption keys 0x" + << keyIds.toStringList().join( ", 0x" ) + << " for " << person << endl; + // Check if all of the keys are a trusted and valid encryption keys + bool keysOk = true; + for( KeyIDList::ConstIterator it = keyIds.begin(); + it != keyIds.end(); ++it ) { + keyTrust( *it ); // this is called to make sure that the trust info + // for this key is read + Key *key = publicKey( *it ); + if( !( key && ( key->isValidEncryptionKey() ) && + ( key->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) ) + keysOk = false; + } + if( keysOk ) { + return keyIds; + } + else { + bool rememberChoice; + keyIds = selectKeys( rememberChoice, mPublicKeys, + i18n("Encryption Key Selection"), + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "There is a problem with the " + "encryption key(s) for \"%1\".\n\n" + "Please re-select the key(s) which should " + "be used for this recipient." + ).arg(person), + keyIds, + allowedKeys ); + if( !keyIds.isEmpty() ) { + if( rememberChoice ) { + setKeysForAddress( person, keyIds ); + } + return keyIds; + } + } + } + + // Now search all public keys for matching keys + KeyListIterator it( mPublicKeys ); + KeyList matchingKeys; + + // search all keys which match the complete address + kdDebug(5100) << "Looking for keys matching " << person << " ...\n"; + for( it.toFirst(); (*it); ++it ) { + // search case insensitively in the list of userIDs of this key + if( (*it)->matchesUserID( person, false ) ) { + keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that + // the trust info for this key is read + if( ( (*it)->isValidEncryptionKey() ) && + ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) { + kdDebug(5100) << "Matching trusted key found: " + << (*it)->primaryKeyID() << endl; + matchingKeys.append( *it ); + } + } + } + + // if no keys match the complete address look for keys which match + // the canonical mail address + kdDebug(5100) << "Looking for keys matching " << address << " ...\n"; + if( matchingKeys.isEmpty() ) { + for ( it.toFirst(); (*it); ++it ) { + // search case insensitively in the list of userIDs of this key + if( (*it)->matchesUserID( address, false ) ) { + keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that + // the trust info for this key is read + if( ( (*it)->isValidEncryptionKey() ) && + ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) ) { + kdDebug(5100) << "Matching trusted key found: " + << (*it)->primaryKeyID() << endl; + matchingKeys.append( *it ); + } + } + } + } + + // no match until now, let the user choose the key + if( matchingKeys.isEmpty() ) { + // FIXME: let user get the key from keyserver + bool rememberChoice; + KeyIDList keyIds = selectKeys( rememberChoice, mPublicKeys, + i18n("Encryption Key Selection"), + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "No valid and trusted OpenPGP key was " + "found for \"%1\".\n\n" + "Select the key(s) which should " + "be used for this recipient." + ).arg(person), + KeyIDList(), + allowedKeys ); + if( !keyIds.isEmpty() ) { + if( rememberChoice ) { + setKeysForAddress( person, keyIds ); + } + return keyIds; + } + } + // only one key matches + else if( matchingKeys.count() == 1 ) { + return KeyIDList( matchingKeys.getFirst()->primaryKeyID() ); + } + // more than one key matches; let the user choose the key(s) + else { + bool rememberChoice; + KeyIDList keyIds = selectKeys( rememberChoice, matchingKeys, + i18n("Encryption Key Selection"), + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "More than one key matches \"%1\".\n\n" + "Select the key(s) which should " + "be used for this recipient." + ).arg(person), + KeyIDList(), + allowedKeys ); + if( !keyIds.isEmpty() ) { + if( rememberChoice ) { + setKeysForAddress( person, keyIds ); + } + return keyIds; + } + } + + return KeyIDList(); +} + +// check if pgp 2.6.x or 5.0 is installed +// kpgp will prefer to user pgp 5.0 +bool +Module::checkForPGP(void) +{ + // get path + QCString path; + QStrList pSearchPaths; + int index = 0; + int lastindex = -1; + + havePgp=FALSE; + + path = getenv("PATH"); + while((index = path.find(":",lastindex+1)) != -1) + { + pSearchPaths.append(path.mid(lastindex+1,index-lastindex-1)); + lastindex = index; + } + if(lastindex != (int)path.length() - 1) + pSearchPaths.append( path.mid(lastindex+1,path.length()-lastindex) ); + + QStrListIterator it(pSearchPaths); + + haveGpg=FALSE; + // lets try gpg + + for ( it.toFirst() ; it.current() ; ++it ) + { + path = (*it); + path += "/gpg"; + if ( !access( path, X_OK ) ) + { + kdDebug(5100) << "Kpgp: gpg found" << endl; + havePgp=TRUE; + haveGpg=TRUE; + break; + } + } + + // search for pgp5.0 + havePGP5=FALSE; + for ( it.toFirst() ; it.current() ; ++it ) + { + path = (*it); + path += "/pgpe"; + if ( !access( path, X_OK ) ) + { + kdDebug(5100) << "Kpgp: pgp 5 found" << endl; + havePgp=TRUE; + havePGP5=TRUE; + break; + } + } + + // lets try pgp2.6.x + if (!havePgp) { + for ( it.toFirst() ; it.current() ; ++it ) + { + path = it.current(); + path += "/pgp"; + if ( !access( path, X_OK ) ) + { + kdDebug(5100) << "Kpgp: pgp 2 or 6 found" << endl; + havePgp=TRUE; + break; + } + } + } + + if (!havePgp) + { + kdDebug(5100) << "Kpgp: no pgp found" << endl; + } + + return havePgp; +} + +void +Module::assignPGPBase(void) +{ + if (pgp) + delete pgp; + + if(havePgp) + { + switch (pgpType) + { + case tGPG: + kdDebug(5100) << "Kpgp: assign pgp - gpg" << endl; + pgp = new BaseG(); + break; + + case tPGP2: + kdDebug(5100) << "Kpgp: assign pgp - pgp 2" << endl; + pgp = new Base2(); + break; + + case tPGP5: + kdDebug(5100) << "Kpgp: assign pgp - pgp 5" << endl; + pgp = new Base5(); + break; + + case tPGP6: + kdDebug(5100) << "Kpgp: assign pgp - pgp 6" << endl; + pgp = new Base6(); + break; + + case tOff: + // dummy handler + kdDebug(5100) << "Kpgp: pgpBase is dummy " << endl; + pgp = new Base(); + break; + + case tAuto: + kdDebug(5100) << "Kpgp: assign pgp - auto" << endl; + // fall through + default: + kdDebug(5100) << "Kpgp: assign pgp - default" << endl; + if (haveGpg) + { + kdDebug(5100) << "Kpgp: pgpBase is gpg " << endl; + pgp = new BaseG(); + pgpType = tGPG; + } + else if(havePGP5) + { + kdDebug(5100) << "Kpgp: pgpBase is pgp 5" << endl; + pgp = new Base5(); + pgpType = tPGP5; + } + else + { + Base6 *pgp_v6 = new Base6(); + if (!pgp_v6->isVersion6()) + { + kdDebug(5100) << "Kpgp: pgpBase is pgp 2 " << endl; + delete pgp_v6; + pgp = new Base2(); + pgpType = tPGP2; + } + else + { + kdDebug(5100) << "Kpgp: pgpBase is pgp 6 " << endl; + pgp = pgp_v6; + pgpType = tPGP6; + } + } + } // switch + } + else + { + // dummy handler + kdDebug(5100) << "Kpgp: pgpBase is dummy " << endl; + pgp = new Base(); + pgpType = tOff; + } +} + +QString +Module::canonicalAddress( const QString& _adress ) +{ + int index,index2; + + QString address = _adress.simplifyWhiteSpace(); + address = address.stripWhiteSpace(); + + // just leave pure e-mail address. + if((index = address.find("<")) != -1) + if((index2 = address.find("@",index+1)) != -1) + if((index2 = address.find(">",index2+1)) != -1) + return address.mid(index,index2-index+1); + + if((index = address.find("@")) == -1) + { + // local address + //char hostname[1024]; + //gethostname(hostname,1024); + //return "<" + address + "@" + hostname + ">"; + return "<" + address + "@localdomain>"; + } + else + { + int index1 = address.findRev(" ",index); + int index2 = address.find(" ",index); + if(index2 == -1) index2 = address.length(); + return "<" + address.mid(index1+1 ,index2-index1-1) + ">"; + } +} + +void +Module::readPublicKeys( bool reread ) +{ + if( 0 == pgp ) assignPGPBase(); + + if( !usePGP() ) + { + mPublicKeys.clear(); + mPublicKeysCached = false; + return; + } + + if( !mPublicKeysCached || reread ) + { + if( mPublicKeys.isEmpty() ) + { + mPublicKeys = pgp->publicKeys(); + } + else + { + KeyList newPublicKeyList = pgp->publicKeys(); + + // merge the trust info from the old key list into the new key list + // FIXME: This is currently O(K^2) where K = #keys. As the key lists + // are sorted this can be done in O(K). + KeyListIterator it( newPublicKeyList ); + for( it.toFirst(); (*it); ++it ) + { + Key* oldKey = publicKey( (*it)->primaryKeyID() ); + if( oldKey ) + { + (*it)->cloneKeyTrust( oldKey ); + } + } + + mPublicKeys = newPublicKeyList; + } + + mPublicKeysCached = true; + mPublicKeys.setAutoDelete( true ); + } +} + +void +Module::readSecretKeys( bool reread ) +{ + if( 0 == pgp ) assignPGPBase(); + + if( !usePGP() ) + { + mSecretKeys.clear(); + mSecretKeysCached = false; + return; + } + + if( mSecretKeys.isEmpty() || reread ) + { + if( mSecretKeys.isEmpty() ) + { + mSecretKeys = pgp->secretKeys(); + } + else + { + KeyList newSecretKeyList = pgp->secretKeys(); + + // merge the trust info from the old key list into the new key list + // FIXME: This is currently O(K^2) where K = #keys. As the key lists + // are sorted this can be done in O(K). + KeyListIterator it( newSecretKeyList ); + for( it.toFirst(); (*it); ++it ) + { + Key* oldKey = secretKey( (*it)->primaryKeyID() ); + if( oldKey ) + { + (*it)->cloneKeyTrust( oldKey ); + } + } + + mSecretKeys = newSecretKeyList; + } + + mSecretKeysCached = true; + mSecretKeys.setAutoDelete( true ); + } +} + +KeyID +Module::selectKey( const KeyList& keys, + const QString& title, + const QString& text /* = QString::null */ , + const KeyID& keyId /* = KeyID() */ , + const unsigned int allowedKeys /* = AllKeys */ ) +{ + KeyID retval = KeyID(); + + KeySelectionDialog dlg( keys, title, text, KeyIDList( keyId ), false, + allowedKeys, false ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + bool rej = ( dlg.exec() == QDialog::Rejected ); + QApplication::restoreOverrideCursor(); + + if( !rej ) { + retval = dlg.key(); + } + + return retval; +} + +KeyIDList +Module::selectKeys( const KeyList& keys, + const QString& title, + const QString& text /* = QString::null */ , + const KeyIDList& keyIds /* = KeyIDList() */ , + const unsigned int allowedKeys /* = AllKeys */ ) +{ + KeyIDList retval = KeyIDList(); + + KeySelectionDialog dlg( keys, title, text, keyIds, false, allowedKeys, + true ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + bool rej = ( dlg.exec() == QDialog::Rejected ); + QApplication::restoreOverrideCursor(); + + if( !rej ) { + retval = dlg.keys(); + } + + return retval; +} + + +KeyID +Module::selectKey( bool& rememberChoice, + const KeyList& keys, + const QString& title, + const QString& text /* = QString::null */ , + const KeyID& keyId /* = KeyID() */ , + const unsigned int allowedKeys /* = AllKeys */ ) +{ + KeyID retval = KeyID(); + + KeySelectionDialog dlg( keys, title, text, KeyIDList( keyId ), false, + allowedKeys, false ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + bool rej = ( dlg.exec() == QDialog::Rejected ); + QApplication::restoreOverrideCursor(); + + if( !rej ) { + retval = dlg.key(); + rememberChoice = dlg.rememberSelection(); + } + else { + rememberChoice = false; + } + + return retval; +} + +KeyIDList +Module::selectKeys( bool& rememberChoice, + const KeyList& keys, + const QString& title, + const QString& text /* = QString::null */ , + const KeyIDList& keyIds /* = KeyIDList() */ , + const unsigned int allowedKeys /* = AllKeys */ ) +{ + KeyIDList retval = KeyIDList(); + + KeySelectionDialog dlg( keys, title, text, keyIds, true, allowedKeys, + true ); + + QApplication::setOverrideCursor( QCursor(QCursor::ArrowCursor) ); + bool rej = ( dlg.exec() == QDialog::Rejected ); + QApplication::restoreOverrideCursor(); + + if( !rej ) { + retval = dlg.keys(); + rememberChoice = dlg.rememberSelection(); + } + else { + rememberChoice = false; + } + + return retval; +} + +KeyIDList +Module::keysForAddress( const QString& address ) +{ + if( address.isEmpty() ) { + return KeyIDList(); + } + QString addr = canonicalAddress( address ).lower(); + if( addressDataDict.contains( addr ) ) { + return addressDataDict[addr].keyIds; + } + else { + return KeyIDList(); + } +} + +void +Module::setKeysForAddress( const QString& address, const KeyIDList& keyIds ) +{ + if( address.isEmpty() ) { + return; + } + QString addr = canonicalAddress( address ).lower(); + if( addressDataDict.contains( addr ) ) { + addressDataDict[addr].keyIds = keyIds; + } + else { + AddressData data; + data.encrPref = UnknownEncryptPref; + data.keyIds = keyIds; + addressDataDict.insert( addr, data ); + } + + //writeAddressData(); +} + +void +Module::readAddressData() +{ + QString address; + AddressData data; + + KConfigGroup general( config, "General" ); + int num = general.readNumEntry( "addressEntries", 0 ); + + addressDataDict.clear(); + for( int i=1; i<=num; i++ ) { + KConfigGroup addrGroup( config, QString("Address #%1").arg(i).local8Bit() ); + address = addrGroup.readEntry( "Address" ); + data.keyIds = KeyIDList::fromStringList( addrGroup.readListEntry( "Key IDs" ) ); + data.encrPref = (EncryptPref) addrGroup.readNumEntry( "EncryptionPreference", + UnknownEncryptPref ); +// kdDebug(5100) << "Read address " << i << ": " << address +// << "\nKey IDs: 0x" << data.keyIds.toStringList().join(", 0x") +// << "\nEncryption preference: " << data.encrPref << endl; + if ( !address.isEmpty() ) { + addressDataDict.insert( address, data ); + } + } +} + +void +Module::writeAddressData() +{ + KConfigGroup general( config, "General" ); + general.writeEntry( "addressEntries", addressDataDict.count() ); + + int i; + AddressDataDict::Iterator it; + for ( i=1, it = addressDataDict.begin(); + it != addressDataDict.end(); + ++it, i++ ) { + KConfigGroup addrGroup( config, QString("Address #%1").arg(i).local8Bit() ); + addrGroup.writeEntry( "Address", it.key() ); + addrGroup.writeEntry( "Key IDs", it.data().keyIds.toStringList() ); + addrGroup.writeEntry( "EncryptionPreference", it.data().encrPref ); + } + + config->sync(); +} + +EncryptPref +Module::encryptionPreference( const QString& address ) +{ + QString addr = canonicalAddress( address ).lower(); + if( addressDataDict.contains( addr ) ) { + return addressDataDict[addr].encrPref; + } + else { + return UnknownEncryptPref; + } +} + +void +Module::setEncryptionPreference( const QString& address, + const EncryptPref pref ) +{ + if( address.isEmpty() ) { + return; + } + QString addr = canonicalAddress( address ).lower(); + if( addressDataDict.contains( addr ) ) { + addressDataDict[addr].encrPref = pref; + } + else { + AddressData data; + data.encrPref = pref; + addressDataDict.insert( addr, data ); + } +} + +} // namespace Kpgp diff --git a/libkpgp/kpgp.h b/libkpgp/kpgp.h new file mode 100644 index 000000000..9b3ade727 --- /dev/null +++ b/libkpgp/kpgp.h @@ -0,0 +1,469 @@ +/* + kpgp.h + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifndef KPGP_H +#define KPGP_H + +#include <stdio.h> +#include <qstring.h> +#include <qstrlist.h> +#include <qdialog.h> +#include <qwidget.h> +#include <qcombobox.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qlistview.h> +#include <qbuttongroup.h> +#include <qradiobutton.h> +#include <qmultilineedit.h> +#include <qcheckbox.h> + +#include <kdialogbase.h> + +#include "kpgpkey.h" +#include <kdepimmacros.h> + +class QLineEdit; +class QCursor; +class QCheckBox; +class QGridLayout; + +class KConfig; + +namespace Kpgp { + +/** This enumerated type is used by Kpgp::* to indicate which keys can be + selected by the user. The following values are available: + - Kpgp::PublicKeys: Public keys are shown in the selection dialog. + - Kpgp::SecretKeys: Secret keys are shown in the selection dialog. + - Kpgp::EncryptionKeys: Encryption keys can be selected. + - Kpgp::SigningKeys: Signing keys can be selected. + - Kpgp::ValidKeys: Only valid keys can be selected. + - Kpgp::TrustedKeys: Only trusted keys can be selected. + - Kpgp::AllKeys == PublicKeys | SecretKeys | EncryptionKeys | SigningKeys +*/ +enum { PublicKeys = 1, + SecretKeys = 2, + EncryptionKeys = 4, + SigningKeys = 8, + ValidKeys = 16, + TrustedKeys = 32, + AllKeys = PublicKeys | SecretKeys | EncryptionKeys | SigningKeys, + PubSecKeys = PublicKeys | SecretKeys, + EncrSignKeys = EncryptionKeys | SigningKeys +}; + +enum Result +{ + Failure = 0, + Ok = 1, + Canceled = 2 +}; + +class Base; +class Block; + +class KDE_EXPORT Module +{ + friend class Block; + +private: + // the class running pgp + Base *pgp; + +public: + Module(); + virtual ~Module(); + + /** the following virtual function form the interface to the + application using Kpgp + */ + virtual void readConfig(); + virtual void writeConfig(bool sync); + virtual void init(); + + /** decrypts the given OpenPGP block if the passphrase is good. + returns false otherwise */ + bool decrypt( Block& block ); + + /** Tries to verify the given OpenPGP block */ + bool verify( Block& block ); + + /** clearsigns the given OpenPGP block with the key corresponding to the + given key id. The charset is needed to display the text correctly. + Returns + Failure if there was an unresolvable error + Canceled if signing was canceled + Ok if everything is o.k. + */ + Kpgp::Result clearsign( Block& block, + const KeyID& keyId, const QCString& charset = 0 ); + + /** encrypts the given OpenPGP block for a list of persons. if sign is true + then the block is clearsigned with the key corresponding to the given + key id. The charset is needed to display the text correctly. + Returns + Failure if there was an unresolvable error + Canceled if encryption was canceled + Ok if everything is o.k. + */ + Kpgp::Result encrypt( Block& block, + const QStringList& receivers, const KeyID& keyId, + bool sign, const QCString& charset = 0 ); + + /** Determines the keys which should be used for encrypting the message + to the given list of recipients. + Returns: + Failure if there was an unresolvable error + Canceled if encryption was canceled + Ok if everything is o.k. + */ + Kpgp::Result getEncryptionKeys( KeyIDList& encryptionKeyIds, + const QStringList& recipients, + const KeyID& keyId ); + + /** checks if encrypting to the given list of persons is possible and + desired, i.e. if we have a (trusted) key for every recipient and + if encryption to all keys is allowed. + Returns + 0 if encryption is not possible or not desired, + 1 if encryption is possible and desired, + 2 if encryption is possible, but the user wants to be asked and + -1 if there is a conflict which can't be automatically resolved. + */ + int encryptionPossible( const QStringList& recipients ); + +protected: + int doEncSign( Block& block, const KeyIDList& recipientKeyIds, bool sign ); + +public: + /** sign a key in the keyring with users signature. */ + bool signKey( const KeyID& keyID ); + + /** get the list of cached public keys. */ + const KeyList publicKeys(); + + /** get the list of cached secret keys. */ + const KeyList secretKeys(); + + /** Reads the list of public keys if necessary or if <em>reread</em> is true. + */ + void readPublicKeys( bool reread = false ); + + /** Reads the list of secret keys if necessary or if <em>reread</em> is true. + */ + void readSecretKeys( bool reread = false ); + + /** try to get an ascii armored key block for the given public key */ + QCString getAsciiPublicKey( const KeyID& keyID ); + + /** Returns the public key with the given key ID or null if no matching + key is found. + */ + Key* publicKey( const KeyID& keyID ); + + /** Returns the first public key with the given user ID or null if no + matching key is found. + */ + Key* publicKey( const QString& userID ); + + /** Returns the secret key with the given key ID or null if no matching + key is found. + */ + Key* secretKey( const KeyID& keyID ); + + /** Returns the trust value for the given key. This is the maximal trust + value of any of the user ids of this key. + */ + Validity keyTrust( const KeyID& keyID ); + + /** Returns the trust value of a key with the given user id. If more than + one key have this user id then the first key with this user id will be + chosen. + */ + Validity keyTrust( const QString& userID ); + + /** Returns TRUE if the given key is at least trusted marginally. Otherwise + FALSE is returned. + */ + bool isTrusted( const KeyID& keyID ); + + /** Rereads the key data for the given key and returns the reread data. If + <em>readTrust</em> is true then the trust of this key will be determined. + */ + Key* rereadKey( const KeyID& keyID, const bool readTrust = true ); + + /** Request the change of the passphrase of the actual secret + key. TBI */ + bool changePassPhrase(); + + /** set a user identity to use (if you have more than one...) + by default, pgp uses the identity which was generated last. */ + void setUser(const KeyID& keyID); + /** Returns the actual key ID of the currently set key. */ + const KeyID user() const; + + /** always encrypt message to oneself? */ + void setEncryptToSelf(bool flag); + bool encryptToSelf(void) const; + + /** store passphrase in pgp object + Problem: passphrase stays in memory. + Advantage: you can call en-/decrypt without always passing the + passphrase + */ + void setStorePassPhrase(bool); + bool storePassPhrase(void) const; + + /** clears everything from memory */ + void clear(const bool erasePassPhrase = FALSE); + + /** returns the last error that occurred */ + const QString lastErrorMsg(void) const; + + // what version of PGP/GPG should we use + enum PGPType { tAuto, tGPG, tPGP2, tPGP5, tPGP6, tOff } pgpType; + + // did we find a pgp executable? + bool havePGP(void) const; + + /** Should PGP/GnuPG be used? */ + bool usePGP(void) const { return (havePGP() && (pgpType != tOff)); } + + // show the result of encryption/signing? + void setShowCipherText(const bool flag); + bool showCipherText(void) const; + + // show the encryption keys for approval? + void setShowKeyApprovalDlg(const bool flag); + bool showKeyApprovalDlg(void) const; + + /** Shows a key selection dialog with all secret keys and the given title + and the (optional) text. If <em>keyId</em> is given, then the + corresponding key is selected. + */ + KeyID selectSecretKey( const QString& title, + const QString& text = QString::null, + const KeyID& keyId = KeyID() ); + + /** Shows a key selection dialog with all public keys and the given title + and the (optional) text. If <em>oldKeyId</em> is given, then the + corresponding key is selected. If <em>address</em> is given, then the + chosen key will be stored (if the user wants it to be stored). + <em>mode</em> specifies which keys can be selected. + */ + KeyID selectPublicKey( const QString& title, + const QString& text = QString::null, + const KeyID& oldKeyId = KeyID(), + const QString& address = QString::null, + const unsigned int allowedKeys = AllKeys ); + + /** Shows a key selection dialog with all public keys and the given title + and the (optional) text. If <em>oldKeyId</em> is given, then the + corresponding key is selected. If <em>address</em> is given, then the + chosen key will be stored (if the user wants it to be stored). + <em>mode</em> specifies which keys can be selected. + */ + KeyIDList selectPublicKeys( const QString& title, + const QString& text = QString::null, + const KeyIDList& oldKeyIds = KeyIDList(), + const QString& address = QString::null, + const unsigned int allowedKeys = AllKeys ); + + // FIXME: key management + + /** Reads the encryption preference for the given address + from the config file. + */ + EncryptPref encryptionPreference( const QString& address ); + + /** Writes the given encryption preference for the given address + to the config file. + */ + void setEncryptionPreference( const QString& address, + const EncryptPref pref ); + + // -- static member functions -------------------------------------------- + + /** return the actual pgp object */ + static Kpgp::Module *getKpgp(); + + /** get the kpgp config object */ + static KConfig *getConfig(); + + /** Parses the given message and splits it into OpenPGP blocks and + Non-OpenPGP blocks. + Returns TRUE if the message contains at least one OpenPGP block and + FALSE otherwise. + The format is then: + <pre> + 1st Non-OpenPGP block + 1st OpenPGP block + 2nd Non-OpenPGP block + ... + n-th OpenPGP block + (n+1)-th Non-OpenPGP block + </pre> + */ + static bool prepareMessageForDecryption( const QCString& msg, + QPtrList<Block>& pgpBlocks, + QStrList& nonPgpBlocks ); + +private: + /** check if we have a trusted encryption key for the given person */ + bool haveTrustedEncryptionKey( const QString& person ); + + /** get a list of encryption keys to be used for the given recipient */ + KeyIDList getEncryptionKeys( const QString& person ); + + /** Set pass phrase */ + bool setPassPhrase(const char* pass); + + /** test if the PGP executable is found and if there is a passphrase + set or given. Returns: + 1 if everything is ok + 0 (together with some warning message) if something is missing + -1 if the passphrase dialog was canceled + */ + int prepare(bool needPassPhrase=FALSE, Block* block = 0 ); + + /** cleanup passphrase if it should not be stored. */ + void cleanupPass() { if (!storePass) wipePassPhrase(); } + + /** Wipes and optionally frees the memory used to hold the + passphrase. */ + void wipePassPhrase(bool free=false); + + // transform an address into canonical form + QString canonicalAddress( const QString& person ); + + /** Shows a dialog to let the user select a key from the given list of keys + */ + KeyID selectKey( const KeyList& keys, + const QString& title, + const QString& text = QString::null, + const KeyID& keyId = KeyID(), + const unsigned int allowedKeys = AllKeys ); + + /** Shows a dialog to let the user select a key from the given list of keys + */ + KeyIDList selectKeys( const KeyList& keys, + const QString& title, + const QString& text = QString::null, + const KeyIDList& keyIds = KeyIDList(), + const unsigned int allowedKeys = AllKeys ); + + /** Shows a dialog to let the user select a key from the given list of keys. + The dialog includes a checkbox ("Remember decision"). The state of the + checkbox is returned in rememberChoice. + */ + KeyID selectKey( bool& rememberChoice, + const KeyList& keys, + const QString& title, + const QString& text = QString::null, + const KeyID& keyId = KeyID(), + const unsigned int allowedKeys = AllKeys ); + + /** Shows a dialog to let the user select a list of keys from the given + list of keys. The dialog includes a checkbox ("Remember decision"). + The state of the checkbox is returned in rememberChoice. + */ + KeyIDList selectKeys( bool& rememberChoice, + const KeyList& keys, + const QString& title, + const QString& text = QString::null, + const KeyIDList& keyIds = KeyIDList(), + const unsigned int allowedKeys = AllKeys ); + + /** Returns the OpenPGP keys which should be used for encryption to the + given address. + */ + KeyIDList keysForAddress( const QString& address ); + + /** Set an email address -> list of OpenPGP keys association. + */ + void setKeysForAddress( const QString& address, const KeyIDList& keyIDs ); + + /** Remove an email address -> OpenPGP key association. */ + void removeKeyForAddress( const QString& address ); + + /** Reads the email address -> OpenPGP key associations from the config + file. + */ + void readAddressData(); + + /** Writes the email address -> OpenPGP key associations to the config + file. + */ + void writeAddressData(); + + bool checkForPGP(void); + void assignPGPBase(void); + + static Kpgp::Module *kpgpObject; + KConfig *config; + + struct AddressData { + KeyIDList keyIds; + EncryptPref encrPref; + }; + typedef QMap<QString, AddressData> AddressDataDict; + AddressDataDict addressDataDict; + + KeyList mPublicKeys; + bool mPublicKeysCached : 1; // did we already read the public keys? + KeyList mSecretKeys; + bool mSecretKeysCached : 1; // did we already read the secret keys? + + bool storePass : 1; + char * passphrase; + size_t passphrase_buffer_len; + + QString errMsg; + + KeyID pgpUser; // the key ID which is used to sign/encrypt to self + bool flagEncryptToSelf : 1; + + bool havePgp : 1; + bool havePGP5 : 1; + bool haveGpg : 1; + bool havePassPhrase : 1; + bool showEncryptionResult : 1; + bool mShowKeyApprovalDlg : 1; +}; // class Module + +// -- inlined member functions --------------------------------------------- + +inline void +Module::setShowKeyApprovalDlg( const bool flag ) +{ + mShowKeyApprovalDlg = flag; +} + +inline bool +Module::showKeyApprovalDlg( void ) const +{ + return mShowKeyApprovalDlg; +} + +// ------------------------------------------------------------------------- + +} // namespace Kpgp + +#endif + diff --git a/libkpgp/kpgp.upd b/libkpgp/kpgp.upd new file mode 100644 index 000000000..ce6c5fcf3 --- /dev/null +++ b/libkpgp/kpgp.upd @@ -0,0 +1,9 @@ +# Remove the old Encryption Preferences (applies only to KDE3 Beta users) +Id=preKDE3_a +File=kpgprc +RemoveGroup=EncryptionPreferences +# new format for storing info about encryption keys and encryption preferences +# for email addresses (in kpgprc) +Id=3.1-1 +File=kpgprc +Script=kpgp-3.1-upgrade-address-data.pl,perl diff --git a/libkpgp/kpgpbase.cpp b/libkpgp/kpgpbase.cpp new file mode 100644 index 000000000..39762333d --- /dev/null +++ b/libkpgp/kpgpbase.cpp @@ -0,0 +1,684 @@ +/* + kpgpbase.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#include <kdebug.h> + +#include <config.h> + +#include "kpgpbase.h" +#include "kpgp.h" +#include "kpgpblock.h" + +#include <stdlib.h> /* setenv, unsetenv */ +#include <unistd.h> /* pipe, close, fork, dup2, execl, _exit, write, read */ +#include <sys/poll.h> /* poll, etc. */ +#include <sys/types.h> /* pid_t */ +#include <sys/wait.h> /* waitpid */ +#include <errno.h> + +#include <qapplication.h> + + +namespace Kpgp { + +Base::Base() + : input(), output(), error(), errMsg(), status(OK) +{ +} + + +Base::~Base() +{ +} + + +void +Base::clear() +{ + input = QCString(); + output = QCString(); + error = QCString(); + errMsg = QString::null; + status = OK; +} + + +int +Base::run( const char *cmd, const char *passphrase, bool onlyReadFromPGP ) +{ + /* the pipe ppass is used for to pass the password to + * pgp. passing the password together with the normal input through + * stdin doesn't seem to work as expected (at least for pgp5.0) + */ + char str[1025] = "\0"; + int pin[2], pout[2], perr[2], ppass[2]; + int len, len2; + FILE *pass; + pid_t child_pid; + int childExitStatus; + struct pollfd pollin, pollout, pollerr; + int pollstatus; + + if(passphrase) + { + pipe(ppass); + + pass = fdopen(ppass[1], "w"); + fwrite(passphrase, sizeof(char), strlen(passphrase), pass); + fwrite("\n", sizeof(char), 1, pass); + fclose(pass); + close(ppass[1]); + + // tell pgp which fd to use for the passphrase + QCString tmp; + tmp.sprintf("%d",ppass[0]); + ::setenv("PGPPASSFD",tmp.data(),1); + + //Uncomment these lines for testing only! Doing so will decrease security! + //kdDebug(5100) << "pgp PGPPASSFD = " << tmp << endl; + //kdDebug(5100) << "pgp pass = " << passphrase << endl; + } + else + ::unsetenv("PGPPASSFD"); + + //Uncomment these lines for testing only! Doing so will decrease security! + kdDebug(5100) << "pgp cmd = " << cmd << endl; + //kdDebug(5100) << "pgp input = " << QString(input) + // << "input length = " << input.length() << endl; + + error = ""; + output = ""; + + pipe(pin); + pipe(pout); + pipe(perr); + + QApplication::flushX(); + if(!(child_pid = fork())) + { + /*We're the child.*/ + close(pin[1]); + dup2(pin[0], 0); + close(pin[0]); + + close(pout[0]); + dup2(pout[1], 1); + close(pout[1]); + + close(perr[0]); + dup2(perr[1], 2); + close(perr[1]); + + execl("/bin/sh", "sh", "-c", cmd, (void *)0); + _exit(127); + } + + /*Only get here if we're the parent.*/ + close(pin[0]); + close(pout[1]); + close(perr[1]); + + // poll for "There is data to read." + pollout.fd = pout[0]; + pollout.events = POLLIN; + pollout.revents = 0; // init with 0, just in case + pollerr.fd = perr[0]; + pollerr.events = POLLIN; + pollerr.revents = 0; // init with 0, just in case + + // poll for "Writing now will not block." + pollin.fd = pin[1]; + pollin.events = POLLOUT; + pollin.revents = 0; // init with 0, just in case + + if (!onlyReadFromPGP) { + if (!input.isEmpty()) { + // write to pin[1] one line after the other to prevent dead lock + for (unsigned int i=0; i<input.length(); i+=len2) { + len2 = 0; + + // check if writing now to pin[1] will not block (5 ms timeout) + //kdDebug(5100) << "Polling pin[1]..." << endl; + pollstatus = poll(&pollin, 1, 5); + if (pollstatus == 1) { + //kdDebug(5100) << "Status for polling pin[1]: " << pollin.revents << endl; + if (pollin.revents & POLLERR) { + kdDebug(5100) << "PGP seems to have hung up" << endl; + break; + } + else if (pollin.revents & POLLOUT) { + // search end of next line + if ((len2 = input.find('\n', i)) == -1) + len2 = input.length()-i; + else + len2 = len2-i+1; + + //kdDebug(5100) << "Trying to write " << len2 << " bytes to pin[1] ..." << endl; + len2 = write(pin[1], input.mid(i,len2).data(), len2); + //kdDebug(5100) << "Wrote " << len2 << " bytes to pin[1] ..." << endl; + } + } + else if (!pollstatus) { + //kdDebug(5100) << "Timeout while polling pin[1]: " + // << pollin.revents << endl; + } + else if (pollstatus == -1) { + kdDebug(5100) << "Error while polling pin[1]: " + << pollin.revents << endl; + } + + if (pout[0] >= 0) { + do { + // check if there is data to read from pout[0] + //kdDebug(5100) << "Polling pout[0]..." << endl; + pollstatus = poll(&pollout, 1, 0); + if (pollstatus == 1) { + //kdDebug(5100) << "Status for polling pout[0]: " << pollout.revents << endl; + if (pollout.revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl; + if ((len = read(pout[0],str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl; + str[len] ='\0'; + output += str; + } + else + break; + } + } + else if (pollstatus == -1) { + kdDebug(5100) << "Error while polling pout[0]: " + << pollout.revents << endl; + } + } while ((pollstatus == 1) && (pollout.revents & POLLIN)); + } + + if (perr[0] >= 0) { + do { + // check if there is data to read from perr[0] + //kdDebug(5100) << "Polling perr[0]..." << endl; + pollstatus = poll(&pollerr, 1, 0); + if (pollstatus == 1) { + //kdDebug(5100) << "Status for polling perr[0]: " << pollerr.revents << endl; + if (pollerr.revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl; + if ((len = read(perr[0],str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl; + str[len] ='\0'; + error += str; + } + else + break; + } + } + else if (pollstatus == -1) { + kdDebug(5100) << "Error while polling perr[0]: " + << pollerr.revents << endl; + } + } while ((pollstatus == 1) && (pollerr.revents & POLLIN)); + } + + // abort writing to PGP if PGP hung up + if ((pollstatus == 1) && + ((pollout.revents & POLLHUP) || (pollerr.revents & POLLHUP))) { + kdDebug(5100) << "PGP hung up" << endl; + break; + } + } + } + else // if input.isEmpty() + write(pin[1], "\n", 1); + //kdDebug(5100) << "All input was written to pin[1]" << endl; + } + close(pin[1]); + + pid_t waitpidRetVal; + + do { + //kdDebug(5100) << "Checking if PGP is still running..." << endl; + childExitStatus = 0; + waitpidRetVal = waitpid(child_pid, &childExitStatus, WNOHANG); + //kdDebug(5100) << "waitpid returned " << waitpidRetVal << endl; + if (pout[0] >= 0) { + do { + // check if there is data to read from pout[0] + //kdDebug(5100) << "Polling pout[0]..." << endl; + pollstatus = poll(&pollout, 1, 0); + if (pollstatus == 1) { + //kdDebug(5100) << "Status for polling pout[0]: " << pollout.revents << endl; + if (pollout.revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl; + if ((len = read(pout[0],str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl; + str[len] ='\0'; + output += str; + } else { + /* + * Apparently, on NetBSD when the child dies, the pipe begins + * receiving empty data packets *before* waitpid() has signaled + * that the child has died. Also, notice that this happens + * without any error bit being set in pollfd.revents (is this a + * NetBSD bug??? ). Notice that these anomalous packets exist + * according to poll(), but have length 0 according to read(). + * Thus, kde can remain stuck inside this loop. + * + * A solution to this problem is to get out of the inner loop + * when read() returns <=0. In this way, kde has another chance + * to call waitpid() to check if the child has died -- and this + * time the call should succeed. + * + * Setting POLLHUP in pollfd.revents is not necessary, but I just + * like the idea of signaling that something strange has + * happened. + */ + pollout.revents |= POLLHUP; + break; + } + } + } + else if (pollstatus == -1) { + kdDebug(5100) << "Error while polling pout[0]: " + << pollout.revents << endl; + } + } while ((pollstatus == 1) && (pollout.revents & POLLIN)); + } + + if (perr[0] >= 0) { + do { + // check if there is data to read from perr[0] + //kdDebug(5100) << "Polling perr[0]..." << endl; + pollstatus = poll(&pollerr, 1, 0); + if (pollstatus == 1) { + //kdDebug(5100) << "Status for polling perr[0]: " << pollerr.revents << endl; + if (pollerr.revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl; + if ((len = read(perr[0],str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl; + str[len] ='\0'; + error += str; + } else { + /* + * Apparently, on NetBSD when the child dies, the pipe begins + * receiving empty data packets *before* waitpid() has signaled + * that the child has died. Also, notice that this happens + * without any error bit being set in pollfd.revents (is this a + * NetBSD bug??? ). Notice that these anomalous packets exist + * according to poll(), but have length 0 according to read(). + * Thus, kde can remain stuck inside this loop. + * + * A solution to this problem is to get out of the inner loop + * when read() returns <=0. In this way, kde has another chance + * to call waitpid() to check if the child has died -- and this + * time the call should succeed. + * + * Setting POLLHUP in pollfd.revents is not necessary, but I just + * like the idea of signaling that something strange has + * happened. + */ + pollerr.revents |= POLLHUP; + break; + } + } + } + else if (pollstatus == -1) { + kdDebug(5100) << "Error while polling perr[0]: " + << pollerr.revents << endl; + } + } while ((pollstatus == 1) && (pollerr.revents & POLLIN)); + } + } while (waitpidRetVal == 0); + + close(pout[0]); + close(perr[0]); + + unsetenv("PGPPASSFD"); + if(passphrase) + close(ppass[0]); + + // Did the child exit normally? + if (WIFEXITED(childExitStatus) != 0) { + // Get the return code of the child + childExitStatus = WEXITSTATUS(childExitStatus); + kdDebug(5100) << "PGP exited with exit status " << childExitStatus + << endl; + } + else { + childExitStatus = -1; + kdDebug(5100) << "PGP exited abnormally!" << endl; + } + + //Uncomment these lines for testing only! Doing so will decrease security! + //kdDebug(5100) << "pgp output = " << QString(output) << endl; + //kdDebug(5100) << "pgp error = " << error << endl; + + /* Make the information visible, so that a user can + * get to know what's going on during the pgp calls. + */ + kdDebug(5100) << error << endl; + + return childExitStatus; +} + + +int +Base::runGpg( const char *cmd, const char *passphrase, bool onlyReadFromGnuPG ) +{ + /* the pipe ppass is used for to pass the password to + * pgp. passing the password together with the normal input through + * stdin doesn't seem to work as expected (at least for pgp5.0) + */ + char str[1025] = "\0"; + int pin[2], pout[2], perr[2], ppass[2]; + int len, len2; + FILE *pass; + pid_t child_pid; + int childExitStatus; + char gpgcmd[1024] = "\0"; + struct pollfd poller[3]; + int num_pollers = 0; + const int STD_OUT = 0; + const int STD_ERR = 1; + const int STD_IN = 2; + int pollstatus; + + if(passphrase) + { + pipe(ppass); + + pass = fdopen(ppass[1], "w"); + fwrite(passphrase, sizeof(char), strlen(passphrase), pass); + fwrite("\n", sizeof(char), 1, pass); + fclose(pass); + close(ppass[1]); + + //Uncomment these lines for testing only! Doing so will decrease security! + //kdDebug(5100) << "pass = " << passphrase << endl; + } + + //Uncomment these lines for testing only! Doing so will decrease security! + //kdDebug(5100) << "pgp cmd = " << cmd << endl; + //kdDebug(5100) << "pgp input = " << QString(input) + // << "input length = " << input.length() << endl; + + error = ""; + output = ""; + + pipe(pin); + pipe(pout); + pipe(perr); + + if( passphrase ) { + if( mVersion >= "1.0.7" ) { + // GnuPG >= 1.0.7 supports the gpg-agent, so we look for it. + if( 0 == getenv("GPG_AGENT_INFO") ) { + // gpg-agent not found, so we tell gpg not to use the agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --no-use-agent --passphrase-fd %d %s", + ppass[0], cmd ); + } + else { + // gpg-agent seems to be running, so we tell gpg to use the agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --use-agent %s", + cmd ); + } + } + else { + // GnuPG < 1.0.7 doesn't know anything about the gpg-agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --passphrase-fd %d %s", + ppass[0], cmd ); + } + } + else { + snprintf(gpgcmd, 1023, "LANGUAGE=C gpg %s",cmd); + } + + QApplication::flushX(); + if(!(child_pid = fork())) + { + /*We're the child.*/ + close(pin[1]); + dup2(pin[0], 0); + close(pin[0]); + + close(pout[0]); + dup2(pout[1], 1); + close(pout[1]); + + close(perr[0]); + dup2(perr[1], 2); + close(perr[1]); + + //#warning FIXME: there has to be a better way to do this + /* this is nasty nasty nasty (but it works) */ + if( passphrase ) { + if( mVersion >= "1.0.7" ) { + // GnuPG >= 1.0.7 supports the gpg-agent, so we look for it. + if( 0 == getenv("GPG_AGENT_INFO") ) { + // gpg-agent not found, so we tell gpg not to use the agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --no-use-agent --passphrase-fd %d %s", + ppass[0], cmd ); + } + else { + // gpg-agent seems to be running, so we tell gpg to use the agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --use-agent %s", + cmd ); + } + } + else { + // GnuPG < 1.0.7 doesn't know anything about the gpg-agent + snprintf( gpgcmd, 1023, + "LANGUAGE=C gpg --passphrase-fd %d %s", + ppass[0], cmd ); + } + } + else { + snprintf(gpgcmd, 1023, "LANGUAGE=C gpg %s",cmd); + } + + kdDebug(5100) << "pgp cmd = " << gpgcmd << endl; + + execl("/bin/sh", "sh", "-c", gpgcmd, (void *)0); + _exit(127); + } + + // Only get here if we're the parent. + + close(pin[0]); + close(pout[1]); + close(perr[1]); + + // poll for "There is data to read." + poller[STD_OUT].fd = pout[0]; + poller[STD_OUT].events = POLLIN; + poller[STD_ERR].fd = perr[0]; + poller[STD_ERR].events = POLLIN; + num_pollers = 2; + + if (!onlyReadFromGnuPG) { + // poll for "Writing now will not block." + poller[STD_IN].fd = pin[1]; + poller[STD_IN].events = POLLOUT; + num_pollers = 3; + } else { + close (pin[1]); + pin[1] = -1; + } + + pid_t waitpidRetVal; + unsigned int input_pos = 0; + + do { + //kdDebug(5100) << "Checking if GnuPG is still running..." << endl; + childExitStatus = 0; + waitpidRetVal = waitpid(child_pid, &childExitStatus, WNOHANG); + //kdDebug(5100) << "waitpid returned " << waitpidRetVal << endl; + do { + // poll the pipes + pollstatus = poll(poller, num_pollers, 10); + if( 0 < pollstatus ) { + // Check stdout. + if (poller[STD_OUT].revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl; + if ((len = read(pout[0],str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl; + str[len] ='\0'; + output += str; + } + else { + // FreeBSD/NetBSD workaround + // + // Apparently, on Free/NetBSD when the child dies, the pipe begins + // receiving empty data packets *before* waitpid() has signaled + // that the child has died. Also, notice that this happens + // without any error bit being set in pollfd.revents (is this a + // Free/NetBSD bug??? ). Notice that these anomalous packets exist + // according to poll(), but have length 0 according to read(). + // Thus, we can remain stuck inside this loop. + // + // A solution to this problem is to get out of the inner loop + // when read() returns <=0. In this way, we have another chance + // to call waitpid() to check if the child has died -- and this + // time the call should succeed. + // + // Set POLLHUP in pollfd.revents to signal that something strange + // has happened and disable polling of stdout. + poller[STD_OUT].revents |= POLLHUP; + poller[STD_OUT].events = 0; + } + } else if (poller[STD_OUT].revents & POLLHUP) { + // disable polling of stdout + poller[STD_OUT].events = 0; + } + + // Check stderr. + if (poller[STD_ERR].revents & POLLIN) { + //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl; + if ((len = read(poller[STD_ERR].fd,str,1024))>0) { + //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl; + str[len] ='\0'; + error += str; + } + else { + // FreeBSD/NetBSD workaround (for details see above) + poller[STD_ERR].revents |= POLLHUP; + poller[STD_ERR].events = 0; + } + } else if (poller[STD_ERR].revents & POLLHUP) { + // disable polling of stderr + poller[STD_ERR].events = 0; + } + + if (num_pollers > 2) { + if (poller[STD_IN].revents & ( POLLERR | POLLHUP ) ) { + kdDebug(5100) << "GnuPG seems to have hung up" << endl; + close (pin[1]); + pin[1] = -1; + --num_pollers; + } + else if (poller[STD_IN].revents & POLLOUT) { + if (!input.isEmpty()) { + // search end of next line + if ((len2 = input.find('\n', input_pos)) == -1) + len2 = input.length()-input_pos; + else + len2 = len2-input_pos+1; + + //kdDebug(5100) << "Trying to write " << len2 << " bytes to pin[1] ..." << endl; + len2 = write(pin[1], input.mid(input_pos,len2).data(), len2); + //kdDebug(5100) << "Wrote " << len2 << " bytes to pin[1] ..." << endl; + input_pos += len2; + + // We are done. + if (input_pos >= input.length()) { + //kdDebug(5100) << "All input was written to pin[1]" << endl; + close (pin[1]); + pin[1] = -1; + --num_pollers; + } + } + else { // if input.isEmpty() + write(pin[1], "\n", 1); + //kdDebug(5100) << "All input was written to pin[1]" << endl; + close (pin[1]); + pin[1] = -1; + --num_pollers; + } + } + } + } + } while ( (pollstatus > 0) && ( (num_pollers > 2) + || (poller[STD_OUT].events != 0) + || (poller[STD_ERR].events != 0) ) ); + + if (pollstatus == -1) { + kdDebug(5100) << "GnuPG poll failed, errno: " << errno << endl; + } + + } while(waitpidRetVal == 0); + + if( 0 <= pin[1] ) + close (pin[1]); + close(pout[0]); + close(perr[0]); + + if(passphrase) + close(ppass[0]); + + // Did the child exit normally? + if (WIFEXITED(childExitStatus) != 0) { + // Get the return code of the child + childExitStatus = WEXITSTATUS(childExitStatus); + kdDebug(5100) << "GnuPG exited with exit status " << childExitStatus + << endl; + } + else { + childExitStatus = -1; + kdDebug(5100) << "GnuPG exited abnormally!" << endl; + } + + //Uncomment these lines for testing only! Doing so will decrease security! + //kdDebug(5100) << "gpg stdout:\n" << QString(output) << endl; + + // Make the information visible, so that a user can + // get to know what's going on during the gpg calls. + kdDebug(5100) << "gpg stderr:\n" << error << endl; + + return childExitStatus; +} + + +QCString +Base::addUserId() +{ + QCString cmd; + QCString pgpUser = Module::getKpgp()->user(); + + if(!pgpUser.isEmpty()) + { + cmd += " -u 0x"; + cmd += pgpUser; + return cmd; + } + return QCString(); +} + + +} // namespace Kpgp diff --git a/libkpgp/kpgpbase.h b/libkpgp/kpgpbase.h new file mode 100644 index 000000000..f0f1d190d --- /dev/null +++ b/libkpgp/kpgpbase.h @@ -0,0 +1,239 @@ +/* + kpgpbase.h + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifndef KPGPBASE_H +#define KPGPBASE_H + +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> + +#include "kpgpkey.h" +#include "kpgpblock.h" + +namespace Kpgp { + +class Base +{ +public: + + /** virtual class used internally by kpgp */ + Base(); + virtual ~Base(); + + + /** Encrypts the message with the given keys. */ + virtual int encrypt( Block& , const KeyIDList& ) { return OK; } + + /** Clearsigns the message with the currently set key. */ + virtual int clearsign( Block& , const char *) { return OK; } + + /** Encrypts and signs the message with the given keys. */ + virtual int encsign( Block& , const KeyIDList& , + const char * = 0) { return OK; } + + /** Decrypts the message. */ + virtual int decrypt( Block& , const char * = 0) { return OK; } + + /** Verifies the message. */ + virtual int verify( Block& block ) { return decrypt( block, 0 ); } + + + /** Reads the key data for the given key and returns it. If + <em>readTrust</em> is true then the trust of this key will be determined. + If <em>key</em> is not null then the key data will be stored in given + key. + */ + virtual Key* readPublicKey( const KeyID&, const bool = false, + Key* = 0 ) + { return 0; } + + /** Returns the list of public keys in the users public keyring. */ + virtual KeyList publicKeys( const QStringList & = QStringList() ) + { return KeyList(); } + + /** Returns the list of secret keys in the users secret keyring. */ + virtual KeyList secretKeys( const QStringList & = QStringList() ) + { return KeyList(); } + + /** Returns the ascii armored data of the public key with the + given key id. */ + virtual QCString getAsciiPublicKey(const KeyID& ) { return QCString(); } + + /** Signs the given key with the currently set user key. This is currently + not implemented. */ + virtual int signKey(const KeyID& , const char *) { return OK; } + + + /** Returns an error message if an error occurred during the last + operation. */ + virtual QString lastErrorMessage() const; + + +protected: + virtual int run( const char *cmd, const char *passphrase = 0, + bool onlyReadFromPGP = false ); + virtual int runGpg( const char *cmd, const char *passphrase = 0, + bool onlyReadFromGnuPG = false ); + virtual void clear(); + + QCString addUserId(); + + QCString input; + QCString output; + QCString error; + QString errMsg; + + QCString mVersion; + + int status; + +}; + +// --------------------------------------------------------------------------- + +class Base2 : public Base +{ + +public: + Base2(); + virtual ~Base2(); + + virtual int encrypt( Block& block, const KeyIDList& recipients ); + virtual int clearsign( Block& block, const char *passphrase ); + virtual int encsign( Block& block, const KeyIDList& recipients, + const char *passphrase = 0 ); + virtual int decrypt( Block& block, const char *passphrase = 0 ); + virtual int verify( Block& block ) { return decrypt( block, 0 ); } + + virtual Key* readPublicKey( const KeyID& keyID, + const bool readTrust = false, + Key* key = 0 ); + virtual KeyList publicKeys( const QStringList & patterns = QStringList() ); + virtual KeyList secretKeys( const QStringList & patterns = QStringList() ); + virtual QCString getAsciiPublicKey( const KeyID& keyID ); + virtual int signKey( const KeyID& keyID, const char *passphrase ); + +protected: + KeyList doGetPublicKeys( const QCString & cmd, + const QStringList & patterns ); + virtual KeyList parseKeyList( const QCString&, bool ); + +private: + Key* parsePublicKeyData( const QCString& output, Key* key = 0 ); + void parseTrustDataForKey( Key* key, const QCString& str ); +}; + +class BaseG : public Base +{ + +public: + BaseG(); + virtual ~BaseG(); + + virtual int encrypt( Block& block, const KeyIDList& recipients ); + virtual int clearsign( Block& block, const char *passphrase ); + virtual int encsign( Block& block, const KeyIDList& recipients, + const char *passphrase = 0 ); + virtual int decrypt( Block& block, const char *passphrase = 0 ); + virtual int verify( Block& block ) { return decrypt( block, 0 ); } + + virtual Key* readPublicKey( const KeyID& keyID, + const bool readTrust = false, + Key* key = 0 ); + virtual KeyList publicKeys( const QStringList & patterns = QStringList() ); + virtual KeyList secretKeys( const QStringList & patterns = QStringList() ); + virtual QCString getAsciiPublicKey( const KeyID& keyID ); + virtual int signKey( const KeyID& keyID, const char *passphrase ); + +private: + Key* parseKeyData( const QCString& output, int& offset, Key* key = 0 ); + KeyList parseKeyList( const QCString&, bool ); +}; + + +class Base5 : public Base +{ + +public: + Base5(); + virtual ~Base5(); + + virtual int encrypt( Block& block, const KeyIDList& recipients ); + virtual int clearsign( Block& block, const char *passphrase ); + virtual int encsign( Block& block, const KeyIDList& recipients, + const char *passphrase = 0 ); + virtual int decrypt( Block& block, const char *passphrase = 0 ); + virtual int verify( Block& block ) { return decrypt( block, 0 ); } + + virtual Key* readPublicKey( const KeyID& keyID, + const bool readTrust = false, + Key* key = 0 ); + virtual KeyList publicKeys( const QStringList & patterns = QStringList() ); + virtual KeyList secretKeys( const QStringList & patterns = QStringList() ); + virtual QCString getAsciiPublicKey( const KeyID& keyID ); + virtual int signKey( const KeyID& keyID, const char *passphrase ); + +private: + Key* parseKeyData( const QCString& output, int& offset, Key* key = 0 ); + Key* parseSingleKey( const QCString& output, Key* key = 0 ); + KeyList parseKeyList( const QCString& output, bool ); + void parseTrustDataForKey( Key* key, const QCString& str ); +}; + + +class Base6 : public Base2 +{ + +public: + Base6(); + virtual ~Base6(); + + virtual int decrypt( Block& block, const char *passphrase = 0 ); + virtual int verify( Block& block ) { return decrypt( block, 0 ); } + + virtual Key* readPublicKey( const KeyID& keyID, + const bool readTrust = false, + Key* key = 0 ); + virtual KeyList publicKeys( const QStringList & patterns = QStringList() ); + virtual KeyList secretKeys( const QStringList & patterns = QStringList() ); + + virtual int isVersion6(); + +protected: + virtual KeyList parseKeyList( const QCString &, bool ); + +private: + Key* parseKeyData( const QCString& output, int& offset, Key* key = 0 ); + Key* parseSingleKey( const QCString& output, Key* key = 0 ); + void parseTrustDataForKey( Key* key, const QCString& str ); +}; + +// --------------------------------------------------------------------------- +// inlined functions + +inline QString +Base::lastErrorMessage() const +{ + return errMsg; +} + + +} // namespace Kpgp + +#endif diff --git a/libkpgp/kpgpbase2.cpp b/libkpgp/kpgpbase2.cpp new file mode 100644 index 000000000..c4fd0358f --- /dev/null +++ b/libkpgp/kpgpbase2.cpp @@ -0,0 +1,1105 @@ +/* + kpgpbase2.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kpgpbase.h" +#include "kpgp.h" + +#include <string.h> /* strncmp */ +#include <assert.h> + +#include <qdatetime.h> + +#include <klocale.h> +#include <kprocess.h> +#include <kdebug.h> + +#define PGP2 "pgp" + +namespace Kpgp { + +Base2::Base2() + : Base() +{ +} + + +Base2::~Base2() +{ +} + + +int +Base2::encrypt( Block& block, const KeyIDList& recipients ) +{ + return encsign( block, recipients, 0 ); +} + + +int +Base2::clearsign( Block& block, const char *passphrase ) +{ + return encsign( block, KeyIDList(), passphrase ); +} + + +int +Base2::encsign( Block& block, const KeyIDList& recipients, + const char *passphrase ) +{ + QCString cmd; + int exitStatus = 0; + + if(!recipients.isEmpty() && passphrase != 0) + cmd = PGP2 " +batchmode +language=en +verbose=1 -seat"; + else if(!recipients.isEmpty()) + cmd = PGP2 " +batchmode +language=en +verbose=1 -eat"; + else if(passphrase != 0) + cmd = PGP2 " +batchmode +language=en +verbose=1 -sat"; + else + { + kdDebug(5100) << "kpgpbase: Neither recipients nor passphrase specified." << endl; + return OK; + } + + if(passphrase != 0) + cmd += addUserId(); + + if(!recipients.isEmpty()) { + if(Module::getKpgp()->encryptToSelf()) + { + cmd += " 0x"; + cmd += Module::getKpgp()->user(); + } + + for( KeyIDList::ConstIterator it = recipients.begin(); + it != recipients.end(); ++it ) { + cmd += " 0x"; + cmd += (*it); + } + } + cmd += " -f"; + + clear(); + input = block.text(); + exitStatus = run(cmd.data(), passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + + if(exitStatus != 0) + status = ERROR; + +#if 0 + // #### FIXME: As we check the keys ourselves the following problems + // shouldn't occur. Therefore I don't handle them for now. + // IK 01/2002 + if(!recipients.isEmpty()) + { + int index = 0; + bool bad = FALSE; + unsigned int num = 0; + QCString badkeys = ""; + if (error.find("Cannot find the public key") != -1) + { + index = 0; + num = 0; + while((index = error.find("Cannot find the public key",index)) + != -1) + { + bad = TRUE; + index = error.find('\'',index); + int index2 = error.find('\'',index+1); + if (num++) + badkeys += ", "; + badkeys += error.mid(index, index2-index+1); + } + if(bad) + { + badkeys.stripWhiteSpace(); + if(num == recipients.count()) + errMsg = i18n("Could not find public keys matching the userid(s)\n" + "%1;\n" + "the message is not encrypted.") + .arg( badkeys.data() ); + else + errMsg = i18n("Could not find public keys matching the userid(s)\n" + "%1;\n" + "these persons will not be able to read the message.") + .arg( badkeys.data() ); + status |= MISSINGKEY; + status |= ERROR; + } + } + if (error.find("skipping userid") != -1) + { + index = 0; + num = 0; + while((index = error.find("skipping userid",index)) + != -1) + { + bad = TRUE; + int index2 = error.find('\n',index+16); + if (num++) + badkeys += ", "; + badkeys += error.mid(index+16, index2-index-16); + index = index2; + } + if(bad) + { + badkeys.stripWhiteSpace(); + if(num == recipients.count()) + errMsg = i18n("Public keys not certified with trusted signature " + "for userid(s)\n" + "%1.\n" + "The message is not encrypted.") + .arg( badkeys.data() ); + else + errMsg = i18n("Public keys not certified with trusted signature " + "for userid(s)\n" + "%1;\n" + "these persons will not be able to read the message.") + .arg( badkeys.data() ); + status |= BADKEYS; + status |= ERROR; + return status; + } + } + } +#endif + if(passphrase != 0) + { + if(error.find("Pass phrase is good") != -1) + { + //kdDebug(5100) << "Base: Good Passphrase!" << endl; + status |= SIGNED; + } + if( error.find("Bad pass phrase") != -1) + { + errMsg = i18n("Bad passphrase; could not sign."); + status |= BADPHRASE; + status |= ERR_SIGNING; + status |= ERROR; + } + } + if (error.find("Signature error") != -1) + { + errMsg = i18n("Signing failed: please check your PGP User Identity, " + "the PGP setup, and the key rings."); + status |= NO_SEC_KEY; + status |= ERR_SIGNING; + status |= ERROR; + } + if (error.find("Encryption error") != -1) + { + errMsg = i18n("Encryption failed: please check your PGP setup " + "and the key rings."); + status |= NO_SEC_KEY; + status |= BADKEYS; + status |= ERROR; + } + + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +int +Base2::decrypt( Block& block, const char *passphrase ) +{ + int index, index2; + int exitStatus = 0; + + clear(); + input = block.text(); + exitStatus = run(PGP2 " +batchmode +language=en -f", passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + + // pgp2.6 has sometimes problems with the ascii armor pgp5.0 produces + // this hack can solve parts of the problem + if(error.find("ASCII armor corrupted.") != -1) + { + kdDebug(5100) << "removing ASCII armor header" << endl; + int index1 = input.find("-----BEGIN PGP SIGNED MESSAGE-----"); + if(index1 != -1) + index1 = input.find("-----BEGIN PGP SIGNATURE-----", index1); + else + index1 = input.find("-----BEGIN PGP MESSAGE-----"); + index1 = input.find('\n', index1); + index2 = input.find("\n\n", index1); + input.remove(index1, index2 - index1); + exitStatus = run(PGP2 " +batchmode +language=en -f", passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + } + + if(exitStatus == -1) { + errMsg = i18n("error running PGP"); + status = RUN_ERR; + block.setStatus( status ); + return status; + } + + /* Example No.1 (PGP 2.6.3in): + * File is encrypted. Secret key is required to read it. + * Key for user ID: Test Key (only for testing) <testkey@ingo-kloecker.de> + * 1024-bit key, key ID E2D074D3, created 2001/09/09 + * + * Error: Bad pass phrase. + * + * This message can only be read by: + * Test key without secret key (for testing only) <nosectestkey@ingo-kloecker.de> + * Test Key (only for testing) <testkey@ingo-kloecker.de> + * + * You do not have the secret key needed to decrypt this file. + */ + /* Example No.2 (PGP 2.6.3in): + * File is encrypted. Secret key is required to read it. + * This message can only be read by: + * Test key without secret key (for testing only) <nosectestkey@ingo-kloecker.de> + * + * You do not have the secret key needed to decrypt this file. + */ + if(error.find("File is encrypted.") != -1) + { + //kdDebug(5100) << "kpgpbase: message is encrypted" << endl; + status |= ENCRYPTED; + if((index = error.find("Key for user ID:")) != -1) + { + // Find out the key for which the phrase is needed + index += 17; + index2 = error.find('\n', index); + block.setRequiredUserId( error.mid(index, index2 - index) ); + //kdDebug(5100) << "Base: key needed is \"" << block.requiredUserId() << "\"!\n"; + + if((passphrase != 0) && (error.find("Bad pass phrase") != -1)) + { + errMsg = i18n("Bad passphrase; could not decrypt."); + kdDebug(5100) << "Base: passphrase is bad" << endl; + status |= BADPHRASE; + status |= ERROR; + } + } + else + { + // no secret key fitting this message + status |= NO_SEC_KEY; + status |= ERROR; + errMsg = i18n("You do not have the secret key needed to decrypt this message."); + kdDebug(5100) << "Base: no secret key for this message" << endl; + } + // check for persons +#if 0 + // ##### FIXME: This information is anyway currently not used + // I'll change it to always determine the recipients. + index = error.find("can only be read by:"); + if(index != -1) + { + index = error.find('\n',index); + int end = error.find("\n\n",index); + + mRecipients.clear(); + while( (index2 = error.find('\n',index+1)) <= end ) + { + QCString item = error.mid(index+1,index2-index-1); + item.stripWhiteSpace(); + mRecipients.append(item); + index = index2; + } + } +#endif + } + + // handle signed message + + // Examples (made with PGP 2.6.3in) + /* Example No. 1 (signed with unknown key): + * File has signature. Public key is required to check signature. + * + * Key matching expected Key ID 12345678 not found in file '/home/user/.pgp/pubring.pgp'. + * + * WARNING: Can't find the right public key-- can't check signature integrity. + */ + /* Example No. 2 (bad signature): + * File has signature. Public key is required to check signature. + * .. + * WARNING: Bad signature, doesn't match file contents! + * + * Bad signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/09/09 16:01 GMT using 1024-bit key, key ID 12345678 + */ + /* Example No. 3.1 (good signature with untrusted key): + * File has signature. Public key is required to check signature. + * . + * Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/09/09 16:01 GMT using 1024-bit key, key ID 12345678 + * + * WARNING: Because this public key is not certified with a trusted + * signature, it is not known with high confidence that this public key + * actually belongs to: "Joe User <joe@foo.bar>". + */ + /* Example No. 3.2 (good signature with untrusted key): + * File has signature. Public key is required to check signature. + * . + * Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/09/09 16:01 GMT using 1024-bit key, key ID 12345678 + * + * WARNING: Because this public key is not certified with enough trusted + * signatures, it is not known with high confidence that this public key + * actually belongs to: "Joe User <joe@foo.bar>". + */ + /* Example No. 4 (good signature with revoked key): + * File has signature. Public key is required to check signature. + * . + * Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/09/09 16:01 GMT using 1024-bit key, key ID 12345678 + * + * + * Key for user ID: Joe User <joe@foo.bar> + * 1024-bit key, key ID 12345678, created 2001/09/09 + * Key has been revoked. + * + * WARNING: This key has been revoked by its owner, + * possibly because the secret key was compromised. + * This could mean that this signature is a forgery. + */ + /* Example No. 5 (good signature with trusted key): + * File has signature. Public key is required to check signature. + * . + * Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/09/09 16:01 GMT using 1024-bit key, key ID 12345678 + */ + + if((index = error.find("File has signature")) != -1) + { + // move index to start of next line + index = error.find('\n', index+18) + 1; + //kdDebug(5100) << "Base: message is signed" << endl; + status |= SIGNED; + // get signature date and signature key ID + if ((index2 = error.find("Signature made", index)) != -1) { + index2 += 15; + int index3 = error.find("using", index2); + block.setSignatureDate( error.mid(index2, index3-index2-1) ); + kdDebug(5100) << "Message was signed on '" << block.signatureDate() << "'\n"; + index3 = error.find("key ID ", index3) + 7; + block.setSignatureKeyId( error.mid(index3,8) ); + kdDebug(5100) << "Message was signed with key '" << block.signatureKeyId() << "'\n"; + } + else { + // if pgp can't find the keyring it unfortunately doesn't print + // the signature date and key ID + block.setSignatureDate( "" ); + block.setSignatureKeyId( "" ); + } + + if( ( index2 = error.find("Key matching expected", index) ) != -1) + { + status |= UNKNOWN_SIG; + status |= GOODSIG; + int index3 = error.find("Key ID ", index2) + 7; + block.setSignatureKeyId( error.mid(index3,8) ); + block.setSignatureUserId( QString::null ); + } + else if( (index2 = error.find("Good signature from", index)) != -1 ) + { + status |= GOODSIG; + // get signer + index = error.find('"',index2+19); + index2 = error.find('"', index+1); + block.setSignatureUserId( error.mid(index+1, index2-index-1) ); + } + else if( (index2 = error.find("Bad signature from", index)) != -1 ) + { + status |= ERROR; + // get signer + index = error.find('"',index2+19); + index2 = error.find('"', index+1); + block.setSignatureUserId( error.mid(index+1, index2-index-1) ); + } + else if( error.find("Keyring file", index) != -1 ) + { + // #### fix this hack + status |= UNKNOWN_SIG; + status |= GOODSIG; // this is a hack... + // determine file name of missing keyring file + index = error.find('\'', index) + 1; + index2 = error.find('\'', index); + block.setSignatureUserId( i18n("The keyring file %1 does not exist.\n" + "Please check your PGP setup.").arg(error.mid(index, index2-index)) ); + } + else + { + status |= ERROR; + block.setSignatureUserId( i18n("Unknown error") ); + } + } + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +Key* +Base2::readPublicKey( const KeyID& keyID, + const bool readTrust /* = false */, + Key* key /* = 0 */ ) +{ + int exitStatus = 0; + + status = 0; + exitStatus = run( PGP2 " +batchmode +language=en +verbose=0 -kvc -f 0x" + + keyID, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + key = parsePublicKeyData( output, key ); + + if( key == 0 ) + { + return 0; + } + + if( readTrust ) + { + exitStatus = run( PGP2 " +batchmode +language=en +verbose=0 -kc -f", + 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + parseTrustDataForKey( key, error ); + } + + return key; +} + + +KeyList +Base2::publicKeys( const QStringList & patterns ) +{ + return doGetPublicKeys( PGP2 " +batchmode +language=en +verbose=0 -kvc -f", + patterns ); +} + +KeyList +Base2::doGetPublicKeys( const QCString & cmd, const QStringList & patterns ) +{ + int exitStatus = 0; + KeyList publicKeys; + + status = 0; + if ( patterns.isEmpty() ) { + exitStatus = run( cmd, 0, true ); + + if ( exitStatus != 0 ) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for public keys + publicKeys = parseKeyList( output, false ); + } + else { + typedef QMap<QCString, Key*> KeyMap; + KeyMap map; + + for ( QStringList::ConstIterator it = patterns.begin(); + it != patterns.end(); ++it ) { + exitStatus = run( cmd + " " + KProcess::quote( *it ).local8Bit(), + 0, true ); + + if ( exitStatus != 0 ) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for public keys + publicKeys = parseKeyList( output, false ); + + // put all new keys into a map, remove duplicates + while ( !publicKeys.isEmpty() ) { + Key * key = publicKeys.take( 0 ); + if ( !map.contains( key->primaryFingerprint() ) ) + map.insert( key->primaryFingerprint(), key ); + else + delete key; + } + } + // build list from the map + for ( KeyMap::ConstIterator it = map.begin(); it != map.end(); ++it ) { + publicKeys.append( it.data() ); + } + } + + // sort the list of public keys + publicKeys.sort(); + + return publicKeys; +} + +KeyList +Base2::secretKeys( const QStringList & patterns ) +{ + return publicKeys( patterns ); +} + + +int +Base2::signKey(const KeyID& keyID, const char *passphrase) +{ + QCString cmd; + int exitStatus = 0; + + cmd = PGP2 " +batchmode +language=en -ks -f "; + cmd += addUserId(); + cmd += " 0x" + keyID; + + status = 0; + exitStatus = run(cmd.data(),passphrase); + + if (exitStatus != 0) + status = ERROR; + + return status; +} + + +QCString Base2::getAsciiPublicKey(const KeyID& keyID) +{ + int exitStatus = 0; + + if (keyID.isEmpty()) + return QCString(); + + status = 0; + exitStatus = run( PGP2 " +batchmode +force +language=en -kxaf 0x" + keyID, + 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return QCString(); + } + + return output; +} + + +Key* +Base2::parsePublicKeyData( const QCString& output, Key* key /* = 0 */ ) +{ + Subkey *subkey = 0; + int index; + + // search start of key data + if( !strncmp( output.data(), "pub", 3 ) || + !strncmp( output.data(), "sec", 3 ) ) + index = 0; + else + { + /* + if( secretKeys ) + index = output.find( "\nsec" ); + else + */ + index = output.find( "\npub" ); + if( index == -1 ) + return 0; + else + index++; + } + + while( true ) + { + int index2; + + // search the end of the current line + if( ( index2 = output.find( '\n', index ) ) == -1 ) + break; + + if( !strncmp( output.data() + index, "pub", 3 ) || + !strncmp( output.data() + index, "sec", 3 ) ) + { // line contains primary key data + // Example 1 (nothing special): + // pub 1024/E2D074D3 2001/09/09 Test Key <testkey@xyz> + // Example 2 (disabled key): + // pub- 1024/8CCB2C1B 2001/11/04 Disabled Test Key <disabled@xyz> + // Example 3 (expired key): + // pub> 1024/7B94827D 2001/09/09 Expired Test Key <expired@xyz> (EXPIRE:2001-09-10) + // Example 4 (revoked key): + // pub 1024/956721F9 2001/09/09 *** KEY REVOKED *** + + int pos, pos2; + + if( key == 0 ) + key = new Key(); + else + key->clear(); + /*key->setSecret( secretKeys );*/ + // set default key capabilities + key->setCanEncrypt( true ); + key->setCanSign( true ); + key->setCanCertify( true ); + + /*subkey = new Subkey( "", secretKeys );*/ + subkey = new Subkey( "", false ); + key->addSubkey( subkey ); + // set default key capabilities + subkey->setCanEncrypt( true ); + subkey->setCanSign( true ); + subkey->setCanCertify( true ); + // expiration date defaults to never + subkey->setExpirationDate( -1 ); + + // Key Flags + switch( output[index+3] ) + { + case ' ': // nothing special + break; + case '-': // disabled key + subkey->setDisabled( true ); + key->setDisabled( true ); + break; + case '>': // expired key + subkey->setExpired( true ); + key->setExpired( true ); + break; + default: + kdDebug(5100) << "Unknown key flag.\n"; + } + + // Key Length + pos = index + 4; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( '/', pos ); + subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() ); + + // Key ID + pos = pos2 + 1; + pos2 = output.find( ' ', pos ); + subkey->setKeyID( output.mid( pos, pos2-pos ) ); + + // Creation Date + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // The calculated creation date isn't exactly correct because QDateTime + // doesn't know anything about timezones and always assumes local time + // although epoch is of course UTC. But as PGP 2 anyway doesn't print + // the time this doesn't matter too much. + subkey->setCreationDate( epoch.secsTo( dt ) ); + + // User ID + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + QCString uid = output.mid( pos, index2-pos ); + if( uid != "*** KEY REVOKED ***" ) + key->addUserID( uid ); + else + { + subkey->setRevoked( true ); + key->setRevoked( true ); + } + } + else if( output[index] == ' ' ) + { // line contains additional key data + + if( key == 0 ) + break; + assert( subkey != 0 ); + + int pos = index + 1; + while( output[pos] == ' ' ) + pos++; + + if( !strncmp( output.data() + pos, "Key fingerprint = ", 18 ) ) + { // line contains a fingerprint + // Example: + // Key fingerprint = 47 30 7C 76 05 BF 5E FB 72 41 00 F2 7D 0B D0 49 + + QCString fingerprint = output.mid( pos, index2-pos ); + // remove white space from the fingerprint + for ( int idx = 0 ; (idx = fingerprint.find(' ', idx)) >= 0 ; ) + fingerprint.replace( idx, 1, "" ); + + subkey->setFingerprint( fingerprint ); + } + else if( !strncmp( output.data() + pos, "Expire: ", 8 ) || + !strncmp( output.data() + pos, "no expire ", 10 ) ) + { // line contains additional key properties + // Examples: + // Expire: 2001/09/10 + // no expire ENCRyption only + // no expire SIGNature only + + if( output[pos] == 'E' ) + { + // Expiration Date + pos += 8; + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // Here the same comments as for the creation date are valid. + subkey->setExpirationDate( epoch.secsTo( dt ) ); + pos += 11; // note that there is always a blank after the expire date + } + else + pos += 10; + + // optional key capabilities (sign/encrypt only) + if( pos != index2 ) + { + if( !strncmp( output.data() + pos, "SIGNature only", 14 ) ) + { + subkey->setCanEncrypt( false ); + key->setCanEncrypt( false ); + } + else if( !strncmp( output.data() + pos, "ENCRyption only", 15 ) ) + { + subkey->setCanSign( false ); + key->setCanSign( false ); + subkey->setCanCertify( false ); + key->setCanCertify( false ); + } + } + } + else + { // line contains an additional user id + // Example: + // Test key (2nd user ID) <abc@xyz> + + key->addUserID( output.mid( pos, index2-pos ) ); + } + } + index = index2 + 1; + } + + //kdDebug(5100) << "finished parsing key data\n"; + + return key; +} + + +void +Base2::parseTrustDataForKey( Key* key, const QCString& str ) +{ + if( ( key == 0 ) || str.isEmpty() ) + return; + + QCString keyID = key->primaryKeyID(); + UserIDList userIDs = key->userIDs(); + + // search the trust data belonging to this key + int index = str.find( '\n' ) + 1; + while( ( index > 0 ) && + ( strncmp( str.data() + index+2, keyID.data(), 8 ) != 0 ) ) + index = str.find( '\n', index ) + 1; + + if( index == 0 ) + return; + + bool ultimateTrust = false; + if( !strncmp( str.data() + index+11, "ultimate", 8 ) ) + ultimateTrust = true; + + bool firstLine = true; + + while( true ) + { // loop over all trust information about this key + int index2; + + // search the end of the current line + if( ( index2 = str.find( '\n', index ) ) == -1 ) + break; + + // check if trust info for the next key starts + if( !firstLine && ( str[index+2] != ' ' ) ) + break; + + if( str[index+21] != ' ' ) + { // line contains a validity value for a user ID + + // determine the validity + Validity validity = KPGP_VALIDITY_UNKNOWN; + if( !strncmp( str.data() + index+21, "complete", 8 ) ) + if( ultimateTrust ) + validity = KPGP_VALIDITY_ULTIMATE; + else + validity = KPGP_VALIDITY_FULL; + else if( !strncmp( str.data() + index+21, "marginal", 8 ) ) + validity = KPGP_VALIDITY_MARGINAL; + else if( !strncmp( str.data() + index+21, "never", 5 ) ) + validity = KPGP_VALIDITY_NEVER; + else if( !strncmp( str.data() + index+21, "undefined", 9 ) ) + validity = KPGP_VALIDITY_UNDEFINED; + + // determine the user ID + int pos = index + 31; + if( str[index+2] == ' ' ) + pos++; // additional user IDs start one column later + QString uid = str.mid( pos, index2-pos ); + + // set the validity of the corresponding user ID + for( UserIDListIterator it( userIDs ); it.current(); ++it ) + if( (*it)->text() == uid ) + { + kdDebug(5100)<<"Setting the validity of "<<uid<<" to "<<validity<<endl; + (*it)->setValidity( validity ); + break; + } + } + + firstLine = false; + index = index2 + 1; + } +} + + +KeyList +Base2::parseKeyList( const QCString& output, bool secretKeys ) +{ + kdDebug(5100) << "Kpgp::Base2::parseKeyList()" << endl; + KeyList keys; + Key *key = 0; + Subkey *subkey = 0; + int index; + + // search start of key data + if( !strncmp( output.data(), "pub", 3 ) || + !strncmp( output.data(), "sec", 3 ) ) + index = 0; + else + { + if( secretKeys ) + index = output.find( "\nsec" ); + else + index = output.find( "\npub" ); + if( index == -1 ) + return keys; + else + index++; + } + + while( true ) + { + int index2; + + // search the end of the current line + if( ( index2 = output.find( '\n', index ) ) == -1 ) + break; + + if( !strncmp( output.data() + index, "pub", 3 ) || + !strncmp( output.data() + index, "sec", 3 ) ) + { // line contains primary key data + // Example 1: + // pub 1024/E2D074D3 2001/09/09 Test Key <testkey@xyz> + // Example 2 (disabled key): + // pub- 1024/8CCB2C1B 2001/11/04 Disabled Test Key <disabled@xyz> + // Example 3 (expired key): + // pub> 1024/7B94827D 2001/09/09 Expired Test Key <expired@xyz> (EXPIRE:2001-09-10) + // Example 4 (revoked key): + // pub 1024/956721F9 2001/09/09 *** KEY REVOKED *** + + int pos, pos2; + + if( key != 0 ) // store the previous key in the key list + keys.append( key ); + + key = new Key(); + key->setSecret( secretKeys ); + // set default key capabilities + key->setCanEncrypt( true ); + key->setCanSign( true ); + key->setCanCertify( true ); + + subkey = new Subkey( "", secretKeys ); + key->addSubkey( subkey ); + // set default key capabilities + subkey->setCanEncrypt( true ); + subkey->setCanSign( true ); + subkey->setCanCertify( true ); + // expiration date defaults to never + subkey->setExpirationDate( -1 ); + + // Key Flags + switch( output[index+3] ) + { + case ' ': // nothing special + break; + case '-': // disabled key + subkey->setDisabled( true ); + key->setDisabled( true ); + break; + case '>': // expired key + subkey->setExpired( true ); + key->setExpired( true ); + break; + default: + kdDebug(5100) << "Unknown key flag.\n"; + } + + // Key Length + pos = index + 4; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( '/', pos ); + subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() ); + + // Key ID + pos = pos2 + 1; + pos2 = output.find( ' ', pos ); + subkey->setKeyID( output.mid( pos, pos2-pos ) ); + + // Creation Date + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // The calculated creation date isn't exactly correct because QDateTime + // doesn't know anything about timezones and always assumes local time + // although epoch is of course UTC. But as PGP 2 anyway doesn't print + // the time this doesn't matter too much. + subkey->setCreationDate( epoch.secsTo( dt ) ); + + // User ID + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + QCString uid = output.mid( pos, index2-pos ); + if( uid != "*** KEY REVOKED ***" ) + key->addUserID( uid ); + else + { + subkey->setRevoked( true ); + key->setRevoked( true ); + } + } + else if( output[index] == ' ' ) + { // line contains additional key data + + if( key == 0 ) + break; + + int pos = index + 1; + while( output[pos] == ' ' ) + pos++; + + if( !strncmp( output.data() + pos, "Key fingerprint = ", 18 ) ) + { // line contains a fingerprint + // Example: + // Key fingerprint = 47 30 7C 76 05 BF 5E FB 72 41 00 F2 7D 0B D0 49 + + int pos2; + pos2 = pos + 18; + QCString fingerprint = output.mid( pos, index2-pos ); + // remove white space from the fingerprint + for ( int idx = 0 ; (idx = fingerprint.find(' ', idx)) >= 0 ; ) + fingerprint.replace( idx, 1, "" ); + + subkey->setFingerprint( fingerprint ); + } + else if( !strncmp( output.data() + pos, "Expire: ", 8 ) || + !strncmp( output.data() + pos, "no expire ", 10 ) ) + { // line contains additional key properties + // Examples: + // Expire: 2001/09/10 + // no expire ENCRyption only + // no expire SIGNature only + + if( output[pos] == 'E' ) + { + // Expiration Date + pos += 8; + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // Here the same comments as for the creation date are valid. + subkey->setExpirationDate( epoch.secsTo( dt ) ); + pos += 11; // note that there is always a blank after the expire date + } + else + pos += 10; + + // optional key capabilities (sign/encrypt only) + if( pos != index2 ) + { + if( !strncmp( output.data() + pos, "SIGNature only", 14 ) ) + { + subkey->setCanEncrypt( false ); + key->setCanEncrypt( false ); + } + else if( !strncmp( output.data() + pos, "ENCRyption only", 15 ) ) + { + subkey->setCanSign( false ); + key->setCanSign( false ); + subkey->setCanCertify( false ); + key->setCanCertify( false ); + } + } + } + else + { // line contains an additional user id + // Example: + // Test key (2nd user ID) <abc@xyz> + + key->addUserID( output.mid( pos, index2-pos ) ); + } + } + + index = index2 + 1; + } + + if (key != 0) // store the last key in the key list + keys.append( key ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return keys; +} + + +} // namespace Kpgp diff --git a/libkpgp/kpgpbase5.cpp b/libkpgp/kpgpbase5.cpp new file mode 100644 index 000000000..c67dfd29b --- /dev/null +++ b/libkpgp/kpgpbase5.cpp @@ -0,0 +1,828 @@ +/* + kpgpbase5.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kpgpbase.h" +#include "kpgp.h" + +#include <string.h> /* strncmp */ +#include <assert.h> + +#include <qregexp.h> +#include <qdatetime.h> + +#include <klocale.h> +#include <kprocess.h> +#include <kdebug.h> + + +namespace Kpgp { + +Base5::Base5() + : Base() +{ +} + + +Base5::~Base5() +{ +} + + +int +Base5::encrypt( Block& block, const KeyIDList& recipients ) +{ + return encsign( block, recipients, 0 ); +} + + +int +Base5::clearsign( Block& block, const char *passphrase ) +{ + return encsign( block, KeyIDList(), passphrase ); +} + + +int +Base5::encsign( Block& block, const KeyIDList& recipients, + const char *passphrase ) +{ + QCString cmd; + int exitStatus = 0; + int index; + // used to work around a bug in pgp5. pgp5 treats files + // with non ascii chars (umlauts, etc...) as binary files, but + // we want a clear signature + bool signonly = false; + + if(!recipients.isEmpty() && passphrase != 0) + cmd = "pgpe +batchmode -afts "; + else if(!recipients.isEmpty()) + cmd = "pgpe +batchmode -aft "; + else if(passphrase != 0) + { + cmd = "pgps +batchmode -abft "; + signonly = true; + } + else + { + errMsg = i18n("Neither recipients nor passphrase specified."); + return OK; + } + + if(passphrase != 0) + cmd += addUserId(); + + if(!recipients.isEmpty()) + { + if(Module::getKpgp()->encryptToSelf()) + { + cmd += " -r 0x"; + cmd += Module::getKpgp()->user(); + } + + for( KeyIDList::ConstIterator it = recipients.begin(); + it != recipients.end(); ++it ) { + cmd += " -r 0x"; + cmd += (*it); + } + } + + clear(); + input = block.text(); + + if (signonly) + { + input.append("\n"); + input.replace(QRegExp("[ \t]+\n"), "\n"); //strip trailing whitespace + } + //We have to do this otherwise it's all in vain + + exitStatus = run(cmd.data(), passphrase); + block.setError( error ); + + if(exitStatus != 0) + status = ERROR; + + // now parse the returned info + if(error.find("Cannot unlock private key") != -1) + { + errMsg = i18n("The passphrase you entered is invalid."); + status |= ERROR; + status |= BADPHRASE; + } +//if(!ignoreUntrusted) +//{ + QCString aStr; + index = -1; + while((index = error.find("WARNING: The above key",index+1)) != -1) + { + int index2 = error.find("But you previously",index); + int index3 = error.find("WARNING: The above key",index+1); + if(index2 == -1 || (index2 > index3 && index3 != -1)) + { + // the key wasn't valid, no encryption to this person + // extract the person + index2 = error.find('\n',index); + index3 = error.find('\n',index2+1); + aStr += error.mid(index2+1, index3-index2-1); + aStr += ", "; + } + } + if(!aStr.isEmpty()) + { + aStr.truncate(aStr.length()-2); + if(error.find("No valid keys found") != -1) + errMsg = i18n("The key(s) you want to encrypt your message " + "to are not trusted. No encryption done."); + else + errMsg = i18n("The following key(s) are not trusted:\n%1\n" + "Their owner(s) will not be able to decrypt the message.") + .arg(aStr); + status |= ERROR; + status |= BADKEYS; + } +//} + if((index = error.find("No encryption keys found for")) != -1) + { + index = error.find(':',index); + int index2 = error.find('\n',index); + + errMsg = i18n("Missing encryption key(s) for:\n%1") + .arg(error.mid(index,index2-index)); +// errMsg = QString("Missing encryption key(s) for: %1") +// .arg(error.mid(index,index2-index)); + status |= ERROR; + status |= MISSINGKEY; + } + + if(signonly) { + // dash-escape the input + if (input[0] == '-') + input = "- " + input; + for ( int idx = 0 ; (idx = input.find("\n-", idx)) >= 0 ; idx += 4 ) + input.replace(idx, 2, "\n- -"); + + output = "-----BEGIN PGP SIGNED MESSAGE-----\n\n" + input + "\n" + output; + } + + block.setProcessedText( output ); + block.setStatus( status ); + return status; +} + + +int +Base5::decrypt( Block& block, const char *passphrase ) +{ + int exitStatus = 0; + + clear(); + input = block.text(); + exitStatus = run("pgpv -f +batchmode=1", passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + + if(exitStatus == -1) { + errMsg = i18n("Error running PGP"); + status = RUN_ERR; + block.setStatus( status ); + return status; + } + + // lets parse the returned information. + int index; + + index = error.find("Cannot decrypt message"); + if(index != -1) + { + //kdDebug(5100) << "message is encrypted" << endl; + status |= ENCRYPTED; + + // ok. we have an encrypted message. Is the passphrase bad, + // or do we not have the secret key? + if(error.find("Need a pass phrase") != -1) + { + if(passphrase != 0) + { + errMsg = i18n("Bad passphrase; could not decrypt."); + kdDebug(5100) << "Base: passphrase is bad" << endl; + status |= BADPHRASE; + status |= ERROR; + } + } + else + { + // we don't have the secret key + status |= NO_SEC_KEY; + status |= ERROR; + errMsg = i18n("You do not have the secret key needed to decrypt this message."); + kdDebug(5100) << "Base: no secret key for this message" << endl; + } + // check for persons +#if 0 + // ##### FIXME: This information is anyway currently not used + // I'll change it to always determine the recipients. + index = error.find("can only be decrypted by:"); + if(index != -1) + { + index = error.find('\n',index); + int end = error.find("\n\n",index); + + mRecipients.clear(); + int index2; + while( (index2 = error.find('\n',index+1)) <= end ) + { + QCString item = error.mid(index+1,index2-index-1); + item.stripWhiteSpace(); + mRecipients.append(item); + index = index2; + } + } +#endif + } + index = error.find("Good signature"); + if(index != -1) + { + //kdDebug(5100) << "good signature" << endl; + status |= SIGNED; + status |= GOODSIG; + + // get key ID of signer + index = error.find("Key ID ", index) + 7; + block.setSignatureKeyId( error.mid(index, 8) ); + + // get signer + index = error.find('"',index) + 1; + int index2 = error.find('"', index); + block.setSignatureUserId( error.mid(index, index2-index) ); + + /// ### FIXME get signature date + block.setSignatureDate( "" ); + } + index = error.find("BAD signature"); + if(index != -1) + { + //kdDebug(5100) << "BAD signature" << endl; + status |= SIGNED; + status |= ERROR; + + // get key ID of signer + index = error.find("Key ID ", index) + 7; + block.setSignatureKeyId( error.mid(index, 8) ); + + // get signer + index = error.find('"',index) + 1; + int index2 = error.find('"', index); + block.setSignatureUserId( error.mid(index, index2-index) ); + + /// ### FIXME get signature date + block.setSignatureDate( "" ); + } + index = error.find("Signature by unknown key"); + if(index != -1) + { + index = error.find("keyid: 0x",index) + 9; + block.setSignatureKeyId( error.mid(index, 8) ); + block.setSignatureUserId( QString::null ); + // FIXME: not a very good solution... + status |= SIGNED; + status |= GOODSIG; + + /// ### FIXME get signature date + block.setSignatureDate( "" ); + } + + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +Key* +Base5::readPublicKey( const KeyID& keyId, const bool readTrust, Key* key ) +{ + int exitStatus = 0; + + status = 0; + exitStatus = run( "pgpk -ll 0x" + keyId, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + key = parseSingleKey( output, key ); + + if( key == 0 ) + { + return 0; + } + + if( readTrust ) + { + exitStatus = run( "pgpk -c 0x" + keyId, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + parseTrustDataForKey( key, output ); + } + + return key; +} + + +KeyList +Base5::publicKeys( const QStringList & patterns ) +{ + int exitStatus = 0; + + QCString cmd = "pgpk -ll"; + for ( QStringList::ConstIterator it = patterns.begin(); + it != patterns.end(); ++it ) { + cmd += " "; + cmd += KProcess::quote( *it ).local8Bit(); + } + status = 0; + exitStatus = run( cmd, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for public keys + KeyList keys = parseKeyList( output, false ); + + // sort the list of public keys + keys.sort(); + + return keys; +} + + +KeyList +Base5::secretKeys( const QStringList & patterns ) +{ + int exitStatus = 0; + + status = 0; + QCString cmd = "pgpk -ll"; + for ( QStringList::ConstIterator it = patterns.begin(); + it != patterns.end(); ++it ) { + cmd += " "; + cmd += KProcess::quote( *it ).local8Bit(); + } + status = 0; + exitStatus = run( cmd, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for secret keys + KeyList keys = parseKeyList( output, true ); + + // sort the list of public keys + keys.sort(); + + return keys; +} + + +QCString Base5::getAsciiPublicKey(const KeyID& keyID) +{ + int exitStatus = 0; + + if (keyID.isEmpty()) + return QCString(); + + status = 0; + exitStatus = run( "pgpk -xa 0x" + keyID, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return QCString(); + } + + return output; +} + + +int +Base5::signKey(const KeyID& keyID, const char *passphrase) +{ + QCString cmd; + int exitStatus = 0; + + if(passphrase == 0) return false; + + cmd = "pgpk -s -f +batchmode=1 0x"; + cmd += keyID; + cmd += addUserId(); + + status = 0; + exitStatus = run(cmd.data(), passphrase); + + if (exitStatus != 0) + status = ERROR; + + return status; +} + +//-- private functions -------------------------------------------------------- + +Key* +Base5::parseKeyData( const QCString& output, int& offset, Key* key /* = 0 */ ) +// This function parses the data for a single key which is output by PGP 5 +// with the following command line: +// pgpk -ll +// It expects the key data to start at offset and returns the start of +// the next key's data in offset. +{ + if( ( strncmp( output.data() + offset, "pub", 3 ) != 0 ) && + ( strncmp( output.data() + offset, "sec", 3 ) != 0 ) ) + { + kdDebug(5100) << "Unknown key type or corrupt key data.\n"; + return 0; + } + + if( key == 0 ) + key = new Key(); + else + key->clear(); + + Subkey *subkey = 0; + bool primaryKey = true; + + while( true ) + { + int eol; + + // search the end of the current line + eol = output.find( '\n', offset ); + if( ( eol == -1 ) || ( eol == offset ) ) + break; + + //kdDebug(5100) << "Parsing: " << output.mid(offset, eol-offset) << endl; + + if( !strncmp( output.data() + offset, "pub", 3 ) || + !strncmp( output.data() + offset, "sec", 3 ) || + !strncmp( output.data() + offset, "sub", 3 ) ) + { // line contains key data + //kdDebug(5100)<<"Key data:\n"; + int pos, pos2; + + subkey = new Subkey( "", false ); + key->addSubkey( subkey ); + + // Key Flags + /* From the PGP 5 manual page for pgpk: + Following this column is a single character which + describes other attributes of the object: + + @ The object is disabled + + The object is axiomatically trusted (i.e., it's + your key) + */ + switch( output[offset+3] ) + { + case ' ': // nothing special + break; + case '@': // disabled key + subkey->setDisabled( true ); + key->setDisabled( true ); + break; + default: // all other flags are ignored + //kdDebug(5100) << "Unknown key flag.\n"; + ; + } + + // Key Length + pos = offset + 4; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() ); + //kdDebug(5100) << "Key Length: "<<subkey->keyLength()<<endl; + + // Key ID + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos += 2; // skip the '0x' + pos2 = output.find( ' ', pos ); + subkey->setKeyID( output.mid( pos, pos2-pos ) ); + //kdDebug(5100) << "Key ID: "<<subkey->keyID()<<endl; + + // Creation Date + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // The calculated creation date isn't exactly correct because QDateTime + // doesn't know anything about timezones and always assumes local time + // although epoch is of course UTC. But as PGP 5 anyway doesn't print + // the time this doesn't matter too much. + subkey->setCreationDate( epoch.secsTo( dt ) ); + + // Expiration Date + // if the primary key has been revoked the expiration date is not printed + if( primaryKey || !key->revoked() ) + { + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + if( output[pos] == '-' ) + { // key doesn't expire + subkey->setExpirationDate( -1 ); + } + else if( !strncmp( output.data() + pos, "*REVOKED*", 9 ) ) + { // key has been revoked + subkey->setRevoked( true ); + key->setRevoked( true ); + } + else + { + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + subkey->setCreationDate( epoch.secsTo( dt ) ); + // has the key already expired? + if( QDateTime::currentDateTime() >= dt ) + { + subkey->setExpired( true ); + key->setExpired( true ); + } + } + } + else if( key->revoked() ) + subkey->setRevoked( true ); + + // Key algorithm (RSA, DSS, Diffie-Hellman) + bool sign = false; + bool encr = false; + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + if( !strncmp( output.data() + pos, "RSA", 3 ) ) + { + sign = true; + encr = true; + } + else if( !strncmp( output.data() + pos, "DSS", 3 ) ) + sign = true; + else if( !strncmp( output.data() + pos, "Diffie-Hellman", 14 ) ) + encr = true; + else + kdDebug(5100)<<"Unknown key algorithm\n"; + + // set key capabilities of the subkey + subkey->setCanEncrypt( encr ); + subkey->setCanSign( sign ); + subkey->setCanCertify( sign ); + + if( primaryKey ) + { + // Global key capabilities + bool canSign = false; + bool canEncr = false; + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = eol; + if( !strncmp( output.data() + pos, "Sign & Encrypt", 14 ) ) + { + canSign = true; + canEncr = true; + } + else if( !strncmp( output.data() + pos, "Sign only", 9 ) ) + canSign = true; + else if( !strncmp( output.data() + pos, "Encrypt only", 12 ) ) + canEncr = true; + else + kdDebug(5100)<<"Unknown key capability\n"; + + // set the global key capabilities + if( !key->expired() && !key->revoked() ) + { + key->setCanEncrypt( canEncr ); + key->setCanSign( canSign ); + key->setCanCertify( canSign ); + } + //kdDebug(5100)<<"Key capabilities: "<<(key->canEncrypt()?"E":"")<<(key->canSign()?"SC":"")<<endl; + primaryKey = false; + } + } + else if( !strncmp( output.data() + offset, "f16", 3 ) || + !strncmp( output.data() + offset, "f20", 3 ) ) + { // line contains a fingerprint + /* Examples: + f16 Fingerprint16 = DE 2A 77 08 78 64 7C 42 72 75 B1 A7 3E 42 3F 79 + f20 Fingerprint20 = 226F 4B63 6DA2 7389 91D1 2A49 D58A 3EC1 5214 181E + + */ + int pos = output.find( '=', offset+3 ) + 2; + QCString fingerprint = output.mid( pos, eol-pos ); + // remove white space from the fingerprint + for ( int idx = 0 ; (idx = fingerprint.find(' ', idx)) >= 0 ; ) + fingerprint.replace( idx, 1, "" ); + assert( subkey != 0 ); + subkey->setFingerprint( fingerprint ); + //kdDebug(5100)<<"Fingerprint: "<<fingerprint<<endl; + } + else if( !strncmp( output.data() + offset, "uid", 3 ) ) + { // line contains a uid + int pos = offset+5; + QCString uid = output.mid( pos, eol-pos ); + key->addUserID( uid ); + // displaying of uids which contain non-ASCII characters is broken in + // PGP 5.0i; it shows these characters as \ooo and truncates the uid + // because it doesn't take the 3 extra characters per non-ASCII char + // into account. Example (with an UTF-8 encoded ö): + // uid Ingo Kl\303\266cker <ingo.kloecker@epo + // because of this and because we anyway don't know which charset was + // used to encode the uid we don't try to decode it + } + else if ( !strncmp( output.data() + offset, "sig", 3 ) || + !strncmp( output.data() + offset, "SIG", 3 ) || + !strncmp( output.data() + offset, "ret", 3 ) ) + { // line contains a signature + // SIG = sig with own key; ret = sig with revoked key + // we ignore it for now + } + + offset = eol + 1; + } + + return key; +} + + +Key* +Base5::parseSingleKey( const QCString& output, Key* key /* = 0 */ ) +{ + int offset; + + // search start of header line + if( !strncmp( output.data(), "Type Bits", 9 ) ) + offset = 0; + else + { + offset = output.find( "\nType Bits" ) + 1; + if( offset == 0 ) + return 0; + } + + // key data begins in the next line + offset = output.find( '\n', offset ) + 1; + if( offset == -1 ) + return 0; + + key = parseKeyData( output, offset, key ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return key; +} + + +KeyList +Base5::parseKeyList( const QCString& output, bool onlySecretKeys ) +{ + KeyList keys; + Key *key = 0; + int offset; + + // search start of header line + if( !strncmp( output.data(), "Type Bits", 9 ) ) + offset = 0; + else + { + offset = output.find( "\nType Bits" ) + 1; + if( offset == 0 ) + return keys; + } + + // key data begins in the next line + offset = output.find( '\n', offset ) + 1; + if( offset == -1 ) + return keys; + + do + { + key = parseKeyData( output, offset ); + if( key != 0 ) + { + // if only secret keys should be read test if the key is secret + if( !onlySecretKeys || !key->secret() ) + keys.append( key ); + // skip the blank line which separates the keys + offset++; + } + } + while( key != 0 ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return keys; +} + + +void +Base5::parseTrustDataForKey( Key* key, const QCString& str ) +{ + if( ( key == 0 ) || str.isEmpty() ) + return; + + QCString keyID = "0x" + key->primaryKeyID(); + UserIDList userIDs = key->userIDs(); + + // search the start of the trust data + int offset = str.find( "\n\n KeyID" ) + 9; + if( offset == -1 + 9 ) + return; + + offset = str.find( '\n', offset ) + 1; + if( offset == -1 + 1 ) + return; + + bool ultimateTrust = false; + if( !strncmp( str.data() + offset+13, "ultimate", 8 ) ) + ultimateTrust = true; + + while( true ) + { // loop over all trust information about this key + + int eol; + + // search the end of the current line + if( ( eol = str.find( '\n', offset ) ) == -1 ) + break; + + if( str[offset+23] != ' ' ) + { // line contains a validity value for a user ID + + // determine the validity + Validity validity = KPGP_VALIDITY_UNKNOWN; + if( !strncmp( str.data() + offset+23, "complete", 8 ) ) + if( ultimateTrust ) + validity = KPGP_VALIDITY_ULTIMATE; + else + validity = KPGP_VALIDITY_FULL; + else if( !strncmp( str.data() + offset+23, "marginal", 8 ) ) + validity = KPGP_VALIDITY_MARGINAL; + else if( !strncmp( str.data() + offset+23, "invalid", 7 ) ) + validity = KPGP_VALIDITY_UNDEFINED; + + // determine the user ID + int pos = offset + 33; + QString uid = str.mid( pos, eol-pos ); + + // set the validity of the corresponding user ID + for( UserIDListIterator it( userIDs ); it.current(); ++it ) + if( (*it)->text() == uid ) + { + kdDebug(5100)<<"Setting the validity of "<<uid<<" to "<<validity<<endl; + (*it)->setValidity( validity ); + break; + } + } + + offset = eol + 1; + } +} + + +} // namespace Kpgp diff --git a/libkpgp/kpgpbase6.cpp b/libkpgp/kpgpbase6.cpp new file mode 100644 index 000000000..75e47e1de --- /dev/null +++ b/libkpgp/kpgpbase6.cpp @@ -0,0 +1,840 @@ +/* + kpgpbase6.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kpgpbase.h" + +#include <string.h> /* strncmp */ +#include <assert.h> + +#include <qdatetime.h> + +#include <klocale.h> +#include <kdebug.h> + +#define PGP6 "pgp" + +namespace Kpgp { + +Base6::Base6() + : Base2() +{ +} + + +Base6::~Base6() +{ +} + + +int +Base6::decrypt( Block& block, const char *passphrase ) +{ + int index, index2; + int exitStatus = 0; + + clear(); + input = block.text(); + exitStatus = run( PGP6 " +batchmode +language=C -f", passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + + if(exitStatus == -1) { + errMsg = i18n("error running PGP"); + status = RUN_ERR; + block.setStatus( status ); + return status; + } + + // encrypted message + if( error.find("File is encrypted.") != -1) + { + //kdDebug(5100) << "kpgpbase: message is encrypted" << endl; + status |= ENCRYPTED; + if((index = error.find("Key for user ID")) != -1) + { + // Find out the key for which the phrase is needed + index = error.find(':', index) + 2; + index2 = error.find('\n', index); + block.setRequiredUserId( error.mid(index, index2 - index) ); + //kdDebug(5100) << "Base: key needed is \"" << block.requiredUserId() << "\"!\n"; + + // Test output length to find out, if the passphrase is + // bad. If someone knows a better way, please fix this. + /// ### This could be done by forcing PGP6 to be more verbose + /// by adding an additional '+verbose=2' to the command line + if (!passphrase || !output.length()) + { + errMsg = i18n("Bad passphrase; could not decrypt."); + //kdDebug(5100) << "Base: passphrase is bad" << endl; + status |= BADPHRASE; + status |= ERROR; + } + } + else if( error.find("You do not have the secret key needed to decrypt this file.") != -1) + { + errMsg = i18n("You do not have the secret key for this message."); + //kdDebug(5100) << "Base: no secret key for this message" << endl; + status |= NO_SEC_KEY; + status |= ERROR; + } + } + + // signed message + + // Examples (made with PGP 6.5.8) + /* Example no. 1 (signed with unknown key): + * File is signed. signature not checked. + * Signature made 2001/11/25 11:55 GMT + * key does not meet validity threshold. + * + * WARNING: Because this public key is not certified with a trusted + * signature, it is not known with high confidence that this public key + * actually belongs to: "(KeyID: 0x475027BD)". + */ + /* Example no. 2 (signed with untrusted key): + * File is signed. Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/12/05 13:09 GMT + * + * WARNING: Because this public key is not certified with a trusted + * signature, it is not known with high confidence that this public key + * actually belongs to: "Joe User <joe@foo.bar>". + */ + /* Example no. 3 (signed with trusted key): + * File is signed. Good signature from user "Joe User <joe@foo.bar>". + * Signature made 2001/12/05 13:09 GMT + */ + if(((index = error.find("File is signed.")) != -1) + || (error.find("Good signature") != -1 )) + { + //kdDebug(5100) << "Base: message is signed" << endl; + status |= SIGNED; + // determine the signature date + if( ( index2 = error.find( "Signature made", index ) ) != -1 ) + { + index2 += 15; + int eol = error.find( '\n', index2 ); + block.setSignatureDate( error.mid( index2, eol-index2 ) ); + kdDebug(5100) << "Message was signed on '" << block.signatureDate() << "'\n"; + } + else + block.setSignatureDate( QCString() ); + // determine signature status and signature key + if( error.find("signature not checked") != -1) + { + index = error.find("KeyID:",index); + block.setSignatureKeyId( error.mid(index+9,8) ); + block.setSignatureUserId( QString::null ); + status |= UNKNOWN_SIG; + status |= GOODSIG; + } + else if((index = error.find("Good signature")) != -1 ) + { + status |= GOODSIG; + // get signer + index = error.find('"',index)+1; + index2 = error.find('"', index); + block.setSignatureUserId( error.mid(index, index2-index) ); + + // get key ID of signer + index = error.find("KeyID:",index2); + if (index == -1) + block.setSignatureKeyId( QCString() ); + else + block.setSignatureKeyId( error.mid(index+9,8) ); + } + else if( error.find("Can't find the right public key") != -1 ) + { + // #### fix this hack + // #### This doesn't happen with PGP 6.5.8 because it seems to + // #### automatically create an empty pubring if it doesn't exist. + status |= UNKNOWN_SIG; + status |= GOODSIG; // this is a hack... + block.setSignatureUserId( i18n("??? (file ~/.pgp/pubring.pkr not found)") ); + block.setSignatureKeyId( "???" ); + } + else + { + status |= ERROR; + block.setSignatureUserId( QString::null ); + block.setSignatureKeyId( QCString() ); + } + } + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +Key* +Base6::readPublicKey( const KeyID& keyID, + const bool readTrust /* = false */, + Key* key /* = 0 */ ) +{ + int exitStatus = 0; + + status = 0; + exitStatus = run( PGP6 " +batchmode -compatible +verbose=0 +language=C -kvvc " + "0x" + keyID, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + key = parseSingleKey( output, key ); + + if( key == 0 ) + { + return 0; + } + + if( readTrust ) + { + exitStatus = run( PGP6 " +batchmode -compatible +verbose=0 +language=C -kc " + "0x" + keyID, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + parseTrustDataForKey( key, output ); + } + + return key; +} + + +KeyList +Base6::publicKeys( const QStringList & patterns ) +{ + return doGetPublicKeys( PGP6 " +batchmode -compatible +verbose=0 " + "+language=C -kvvc", patterns ); +} + + +/* +QStrList +Base6::pubKeys() +{ + int index, index2; + int exitStatus = 0; + int compatibleMode = 1; + + status = 0; + exitStatus = run("pgp +batchmode +language=C -kv -f"); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + //truncate trailing "\n" + if (error.length() > 1) error.truncate(error.length()-1); + + QStrList publicKeys; + index = error.find("bits/keyID",1); // skip first to "\n" + if (index ==-1) + { + index = error.find("Type bits",1); // skip first to "\n" + if (index == -1) + return 0; + else + compatibleMode = 0; + } + + while( (index = error.find("\n",index)) != -1) + { + //parse line + QCString line; + if( (index2 = error.find("\n",index+1)) != -1) + // skip last line + { + int index3; + if (compatibleMode) + { + int index_pub = error.find("pub ",index); + int index_sec = error.find("sec ",index); + if (index_pub < 0) + index3 = index_sec; + else if (index_sec < 0) + index3 = index_pub; + else + index3 = (index_pub < index_sec ? index_pub : index_sec); + } + else + { + int index_rsa = error.find("RSA ",index); + int index_dss = error.find("DSS ",index); + if (index_rsa < 0) + index3 = index_dss; + else if (index_dss < 0) + index3 = index_rsa; + else + index3 = (index_rsa < index_dss ? index_rsa : index_dss); + } + + if( (index3 >index2) || (index3 == -1) ) + { + // second address for the same key + line = error.mid(index+1,index2-index-1); + line = line.stripWhiteSpace(); + } else { + // line with new key + int index4 = error.find(QRegExp("/\\d{2}/\\d{2} "), index); + line = error.mid(index4+7,index2-index4-7); + } + //kdDebug(5100) << "Base: found key for " << (const char *)line << endl; + + // don't add PGP's comments to the key list + if (strncmp(line.data(),"*** KEY EXPIRED ***",19) && + line.find(QRegExp("^expires \\d{4}/\\d{2}/\\d{2}")) < 0 && + strncmp(line.data(),"*** DEFAULT SIGNING KEY ***",27)) { + publicKeys.append(line); + } + } + else + break; + index = index2; + } + + // Also look for pgp key groups + exitStatus = run("pgp +batchmode +language=C -gv -f"); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + index = 0; + while ( (index = error.find("\n >", index)) != -1 ) { + QCString line; + index += 4; + index2 = error.find(" \"", index); + line = error.mid(index, index2-index+1).stripWhiteSpace(); + + //kdDebug(5100) << "Base6: found key group for " << line << endl; + publicKeys.append(line); + } + + return publicKeys; +} +*/ + + +KeyList +Base6::secretKeys( const QStringList & patterns ) +{ + return publicKeys( patterns ); +} + + +int +Base6::isVersion6() +{ + int exitStatus = 0; + + exitStatus = run( PGP6, 0, true ); + + if(exitStatus == -1) { + errMsg = i18n("error running PGP"); + status = RUN_ERR; + return 0; + } + + if( error.find("Version 6") != -1) + { + //kdDebug(5100) << "kpgpbase: pgp version 6.x detected" << endl; + return 1; + } + + //kdDebug(5100) << "kpgpbase: not pgp version 6.x" << endl; + return 0; +} + + +Key* +Base6::parseKeyData( const QCString& output, int& offset, Key* key /* = 0 */ ) +// This function parses the data for a single key which is output by PGP 6 +// with the following command line arguments: +// +batchmode -compatible +verbose=0 +language=C -kvvc +// It expects the key data to start at offset and returns the start of +// the next key's data in offset. +{ + if( ( strncmp( output.data() + offset, "DSS", 3 ) != 0 ) && + ( strncmp( output.data() + offset, "RSA", 3 ) != 0 ) ) + { + kdDebug(5100) << "Unknown key type or corrupt key data.\n"; + return 0; + } + + Subkey *subkey = 0; + bool firstLine = true; + bool canSign = false; + bool canEncr = false; + bool fpr = false; + + while( true ) + { + int eol; + + // search the end of the current line + if( ( eol = output.find( '\n', offset ) ) == -1 ) + break; + + //kdDebug(5100) << "Parsing: " << output.mid(offset, eol-offset) << endl; + + if( firstLine && ( !strncmp( output.data() + offset, "DSS", 3 ) || + !strncmp( output.data() + offset, "RSA", 3 ) ) ) + { // line contains primary key data + // Example 1: + // RSA 1024 0xE2D074D3 2001/09/09 Test Key <testkey@xyz> + // Example 2 (disabled key): + // RSA@ 1024 0x8CCB2C1B 2001/11/04 Disabled Test Key <disabled@xyz> + // Example 3 (expired key): + // RSA 1024 0x7B94827D 2001/09/09 *** KEY EXPIRED *** + // Example 4 (revoked key): + // RSA 1024 0x956721F9 2001/09/09 *** KEY REVOKED *** + // Example 5 (default signing key): + // RSA 1024 0x12345678 2001/09/09 *** DEFAULT SIGNING KEY *** + // Example 6 (expiring key): + // RSA 2048 0xC11DB2E5 2000/02/24 expires 2001/12/31 + // Example 7 (complex example): + // DSS 1024 0x80E104A7 2000/06/05 expires 2002/05/31 + // DSS 1024 0x80E104A7 2001/06/27 *** KEY REVOKED ***expires 2002/06/27 + // DH 1024 0x80E104A7 2000/06/05 *** KEY REVOKED ****** KEY EXPIRED *** + //kdDebug(5100)<<"Primary key data:\n"; + bool sign = false; + bool encr = false; + + // set default key capabilities + if( !strncmp( output.data() + offset, "DSS", 3 ) ) + sign = true; + if( !strncmp( output.data() + offset, "RSA", 3 ) ) + { + sign = true; + encr = true; + } + + int pos, pos2; + + if( key == 0 ) + key = new Key(); + else + key->clear(); + + subkey = new Subkey( "", false ); + key->addSubkey( subkey ); + // expiration date defaults to never + subkey->setExpirationDate( -1 ); + + // Key Flags + switch( output[offset+3] ) + { + case ' ': // nothing special + break; + case '@': // disabled key + subkey->setDisabled( true ); + key->setDisabled( true ); + break; + default: + kdDebug(5100) << "Unknown key flag.\n"; + } + + // Key Length + pos = offset + 4; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() ); + //kdDebug(5100) << "Key Length: "<<subkey->keyLength()<<endl; + + // Key ID + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos += 2; // skip the '0x' + pos2 = output.find( ' ', pos ); + subkey->setKeyID( output.mid( pos, pos2-pos ) ); + //kdDebug(5100) << "Key ID: "<<subkey->keyID()<<endl; + + // Creation Date + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) ); + // The calculated creation date isn't exactly correct because QDateTime + // doesn't know anything about timezones and always assumes local time + // although epoch is of course UTC. But as PGP 6 anyway doesn't print + // the time this doesn't matter too much. + subkey->setCreationDate( epoch.secsTo( dt ) ); + + // User ID or key properties + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + while( pos < eol ) + { // loop over User ID resp. key properties + if( !strncmp( output.data() + pos, "*** KEY REVOKED ***", 19 ) ) + { + sign = false; + encr = false; + subkey->setRevoked( true ); + key->setRevoked( true ); + pos += 19; + //kdDebug(5100) << "Key was revoked.\n"; + } + else if( !strncmp( output.data() + pos, "*** KEY EXPIRED ***", 19 ) ) + { + sign = false; + encr = false; + subkey->setExpired( true ); + key->setExpired( true ); + pos += 19; + //kdDebug(5100) << "Key has expired.\n"; + } + else if( !strncmp( output.data() + pos, "expires ", 8 ) ) + { + pos += 8; + int year = output.mid( pos, 4 ).toInt(); + int month = output.mid( pos+5, 2 ).toInt(); + int day = output.mid( pos+8, 2 ).toInt(); + QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) ); + // Here the same comments as for the creation date are valid. + subkey->setExpirationDate( epoch.secsTo( dt ) ); + pos += 10; + //kdDebug(5100) << "Key expires...\n"; + } + else if( !strncmp( output.data() + pos, "*** DEFAULT SIGNING KEY ***", 27 ) ) + { + pos += 27; + //kdDebug(5100) << "Key is default signing key.\n"; + } + else + { + QCString uid = output.mid( pos, eol-pos ); + key->addUserID( uid ); + pos = eol; + //kdDebug(5100) << "User ID:"<<uid<<endl; + } + } + // set key capabilities of the primary subkey + subkey->setCanEncrypt( encr ); + subkey->setCanSign( sign ); + subkey->setCanCertify( sign ); + // remember the global key capabilities + canSign = sign; + canEncr = encr; + } + else if( !strncmp( output.data() + offset, "DSS", 3 ) || + !strncmp( output.data() + offset, " DH", 3 ) || + !strncmp( output.data() + offset, "RSA", 3 ) ) + { // line contains secondary key data (or data for the next key) + if( fpr ) + break; // here begins the next key's data + //kdDebug(5100)<<"Secondary key data:\n"; + + if( key == 0 ) + break; + + bool sign = false; + bool encr = false; + + // set default key capabilities + if( !strncmp( output.data() + offset, "DSS", 3 ) ) + sign = true; + if( !strncmp( output.data() + offset, " DH", 3 ) ) + encr = true; + if( !strncmp( output.data() + offset, "RSA", 3 ) ) + { + sign = true; + encr = true; + } + + int pos, pos2; + + // Key Length of secondary key (ignored) + pos = offset + 4; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + + // Key ID (ignored as it is anyway equal to the primary key id) + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + + // Creation Date of secondary key (ignored) + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + pos2 = output.find( ' ', pos ); + + // User ID or key properties + pos = pos2 + 1; + while( output[pos] == ' ' ) + pos++; + while( pos < eol ) + { // loop over User ID resp. key properties + if( !strncmp( output.data() + pos, "*** KEY REVOKED ***", 19 ) ) + { + sign = false; + encr = false; + pos += 19; + //kdDebug(5100) << "Key was revoked.\n"; + } + else if( !strncmp( output.data() + pos, "*** KEY EXPIRED ***", 19 ) ) + { + sign = false; + encr = false; + pos += 19; + //kdDebug(5100) << "Key has expired.\n"; + } + else if( !strncmp( output.data() + pos, "expires ", 8 ) ) + { + pos += 18; // skip the expiration date + //kdDebug(5100) << "Key expires...\n"; + } + else if( !strncmp( output.data() + pos, "*** DEFAULT SIGNING KEY ***", 27 ) ) + { + pos += 27; + //kdDebug(5100) << "Key is default signing key.\n"; + } + else + { + QCString uid = output.mid( pos, eol-pos ); + key->addUserID( uid ); + pos = eol; + //kdDebug(5100) << "User ID:"<<uid<<endl; + } + } + // store the global key capabilities + canSign = canSign || sign; + canEncr = canEncr || encr; + } + else if( !strncmp( output.data() + offset, "Unknown type", 12 ) ) + { // line contains key data of unknown type (ignored) + kdDebug(5100)<<"Unknown key type.\n"; + } + else if( output[offset] == ' ' ) + { // line contains additional key data + if( key == 0 ) + break; + //kdDebug(5100)<<"Additional key data:\n"; + + int pos = offset + 1; + while( output[pos] == ' ' ) + pos++; + + if( !strncmp( output.data() + pos, "Key fingerprint = ", 18 ) ) + { // line contains a fingerprint + // Example: + // Key fingerprint = D0 6C BB 3A F5 16 82 C4 F3 A0 8A B3 7B 16 99 70 + + fpr = true; // we found a fingerprint + + pos += 18; + QCString fingerprint = output.mid( pos, eol-pos ); + // remove white space from the fingerprint + for ( int idx = 0 ; (idx = fingerprint.find(' ', idx)) >= 0 ; ) + fingerprint.replace( idx, 1, "" ); + + //kdDebug(5100)<<"Fingerprint: "<<fingerprint<<endl; + assert( subkey != 0 ); + subkey->setFingerprint( fingerprint ); + } + else + { // line contains an additional user id + // Example: + // Test key (2nd user ID) <abc@xyz> + + //kdDebug(5100)<<"User ID: "<<output.mid( pos, eol-pos )<<endl; + key->addUserID( output.mid( pos, eol-pos ) ); + } + } + else if( !strncmp( output.data() + offset, "sig", 3 ) ) + { // line contains signature data (ignored) + //kdDebug(5100)<<"Signature.\n"; + } + else // end of key data + break; + + firstLine = false; + offset = eol + 1; + } + + if( key != 0 ) + { + // set the global key capabilities + key->setCanEncrypt( canEncr ); + key->setCanSign( canSign ); + key->setCanCertify( canSign ); + //kdDebug(5100)<<"Key capabilities: "<<(canEncr?"E":"")<<(canSign?"SC":"")<<endl; + } + + return key; +} + + +Key* +Base6::parseSingleKey( const QCString& output, Key* key /* = 0 */ ) +{ + int offset; + + // search start of header line + if( !strncmp( output.data(), "Type bits", 9 ) ) + offset = 9; + else + { + offset = output.find( "\nType bits" ); + if( offset == -1 ) + return 0; + else + offset += 10; + } + + // key data begins in the next line + offset = output.find( '\n', offset ) + 1; + if( offset == 0 ) + return 0; + + key = parseKeyData( output, offset, key ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return key; +} + + +KeyList +Base6::parseKeyList( const QCString& output, bool secretKeys ) +{ + kdDebug(5100) << "Kpgp::Base6::parseKeyList()" << endl; + KeyList keys; + Key *key = 0; + int offset; + + // search start of header line + if( !strncmp( output.data(), "Type bits", 9 ) ) + offset = 0; + else + { + offset = output.find( "\nType bits" ) + 1; + if( offset == 0 ) + return keys; + } + + // key data begins in the next line + offset = output.find( '\n', offset ) + 1; + if( offset == -1 ) + return keys; + + do + { + key = parseKeyData( output, offset ); + if( key != 0 ) + { + key->setSecret( secretKeys ); + keys.append( key ); + } + } + while( key != 0 ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return keys; +} + + +void +Base6::parseTrustDataForKey( Key* key, const QCString& str ) +{ + if( ( key == 0 ) || str.isEmpty() ) + return; + + QCString keyID = "0x" + key->primaryKeyID(); + UserIDList userIDs = key->userIDs(); + + // search the start of the trust data + int offset = str.find( "\n\n KeyID" ); + if( offset == -1 ) + return; + + offset = str.find( '\n', offset ) + 1; + if( offset == 0 ) + return; + + bool ultimateTrust = false; + if( !strncmp( str.data() + offset+13, "ultimate", 8 ) ) + ultimateTrust = true; + + while( true ) + { // loop over all trust information about this key + + int eol; + + // search the end of the current line + if( ( eol = str.find( '\n', offset ) ) == -1 ) + break; + + if( str[offset+23] != ' ' ) + { // line contains a validity value for a user ID + + // determine the validity + Validity validity = KPGP_VALIDITY_UNKNOWN; + if( !strncmp( str.data() + offset+23, "complete", 8 ) ) + if( ultimateTrust ) + validity = KPGP_VALIDITY_ULTIMATE; + else + validity = KPGP_VALIDITY_FULL; + else if( !strncmp( str.data() + offset+23, "marginal", 8 ) ) + validity = KPGP_VALIDITY_MARGINAL; + else if( !strncmp( str.data() + offset+23, "invalid", 7 ) ) + validity = KPGP_VALIDITY_UNDEFINED; + + // determine the user ID + int pos = offset + 33; + QString uid = str.mid( pos, eol-pos ); + + // set the validity of the corresponding user ID + for( UserIDListIterator it( userIDs ); it.current(); ++it ) + if( (*it)->text() == uid ) + { + kdDebug(5100)<<"Setting the validity of "<<uid<<" to "<<validity<<endl; + (*it)->setValidity( validity ); + break; + } + } + + offset = eol + 1; + } +} + + +} // namespace Kpgp diff --git a/libkpgp/kpgpbaseG.cpp b/libkpgp/kpgpbaseG.cpp new file mode 100644 index 000000000..0a66a0651 --- /dev/null +++ b/libkpgp/kpgpbaseG.cpp @@ -0,0 +1,855 @@ +/* + kpgpbaseG.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kpgpbase.h" +#include "kpgp.h" + +#include <klocale.h> +#include <kprocess.h> +#include <kdebug.h> + +#include <qtextcodec.h> + +#include <string.h> /* strncmp */ + +namespace Kpgp { + +BaseG::BaseG() + : Base() +{ + // determine the version of gpg (the method is equivalent to gpgme's method) + runGpg( "--version", 0 ); + int eol = output.find( '\n' ); + if( eol > 0 ) { + int pos = output.findRev( ' ', eol - 1 ); + if( pos != -1 ) { + mVersion = output.mid( pos + 1, eol - pos - 1 ); + kdDebug(5100) << "found GnuPG " << mVersion << endl; + } + } +} + + +BaseG::~BaseG() +{ +} + + +int +BaseG::encrypt( Block& block, const KeyIDList& recipients ) +{ + return encsign( block, recipients, 0 ); +} + + +int +BaseG::clearsign( Block& block, const char *passphrase ) +{ + return encsign( block, KeyIDList(), passphrase ); +} + + +int +BaseG::encsign( Block& block, const KeyIDList& recipients, + const char *passphrase ) +{ + QCString cmd; + int exitStatus = 0; + + if(!recipients.isEmpty() && passphrase != 0) + cmd = "--batch --armor --sign --encrypt --textmode"; + else if(!recipients.isEmpty()) + cmd = "--batch --armor --encrypt --textmode"; + else if(passphrase != 0) + cmd = "--batch --escape-from --clearsign"; + else + { + kdDebug(5100) << "kpgpbase: Neither recipients nor passphrase specified." << endl; + return OK; + } + + if(passphrase != 0) + cmd += addUserId(); + + if(!recipients.isEmpty()) + { + cmd += " --set-filename stdin"; + + QCString pgpUser = Module::getKpgp()->user(); + if(Module::getKpgp()->encryptToSelf() && !pgpUser.isEmpty()) { + cmd += " -r 0x"; + cmd += pgpUser; + } + + for( KeyIDList::ConstIterator it = recipients.begin(); + it != recipients.end(); ++it ) { + cmd += " -r 0x"; + cmd += (*it); + } + } + + clear(); + input = block.text(); + exitStatus = runGpg(cmd.data(), passphrase); + if( !output.isEmpty() ) + block.setProcessedText( output ); + block.setError( error ); + + if( exitStatus != 0 ) + { + // this error message is later hopefully overwritten + errMsg = i18n( "Unknown error." ); + status = ERROR; + } + +#if 0 + // #### FIXME: As we check the keys ourselves the following problems + // shouldn't occur. Therefore I don't handle them for now. + // IK 01/2002 + if(!recipients.isEmpty()) + { + int index = 0; + bool bad = FALSE; + unsigned int num = 0; + QCString badkeys = ""; + // Examples: + // gpg: 0x12345678: skipped: public key not found + // gpg: 0x12345678: skipped: public key is disabled + // gpg: 0x12345678: skipped: unusable public key + // (expired or revoked key) + // gpg: 23456789: no info to calculate a trust probability + // (untrusted key, 23456789 is the key Id of the encryption sub key) + while((index = error.find("skipped: ",index)) != -1) + { + bad = TRUE; + index = error.find('\'',index); + int index2 = error.find('\'',index+1); + badkeys += error.mid(index, index2-index+1) + ", "; + num++; + } + if(bad) + { + badkeys.stripWhiteSpace(); + if(num == recipients.count()) + errMsg = i18n("Could not find public keys matching the userid(s)\n" + "%1;\n" + "the message is not encrypted.") + .arg( badkeys.data() ); + else + errMsg = i18n("Could not find public keys matching the userid(s)\n" + "%1;\n" + "these persons will not be able to read the message.") + .arg( badkeys.data() ); + status |= MISSINGKEY; + status |= ERROR; + } + } +#endif + if( passphrase != 0 ) + { + // Example 1 (bad passphrase, clearsign only): + // gpg: skipped `0x12345678': bad passphrase + // gpg: [stdin]: clearsign failed: bad passphrase + // Example 2 (bad passphrase, sign & encrypt): + // gpg: skipped `0x12345678': bad passphrase + // gpg: [stdin]: sign+encrypt failed: bad passphrase + // Example 3 (unusable secret key, clearsign only): + // gpg: skipped `0x12345678': unusable secret key + // gpg: [stdin]: clearsign failed: unusable secret key + // Example 4 (unusable secret key, sign & encrypt): + // gpg: skipped `0xAC0EB35D': unusable secret key + // gpg: [stdin]: sign+encrypt failed: unusable secret key + if( error.find("bad passphrase") != -1 ) + { + errMsg = i18n("Signing failed because the passphrase is wrong."); + status |= BADPHRASE; + status |= ERR_SIGNING; + status |= ERROR; + } + else if( error.find("unusable secret key") != -1 ) + { + errMsg = i18n("Signing failed because your secret key is unusable."); + status |= ERR_SIGNING; + status |= ERROR; + } + else if( !( status & ERROR ) ) + { + //kdDebug(5100) << "Base: Good Passphrase!" << endl; + status |= SIGNED; + } + } + + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +int +BaseG::decrypt( Block& block, const char *passphrase ) +{ + int index, index2; + int exitStatus = 0; + + clear(); + input = block.text(); + exitStatus = runGpg("--batch --decrypt", passphrase); + if( !output.isEmpty() && ( error.find( "gpg: quoted printable" ) == -1 ) ) + block.setProcessedText( output ); + block.setError( error ); + + if(exitStatus == -1) { + errMsg = i18n("Error running gpg"); + status = RUN_ERR; + block.setStatus( status ); + return status; + } + + // Example 1 (good passphrase, decryption successful): + // gpg: encrypted with 2048-bit ELG-E key, ID 12345678, created 2000-11-11 + // "Foo Bar <foo@bar.xyz>" + // + // Example 2 (bad passphrase): + // gpg: encrypted with 1024-bit RSA key, ID 12345678, created 1991-01-01 + // "Foo Bar <foo@bar.xyz>" + // gpg: public key decryption failed: bad passphrase + // gpg: decryption failed: secret key not available + // + // Example 3 (no secret key available): + // gpg: encrypted with RSA key, ID 12345678 + // gpg: decryption failed: secret key not available + // + // Example 4 (good passphrase for second key, decryption successful): + // gpg: encrypted with 2048-bit ELG-E key, ID 12345678, created 2000-01-01 + // "Foo Bar (work) <foo@bar.xyz>" + // gpg: public key decryption failed: bad passphrase + // gpg: encrypted with 2048-bit ELG-E key, ID 23456789, created 2000-02-02 + // "Foo Bar (home) <foo@bar.xyz>" + if( error.find( "gpg: encrypted with" ) != -1 ) + { + //kdDebug(5100) << "kpgpbase: message is encrypted" << endl; + status |= ENCRYPTED; + if( error.find( "\ngpg: decryption failed" ) != -1 ) + { + if( ( index = error.find( "bad passphrase" ) ) != -1 ) + { + if( passphrase != 0 ) + { + errMsg = i18n( "Bad passphrase; could not decrypt." ); + kdDebug(5100) << "Base: passphrase is bad" << endl; + status |= BADPHRASE; + status |= ERROR; + } + else + { + // Search backwards the user ID of the needed key + index2 = error.findRev('"', index) - 1; + index = error.findRev(" \"", index2) + 7; + // The conversion from UTF8 is necessary because gpg stores and + // prints user IDs in UTF8 + block.setRequiredUserId( QString::fromUtf8( error.mid( index, index2 - index + 1 ) ) ); + kdDebug(5100) << "Base: key needed is \"" << block.requiredUserId() << "\"!" << endl; + } + } + else if( error.find( "secret key not available" ) != -1 ) + { + // no secret key fitting this message + status |= NO_SEC_KEY; + status |= ERROR; + errMsg = i18n("You do not have the secret key needed to decrypt this message."); + kdDebug(5100) << "Base: no secret key for this message" << endl; + } + } + // check for persons +#if 0 + // ##### FIXME: This information is anyway currently not used + // I'll change it to always determine the recipients. + index = error.find("can only be read by:"); + if(index != -1) + { + index = error.find('\n',index); + int end = error.find("\n\n",index); + + mRecipients.clear(); + while( (index2 = error.find('\n',index+1)) <= end ) + { + QCString item = error.mid(index+1,index2-index-1); + item.stripWhiteSpace(); + mRecipients.append(item); + index = index2; + } + } +#endif + } + + // Example 1 (unknown signature key): + // gpg: Signature made Wed 02 Jan 2002 11:26:33 AM CET using DSA key ID 2E250C64 + // gpg: Can't check signature: public key not found + if((index = error.find("Signature made")) != -1) + { + //kdDebug(5100) << "Base: message is signed" << endl; + status |= SIGNED; + // get signature date and signature key ID + // Example: Signature made Sun 06 May 2001 03:49:27 PM CEST using DSA key ID 12345678 + index2 = error.find("using", index+15); + block.setSignatureDate( error.mid(index+15, index2-(index+15)-1) ); + kdDebug(5100) << "Message was signed on '" << block.signatureDate() << "'\n"; + index2 = error.find("key ID ", index2) + 7; + block.setSignatureKeyId( error.mid(index2,8) ); + kdDebug(5100) << "Message was signed with key '" << block.signatureKeyId() << "'\n"; + // move index to start of next line + index = error.find('\n', index2)+1; + + if ((error.find("Key matching expected", index) != -1) + || (error.find("Can't check signature", index) != -1)) + { + status |= UNKNOWN_SIG; + status |= GOODSIG; + block.setSignatureUserId( QString::null ); + } + else if( error.find("Good signature", index) != -1 ) + { + status |= GOODSIG; + // get the primary user ID of the signer + index = error.find('"',index); + index2 = error.find('\n',index+1); + index2 = error.findRev('"', index2-1); + block.setSignatureUserId( error.mid( index+1, index2-index-1 ) ); + } + else if( error.find("BAD signature", index) != -1 ) + { + //kdDebug(5100) << "BAD signature" << endl; + status |= ERROR; + // get the primary user ID of the signer + index = error.find('"',index); + index2 = error.find('\n',index+1); + index2 = error.findRev('"', index2-1); + block.setSignatureUserId( error.mid( index+1, index2-index-1 ) ); + } + else if( error.find("Can't find the right public key", index) != -1 ) + { + // #### fix this hack + // I think this can't happen anymore because if the pubring is missing + // the current GnuPG creates a new empty one. + status |= UNKNOWN_SIG; + status |= GOODSIG; // this is a hack... + block.setSignatureUserId( i18n("??? (file ~/.gnupg/pubring.gpg not found)") ); + } + else + { + status |= ERROR; + block.setSignatureUserId( QString::null ); + } + } + //kdDebug(5100) << "status = " << status << endl; + block.setStatus( status ); + return status; +} + + +Key* +BaseG::readPublicKey( const KeyID& keyID, + const bool readTrust /* = false */, + Key* key /* = 0 */ ) +{ + int exitStatus = 0; + + status = 0; + if( readTrust ) + exitStatus = runGpg( "--batch --list-public-keys --with-fingerprint --with-colons --fixed-list-mode 0x" + keyID, 0, true ); + else + exitStatus = runGpg( "--batch --list-public-keys --with-fingerprint --with-colons --fixed-list-mode --no-expensive-trust-checks 0x" + keyID, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return 0; + } + + int offset; + // search start of key data + if( !strncmp( output.data(), "pub:", 4 ) ) + offset = 0; + else { + offset = output.find( "\npub:" ); + if( offset == -1 ) + return 0; + else + offset++; + } + + key = parseKeyData( output, offset, key ); + + return key; +} + + +KeyList +BaseG::publicKeys( const QStringList & patterns ) +{ + int exitStatus = 0; + + // the option --with-colons should be used for interprocess communication + // with gpg (according to Werner Koch) + QCString cmd = "--batch --list-public-keys --with-fingerprint --with-colons " + "--fixed-list-mode --no-expensive-trust-checks"; + for ( QStringList::ConstIterator it = patterns.begin(); + it != patterns.end(); ++it ) { + cmd += " "; + cmd += KProcess::quote( *it ).local8Bit(); + } + status = 0; + exitStatus = runGpg( cmd, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for public keys + KeyList publicKeys = parseKeyList(output, false); + + // sort the list of public keys + publicKeys.sort(); + + return publicKeys; +} + + +KeyList +BaseG::secretKeys( const QStringList & patterns ) +{ + int exitStatus = 0; + + // the option --with-colons should be used for interprocess communication + // with gpg (according to Werner Koch) + QCString cmd = "--batch --list-secret-keys --with-fingerprint --with-colons " + "--fixed-list-mode"; + for ( QStringList::ConstIterator it = patterns.begin(); + it != patterns.end(); ++it ) { + cmd += " "; + cmd += KProcess::quote( *it ).local8Bit(); + } + status = 0; + exitStatus = runGpg( cmd, 0, true ); + + if(exitStatus != 0) { + status = ERROR; + return KeyList(); + } + + // now we need to parse the output for secret keys + KeyList secretKeys = parseKeyList(output, true); + + // sort the list of secret keys + secretKeys.sort(); + + return secretKeys; +} + + +int +BaseG::signKey(const KeyID& keyID, const char *passphrase) +{ + QCString cmd; + int exitStatus = 0; + + cmd = "--batch"; + cmd += addUserId(); + cmd += " --sign-key 0x"; + cmd += keyID; + + status = 0; + exitStatus = runGpg(cmd.data(), passphrase); + + if (exitStatus != 0) + status = ERROR; + + return status; +} + + +QCString +BaseG::getAsciiPublicKey(const KeyID& keyID) +{ + int exitStatus = 0; + + if (keyID.isEmpty()) + return QCString(); + + status = 0; + exitStatus = runGpg("--batch --armor --export 0x" + keyID, 0, true); + + if(exitStatus != 0) { + status = ERROR; + return QCString(); + } + + return output; +} + + +Key* +BaseG::parseKeyData( const QCString& output, int& offset, Key* key /* = 0 */ ) +// This function parses the data for a single key which is output by GnuPG +// with the following command line arguments: +// --batch --list-public-keys --with-fingerprint --with-colons +// --fixed-list-mode [--no-expensive-trust-checks] +// It expects the key data to start at offset and returns the start of +// the next key's data in offset. +// Subkeys are currently ignored. +{ + int index = offset; + + if( ( strncmp( output.data() + offset, "pub:", 4 ) != 0 ) + && ( strncmp( output.data() + offset, "sec:", 4 ) != 0 ) ) { + return 0; + } + + if( key == 0 ) + key = new Key(); + else + key->clear(); + + QCString keyID; + bool firstKey = true; + + while( true ) + { + int eol; + // search the end of the current line + if( ( eol = output.find( '\n', index ) ) == -1 ) + break; + + bool bIsPublicKey = false; + if( ( bIsPublicKey = !strncmp( output.data() + index, "pub:", 4 ) ) + || !strncmp( output.data() + index, "sec:", 4 ) ) + { // line contains primary key data + // Example: pub:f:1024:17:63CB691DFAEBD5FC:860451781::379:-:::scESC: + + // abort parsing if we found the start of the next key + if( !firstKey ) + break; + firstKey = false; + + key->setSecret( !bIsPublicKey ); + + Subkey *subkey = new Subkey( QCString(), !bIsPublicKey ); + + int pos = index + 4; // begin of 2nd field + int pos2 = output.find( ':', pos ); + for( int field = 2; field <= 12; field++ ) + { + switch( field ) + { + case 2: // the calculated trust + if( pos2 > pos ) + { + switch( output[pos] ) + { + case 'o': // unknown (this key is new to the system) + break; + case 'i': // the key is invalid, e.g. missing self-signature + subkey->setInvalid( true ); + key->setInvalid( true ); + break; + case 'd': // the key has been disabled + subkey->setDisabled( true ); + key->setDisabled( true ); + break; + case 'r': // the key has been revoked + subkey->setRevoked( true ); + key->setRevoked( true ); + break; + case 'e': // the key has expired + subkey->setExpired( true ); + key->setExpired( true ); + break; + case '-': // undefined (no path leads to the key) + case 'q': // undefined (no trusted path leads to the key) + case 'n': // don't trust this key at all + case 'm': // the key is marginally trusted + case 'f': // the key is fully trusted + case 'u': // the key is ultimately trusted (secret key available) + // These values are ignored since we determine the key trust + // from the trust values of the user ids. + break; + default: + kdDebug(5100) << "Unknown trust value\n"; + } + } + break; + case 3: // length of key in bits + if( pos2 > pos ) + subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() ); + break; + case 4: // the key algorithm + if( pos2 > pos ) + subkey->setKeyAlgorithm( output.mid( pos, pos2-pos ).toUInt() ); + break; + case 5: // the long key id + keyID = output.mid( pos, pos2-pos ); + subkey->setKeyID( keyID ); + break; + case 6: // the creation date (in seconds since 1970-01-01 00:00:00) + if( pos2 > pos ) + subkey->setCreationDate( output.mid( pos, pos2-pos ).toLong() ); + break; + case 7: // the expiration date (in seconds since 1970-01-01 00:00:00) + if( pos2 > pos ) + subkey->setExpirationDate( output.mid( pos, pos2-pos ).toLong() ); + else + subkey->setExpirationDate( -1 ); // key expires never + break; + case 8: // local ID (ignored) + case 9: // Ownertrust (ignored for now) + case 10: // User-ID (always empty in --fixed-list-mode) + case 11: // signature class (always empty except for key signatures) + break; + case 12: // key capabilities + for( int i=pos; i<pos2; i++ ) + switch( output[i] ) + { + case 'e': + subkey->setCanEncrypt( true ); + break; + case 's': + subkey->setCanSign( true ); + break; + case 'c': + subkey->setCanCertify( true ); + break; + case 'E': + key->setCanEncrypt( true ); + break; + case 'S': + key->setCanSign( true ); + break; + case 'C': + key->setCanCertify( true ); + break; + default: + kdDebug(5100) << "Unknown key capability\n"; + } + break; + } + pos = pos2 + 1; + pos2 = output.find( ':', pos ); + } + key->addSubkey( subkey ); + } + else if( !strncmp( output.data() + index, "uid:", 4 ) ) + { // line contains a user id + // Example: uid:f::::::::Philip R. Zimmermann <prz@pgp.com>: + + UserID *userID = new UserID( "" ); + + int pos = index + 4; // begin of 2nd field + int pos2 = output.find( ':', pos ); + for( int field=2; field <= 10; field++ ) + { + switch( field ) + { + case 2: // the calculated trust + if( pos2 > pos ) + { + switch( output[pos] ) + { + case 'i': // the user id is invalid, e.g. missing self-signature + userID->setInvalid( true ); + break; + case 'r': // the user id has been revoked + userID->setRevoked( true ); + break; + case '-': // undefined (no path leads to the key) + case 'q': // undefined (no trusted path leads to the key) + userID->setValidity( KPGP_VALIDITY_UNDEFINED ); + break; + case 'n': // don't trust this key at all + userID->setValidity( KPGP_VALIDITY_NEVER ); + break; + case 'm': // the key is marginally trusted + userID->setValidity( KPGP_VALIDITY_MARGINAL ); + break; + case 'f': // the key is fully trusted + userID->setValidity( KPGP_VALIDITY_FULL ); + break; + case 'u': // the key is ultimately trusted (secret key available) + userID->setValidity( KPGP_VALIDITY_ULTIMATE ); + break; + default: + kdDebug(5100) << "Unknown trust value\n"; + } + } + break; + case 3: // these fields are empty + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + break; + case 10: // User-ID + QCString uid = output.mid( pos, pos2-pos ); + // replace "\xXX" with the corresponding character; + // other escaped characters, i.e. \n, \r etc., are ignored + // because they shouldn't appear in user IDs + for ( int idx = 0 ; (idx = uid.find( "\\x", idx )) >= 0 ; ++idx ) { + char str[2] = "x"; + str[0] = (char) QString( uid.mid( idx + 2, 2 ) ).toShort( 0, 16 ); + uid.replace( idx, 4, str ); + } + QString uidString = QString::fromUtf8( uid.data() ); + // check whether uid was utf-8 encoded + bool isUtf8 = true; + for ( unsigned int i = 0; i + 1 < uidString.length(); ++i ) { + if ( uidString[i].unicode() == 0xdbff && + uidString[i+1].row() == 0xde ) { + // we found a non-Unicode character (see QString::fromUtf8()) + isUtf8 = false; + break; + } + } + if( !isUtf8 ) { + // The user id isn't utf-8 encoded. It was most likely + // created with PGP which either used latin1 or koi8-r. + kdDebug(5100) << "User Id '" << uid + << "' doesn't seem to be utf-8 encoded." << endl; + + // We determine the ratio between non-ASCII and ASCII chars. + // A koi8-r user id should have lots of non-ASCII chars. + int nonAsciiCount = 0, asciiCount = 0; + + // We only look at the first part of the user id (i. e. everything + // before the email address resp. before a comment) + for( signed char* ch = (signed char*)uid.data(); + *ch && ( *ch != '(' ) && ( *ch != '<' ); + ++ch ) { + if( ( ( *ch >= 'A' ) && ( *ch <= 'Z' ) ) + || ( ( *ch >= 'a' ) && ( *ch <= 'z' ) ) ) + ++asciiCount; + else if( *ch < 0 ) + ++nonAsciiCount; + } + kdDebug(5100) << "ascii-nonAscii ratio : " << asciiCount + << ":" << nonAsciiCount << endl; + if( nonAsciiCount > asciiCount ) { + // assume koi8-r encoding + kdDebug(5100) << "Assume koi8-r encoding." << endl; + QTextCodec *codec = QTextCodec::codecForName("KOI8-R"); + uidString = codec->toUnicode( uid.data() ); + // check the case of the first two characters to find out + // whether the user id is probably CP1251 encoded (for some + // reason in CP1251 the lower case characters have smaller + // codes than the upper case characters, so if the first char + // of the koi8-r decoded user id is lower case and the second + // char is upper case then it's likely that the user id is + // CP1251 encoded) + if( ( uidString.length() >= 2 ) + && ( uidString[0].lower() == uidString[0] ) + && ( uidString[1].upper() == uidString[1] ) ) { + // koi8-r decoded user id has inverted case, so assume + // CP1251 encoding + kdDebug(5100) << "No, it doesn't seem to be koi8-r. " + "Use CP 1251 instead." << endl; + QTextCodec *codec = QTextCodec::codecForName("CP1251"); + uidString = codec->toUnicode( uid.data() ); + } + } + else { + // assume latin1 encoding + kdDebug(5100) << "Assume latin1 encoding." << endl; + uidString = QString::fromLatin1( uid.data() ); + } + } + userID->setText( uidString ); + break; + } + pos = pos2 + 1; + pos2 = output.find( ':', pos ); + } + + // user IDs are printed in UTF-8 by gpg (if one uses --with-colons) + key->addUserID( userID ); + } + else if( !strncmp( output.data() + index, "fpr:", 4 ) ) + { // line contains a fingerprint + // Example: fpr:::::::::17AFBAAF21064E513F037E6E63CB691DFAEBD5FC: + + if (key == 0) // invalid key data + break; + + // search the fingerprint (it's in the 10th field) + int pos = index + 4; + for( int i = 0; i < 8; i++ ) + pos = output.find( ':', pos ) + 1; + int pos2 = output.find( ':', pos ); + + key->setFingerprint( keyID, output.mid( pos, pos2-pos ) ); + } + index = eol + 1; + } + + //kdDebug(5100) << "finished parsing key data\n"; + + offset = index; + + return key; +} + + +KeyList +BaseG::parseKeyList( const QCString& output, bool secretKeys ) +{ + KeyList keys; + Key *key = 0; + int offset; + + // search start of key data + if( !strncmp( output.data(), "pub:", 4 ) + || !strncmp( output.data(), "sec:", 4 ) ) + offset = 0; + else { + if( secretKeys ) + offset = output.find( "\nsec:" ); + else + offset = output.find( "\npub:" ); + if( offset == -1 ) + return keys; + else + offset++; + } + + do { + key = parseKeyData( output, offset ); + if( key != 0 ) + keys.append( key ); + } + while( key != 0 ); + + //kdDebug(5100) << "finished parsing keys" << endl; + + return keys; +} + + +} // namespace Kpgp diff --git a/libkpgp/kpgpblock.cpp b/libkpgp/kpgpblock.cpp new file mode 100644 index 000000000..8bf551199 --- /dev/null +++ b/libkpgp/kpgpblock.cpp @@ -0,0 +1,136 @@ +/* + kpgpblock.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#include "kpgpblock.h" +#include "kpgp.h" + +#include <string.h> + +namespace Kpgp { + +Block::Block( const QCString& str ) + : mText(str), mProcessedText(), mError(), + mSignatureUserId(), mSignatureKeyId(), mSignatureDate(), + mRequiredKey(), mEncryptedFor(), + mStatus(0), mHasBeenProcessed(false), mType(NoPgpBlock) +{ + mEncryptedFor.setAutoDelete( true ); +} + +Block::~Block() +{ +} + +void +Block::reset() +{ + mProcessedText = QCString(); + mError = QCString(); + mSignatureUserId = QString::null; + mSignatureKeyId = QCString(); + mSignatureDate = QCString(); + mRequiredKey = QCString(); + mEncryptedFor.clear(); + mStatus = 0; + mHasBeenProcessed = false; +} + +void +Block::clear() +{ + reset(); + mText = QCString(); + mType = NoPgpBlock; +} + +BlockType +Block::determineType() const +{ + if( !strncmp( mText.data(), "-----BEGIN PGP ", 15 ) ) + { + if( !strncmp( mText.data() + 15, "SIGNED", 6 ) ) + return ClearsignedBlock; + else if( !strncmp( mText.data() + 15, "SIGNATURE", 9 ) ) + return SignatureBlock; + else if( !strncmp( mText.data() + 15, "PUBLIC", 6 ) ) + return PublicKeyBlock; + else if( !strncmp( mText.data() + 15, "PRIVATE", 7 ) || + !strncmp( mText.data() + 15, "SECRET", 6 ) ) + return PrivateKeyBlock; + else if( !strncmp( mText.data() + 15, "MESSAGE", 7 ) ) + { + if( !strncmp( mText.data() + 22, ", PART", 6 ) ) + return MultiPgpMessageBlock; + else + return PgpMessageBlock; + } + else if( !strncmp( mText.data() + 15, "ARMORED FILE", 12 ) ) + return PgpMessageBlock; + else + return UnknownBlock; + } + else + return NoPgpBlock; +} + +bool +Block::decrypt() +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) + return false; + + return pgp->decrypt( *this ); +} + +bool +Block::verify() +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) + return false; + + return pgp->verify( *this ); +} + +Kpgp::Result +Block::clearsign( const QCString& keyId, const QCString& charset ) +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) + return Kpgp::Failure; + + return pgp->clearsign( *this, keyId, charset ); +} + +Kpgp::Result +Block::encrypt( const QStringList& receivers, const QCString& keyId, + const bool sign, const QCString& charset ) +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) + return Kpgp::Failure; + + return pgp->encrypt( *this, receivers, keyId, sign, charset ); +} + +} // namespace Kpgp diff --git a/libkpgp/kpgpblock.h b/libkpgp/kpgpblock.h new file mode 100644 index 000000000..32978771c --- /dev/null +++ b/libkpgp/kpgpblock.h @@ -0,0 +1,356 @@ +/* + kpgpblock.h + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifndef KPGPBLOCK_H +#define KPGPBLOCK_H + +#include <qcstring.h> +#include <qstring.h> +#include <qstrlist.h> + +#include <kdepimmacros.h> + +//#include <qstringlist.h> +class QStringList; + +#include "kpgp.h" + +namespace Kpgp { + +typedef enum { + UnknownBlock = -1, // BEGIN PGP ??? + NoPgpBlock = 0, + PgpMessageBlock = 1, // BEGIN PGP MESSAGE + MultiPgpMessageBlock = 2, // BEGIN PGP MESSAGE, PART X[/Y] + SignatureBlock = 3, // BEGIN PGP SIGNATURE + ClearsignedBlock = 4, // BEGIN PGP SIGNED MESSAGE + PublicKeyBlock = 5, // BEGIN PGP PUBLIC KEY BLOCK + PrivateKeyBlock = 6 // BEGIN PGP PRIVATE KEY BLOCK (PGP 2.x: ...SECRET...) +} BlockType; + +typedef enum { + OK = 0x0000, + CLEARTEXT = 0x0000, + RUN_ERR = 0x0001, + ERROR = 0x0001, + ENCRYPTED = 0x0002, + SIGNED = 0x0004, + GOODSIG = 0x0008, + ERR_SIGNING = 0x0010, + UNKNOWN_SIG = 0x0020, + BADPHRASE = 0x0040, + BADKEYS = 0x0080, + NO_SEC_KEY = 0x0100, + MISSINGKEY = 0x0200, + CANCEL = 0x8000 +} MessageStatus; + +class Base; +class Module; + + /* + * BEGIN PGP MESSAGE + * Used for signed, encrypted, or compressed files. + * + * BEGIN PGP PUBLIC KEY BLOCK + * Used for armoring public keys + * + * BEGIN PGP PRIVATE KEY BLOCK (PGP 2.x: BEGIN PGP SECRET KEY BLOCK) + * Used for armoring private keys + * + * BEGIN PGP MESSAGE, PART X/Y + * Used for multi-part messages, where the armor is split amongst Y + * parts, and this is the Xth part out of Y. + * + * BEGIN PGP MESSAGE, PART X + * Used for multi-part messages, where this is the Xth part of an + * unspecified number of parts. Requires the MESSAGE-ID Armor + * Header to be used. + * + * BEGIN PGP SIGNATURE + * Used for detached signatures, OpenPGP/MIME signatures, and + * signatures following clearsigned messages. Note that PGP 2.x + * uses BEGIN PGP MESSAGE for detached signatures. + * + * BEGIN PGP SIGNED MESSAGE + * Used for cleartext signed messages. + */ +class KDE_EXPORT Block +{ + public: + + Block( const QCString& str = QCString() ); + ~Block(); + + QCString text() const; + void setText( const QCString& str ); + + void setProcessedText( const QCString& str ); + + int status() const; + void setStatus( const int status ); + + BlockType type(); + + /** is the message encrypted ? */ + bool isEncrypted() const; + + /** is the message signed by someone */ + bool isSigned() const; + + /** is the signature good ? */ + bool goodSignature() const; + + /** returns the primary user id of the signer or a null string if we + don't have the public key of the signer */ + QString signatureUserId() const; + void setSignatureUserId( const QString& userId ); + + /** keyID of signer */ + QCString signatureKeyId() const; + void setSignatureKeyId( const QCString& keyId ); + + /** date of the signature + WARNING: Will most likely be changed to QDateTime */ + QCString signatureDate() const; + void setSignatureDate( const QCString& date ); + + /** the persons who can decrypt the message */ + const QStrList encryptedFor() const; + + /** shows the secret key which is needed + to decrypt the message */ + QString requiredKey() const; + void setRequiredKey( const QCString& keyId ); + + QString requiredUserId() const; + void setRequiredUserId( const QString& userId ); + + QCString error() const; + void setError( const QCString& str ); + + /** Resets all information about this OpenPGP block */ + void reset(); + + /** decrypts this OpenPGP block if the passphrase is good. + returns false otherwise */ + bool decrypt(); + + /** tries to verify this (clearsigned) OpenPGP block */ + bool verify(); + + /** clearsigns this OpenPGP block with the key corresponding to the + given key id. The charset is needed to display the text correctly. + Returns + false if there was an unresolvable error or if signing was canceled + true if everything is o.k. + */ + Kpgp::Result clearsign( const QCString& keyId, + const QCString& charset = QCString() ); + + /** encrypts this OpenPGP block for a list of persons. if sign is true then + the message is signed with the key corresponding to the given key id. + Returns + false if there was an unresolvable error or if encryption was canceled + true if everything is o.k. + */ + Kpgp::Result encrypt( const QStringList& receivers, const QCString& keyId, + const bool sign, const QCString& charset = QCString() ); + + private: + void clear(); + + BlockType determineType() const; + + QCString mText; + QCString mProcessedText; + QCString mError; + QString mSignatureUserId; + QCString mSignatureKeyId; + QCString mSignatureDate; + QCString mRequiredKey; + QString mRequiredUserId; + QStrList mEncryptedFor; + int mStatus; + bool mHasBeenProcessed; + BlockType mType; +}; + +// -- inlined member functions --------------------------------------------- + +inline QCString +Block::text() const +{ + if( mHasBeenProcessed ) + return mProcessedText; + else + return mText; +} + +inline void +Block::setText( const QCString& str ) +{ + clear(); + mText = str; +} + +inline void +Block::setProcessedText( const QCString& str ) +{ + mProcessedText = str; + mHasBeenProcessed = true; +} + +inline QCString +Block::error() const +{ + return mError; +} + +inline void +Block::setError( const QCString& str ) +{ + mError = str; +} + +inline int +Block::status() const +{ + return mStatus; +} + +inline void +Block::setStatus( const int status ) +{ + mStatus = status; +} + +inline BlockType +Block::type() +{ + if( mType == NoPgpBlock ) + mType = determineType(); + return mType; +} + +inline QString +Block::signatureUserId() const +{ + return mSignatureUserId; +} + +inline void +Block::setSignatureUserId( const QString& userId ) +{ + mSignatureUserId = userId; +} + +inline QCString +Block::signatureKeyId() const +{ + return mSignatureKeyId; +} + +inline void +Block::setSignatureKeyId( const QCString& keyId ) +{ + mSignatureKeyId = keyId; +} + +inline QCString +Block::signatureDate() const +{ + return mSignatureDate; +} + +inline void +Block::setSignatureDate( const QCString& date ) +{ + mSignatureDate = date; +} + +inline QString +Block::requiredKey() const +{ + return mRequiredKey; +} + +inline void +Block::setRequiredKey( const QCString& keyId ) +{ + mRequiredKey = keyId; +} + +inline QString +Block::requiredUserId() const +{ + return mRequiredUserId; +} + +inline void +Block::setRequiredUserId( const QString& userId ) +{ + mRequiredUserId = userId; +} + +inline const QStrList +Block::encryptedFor() const +{ + return mEncryptedFor; +} + +inline bool +Block::isEncrypted() const +{ + if( mStatus & ENCRYPTED ) + return true; + return false; +} + +inline bool +Block::isSigned() const +{ + if( mStatus & SIGNED ) + return true; + return false; +} + +inline bool +Block::goodSignature() const +{ + if( mStatus & GOODSIG ) + return true; + return false; +} + +/* +inline bool +Block::unknownSigner() const +{ + if( mStatus & UNKNOWN_SIG ) + return true; + return false; +} +*/ + +// ------------------------------------------------------------------------- + +} // namespace Kpgp + +#endif + diff --git a/libkpgp/kpgpkey.cpp b/libkpgp/kpgpkey.cpp new file mode 100644 index 000000000..d0d2e4ec3 --- /dev/null +++ b/libkpgp/kpgpkey.cpp @@ -0,0 +1,260 @@ +/* + kpgpkey.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#include "kpgpkey.h" +#include "kdebug.h" + +namespace Kpgp { + +/* member functions of Kpgp::KeyIDList --------------------------------- */ + +/** Converts from a KeyIDList to a QStringList. +*/ +QStringList KeyIDList::toStringList() const +{ + QStringList res; + for( KeyIDList::ConstIterator it = begin(); it != end(); ++it ) { + res << (*it).data(); + } + return res; +} + +/** Converts from a QStringList to a KeyIDList. +*/ +KeyIDList KeyIDList::fromStringList( const QStringList& l ) +{ + KeyIDList res; + for( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) { + res << (*it).local8Bit(); + } + return res; +} + +/* member functions of Kpgp::UserID ------------------------------------ */ + +UserID::UserID(const QString& str, const Validity validity, + const bool revoked, const bool invalid) +{ + mText = str; + mValidity = validity; + mRevoked = revoked; + mInvalid = invalid; +} + + +/* member functions of Kpgp::Subkey ------------------------------------ */ + +Subkey::Subkey(const KeyID& keyID, const bool secret) +{ + mSecret = secret; + mKeyID = keyID; + + mRevoked = false; + mExpired = false; + mDisabled = false; + mInvalid = false; + mCanEncrypt = false; + mCanSign = false; + mCanCertify = false; + mKeyAlgo = 0; + mKeyLen = 0; + mFingerprint = 0; + mTimestamp = 0; + mExpiration = 0; +} + + +/* member functions of Kpgp::Key --------------------------------------- */ + +Key::Key(const KeyID& keyid, const QString& uid, const bool secret) : + mSubkeys(), mUserIDs() +{ + mSecret = secret; + if (!keyid.isEmpty()) + addSubkey(keyid, secret); + if (!uid.isEmpty()) + addUserID(uid); + + mRevoked = false; + mExpired = false; + mDisabled = false; + mInvalid = false; + mCanEncrypt = false; + mCanSign = false; + mCanCertify = false; + + mEncryptPref = UnknownEncryptPref; +} + +Key::~Key() +{ + //kdDebug(5100) << "Kpgp::Key: Deleting key " << primaryUserID() << endl; + mUserIDs.setAutoDelete(true); + mUserIDs.clear(); + mSubkeys.setAutoDelete(true); + mSubkeys.clear(); +} + +void +Key::clear() +{ + mSecret = false; + mRevoked = false; + mExpired = false; + mDisabled = false; + mInvalid = false; + mCanEncrypt = false; + mCanSign = false; + mCanCertify = false; + + mEncryptPref = UnknownEncryptPref; + + mSubkeys.setAutoDelete(true); + mSubkeys.clear(); + mUserIDs.setAutoDelete(true); + mUserIDs.clear(); +} + +Validity +Key::keyTrust() const +{ + Validity trust = KPGP_VALIDITY_UNKNOWN; + + for( UserIDListIterator it(mUserIDs); it.current(); ++it ) + { + if( (*it)->validity() > trust ) + trust = (*it)->validity(); + } + + return trust; +} + +Validity +Key::keyTrust( const QString& uid ) const +{ + Validity trust = KPGP_VALIDITY_UNKNOWN; + + if( uid.isEmpty() ) + return trust; + + for( UserIDListIterator it(mUserIDs); it.current(); ++it ) + { + if( (*it)->text() == uid ) + trust = (*it)->validity(); + } + + return trust; +} + +void +Key::cloneKeyTrust( const Key* key ) +{ + if( !key ) + return; + + for( UserIDListIterator it(mUserIDs); it.current(); ++it ) + { + (*it)->setValidity( key->keyTrust( (*it)->text() ) ); + } +} + +bool +Key::isValid() const +{ + return ( !mRevoked && !mExpired && !mDisabled && !mInvalid ); +} + + +bool +Key::isValidEncryptionKey() const +{ + return ( !mRevoked && !mExpired && !mDisabled && !mInvalid && mCanEncrypt ); +} + + +bool +Key::isValidSigningKey() const +{ + return ( !mRevoked && !mExpired && !mDisabled && !mInvalid && mCanSign ); +} + + +void Key::addUserID(const QString &uid, const Validity validity, + const bool revoked, const bool invalid) +{ + if (!uid.isEmpty()) { + UserID *userID = new UserID(uid, validity, revoked, invalid); + mUserIDs.append(userID); + } +} + +bool Key::matchesUserID(const QString& str, bool cs) +{ + if (str.isEmpty() || mUserIDs.isEmpty()) + return false; + + for (UserIDListIterator it(mUserIDs); it.current(); ++it) { + if (((*it)->text().find(str, 0, cs)) != -1) + return true; + } + + return false; +} + +void Key::addSubkey(const KeyID& keyID, const bool secret) +{ + if (!keyID.isEmpty()) { + Subkey *key = new Subkey(keyID, secret); + mSubkeys.append(key); + } +} + +Subkey *Key::getSubkey(const KeyID& keyID) +{ + if (keyID.isEmpty() || mSubkeys.isEmpty()) + return 0; + + // is the given key ID a long (16 chars) or a short (8 chars) key ID? + bool longKeyID = (keyID.length() == 16); + + for (SubkeyListIterator it(mSubkeys); it.current(); ++it) { + if (longKeyID) { + if ((*it)->longKeyID() == keyID) + return (*it); + } + else { + if ((*it)->keyID() == keyID) + return (*it); + } + } + + return 0; +} + +void Key::setFingerprint(const KeyID& keyID, const QCString &fpr) +{ + Subkey *key; + if ((key = getSubkey(keyID)) != 0) { + key->setFingerprint(fpr); + } + else + kdDebug(5006) << "Error: Can't set fingerprint. A subkey with key ID 0x" + << keyID << " doesn't exist." << endl; +} + +} // namespace Kpgp diff --git a/libkpgp/kpgpkey.h b/libkpgp/kpgpkey.h new file mode 100644 index 000000000..fd92f5a1e --- /dev/null +++ b/libkpgp/kpgpkey.h @@ -0,0 +1,780 @@ +/* + kpgpkey.h + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifndef KPGPKEY_H +#define KPGPKEY_H + +#include <time.h> + +#include <qcstring.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +namespace Kpgp { + +/** These are the possible validity values for a PGP user id and for the owner + trust. + */ +typedef enum +{ // this is copied from gpgme.h which is a part of GPGME + KPGP_VALIDITY_UNKNOWN = 0, // the trust hasn't been determined + KPGP_VALIDITY_UNDEFINED = 1, // trust is undefined + KPGP_VALIDITY_NEVER = 2, + KPGP_VALIDITY_MARGINAL = 3, + KPGP_VALIDITY_FULL = 4, + KPGP_VALIDITY_ULTIMATE = 5 +} Validity; + +/** These are the possible preferences for encryption. + */ +typedef enum +{ + NeverEncrypt = -1, + UnknownEncryptPref = 0, + AlwaysEncrypt = 1, + AlwaysEncryptIfPossible = 2, + AlwaysAskForEncryption = 3, + AskWheneverPossible = 4 +} EncryptPref; + + +typedef QCString KeyID; + +class KeyIDList : public QValueList<KeyID> +{ + public: + KeyIDList() { } + KeyIDList( const KeyIDList& l ) : QValueList<KeyID>(l) { } + KeyIDList( const QValueList<KeyID>& l ) : QValueList<KeyID>(l) { } + KeyIDList( const KeyID& i ) { append(i); } + + QStringList toStringList() const; + + static KeyIDList fromStringList( const QStringList& ); +}; + +/** This class is used to store information about a user id of a PGP key. + */ +class UserID +{ + public: + /** Constructs a new user id with the given values. */ + UserID(const QString& str, + const Validity validity = KPGP_VALIDITY_UNKNOWN, + const bool revoked = false, + const bool invalid = false); + ~UserID() {}; + + /** Returns the text of the user id. */ + QString text() const; + + /** Returns true if the user id has been revoked. */ + bool revoked() const; + + /** Returns true if the user id is invalid. */ + bool invalid() const; + + /** Returns the validity of resp. the trust in the user id. */ + Validity validity() const; + + /** Sets the text of the user id to <em>str</em>. */ + void setText(const QString& str); + + /** Sets the flag if the user id has been revoked to <em>revoked</em>. */ + void setRevoked(const bool revoked); + + /** Sets the flag if the user id is invalid to <em>invalid</em>. */ + void setInvalid(const bool invalid); + + /** Sets the validity of resp. the trust in the user id to + <em>validity</em>. */ + void setValidity(const Validity validity); + + protected: + bool mRevoked : 1; + bool mInvalid : 1; + Validity mValidity; + QString mText; +}; + +typedef QPtrList<UserID> UserIDList; +typedef QPtrListIterator<UserID> UserIDListIterator; + +inline QString UserID::text() const +{ + return mText; +} + +inline bool UserID::revoked() const +{ + return mRevoked; +} + +inline bool UserID::invalid() const +{ + return mInvalid; +} + +inline Validity UserID::validity() const +{ + return mValidity; +} + +inline void UserID::setText(const QString& str) +{ + mText = str; +} + +inline void UserID::setRevoked(const bool revoked) +{ + mRevoked = revoked; +} + +inline void UserID::setInvalid(const bool invalid) +{ + mInvalid = invalid; +} + +inline void UserID::setValidity(const Validity validity) +{ + mValidity = validity; +} + + +/** This class is used to store information about a subkey of a PGP key. + */ +class Subkey +{ + public: + /** Constructs a new subkey with the given key ID. */ + Subkey(const KeyID& keyID, const bool secret = false); + ~Subkey() {}; + + /** Returns true if the subkey is a secret subkey. */ + bool secret() const; + + /** Returns true if the subkey has been revoked. */ + bool revoked() const; + + /** Returns true if the subkey has expired. */ + bool expired() const; + + /** Returns true if the subkey has been disabled. */ + bool disabled() const; + + /** Returns true if the subkey is invalid. */ + bool invalid() const; + + /** Returns true if the subkey can be used to encrypt data. */ + bool canEncrypt() const; + + /** Returns true if the subkey can be used to sign data. */ + bool canSign() const; + + /** Returns true if the subkey can be used to certify keys. */ + bool canCertify() const; + + /** Returns the key algorithm of the subkey. */ + unsigned int keyAlgorithm() const; + + /** Returns the length of the subkey in bits. */ + unsigned int keyLength() const; + + /** Returns the long 64 bit key ID of the subkey if it's available. + Otherwise the short 32 bit key ID is returned. */ + KeyID longKeyID() const; + + /** Returns the (short) 32 bit key ID of the subkey. */ + KeyID keyID() const; + + /** Returns the fingerprint of the subkey. */ + QCString fingerprint() const; + + /** Returns the creation date of the subkey. */ + time_t creationDate() const; + + /** Returns the expiration date of the subkey. */ + time_t expirationDate() const; + + /** Sets the flag if the subkey is a secret subkey to <em>secret</em>. */ + void setSecret(const bool secret); + + /** Sets the flag if the subkey has been revoked to <em>revoked</em>. */ + void setRevoked(const bool revoked); + + /** Sets the flag if the subkey has expired to <em>expired</em>. */ + void setExpired(const bool expired); + + /** Sets the flag if the subkey has been disabled to <em>disabled</em>. */ + void setDisabled(const bool disabled); + + /** Sets the flag if the subkey is invalid to <em>invalid</em>. */ + void setInvalid(const bool invalid); + + /** Sets the flag if the subkey can be used to encrypt data to + <em>canEncrypt</em>. */ + void setCanEncrypt(const bool canEncrypt); + + /** Sets the flag if the subkey can be used to sign data to + <em>canSign</em>. */ + void setCanSign(const bool canSign); + + /** Sets the flag if the subkey can be used to certify keys to + <em>canCertify</em>. */ + void setCanCertify(const bool canCertify); + + /** Sets the key algorithm of the subkey to <em>keyAlgo</em>. */ + void setKeyAlgorithm(const unsigned int keyAlgo); + + /** Sets the key length of the subkey to <em>keyLen</em> bits. */ + void setKeyLength(const unsigned int keyLen); + + /** Sets the key ID of the subkey to <em>keyID</em>. */ + void setKeyID(const KeyID& keyID); + + /** Sets the fingerprint of the subkey to <em>fingerprint</em>. */ + void setFingerprint(const QCString& fingerprint); + + /** Sets the creation date of the subkey to <em>creationDate</em> seconds + since Epoch. */ + void setCreationDate(const time_t creationDate); + + /** Sets the expiration date of the subkey to <em>expirationDate</em> seconds + since Epoch. */ + void setExpirationDate(const time_t expirationDate); + + protected: + bool mSecret : 1; + /* various flags */ + bool mRevoked : 1; + bool mExpired : 1; + bool mDisabled : 1; + bool mInvalid : 1; + bool mCanEncrypt : 1; + bool mCanSign : 1; + bool mCanCertify : 1; + + unsigned int mKeyAlgo; + unsigned int mKeyLen; + KeyID mKeyID; + QCString mFingerprint; + time_t mTimestamp; /* -1 for invalid, 0 for not available */ + time_t mExpiration; /* -1 for never, 0 for not available */ +}; + +inline bool Subkey::secret() const +{ + return mSecret; +} + +inline bool Subkey::revoked() const +{ + return mRevoked; +} + +inline bool Subkey::expired() const +{ + return mExpired; +} + +inline bool Subkey::disabled() const +{ + return mDisabled; +} + +inline bool Subkey::invalid() const +{ + return mInvalid; +} + +inline bool Subkey::canEncrypt() const +{ + return mCanEncrypt; +} + +inline bool Subkey::canSign() const +{ + return mCanSign; +} + +inline bool Subkey::canCertify() const +{ + return mCanCertify; +} + +inline unsigned int Subkey::keyAlgorithm() const +{ + return mKeyAlgo; +} + +inline unsigned int Subkey::keyLength() const +{ + return mKeyLen; +} + +inline KeyID Subkey::longKeyID() const +{ + return mKeyID; +} + +inline KeyID Subkey::keyID() const +{ + return mKeyID.right(8); +} + +inline QCString Subkey::fingerprint() const +{ + return mFingerprint; +} + +inline time_t Subkey::creationDate() const +{ + return mTimestamp; +} + +inline time_t Subkey::expirationDate() const +{ + return mExpiration; +} + +inline void Subkey::setSecret(const bool secret) +{ + mSecret = secret; +} + +inline void Subkey::setRevoked(const bool revoked) +{ + mRevoked = revoked; +} + +inline void Subkey::setExpired(const bool expired) +{ + mExpired = expired; +} + +inline void Subkey::setDisabled(const bool disabled) +{ + mDisabled = disabled; +} + +inline void Subkey::setInvalid(const bool invalid) +{ + mInvalid = invalid; +} + +inline void Subkey::setCanEncrypt(const bool canEncrypt) +{ + mCanEncrypt = canEncrypt; +} + +inline void Subkey::setCanSign(const bool canSign) +{ + mCanSign = canSign; +} + +inline void Subkey::setCanCertify(const bool canCertify) +{ + mCanCertify = canCertify; +} + +inline void Subkey::setKeyAlgorithm(const unsigned int keyAlgo) +{ + mKeyAlgo = keyAlgo; +} + +inline void Subkey::setKeyLength(const unsigned int keyLen) +{ + mKeyLen = keyLen; +} + +inline void Subkey::setKeyID(const KeyID& keyID) +{ + mKeyID = keyID; +} + +inline void Subkey::setFingerprint(const QCString& fingerprint) +{ + mFingerprint = fingerprint; +} + +inline void Subkey::setCreationDate(const time_t creationDate) +{ + mTimestamp = creationDate; +} + +inline void Subkey::setExpirationDate(const time_t expirationDate) +{ + mExpiration = expirationDate; +} + +typedef QPtrList<Subkey> SubkeyList; +typedef QPtrListIterator<Subkey> SubkeyListIterator; + + +/** This class is used to store information about a PGP key. + */ +class Key +{ + public: + /** Constructs a new PGP key with <em>keyid</em> as key ID of the + primary key and <em>uid</em> as primary user ID. */ + Key( const KeyID& keyid = KeyID(), + const QString& uid = QString::null, + const bool secret = false); + ~Key(); + + /** Clears/resets all key data. */ + void clear(); + + /** Returns true if the key is a secret key. */ + bool secret() const; + + /** Returns true if the key has been revoked. */ + bool revoked() const; + + /** Returns true if the key has expired. */ + bool expired() const; + + /** Returns true if the key has been disabled. */ + bool disabled() const; + + /** Returns true if the key is invalid. */ + bool invalid() const; + + /** Returns true if the key can be used to encrypt data. */ + bool canEncrypt() const; + + /** Returns true if the key can be used to sign data. */ + bool canSign() const; + + /** Returns true if the key can be used to certify keys. */ + bool canCertify() const; + + /** Sets the flag if the key is a secret key to <em>secret</em>. */ + void setSecret(const bool secret); + + /** Sets the flag if the key has been revoked to <em>revoked</em>. */ + void setRevoked(const bool revoked); + + /** Sets the flag if the key has expired to <em>expired</em>. */ + void setExpired(const bool expired); + + /** Sets the flag if the key has been disabled to <em>disabled</em>. */ + void setDisabled(const bool disabled); + + /** Sets the flag if the key is invalid to <em>invalid</em>. */ + void setInvalid(const bool invalid); + + /** Sets the flag if the key can be used to encrypt data to + <em>canEncrypt</em>. */ + void setCanEncrypt(const bool canEncrypt); + + /** Sets the flag if the key can be used to sign data to + <em>canSign</em>. */ + void setCanSign(const bool canSign); + + /** Sets the flag if the key can be used to certify keys to + <em>canCertify</em>. */ + void setCanCertify(const bool canCertify); + + + /** Returns the encryption preference for this key. */ + EncryptPref encryptionPreference(); + + /** Sets the encryption preference for this key to <em>encrPref</em>. */ + void setEncryptionPreference( const EncryptPref encrPref ); + + + /** Returns the primary user ID or a null string if there are no + user IDs. */ + QString primaryUserID() const; + + /** Returns the key ID of the primary key or a null string if there + are no subkeys. */ + KeyID primaryKeyID() const; + + /** Returns the fingerprint of the primary key or a null string if there + are no subkeys. */ + QCString primaryFingerprint() const; + + /** Returns true if there are no user IDs or no subkeys.*/ + bool isNull() const; + + /** Returns the creation date of the primary subkey. + */ + time_t creationDate() const; + + /** Returns the trust value of this key. This is the maximal trust value + of any of the user ids of this key. + */ + Validity keyTrust() const; + + /** Returns the trust value for the given user id of this key. + */ + Validity keyTrust( const QString& uid ) const; + + /** Set the validity values for the user ids to the validity values of + the given key. This is useful after rereading a key without expensive + trust checking. + */ + void cloneKeyTrust( const Key* key ); + + /** Returns true if the key is valid, i.e. not revoked, expired, disabled + or invalid. + */ + bool isValid() const; + + /** Returns true if the key is a valid encryption key. The trust is not + checked. + */ + bool isValidEncryptionKey() const; + + /** Returns true if the key is a valid signing key. The trust is not checked. + */ + bool isValidSigningKey() const; + + /** Returns the list of userIDs. */ + const UserIDList userIDs() const; + + /** Returns the list of subkeys. */ + const SubkeyList subkeys() const; + + /** Adds a user ID with the given values to the key if <em>uid</em> isn't + an empty string. */ + void addUserID(const QString& uid, + const Validity validity = KPGP_VALIDITY_UNKNOWN, + const bool revoked = false, + const bool invalid = false); + + /** Adds the given user ID to the key. */ + void addUserID(const UserID *userID); + + /** Returns true if the given string matches one of the user IDs. + The match is case sensitive if <em>cs</em> is true or case insensitive + if <em>cs</em> is false. */ + bool matchesUserID(const QString& str, bool cs = true); + + /** Adds a subkey with the given values to the key if <em>keyID</em> isn't + an empty string. */ + void addSubkey(const KeyID& keyID, const bool secret = false); + + /** Adds the given subkey to the key. */ + void addSubkey(const Subkey *subkey); + + /** Returns a pointer to the subkey with the given key ID. */ + Subkey *getSubkey(const KeyID& keyID); + + /** Sets the fingerprint of the given subkey to <em>fpr</em>. */ + void setFingerprint(const KeyID& keyID, const QCString& fpr); + + protected: + bool mSecret : 1; + /* global flags */ + bool mRevoked : 1; + bool mExpired : 1; + bool mDisabled : 1; + bool mInvalid : 1; + bool mCanEncrypt : 1; + bool mCanSign : 1; + bool mCanCertify : 1; + + EncryptPref mEncryptPref; + + SubkeyList mSubkeys; + UserIDList mUserIDs; +}; + +inline bool Key::secret() const +{ + return mSecret; +} + +inline bool Key::revoked() const +{ + return mRevoked; +} + +inline bool Key::expired() const +{ + return mExpired; +} + +inline bool Key::disabled() const +{ + return mDisabled; +} + +inline bool Key::invalid() const +{ + return mInvalid; +} + +inline bool Key::canEncrypt() const +{ + return mCanEncrypt; +} + +inline bool Key::canSign() const +{ + return mCanSign; +} + +inline bool Key::canCertify() const +{ + return mCanCertify; +} + +inline void Key::setSecret(const bool secret) +{ + mSecret = secret; +} + +inline void Key::setRevoked(const bool revoked) +{ + mRevoked = revoked; +} + +inline void Key::setExpired(const bool expired) +{ + mExpired = expired; +} + +inline void Key::setDisabled(const bool disabled) +{ + mDisabled = disabled; +} + +inline void Key::setInvalid(const bool invalid) +{ + mInvalid = invalid; +} + +inline void Key::setCanEncrypt(const bool canEncrypt) +{ + mCanEncrypt = canEncrypt; +} + +inline void Key::setCanSign(const bool canSign) +{ + mCanSign = canSign; +} + +inline void Key::setCanCertify(const bool canCertify) +{ + mCanCertify = canCertify; +} + +inline EncryptPref Key::encryptionPreference() +{ + return mEncryptPref; +} + +inline void Key::setEncryptionPreference( const EncryptPref encrPref ) +{ + mEncryptPref = encrPref; +} + +inline QString Key::primaryUserID() const +{ + UserID *uid = mUserIDs.getFirst(); + + if (uid) + return uid->text(); + else + return QString::null; +} + +inline KeyID Key::primaryKeyID() const +{ + Subkey *key = mSubkeys.getFirst(); + + if (key) + return key->keyID(); + else + return KeyID(); +} + +inline QCString Key::primaryFingerprint() const +{ + Subkey *key = mSubkeys.getFirst(); + + if (key) + return key->fingerprint(); + else + return QCString(); +} + +inline const UserIDList Key::userIDs() const +{ + return mUserIDs; +} + +inline const SubkeyList Key::subkeys() const +{ + return mSubkeys; +} + +inline bool Key::isNull() const +{ + return (mUserIDs.isEmpty() || mSubkeys.isEmpty()); +} + +inline time_t Key::creationDate() const +{ + if( !mSubkeys.isEmpty() ) + return mSubkeys.getFirst()->creationDate(); + else + return -1; +} + +inline void Key::addUserID(const UserID *userID) +{ + if (userID) + mUserIDs.append(userID); +} + +inline void Key::addSubkey(const Subkey *subkey) +{ + if (subkey) + mSubkeys.append(subkey); +} + + + +typedef QPtrList<Key> KeyListBase; +typedef QPtrListIterator<Key> KeyListIterator; + +class KeyList : public KeyListBase +{ + public: + ~KeyList() + { clear(); } + + private: + int compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 ) + { + // sort case insensitively by the primary User IDs + return QString::compare((static_cast<Key*>(s1))->primaryUserID().lower(), + (static_cast<Key*>(s2))->primaryUserID().lower()); + } +}; + +} // namespace Kpgp + +#endif diff --git a/libkpgp/kpgpui.cpp b/libkpgp/kpgpui.cpp new file mode 100644 index 000000000..7483bb0c9 --- /dev/null +++ b/libkpgp/kpgpui.cpp @@ -0,0 +1,1694 @@ +/* + kpgpui.cpp + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +//#include <stdio.h> + +#include <qvgroupbox.h> +#include <qvbox.h> +#include <qlabel.h> +#include <qwhatsthis.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qtextcodec.h> +#include <qdatetime.h> +#include <qpixmap.h> +#include <qlayout.h> +#include <qtimer.h> +#include <qpopupmenu.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kpassdlg.h> +#include <kcharsets.h> +#include <kseparator.h> +#include <kiconloader.h> +#include <klistview.h> +#include <kconfigbase.h> +#include <kconfig.h> +#include <kprogress.h> +#include <kapplication.h> +#include <kwin.h> +#if KDE_IS_VERSION( 3, 1, 90 ) +#include <kglobalsettings.h> +#endif + +#include "kpgp.h" +#include "kpgpui.h" +#include "kpgpkey.h" + +#include <assert.h> +#include <string.h> // for memcpy(3) + +const int Kpgp::KeySelectionDialog::sCheckSelectionDelay = 250; + +namespace Kpgp { + +PassphraseDialog::PassphraseDialog( QWidget *parent, + const QString &caption, bool modal, + const QString &keyID ) + :KDialogBase( parent, 0, modal, caption, Ok|Cancel ) +{ + QHBox *hbox = makeHBoxMainWidget(); + hbox->setSpacing( spacingHint() ); + hbox->setMargin( marginHint() ); + + QLabel *label = new QLabel(hbox); + label->setPixmap( BarIcon("pgp-keys") ); + + QWidget *rightArea = new QWidget( hbox ); + QVBoxLayout *vlay = new QVBoxLayout( rightArea, 0, spacingHint() ); + + if (keyID.isNull()) + label = new QLabel(i18n("Please enter your OpenPGP passphrase:"),rightArea); + else + label = new QLabel(i18n("Please enter the OpenPGP passphrase for\n\"%1\":").arg(keyID), + rightArea); + lineedit = new KPasswordEdit( rightArea ); + lineedit->setEchoMode(QLineEdit::Password); + lineedit->setMinimumWidth( fontMetrics().maxWidth()*20 ); + lineedit->setFocus(); + connect( lineedit, SIGNAL(returnPressed()), this, SLOT(slotOk()) ); + + vlay->addWidget( label ); + vlay->addWidget( lineedit ); + + disableResize(); +} + + +PassphraseDialog::~PassphraseDialog() +{ +} + +const char * PassphraseDialog::passphrase() +{ + return lineedit->password(); +} + + +// ------------------------------------------------------------------------ +// Forbidden accels for KMail: AC GH OP +// for KNode: ACE H O +Config::Config( QWidget *parent, const char *name, bool encrypt ) + : QWidget( parent, name ), pgp( Module::getKpgp() ) +{ + QGroupBox * group; + QLabel * label; + QString msg; + + + QVBoxLayout *topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint() ); + + group = new QVGroupBox( i18n("Warning"), this ); + group->layout()->setSpacing( KDialog::spacingHint() ); + // (mmutz) work around Qt label bug in 3.0.0 (and possibly later): + // 1. Don't use rich text: No <qt><b>...</b></qt> + label = new QLabel( i18n("Please check if encryption really " + "works before you start using it seriously. Also note that attachments " + "are not encrypted by the PGP/GPG module."), group ); + // 2. instead, set the font to bold: + QFont labelFont = label->font(); + labelFont.setBold( true ); + label->setFont( labelFont ); + // 3. and activate wordwarp: + label->setAlignment( AlignLeft|WordBreak ); + // end; to remove the workaround, add <qt><b>..</b></qt> around the + // text and remove lines QFont... -> label->setAlignment(...). + topLayout->addWidget( group ); + + group = new QVGroupBox( i18n("Encryption Tool"), this ); + group->layout()->setSpacing( KDialog::spacingHint() ); + + QHBox * hbox = new QHBox( group ); + label = new QLabel( i18n("Select encryption tool to &use:"), hbox ); + toolCombo = new QComboBox( false, hbox ); + toolCombo->insertStringList( QStringList() + << i18n("Autodetect") + << i18n("GnuPG - Gnu Privacy Guard") + << i18n("PGP Version 2.x") + << i18n("PGP Version 5.x") + << i18n("PGP Version 6.x") + << i18n("Do not use any encryption tool") ); + label->setBuddy( toolCombo ); + hbox->setStretchFactor( toolCombo, 1 ); + connect( toolCombo, SIGNAL( activated( int ) ), + this, SIGNAL( changed( void ) ) ); + // This is the place to add a KURLRequester to be used for asking + // the user for the path to the executable... + topLayout->addWidget( group ); + + mpOptionsGroupBox = new QVGroupBox( i18n("Options"), this ); + mpOptionsGroupBox->layout()->setSpacing( KDialog::spacingHint() ); + storePass = new QCheckBox( i18n("&Keep passphrase in memory"), + mpOptionsGroupBox ); + connect( storePass, SIGNAL( toggled( bool ) ), + this, SIGNAL( changed( void ) ) ); + msg = i18n( "<qt><p>When this option is enabled, the passphrase of your " + "private key will be remembered by the application as long " + "as the application is running. Thus you will only have to " + "enter the passphrase once.</p><p>Be aware that this could be a " + "security risk. If you leave your computer, others " + "can use it to send signed messages and/or read your encrypted " + "messages. If a core dump occurs, the contents of your RAM will " + "be saved onto disk, including your passphrase.</p>" + "<p>Note that when using KMail, this setting only applies " + "if you are not using gpg-agent. It is also ignored " + "if you are using crypto plugins.</p></qt>" ); + QWhatsThis::add( storePass, msg ); + if( encrypt ) { + encToSelf = new QCheckBox( i18n("Always encr&ypt to self"), + mpOptionsGroupBox ); + connect( encToSelf, SIGNAL( toggled( bool ) ), + this, SIGNAL( changed( void ) ) ); + + msg = i18n( "<qt><p>When this option is enabled, the message/file " + "will not only be encrypted with the receiver's public key, " + "but also with your key. This will enable you to decrypt the " + "message/file at a later time. This is generally a good idea." + "</p></qt>" ); + QWhatsThis::add( encToSelf, msg ); + } + else + encToSelf = 0; + showCipherText = new QCheckBox( i18n("&Show signed/encrypted text after " + "composing"), + mpOptionsGroupBox ); + connect( showCipherText, SIGNAL( toggled( bool ) ), + this, SIGNAL( changed( void ) ) ); + + msg = i18n( "<qt><p>When this option is enabled, the signed/encrypted text " + "will be shown in a separate window, enabling you to know how " + "it will look before it is sent. This is a good idea when " + "you are verifying that your encryption system works.</p></qt>" ); + QWhatsThis::add( showCipherText, msg ); + if( encrypt ) { + showKeyApprovalDlg = new QCheckBox( i18n("Always show the encryption " + "keys &for approval"), + mpOptionsGroupBox ); + connect( showKeyApprovalDlg, SIGNAL( toggled( bool ) ), + this, SIGNAL( changed( void ) ) ); + msg = i18n( "<qt><p>When this option is enabled, the application will " + "always show you a list of public keys from which you can " + "choose the one it will use for encryption. If it is off, " + "the application will only show the dialog if it cannot find " + "the right key or if there are several which could be used. " + "</p></qt>" ); + QWhatsThis::add( showKeyApprovalDlg, msg ); +} + else + showKeyApprovalDlg = 0; + + topLayout->addWidget( mpOptionsGroupBox ); + + topLayout->addStretch(1); + + setValues(); // is this needed by KNode, b/c for KMail, it's not. +} + + +Config::~Config() +{ +} + +void +Config::setValues() +{ + // set default values + storePass->setChecked( pgp->storePassPhrase() ); + if( 0 != encToSelf ) + encToSelf->setChecked( pgp->encryptToSelf() ); + showCipherText->setChecked( pgp->showCipherText() ); + if( 0 != showKeyApprovalDlg ) + showKeyApprovalDlg->setChecked( pgp->showKeyApprovalDlg() ); + + int type = 0; + switch (pgp->pgpType) { + // translate Kpgp::Module enum to combobox' entries: + default: + case Module::tAuto: type = 0; break; + case Module::tGPG: type = 1; break; + case Module::tPGP2: type = 2; break; + case Module::tPGP5: type = 3; break; + case Module::tPGP6: type = 4; break; + case Module::tOff: type = 5; break; + } + toolCombo->setCurrentItem( type ); +} + +void +Config::applySettings() +{ + pgp->setStorePassPhrase(storePass->isChecked()); + if( 0 != encToSelf ) + pgp->setEncryptToSelf(encToSelf->isChecked()); + pgp->setShowCipherText(showCipherText->isChecked()); + if( 0 != showKeyApprovalDlg ) + pgp->setShowKeyApprovalDlg( showKeyApprovalDlg->isChecked() ); + + Module::PGPType type; + switch ( toolCombo->currentItem() ) { + // convert combobox entry indices to Kpgp::Module constants: + default: + case 0: type = Module::tAuto; break; + case 1: type = Module::tGPG; break; + case 2: type = Module::tPGP2; break; + case 3: type = Module::tPGP5; break; + case 4: type = Module::tPGP6; break; + case 5: type = Module::tOff; break; + } + pgp->pgpType = type; + + pgp->writeConfig(true); +} + + + +// ------------------------------------------------------------------------ +KeySelectionDialog::KeySelectionDialog( const KeyList& keyList, + const QString& title, + const QString& text, + const KeyIDList& keyIds, + const bool rememberChoice, + const unsigned int allowedKeys, + const bool extendedSelection, + QWidget *parent, const char *name, + bool modal ) + : KDialogBase( parent, name, modal, title, Default|Ok|Cancel, Ok ), + mRememberCB( 0 ), + mAllowedKeys( allowedKeys ), + mCurrentContextMenuItem( 0 ) +{ + if ( kapp ) + KWin::setIcons( winId(), kapp->icon(), kapp->miniIcon() ); + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + KConfig *config = pgp->getConfig(); + KConfigGroup dialogConfig( config, "Key Selection Dialog" ); + + QSize defaultSize( 580, 400 ); + QSize dialogSize = dialogConfig.readSizeEntry( "Dialog size", &defaultSize ); + + resize( dialogSize ); + + mCheckSelectionTimer = new QTimer( this, "mCheckSelectionTimer" ); + mStartSearchTimer = new QTimer( this, "mStartSearchTimer" ); + + // load the key status icons + mKeyGoodPix = new QPixmap( UserIcon("key_ok") ); + mKeyBadPix = new QPixmap( UserIcon("key_bad") ); + mKeyUnknownPix = new QPixmap( UserIcon("key_unknown") ); + mKeyValidPix = new QPixmap( UserIcon("key") ); + + QFrame *page = makeMainWidget(); + QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); + + if( !text.isEmpty() ) { + QLabel *label = new QLabel( page ); + label->setText( text ); + topLayout->addWidget( label ); + } + + QHBoxLayout * hlay = new QHBoxLayout( topLayout ); // inherits spacing + QLineEdit * le = new QLineEdit( page ); + hlay->addWidget( new QLabel( le, i18n("&Search for:"), page ) ); + hlay->addWidget( le, 1 ); + le->setFocus(); + + connect( le, SIGNAL(textChanged(const QString&)), + this, SLOT(slotSearch(const QString&)) ); + connect( mStartSearchTimer, SIGNAL(timeout()), SLOT(slotFilter()) ); + + mListView = new KListView( page ); + mListView->addColumn( i18n("Key ID") ); + mListView->addColumn( i18n("User ID") ); + mListView->setAllColumnsShowFocus( true ); + mListView->setResizeMode( QListView::LastColumn ); + mListView->setRootIsDecorated( true ); + mListView->setShowSortIndicator( true ); + mListView->setSorting( 1, true ); // sort by User ID + mListView->setShowToolTips( true ); + if( extendedSelection ) { + mListView->setSelectionMode( QListView::Extended ); + //mListView->setSelectionMode( QListView::Multi ); + } + topLayout->addWidget( mListView, 10 ); + + if (rememberChoice) { + mRememberCB = new QCheckBox( i18n("Remember choice"), page ); + topLayout->addWidget( mRememberCB ); + QWhatsThis::add(mRememberCB, + i18n("<qt><p>If you check this box your choice will " + "be stored and you will not be asked again." + "</p></qt>")); + } + + initKeylist( keyList, keyIds ); + + QListViewItem *lvi; + if( extendedSelection ) { + lvi = mListView->currentItem(); + slotCheckSelection(); + } + else { + lvi = mListView->selectedItem(); + slotCheckSelection( lvi ); + } + // make sure that the selected item is visible + // (ensureItemVisible(...) doesn't work correctly in Qt 3.0.0) + if( lvi != 0 ) + mListView->center( mListView->contentsX(), mListView->itemPos( lvi ) ); + + if( extendedSelection ) { + connect( mCheckSelectionTimer, SIGNAL( timeout() ), + this, SLOT( slotCheckSelection() ) ); + connect( mListView, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + } + else { + connect( mListView, SIGNAL( selectionChanged( QListViewItem* ) ), + this, SLOT( slotSelectionChanged( QListViewItem* ) ) ); + } + connect( mListView, SIGNAL( doubleClicked ( QListViewItem *, const QPoint &, int ) ), this, SLOT( accept() ) ); + + connect( mListView, SIGNAL( contextMenuRequested( QListViewItem*, + const QPoint&, int ) ), + this, SLOT( slotRMB( QListViewItem*, const QPoint&, int ) ) ); + + setButtonText( KDialogBase::Default, i18n("&Reread Keys") ); + connect( this, SIGNAL( defaultClicked() ), + this, SLOT( slotRereadKeys() ) ); +} + + +KeySelectionDialog::~KeySelectionDialog() +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + KConfig *config = pgp->getConfig(); + KConfigGroup dialogConfig( config, "Key Selection Dialog" ); + dialogConfig.writeEntry( "Dialog size", size() ); + config->sync(); + delete mKeyGoodPix; + delete mKeyBadPix; + delete mKeyUnknownPix; + delete mKeyValidPix; +} + + +KeyID KeySelectionDialog::key() const +{ + if( mListView->isMultiSelection() || mKeyIds.isEmpty() ) + return KeyID(); + else + return mKeyIds.first(); +} + + +void KeySelectionDialog::initKeylist( const KeyList& keyList, + const KeyIDList& keyIds ) +{ + QListViewItem* firstSelectedItem = 0; + mKeyIds.clear(); + mListView->clear(); + + // build a list of all public keys + for( KeyListIterator it( keyList ); it.current(); ++it ) { + KeyID curKeyId = (*it)->primaryKeyID(); + + QListViewItem* primaryUserID = new QListViewItem( mListView, curKeyId, + (*it)->primaryUserID() ); + + // select and open the given key + if( keyIds.findIndex( curKeyId ) != -1 ) { + if( 0 == firstSelectedItem ) { + firstSelectedItem = primaryUserID; + } + mListView->setSelected( primaryUserID, true ); + mKeyIds.append( curKeyId ); + } + primaryUserID->setOpen( false ); + + // set icon for this key + switch( keyValidity( *it ) ) { + case 0: // the key's validity can't be determined + primaryUserID->setPixmap( 0, *mKeyUnknownPix ); + break; + case 1: // key is valid but not trusted + primaryUserID->setPixmap( 0, *mKeyValidPix ); + break; + case 2: // key is valid and trusted + primaryUserID->setPixmap( 0, *mKeyGoodPix ); + break; + case -1: // key is invalid + primaryUserID->setPixmap( 0, *mKeyBadPix ); + break; + } + + QListViewItem* childItem; + + childItem = new QListViewItem( primaryUserID, "", + i18n( "Fingerprint: %1" ) + .arg( beautifyFingerprint( (*it)->primaryFingerprint() ) ) ); + if( primaryUserID->isSelected() && mListView->isMultiSelection() ) { + mListView->setSelected( childItem, true ); + } + + childItem = new QListViewItem( primaryUserID, "", keyInfo( *it ) ); + if( primaryUserID->isSelected() && mListView->isMultiSelection() ) { + mListView->setSelected( childItem, true ); + } + + UserIDList userIDs = (*it)->userIDs(); + UserIDListIterator uidit( userIDs ); + if( *uidit ) { + ++uidit; // skip the primary user ID + for( ; *uidit; ++uidit ) { + childItem = new QListViewItem( primaryUserID, "", (*uidit)->text() ); + if( primaryUserID->isSelected() && mListView->isMultiSelection() ) { + mListView->setSelected( childItem, true ); + } + } + } + } + + if( 0 != firstSelectedItem ) { + mListView->setCurrentItem( firstSelectedItem ); + } +} + + +QString KeySelectionDialog::keyInfo( const Kpgp::Key *key ) const +{ + QString status, remark; + if( key->revoked() ) { + status = i18n("Revoked"); + } + else if( key->expired() ) { + status = i18n("Expired"); + } + else if( key->disabled() ) { + status = i18n("Disabled"); + } + else if( key->invalid() ) { + status = i18n("Invalid"); + } + else { + Validity keyTrust = key->keyTrust(); + switch( keyTrust ) { + case KPGP_VALIDITY_UNDEFINED: + status = i18n("Undefined trust"); + break; + case KPGP_VALIDITY_NEVER: + status = i18n("Untrusted"); + break; + case KPGP_VALIDITY_MARGINAL: + status = i18n("Marginally trusted"); + break; + case KPGP_VALIDITY_FULL: + status = i18n("Fully trusted"); + break; + case KPGP_VALIDITY_ULTIMATE: + status = i18n("Ultimately trusted"); + break; + case KPGP_VALIDITY_UNKNOWN: + default: + status = i18n("Unknown"); + } + if( key->secret() ) { + remark = i18n("Secret key available"); + } + else if( !key->canEncrypt() ) { + remark = i18n("Sign only key"); + } + else if( !key->canSign() ) { + remark = i18n("Encryption only key"); + } + } + + QDateTime dt; + dt.setTime_t( key->creationDate() ); + if( remark.isEmpty() ) { + return " " + i18n("creation date and status of an OpenPGP key", + "Creation date: %1, Status: %2") + .arg( KGlobal::locale()->formatDate( dt.date(), true ) ) + .arg( status ); + } + else { + return " " + i18n("creation date, status and remark of an OpenPGP key", + "Creation date: %1, Status: %2 (%3)") + .arg( KGlobal::locale()->formatDate( dt.date(), true ) ) + .arg( status ) + .arg( remark ); + } +} + +QString KeySelectionDialog::beautifyFingerprint( const QCString& fpr ) const +{ + QCString result; + + if( 40 == fpr.length() ) { + // convert to this format: + // 0000 1111 2222 3333 4444 5555 6666 7777 8888 9999 + result.fill( ' ', 50 ); + memcpy( result.data() , fpr.data() , 4 ); + memcpy( result.data() + 5, fpr.data() + 4, 4 ); + memcpy( result.data() + 10, fpr.data() + 8, 4 ); + memcpy( result.data() + 15, fpr.data() + 12, 4 ); + memcpy( result.data() + 20, fpr.data() + 16, 4 ); + memcpy( result.data() + 26, fpr.data() + 20, 4 ); + memcpy( result.data() + 31, fpr.data() + 24, 4 ); + memcpy( result.data() + 36, fpr.data() + 28, 4 ); + memcpy( result.data() + 41, fpr.data() + 32, 4 ); + memcpy( result.data() + 46, fpr.data() + 36, 4 ); + } + else if( 32 == fpr.length() ) { + // convert to this format: + // 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF + result.fill( ' ', 48 ); + memcpy( result.data() , fpr.data() , 2 ); + memcpy( result.data() + 3, fpr.data() + 2, 2 ); + memcpy( result.data() + 6, fpr.data() + 4, 2 ); + memcpy( result.data() + 9, fpr.data() + 6, 2 ); + memcpy( result.data() + 12, fpr.data() + 8, 2 ); + memcpy( result.data() + 15, fpr.data() + 10, 2 ); + memcpy( result.data() + 18, fpr.data() + 12, 2 ); + memcpy( result.data() + 21, fpr.data() + 14, 2 ); + memcpy( result.data() + 25, fpr.data() + 16, 2 ); + memcpy( result.data() + 28, fpr.data() + 18, 2 ); + memcpy( result.data() + 31, fpr.data() + 20, 2 ); + memcpy( result.data() + 34, fpr.data() + 22, 2 ); + memcpy( result.data() + 37, fpr.data() + 24, 2 ); + memcpy( result.data() + 40, fpr.data() + 26, 2 ); + memcpy( result.data() + 43, fpr.data() + 28, 2 ); + memcpy( result.data() + 46, fpr.data() + 30, 2 ); + } + else { // unknown length of fingerprint + result = fpr; + } + + return result; +} + +int KeySelectionDialog::keyValidity( const Kpgp::Key *key ) const +{ + if( 0 == key ) { + return -1; + } + + if( ( mAllowedKeys & EncrSignKeys ) == EncryptionKeys ) { + // only encryption keys are allowed + if( ( mAllowedKeys & ValidKeys ) && !key->isValidEncryptionKey() ) { + // only valid encryption keys are allowed + return -1; + } + else if( !key->canEncrypt() ) { + return -1; + } + } + else if( ( mAllowedKeys & EncrSignKeys ) == SigningKeys ) { + // only signing keys are allowed + if( ( mAllowedKeys & ValidKeys ) && !key->isValidSigningKey() ) { + // only valid signing keys are allowed + return -1; + } + else if( !key->canSign() ) { + return -1; + } + } + else if( ( mAllowedKeys & ValidKeys ) && !key->isValid() ) { + // only valid keys are allowed + return -1; + } + + // check the key's trust + int val = 0; + Validity keyTrust = key->keyTrust(); + switch( keyTrust ) { + case KPGP_VALIDITY_NEVER: + val = -1; + break; + case KPGP_VALIDITY_MARGINAL: + case KPGP_VALIDITY_FULL: + case KPGP_VALIDITY_ULTIMATE: + val = 2; + break; + case KPGP_VALIDITY_UNDEFINED: + if( mAllowedKeys & TrustedKeys ) { + // only trusted keys are allowed + val = -1; + } + else { + val = 1; + } + break; + case KPGP_VALIDITY_UNKNOWN: + default: + val = 0; + } + + return val; +} + + +void KeySelectionDialog::updateKeyInfo( const Kpgp::Key* key, + QListViewItem* lvi ) const +{ + if( 0 == lvi ) { + return; + } + + if( lvi->parent() != 0 ) { + lvi = lvi->parent(); + } + + if( 0 == key ) { + // the key doesn't exist anymore -> delete it from the list view + while( lvi->firstChild() ) { + kdDebug(5100) << "Deleting '" << lvi->firstChild()->text( 1 ) << "'\n"; + delete lvi->firstChild(); + } + kdDebug(5100) << "Deleting key 0x" << lvi->text( 0 ) << " (" + << lvi->text( 1 ) << ")\n"; + delete lvi; + lvi = 0; + return; + } + + // update the icon for this key + switch( keyValidity( key ) ) { + case 0: // the key's validity can't be determined + lvi->setPixmap( 0, *mKeyUnknownPix ); + break; + case 1: // key is valid but not trusted + lvi->setPixmap( 0, *mKeyValidPix ); + break; + case 2: // key is valid and trusted + lvi->setPixmap( 0, *mKeyGoodPix ); + break; + case -1: // key is invalid + lvi->setPixmap( 0, *mKeyBadPix ); + break; + } + + // update the key info for this key + // the key info is identified by a leading space; this shouldn't be + // a problem because User Ids shouldn't start with a space + for( lvi = lvi->firstChild(); lvi; lvi = lvi->nextSibling() ) { + if( lvi->text( 1 ).at(0) == ' ' ) { + lvi->setText( 1, keyInfo( key ) ); + break; + } + } +} + + +int +KeySelectionDialog::keyAdmissibility( QListViewItem* lvi, + TrustCheckMode trustCheckMode ) const +{ + // Return: + // -1 = key must not be chosen, + // 0 = not enough information to decide whether the give key is allowed + // or not, + // 1 = key can be chosen + + if( mAllowedKeys == AllKeys ) { + return 1; + } + + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( 0 == pgp ) { + return 0; + } + + KeyID keyId = getKeyId( lvi ); + Kpgp::Key* key = pgp->publicKey( keyId ); + + if( 0 == key ) { + return 0; + } + + int val = 0; + if( trustCheckMode == ForceTrustCheck ) { + key = pgp->rereadKey( keyId, true ); + updateKeyInfo( key, lvi ); + val = keyValidity( key ); + } + else { + val = keyValidity( key ); + if( ( trustCheckMode == AllowExpensiveTrustCheck ) && ( 0 == val ) ) { + key = pgp->rereadKey( keyId, true ); + updateKeyInfo( key, lvi ); + val = keyValidity( key ); + } + } + + switch( val ) { + case -1: // key is not usable + return -1; + break; + case 0: // key status unknown + return 0; + break; + case 1: // key is valid, but untrusted + if( mAllowedKeys & TrustedKeys ) { + // only trusted keys are allowed + return -1; + } + return 1; + break; + case 2: // key is trusted + return 1; + break; + default: + kdDebug( 5100 ) << "Error: Invalid key status value.\n"; + } + + return 0; +} + + +KeyID +KeySelectionDialog::getKeyId( const QListViewItem* lvi ) const +{ + KeyID keyId; + + if( 0 != lvi ) { + if( 0 != lvi->parent() ) { + keyId = lvi->parent()->text(0).local8Bit(); + } + else { + keyId = lvi->text(0).local8Bit(); + } + } + + return keyId; +} + + +void KeySelectionDialog::slotRereadKeys() +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( 0 == pgp ) { + return; + } + + KeyList keys; + + if( PublicKeys & mAllowedKeys ) { + pgp->readPublicKeys( true ); + keys = pgp->publicKeys(); + } + else { + pgp->readSecretKeys( true ); + keys = pgp->secretKeys(); + } + + // save the current position of the contents + int offsetY = mListView->contentsY(); + + if( mListView->isMultiSelection() ) { + disconnect( mListView, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + } + else { + disconnect( mListView, SIGNAL( selectionChanged( QListViewItem * ) ), + this, SLOT( slotSelectionChanged( QListViewItem * ) ) ); + } + + initKeylist( keys, KeyIDList( mKeyIds ) ); + slotFilter(); + + if( mListView->isMultiSelection() ) { + connect( mListView, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + slotSelectionChanged(); + } + else { + connect( mListView, SIGNAL( selectionChanged( QListViewItem * ) ), + this, SLOT( slotSelectionChanged( QListViewItem * ) ) ); + } + + // restore the saved position of the contents + mListView->setContentsPos( 0, offsetY ); +} + + +void KeySelectionDialog::slotSelectionChanged( QListViewItem * lvi ) +{ + slotCheckSelection( lvi ); +} + + +void KeySelectionDialog::slotSelectionChanged() +{ + kdDebug(5100) << "KeySelectionDialog::slotSelectionChanged()\n"; + + // (re)start the check selection timer. Checking the selection is delayed + // because else drag-selection doesn't work very good (checking key trust + // is slow). + mCheckSelectionTimer->start( sCheckSelectionDelay ); +} + + +void KeySelectionDialog::slotCheckSelection( QListViewItem* plvi /* = 0 */ ) +{ + kdDebug(5100) << "KeySelectionDialog::slotCheckSelection()\n"; + + if( !mListView->isMultiSelection() ) { + mKeyIds.clear(); + KeyID keyId = getKeyId( plvi ); + if( !keyId.isEmpty() ) { + mKeyIds.append( keyId ); + enableButtonOK( 1 == keyAdmissibility( plvi, AllowExpensiveTrustCheck ) ); + } + else { + enableButtonOK( false ); + } + } + else { + mCheckSelectionTimer->stop(); + + // As we might change the selection, we have to disconnect the slot + // to prevent recursion + disconnect( mListView, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + + KeyIDList newKeyIdList; + QValueList<QListViewItem*> keysToBeChecked; + + bool keysAllowed = true; + enum { UNKNOWN, SELECTED, DESELECTED } userAction = UNKNOWN; + // Iterate over the tree to find selected keys. + for( QListViewItem *lvi = mListView->firstChild(); + 0 != lvi; + lvi = lvi->nextSibling() ) { + // We make sure that either all items belonging to a key are selected + // or unselected. As it's possible to select/deselect multiple keys at + // once in extended selection mode we have to figure out whether the user + // selected or deselected keys. + + // First count the selected items of this key + int itemCount = 1 + lvi->childCount(); + int selectedCount = lvi->isSelected() ? 1 : 0; + for( QListViewItem *clvi = lvi->firstChild(); + 0 != clvi; + clvi = clvi->nextSibling() ) { + if( clvi->isSelected() ) { + ++selectedCount; + } + } + + if( userAction == UNKNOWN ) { + // Figure out whether the user selected or deselected this key + // Remark: A selected count of 0 doesn't mean anything since in + // extended selection mode a normal left click deselects + // the not clicked items. + if( 0 < selectedCount ) { + if( -1 == mKeyIds.findIndex( lvi->text(0).local8Bit() ) ) { + // some items of this key are selected and the key wasn't selected + // before => the user selected something + kdDebug(5100) << "selectedCount: "<<selectedCount<<"/"<<itemCount + <<" --- User selected key "<<lvi->text(0)<<endl; + userAction = SELECTED; + } + else if( ( itemCount > selectedCount ) && + ( -1 != mKeyIds.findIndex( lvi->text(0).local8Bit() ) ) ) { + // some items of this key are unselected and the key was selected + // before => the user deselected something + kdDebug(5100) << "selectedCount: "<<selectedCount<<"/"<<itemCount + <<" --- User deselected key "<<lvi->text(0)<<endl; + userAction = DESELECTED; + } + } + } + if( itemCount == selectedCount ) { + // add key to the list of selected keys + KeyID keyId = lvi->text(0).local8Bit(); + newKeyIdList.append( keyId ); + int admissibility = keyAdmissibility( lvi, NoExpensiveTrustCheck ); + if( -1 == admissibility ) { + keysAllowed = false; + } + else if ( 0 == admissibility ) { + keysToBeChecked.append( lvi ); + } + } + else if( 0 < selectedCount ) { + // not all items of this key are selected or unselected. change this + // according to the user's action + if( userAction == SELECTED ) { + // select all items of this key + mListView->setSelected( lvi, true ); + for( QListViewItem *clvi = lvi->firstChild(); + 0 != clvi; + clvi = clvi->nextSibling() ) { + mListView->setSelected( clvi, true ); + } + // add key to the list of selected keys + KeyID keyId = lvi->text(0).local8Bit(); + newKeyIdList.append( keyId ); + int admissibility = keyAdmissibility( lvi, NoExpensiveTrustCheck ); + if( -1 == admissibility ) { + keysAllowed = false; + } + else if ( 0 == admissibility ) { + keysToBeChecked.append( lvi ); + } + } + else { // userAction == DESELECTED + // deselect all items of this key + mListView->setSelected( lvi, false ); + for( QListViewItem *clvi = lvi->firstChild(); + 0 != clvi; + clvi = clvi->nextSibling() ) { + mListView->setSelected( clvi, false ); + } + } + } + } + kdDebug(5100) << "Selected keys: " << newKeyIdList.toStringList().join(", ") << endl; + mKeyIds = newKeyIdList; + if( !keysToBeChecked.isEmpty() ) { + keysAllowed = keysAllowed && checkKeys( keysToBeChecked ); + } + enableButtonOK( keysAllowed ); + + connect( mListView, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + } +} + + +bool KeySelectionDialog::checkKeys( const QValueList<QListViewItem*>& keys ) const +{ + KProgressDialog* pProgressDlg = 0; + bool keysAllowed = true; + kdDebug(5100) << "Checking keys...\n"; + + pProgressDlg = new KProgressDialog( 0, 0, i18n("Checking Keys"), + i18n("Checking key 0xMMMMMMMM..."), + true ); + pProgressDlg->setAllowCancel( false ); + pProgressDlg->progressBar()->setTotalSteps( keys.count() ); + pProgressDlg->setMinimumDuration( 1000 ); + pProgressDlg->show(); + + for( QValueList<QListViewItem*>::ConstIterator it = keys.begin(); + it != keys.end(); + ++it ) { + kdDebug(5100) << "Checking key 0x" << getKeyId( *it ) << "...\n"; + pProgressDlg->setLabel( i18n("Checking key 0x%1...") + .arg( getKeyId( *it ) ) ); + kapp->processEvents(); + keysAllowed = keysAllowed && ( -1 != keyAdmissibility( *it, AllowExpensiveTrustCheck ) ); + pProgressDlg->progressBar()->advance( 1 ); + kapp->processEvents(); + } + + delete pProgressDlg; + pProgressDlg = 0; + + return keysAllowed; +} + + +void KeySelectionDialog::slotRMB( QListViewItem* lvi, const QPoint& pos, int ) +{ + if( !lvi ) { + return; + } + + mCurrentContextMenuItem = lvi; + + QPopupMenu menu(this); + menu.insertItem( i18n( "Recheck Key" ), this, SLOT( slotRecheckKey() ) ); + menu.exec( pos ); +} + + +void KeySelectionDialog::slotRecheckKey() +{ + if( 0 != mCurrentContextMenuItem ) { + // force rereading the key + keyAdmissibility( mCurrentContextMenuItem, ForceTrustCheck ); + // recheck the selection + slotCheckSelection( mCurrentContextMenuItem ); + } +} + +void KeySelectionDialog::slotOk() +{ + if( mCheckSelectionTimer->isActive() ) { + slotCheckSelection(); + } + mStartSearchTimer->stop(); + accept(); +} + + +void KeySelectionDialog::slotCancel() +{ + mCheckSelectionTimer->stop(); + mStartSearchTimer->stop(); + mKeyIds.clear(); + reject(); +} + +void KeySelectionDialog::slotSearch( const QString & text ) +{ + mSearchText = text.stripWhiteSpace().upper(); + mStartSearchTimer->start( sCheckSelectionDelay, true /*single-shot*/ ); +} + +void KeySelectionDialog::slotFilter() +{ + if ( mSearchText.isEmpty() ) { + showAllItems(); + return; + } + + // OK, so we need to filter: + QRegExp keyIdRegExp( "(?:0x)?[A-F0-9]{1,8}", false /*case-insens.*/ ); + if ( keyIdRegExp.exactMatch( mSearchText ) ) { + if ( mSearchText.startsWith( "0X" ) ) + // search for keyID only: + filterByKeyID( mSearchText.mid( 2 ) ); + else + // search for UID and keyID: + filterByKeyIDOrUID( mSearchText ); + } else { + // search in UID: + filterByUID( mSearchText ); + } +} + +void KeySelectionDialog::filterByKeyID( const QString & keyID ) +{ + assert( keyID.length() <= 8 ); + assert( !keyID.isEmpty() ); // regexp in slotFilter should prevent these + if ( keyID.isEmpty() ) + showAllItems(); + else + for ( QListViewItem * item = mListView->firstChild() ; item ; item = item->nextSibling() ) + item->setVisible( item->text( 0 ).upper().startsWith( keyID ) ); +} + +void KeySelectionDialog::filterByKeyIDOrUID( const QString & str ) +{ + assert( !str.isEmpty() ); + + // match beginnings of words: + QRegExp rx( "\\b" + QRegExp::escape( str ), false ); + + for ( QListViewItem * item = mListView->firstChild() ; item ; item = item->nextSibling() ) + item->setVisible( item->text( 0 ).upper().startsWith( str ) + || rx.search( item->text( 1 ) ) >= 0 + || anyChildMatches( item, rx ) ); + +} + +void KeySelectionDialog::filterByUID( const QString & str ) +{ + assert( !str.isEmpty() ); + + // match beginnings of words: + QRegExp rx( "\\b" + QRegExp::escape( str ), false ); + + for ( QListViewItem * item = mListView->firstChild() ; item ; item = item->nextSibling() ) + item->setVisible( rx.search( item->text( 1 ) ) >= 0 + || anyChildMatches( item, rx ) ); +} + + +bool KeySelectionDialog::anyChildMatches( const QListViewItem * item, QRegExp & rx ) const +{ + if ( !item ) + return false; + + QListViewItem * stop = item->nextSibling(); // It's OK if stop is NULL... + + for ( QListViewItemIterator it( item->firstChild() ) ; it.current() && it.current() != stop ; ++it ) + if ( rx.search( it.current()->text( 1 ) ) >= 0 ) { + //item->setOpen( true ); // do we want that? + return true; + } + return false; +} + +void KeySelectionDialog::showAllItems() +{ + for ( QListViewItem * item = mListView->firstChild() ; item ; item = item->nextSibling() ) + item->setVisible( true ); +} + +// ------------------------------------------------------------------------ +KeyRequester::KeyRequester( QWidget * parent, bool multipleKeys, + unsigned int allowedKeys, const char * name ) + : QWidget( parent, name ), + mDialogCaption( i18n("OpenPGP Key Selection") ), + mDialogMessage( i18n("Please select an OpenPGP key to use.") ), + mMulti( multipleKeys ), + mAllowedKeys( allowedKeys ), + d( 0 ) +{ + QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() ); + + // the label where the key id is to be displayed: + mLabel = new QLabel( this ); + mLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + // the button to unset any key: + mEraseButton = new QPushButton( this ); + mEraseButton->setAutoDefault( false ); + mEraseButton->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, + QSizePolicy::Minimum ) ); + mEraseButton->setPixmap( SmallIcon( "clear_left" ) ); + QToolTip::add( mEraseButton, i18n("Clear") ); + + // the button to call the KeySelectionDialog: + mDialogButton = new QPushButton( i18n("Change..."), this ); + mDialogButton->setAutoDefault( false ); + + hlay->addWidget( mLabel, 1 ); + hlay->addWidget( mEraseButton ); + hlay->addWidget( mDialogButton ); + + connect( mEraseButton, SIGNAL(clicked()), SLOT(slotEraseButtonClicked()) ); + connect( mDialogButton, SIGNAL(clicked()), SLOT(slotDialogButtonClicked()) ); + + setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::Fixed ) ); +} + +KeyRequester::~KeyRequester() { + +} + +KeyIDList KeyRequester::keyIDs() const { + return mKeys; +} + +void KeyRequester::setKeyIDs( const KeyIDList & keyIDs ) { + mKeys = keyIDs; + if ( mKeys.empty() ) { + mLabel->clear(); + return; + } + if ( mKeys.size() > 1 ) + setMultipleKeysEnabled( true ); + + QString s = mKeys.toStringList().join(", "); + + mLabel->setText( s ); + QToolTip::remove( mLabel ); + QToolTip::add( mLabel, s ); +} + +void KeyRequester::slotDialogButtonClicked() { + Module * pgp = Module::getKpgp(); + + if ( !pgp ) { + kdWarning() << "Kpgp::KeyRequester::slotDialogButtonClicked(): No pgp module found!" << endl; + return; + } + + setKeyIDs( keyRequestHook( pgp ) ); + emit changed(); +} + +void KeyRequester::slotEraseButtonClicked() { + mKeys.clear(); + mLabel->clear(); + emit changed(); +} + +void KeyRequester::setDialogCaption( const QString & caption ) { + mDialogCaption = caption; +} + +void KeyRequester::setDialogMessage( const QString & msg ) { + mDialogMessage = msg; +} + +bool KeyRequester::isMultipleKeysEnabled() const { + return mMulti; +} + +void KeyRequester::setMultipleKeysEnabled( bool multi ) { + if ( multi == mMulti ) return; + + if ( !multi && mKeys.size() > 1 ) + mKeys.erase( ++mKeys.begin(), mKeys.end() ); + + mMulti = multi; +} + +int KeyRequester::allowedKeys() const { + return mAllowedKeys; +} + +void KeyRequester::setAllowedKeys( int allowedKeys ) { + mAllowedKeys = allowedKeys; +} + + +PublicKeyRequester::PublicKeyRequester( QWidget * parent, bool multi, + unsigned int allowed, const char * name ) + : KeyRequester( parent, multi, allowed & ~SecretKeys, name ) +{ + +} + +PublicKeyRequester::~PublicKeyRequester() { + +} + +KeyIDList PublicKeyRequester::keyRequestHook( Module * pgp ) const { + assert( pgp ); + return pgp->selectPublicKeys( mDialogCaption, mDialogMessage, mKeys, QString::null, mAllowedKeys ); +} + +SecretKeyRequester::SecretKeyRequester( QWidget * parent, bool multi, + unsigned int allowed, const char * name ) + : KeyRequester( parent, multi, allowed & ~PublicKeys, name ) +{ + +} + +SecretKeyRequester::~SecretKeyRequester() { + +} + +KeyIDList SecretKeyRequester::keyRequestHook( Module * pgp ) const { + assert( pgp ); + + KeyID keyID = mKeys.first(); + keyID = pgp->selectSecretKey( mDialogCaption, mDialogMessage, keyID ); + + return KeyIDList() << keyID; +} + + + +// ------------------------------------------------------------------------ +KeyApprovalDialog::KeyApprovalDialog( const QStringList& addresses, + const QValueVector<KeyIDList>& keyIDs, + const int allowedKeys, + QWidget *parent, const char *name, + bool modal ) + : KDialogBase( parent, name, modal, i18n("Encryption Key Approval"), + Ok|Cancel, Ok ), + mKeys( keyIDs ), + mAllowedKeys( allowedKeys ), + mPrefsChanged( false ) +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) + return; + + // ##### error handling + // if( addresses.isEmpty() || keyList.isEmpty() || + // addresses.count()+1 != keyList.count() ) + // do something; + + QFrame *page = makeMainWidget(); + QVBoxLayout *topLayout = new QVBoxLayout( page, 0, KDialog::spacingHint() ); + + QLabel *label = new QLabel( i18n("The following keys will be used for " + "encryption:"), + page ); + topLayout->addWidget( label ); + + QScrollView* sv = new QScrollView( page ); + sv->setResizePolicy( QScrollView::AutoOneFit ); + topLayout->addWidget( sv ); + QVBox* bigvbox = new QVBox( sv->viewport() ); + bigvbox->setMargin( KDialog::marginHint() ); + bigvbox->setSpacing( KDialog::spacingHint() ); + sv->addChild( bigvbox ); + + QButtonGroup *mChangeButtonGroup = new QButtonGroup( bigvbox ); + mChangeButtonGroup->hide(); + mAddressLabels.resize( addresses.count() ); + mKeyIdsLabels.resize( keyIDs.size() ); + //mKeyIdListBoxes.resize( keyIDs.size() ); + mEncrPrefCombos.resize( addresses.count() ); + + // the sender's key + if( pgp->encryptToSelf() ) { + mEncryptToSelf = 1; + QHBox* hbox = new QHBox( bigvbox ); + new QLabel( i18n("Your keys:"), hbox ); + QLabel* keyidsL = new QLabel( hbox ); + if( keyIDs[0].isEmpty() ) { + keyidsL->setText( i18n("<none> means 'no key'", "<none>") ); + } + else { + keyidsL->setText( "0x" + keyIDs[0].toStringList().join( "\n0x" ) ); + } + keyidsL->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + /* + QListBox* keyidLB = new QListBox( hbox ); + if( keyIDs[0].isEmpty() ) { + keyidLB->insertItem( i18n("<none>") ); + } + else { + keyidLB->insertStringList( keyIDs[0].toStringList() ); + } + keyidLB->setSelectionMode( QListBox::NoSelection ); + keyidLB->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + */ + QPushButton *button = new QPushButton( i18n("Change..."), hbox ); + mChangeButtonGroup->insert( button ); + button->setAutoDefault( false ); + hbox->setStretchFactor( keyidsL, 10 ); + mKeyIdsLabels.insert( 0, keyidsL ); + //hbox->setStretchFactor( keyidLB, 10 ); + //mKeyIdListBoxes.insert( 0, keyidLB ); + + new KSeparator( Horizontal, bigvbox ); + } + else { + mEncryptToSelf = 0; + // insert dummy KeyIdListBox + mKeyIdsLabels.insert( 0, 0 ); + //mKeyIdListBoxes.insert( 0, 0 ); + } + + QStringList::ConstIterator ait; + QValueVector<KeyIDList>::const_iterator kit; + int i; + for( ait = addresses.begin(), kit = keyIDs.begin(), i = 0; + ( ait != addresses.end() ) && ( kit != keyIDs.end() ); + ++ait, ++kit, ++i ) { + if( i == 0 ) { + ++kit; // skip the sender's key id + } + else { + new KSeparator( Horizontal, bigvbox ); + } + + QHBox *hbox = new QHBox( bigvbox ); + new QLabel( i18n("Recipient:"), hbox ); + QLabel *addressL = new QLabel( *ait, hbox ); + hbox->setStretchFactor( addressL, 10 ); + mAddressLabels.insert( i, addressL ); + + hbox = new QHBox( bigvbox ); + new QLabel( i18n("Encryption keys:"), hbox ); + QLabel* keyidsL = new QLabel( hbox ); + if( (*kit).isEmpty() ) { + keyidsL->setText( i18n("<none> means 'no key'", "<none>") ); + } + else { + keyidsL->setText( "0x" + (*kit).toStringList().join( "\n0x" ) ); + } + keyidsL->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + /* + QListBox* keyidLB = new QListBox( hbox ); + if( (*kit).isEmpty() ) { + keyidLB->insertItem( i18n("<none>") ); + } + else { + keyidLB->insertStringList( (*kit).toStringList() ); + } + keyidLB->setSelectionMode( QListBox::NoSelection ); + keyidLB->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + */ + QPushButton *button = new QPushButton( i18n("Change..."), hbox ); + mChangeButtonGroup->insert( button ); + button->setAutoDefault( false ); + hbox->setStretchFactor( keyidsL, 10 ); + mKeyIdsLabels.insert( i + 1, keyidsL ); + //hbox->setStretchFactor( keyidLB, 10 ); + //mKeyIdListBoxes.insert( i + 1, keyidLB ); + + hbox = new QHBox( bigvbox ); + new QLabel( i18n("Encryption preference:"), hbox ); + QComboBox *encrPrefCombo = new QComboBox( hbox ); + encrPrefCombo->insertItem( i18n("<none>") ); + encrPrefCombo->insertItem( i18n("Never Encrypt with This Key") ); + encrPrefCombo->insertItem( i18n("Always Encrypt with This Key") ); + encrPrefCombo->insertItem( i18n("Encrypt Whenever Encryption is Possible") ); + encrPrefCombo->insertItem( i18n("Always Ask") ); + encrPrefCombo->insertItem( i18n("Ask Whenever Encryption is Possible") ); + + EncryptPref encrPref = pgp->encryptionPreference( *ait ); + switch( encrPref ) { + case NeverEncrypt: + encrPrefCombo->setCurrentItem( 1 ); + break; + case AlwaysEncrypt: + encrPrefCombo->setCurrentItem( 2 ); + break; + case AlwaysEncryptIfPossible: + encrPrefCombo->setCurrentItem( 3 ); + break; + case AlwaysAskForEncryption: + encrPrefCombo->setCurrentItem( 4 ); + break; + case AskWheneverPossible: + encrPrefCombo->setCurrentItem( 5 ); + break; + default: + encrPrefCombo->setCurrentItem( 0 ); + } + connect( encrPrefCombo, SIGNAL(activated(int)), + this, SLOT(slotPrefsChanged(int)) ); + mEncrPrefCombos.insert( i, encrPrefCombo ); + } + connect( mChangeButtonGroup, SIGNAL(clicked(int)), + this, SLOT(slotChangeEncryptionKey(int)) ); + + // calculate the optimal width for the dialog + int dialogWidth = marginHint() + + sv->frameWidth() + + bigvbox->sizeHint().width() + + sv->verticalScrollBar()->sizeHint().width() + + sv->frameWidth() + + marginHint() + + 2; + // calculate the optimal height for the dialog + int dialogHeight = marginHint() + + label->sizeHint().height() + + topLayout->spacing() + + sv->frameWidth() + + bigvbox->sizeHint().height() + + sv->horizontalScrollBar()->sizeHint().height() + + sv->frameWidth() + + topLayout->spacing() + + actionButton( KDialogBase::Cancel )->sizeHint().height() + + marginHint() + + 2; + // don't make the dialog too large + QRect desk = KGlobalSettings::desktopGeometry(this); + int screenWidth = desk.width(); + if( dialogWidth > 3*screenWidth/4 ) + dialogWidth = 3*screenWidth/4; + int screenHeight = desk.height(); + if( dialogHeight > 7*screenHeight/8 ) + dialogHeight = 7*screenHeight/8; + + setInitialSize( QSize( dialogWidth, dialogHeight ) ); +} + +void +KeyApprovalDialog::slotChangeEncryptionKey( int nr ) +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + kdDebug(5100)<<"Key approval dialog size is " + <<width()<<"x"<<height()<<endl; + + if( pgp == 0 ) + return; + + if( !mEncryptToSelf ) + nr++; + KeyIDList keyIds = mKeys[nr]; + if( nr == 0 ) { + keyIds = pgp->selectPublicKeys( i18n("Encryption Key Selection"), + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "Select the key(s) which should " + "be used to encrypt the message " + "to yourself."), + keyIds, + "", + mAllowedKeys ); + } + else { + keyIds = pgp->selectPublicKeys( i18n("Encryption Key Selection"), + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "Select the key(s) which should " + "be used to encrypt the message " + "for\n%1") + .arg( mAddressLabels[nr-1]->text() ), + keyIds, + mAddressLabels[nr-1]->text(), + mAllowedKeys ); + } + if( !keyIds.isEmpty() ) { + mKeys[nr] = keyIds; + QLabel* keyidsL = mKeyIdsLabels[nr]; + keyidsL->setText( "0x" + keyIds.toStringList().join( "\n0x" ) ); + /* + QListBox* qlb = mKeyIdListBoxes[nr]; + qlb->clear(); + qlb->insertStringList( keyIds.toStringList() ); + */ + } +} + + +void +KeyApprovalDialog::slotOk() +{ + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + + if( pgp == 0 ) { + accept(); + return; + } + + if( mPrefsChanged ) { + // store the changed preferences + for( unsigned int i = 0; i < mAddressLabels.size(); i++ ) { + // traverse all Address and Encryption Preference widgets + EncryptPref encrPref; + switch( mEncrPrefCombos[i]->currentItem() ) { + case 1: + encrPref = NeverEncrypt; + break; + case 2: + encrPref = AlwaysEncrypt; + break; + case 3: + encrPref = AlwaysEncryptIfPossible; + break; + case 4: + encrPref = AlwaysAskForEncryption; + break; + case 5: + encrPref = AskWheneverPossible; + break; + default: + case 0: + encrPref = UnknownEncryptPref; + } + pgp->setEncryptionPreference( mAddressLabels[i]->text(), encrPref ); + } + } + + accept(); +} + + +void +KeyApprovalDialog::slotCancel() +{ + reject(); +} + + + +// ------------------------------------------------------------------------ +CipherTextDialog::CipherTextDialog( const QCString & text, + const QCString & charset, QWidget *parent, + const char *name, bool modal ) + :KDialogBase( parent, name, modal, i18n("OpenPGP Information"), Ok|Cancel, Ok) +{ + // FIXME (post KDE2.2): show some more info, e.g. the output of GnuPG/PGP + QFrame *page = makeMainWidget(); + QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); + + QLabel *label = new QLabel( page ); + label->setText(i18n("Result of the last encryption/sign operation:")); + topLayout->addWidget( label ); + + mEditBox = new QMultiLineEdit( page ); + mEditBox->setReadOnly(true); + topLayout->addWidget( mEditBox, 10 ); + + QString unicodeText; + if (charset.isEmpty()) + unicodeText = QString::fromLocal8Bit(text.data()); + else { + bool ok=true; + QTextCodec *codec = KGlobal::charsets()->codecForName(charset, ok); + if(!ok) + unicodeText = QString::fromLocal8Bit(text.data()); + else + unicodeText = codec->toUnicode(text.data(), text.length()); + } + + mEditBox->setText(unicodeText); + + setMinimumSize(); +} + +void CipherTextDialog::setMinimumSize() +{ + // this seems to force a layout of the entire document, so we get a + // a proper contentsWidth(). Is there a better way? + for ( int i = 0; i < mEditBox->paragraphs(); i++ ) + (void) mEditBox->paragraphRect( i ); + + mEditBox->setMinimumHeight( mEditBox->fontMetrics().lineSpacing() * 25 ); + + int textWidth = mEditBox->contentsWidth() + 30; + + +#if KDE_IS_VERSION( 3, 1, 90 ) + int maxWidth = KGlobalSettings::desktopGeometry(parentWidget()).width()-100; +#else + KConfig gc("kdeglobals", false, false); + gc.setGroup("Windows"); + int maxWidth; + if (QApplication::desktop()->isVirtualDesktop() && + gc.readBoolEntry("XineramaEnabled", true) && + gc.readBoolEntry("XineramaPlacementEnabled", true)) { + maxWidth = QApplication::desktop()->screenGeometry(QApplication::desktop()->screenNumber(parentWidget())).width()-100; + } else { + maxWidth = QApplication::desktop()->geometry().width()-100; + } +#endif + + mEditBox->setMinimumWidth( QMIN( textWidth, maxWidth ) ); +} + +void KeyRequester::virtual_hook( int, void* ) {} + +void PublicKeyRequester::virtual_hook( int id, void* data ) { + base::virtual_hook( id, data ); +} + +void SecretKeyRequester::virtual_hook( int id, void* data ) { + base::virtual_hook( id, data ); +} + +} // namespace Kpgp + + + +#include "kpgpui.moc" diff --git a/libkpgp/kpgpui.h b/libkpgp/kpgpui.h new file mode 100644 index 000000000..eab9a53ea --- /dev/null +++ b/libkpgp/kpgpui.h @@ -0,0 +1,343 @@ +/* -*- c++ -*- + kpgpui.h + + Copyright (C) 2001,2002 the KPGP authors + See file AUTHORS.kpgp for details + + This file is part of KPGP, the KDE PGP/GnuPG support library. + + KPGP 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; either version 2 of the License, or + (at your option) any later version. + + 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 + */ + +#ifndef KPGPUI_H +#define KPGPUI_H + +#include <kdialogbase.h> // base class of all dialogs here +#include <qwidget.h> // base class of Config +#include <qcheckbox.h> // used in inlined methods +#include <kdebug.h> // used in inlined methods +#include <qcstring.h> // used in return-by-value +#include <qstring.h> // is a member in KeyRequester +#include <qvaluevector.h> // used in KeyApprovalDialog + +#include "kpgp.h" + +#include <kdepimmacros.h> + +class QString; +class QRegExp; +class QCString; +class QCheckBox; // needed by Config, KeySelectionDialog +class QMultiLineEdit; // needed by CipherTextDialog +class QComboBox; // needed by Config +class QPixmap; // needed by KeySelectionDialog +class QPushButton; // needed by KeyRequester +class QTimer; // needed by KeySelectionDialog + +class KListView; // needed by KeySelectionDialog +class KPasswordEdit; // needed by PassphraseDialog + +namespace Kpgp { + +class Module; +class KeyList; // needed by KeySelectionDialog +class Key; // needed by KeySelectionDialog +class KeyIDList; // needed by KeySelectionDialog + +/** the passphrase dialog */ +class KDE_EXPORT PassphraseDialog : public KDialogBase +{ + Q_OBJECT + + public: + PassphraseDialog( QWidget *parent=0, const QString &caption=QString::null, + bool modal=true, const QString &keyID=QString::null); + virtual ~PassphraseDialog(); + + const char * passphrase(); + + private: + KPasswordEdit *lineedit; +}; + + +// ------------------------------------------------------------------------- +/** a widget for configuring the pgp interface. Can be included into + a tabdialog. This widget by itself does not provide an apply/cancel + button mechanism. */ +class KDE_EXPORT Config : public QWidget +{ + Q_OBJECT + + public: + Config(QWidget *parent = 0, const char *name = 0, bool encrypt =true); + virtual ~Config(); + + virtual void setValues(); + virtual void applySettings(); + QGroupBox* optionsGroupBox() { return mpOptionsGroupBox; }; + signals: + void changed(); + + protected: + Module *pgp; + QCheckBox *storePass; + QCheckBox *encToSelf; + QCheckBox *showCipherText; + QCheckBox *showKeyApprovalDlg; + QComboBox *toolCombo; + QGroupBox* mpOptionsGroupBox; +}; + + +// ------------------------------------------------------------------------- +#define KeySelectionDialogSuper KDialogBase +class KDE_EXPORT KeySelectionDialog: public KeySelectionDialogSuper +{ + Q_OBJECT + + enum TrustCheckMode { NoExpensiveTrustCheck, + AllowExpensiveTrustCheck, + ForceTrustCheck + }; + + public: + /** allowedKeys: see kpgp.h + */ + KeySelectionDialog( const KeyList& keyList, + const QString& title, + const QString& text = QString::null, + const KeyIDList& keyIds = KeyIDList(), + const bool rememberChoice = false, + const unsigned int allowedKeys = AllKeys, + const bool extendedSelection = false, + QWidget *parent=0, const char *name=0, + bool modal=true ); + virtual ~KeySelectionDialog(); + + /** Returns the key ID of the selected key in single selection mode. + Otherwise it returns a null string. */ + virtual KeyID key() const; + + /** Returns a list of selected key IDs. */ + virtual KeyIDList keys() const + { return mKeyIds; }; + + virtual bool rememberSelection() const + { if( mRememberCB ) + return mRememberCB->isChecked(); + else + return false; + }; + + protected slots: + virtual void slotRereadKeys(); + virtual void slotSelectionChanged( QListViewItem* ); + virtual void slotSelectionChanged(); + virtual void slotCheckSelection( QListViewItem* = 0 ); + virtual void slotRMB( QListViewItem*, const QPoint&, int ); + virtual void slotRecheckKey(); + virtual void slotOk(); + virtual void slotCancel(); + virtual void slotSearch( const QString & text ); + virtual void slotFilter(); + + private: + void filterByKeyID( const QString & keyID ); + void filterByKeyIDOrUID( const QString & keyID ); + void filterByUID( const QString & uid ); + void showAllItems(); + bool anyChildMatches( const QListViewItem * item, QRegExp & rx ) const; + + void initKeylist( const KeyList& keyList, const KeyIDList& keyIds ); + + QString keyInfo( const Kpgp::Key* ) const; + + QString beautifyFingerprint( const QCString& ) const; + + // Returns the key ID of the key the given QListViewItem belongs to + KeyID getKeyId( const QListViewItem* ) const; + + // Returns: -1 = unusable, 0 = unknown, 1 = valid, but untrusted, 2 = trusted + int keyValidity( const Kpgp::Key* ) const; + + // Updates the given QListViewItem with the data of the given key + void updateKeyInfo( const Kpgp::Key*, QListViewItem* ) const; + + /** Checks if choosing the given key is allowed + Returns: + -1 = key must not be chosen, + 0 = not enough information to decide whether the give key is allowed + or not, + 1 = key can be chosen + */ + int keyAdmissibility( QListViewItem*, + TrustCheckMode = NoExpensiveTrustCheck ) const; + + // Perform expensive trust checks for the given keys + bool checkKeys( const QValueList<QListViewItem*>& ) const; + + private: + KListView *mListView; + QCheckBox *mRememberCB; + QPixmap *mKeyGoodPix, *mKeyBadPix, *mKeyUnknownPix, *mKeyValidPix; + KeyIDList mKeyIds; + unsigned int mAllowedKeys; + QTimer* mCheckSelectionTimer; + QTimer* mStartSearchTimer; + QString mSearchText; + QListViewItem* mCurrentContextMenuItem; + + static const int sCheckSelectionDelay; +}; + +class KDE_EXPORT KeyRequester: public QWidget +{ + Q_OBJECT + +public: + KeyRequester( QWidget * parent=0, bool multipleKeys=false, + unsigned int allowedKeys=AllKeys, const char * name=0 ); + virtual ~KeyRequester(); + + KeyIDList keyIDs() const; + void setKeyIDs( const KeyIDList & keyIDs ); + + QPushButton * eraseButton() const { return mEraseButton; } + QPushButton * dialogButton() const { return mDialogButton; } + + void setDialogCaption( const QString & caption ); + void setDialogMessage( const QString & message ); + + bool isMultipleKeysEnabled() const; + void setMultipleKeysEnabled( bool enable ); + + int allowedKeys() const; + void setAllowedKeys( int allowed ); + +protected: + /** Reimplement this to return a list of selected keys. */ + virtual KeyIDList keyRequestHook( Module * pgp ) const = 0; + +protected: + QLabel * mLabel; + QPushButton * mEraseButton; + QPushButton * mDialogButton; + QString mDialogCaption, mDialogMessage; + bool mMulti; + int mAllowedKeys; + KeyIDList mKeys; + +protected slots: + void slotDialogButtonClicked(); + void slotEraseButtonClicked(); + +signals: + void changed(); + +private: + class Private; + Private * d; +protected: + virtual void virtual_hook( int, void* ); +}; + + +class KDE_EXPORT PublicKeyRequester : public KeyRequester { + Q_OBJECT +public: + PublicKeyRequester( QWidget * parent=0, bool multipleKeys=false, + unsigned int allowedKeys=PublicKeys, const char * name=0 ); + virtual ~PublicKeyRequester(); + +protected: + KeyIDList keyRequestHook( Module * pgp ) const; + +private: + typedef KeyRequester base; + class Private; + Private * d; +protected: + virtual void virtual_hook( int, void* ); +}; + + +class KDE_EXPORT SecretKeyRequester : public KeyRequester { + Q_OBJECT +public: + SecretKeyRequester( QWidget * parent=0, bool multipleKeys=false, + unsigned int allowedKeys=SecretKeys, const char * name=0 ); + virtual ~SecretKeyRequester(); + +protected: + KeyIDList keyRequestHook( Module * pgp ) const; + +private: + typedef KeyRequester base; + class Private; + Private * d; +protected: + virtual void virtual_hook( int, void* ); +}; + + +// ------------------------------------------------------------------------- +class KDE_EXPORT KeyApprovalDialog: public KDialogBase +{ + Q_OBJECT + + public: + KeyApprovalDialog( const QStringList&, + const QValueVector<KeyIDList>&, + const int allowedKeys, + QWidget *parent = 0, const char *name = 0, + bool modal = true ); + virtual ~KeyApprovalDialog() {}; + + QValueVector<KeyIDList> keys() const { return mKeys; }; + + bool preferencesChanged() const { return mPrefsChanged; } + + protected slots: + void slotPrefsChanged( int ) { mPrefsChanged = true; }; + void slotChangeEncryptionKey( int ); + virtual void slotOk(); + virtual void slotCancel(); + + private: + QValueVector<KeyIDList> mKeys; + int mAllowedKeys; + int mEncryptToSelf; + bool mPrefsChanged; + QPtrVector<QLabel> mAddressLabels; + QPtrVector<QLabel> mKeyIdsLabels; + //QPtrVector<QListBox> mKeyIdListBoxes; + QPtrVector<QComboBox> mEncrPrefCombos; +}; + + +// ------------------------------------------------------------------------- +class KDE_EXPORT CipherTextDialog: public KDialogBase +{ + Q_OBJECT + + public: + CipherTextDialog( const QCString & text, const QCString & charset=0, + QWidget *parent=0, const char *name=0, bool modal=true ); + virtual ~CipherTextDialog() {}; + + private: + void setMinimumSize(); + QMultiLineEdit *mEditBox; +}; + +} // namespace Kpgp + +#endif diff --git a/libkpgp/pics/Makefile.am b/libkpgp/pics/Makefile.am new file mode 100644 index 000000000..7db72de1d --- /dev/null +++ b/libkpgp/pics/Makefile.am @@ -0,0 +1,9 @@ + +kmaildata_DATA = key_ok.png key_bad.png key_unknown.png key.png +knodedata_DATA = $(kmaildata_DATA) + +kmaildatadir = $(kde_datadir)/kmail/pics +knodedatadir = $(kde_datadir)/knode/pics + +EXTRA_DIST = $(kmaildata_DATA) + diff --git a/libkpgp/pics/key.png b/libkpgp/pics/key.png Binary files differnew file mode 100644 index 000000000..2da8bd755 --- /dev/null +++ b/libkpgp/pics/key.png diff --git a/libkpgp/pics/key_bad.png b/libkpgp/pics/key_bad.png Binary files differnew file mode 100644 index 000000000..1e0f8ca18 --- /dev/null +++ b/libkpgp/pics/key_bad.png diff --git a/libkpgp/pics/key_ok.png b/libkpgp/pics/key_ok.png Binary files differnew file mode 100644 index 000000000..3fa7e6f0d --- /dev/null +++ b/libkpgp/pics/key_ok.png diff --git a/libkpgp/pics/key_unknown.png b/libkpgp/pics/key_unknown.png Binary files differnew file mode 100644 index 000000000..ee3f542e2 --- /dev/null +++ b/libkpgp/pics/key_unknown.png |