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 /certmanager/certmanager.cpp | |
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 'certmanager/certmanager.cpp')
-rw-r--r-- | certmanager/certmanager.cpp | 1432 |
1 files changed, 1432 insertions, 0 deletions
diff --git a/certmanager/certmanager.cpp b/certmanager/certmanager.cpp new file mode 100644 index 000000000..b27f2ad57 --- /dev/null +++ b/certmanager/certmanager.cpp @@ -0,0 +1,1432 @@ +/* + certmanager.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2001,2002,2004 Klarälvdalens Datakonsult AB + + Kleopatra 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. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "certmanager.h" + +#include "certlistview.h" +#include "certificatewizardimpl.h" +#include "certificateinfowidgetimpl.h" +#include "crlview.h" +#include "customactions.h" +#include "hierarchyanalyser.h" +#include "storedtransferjob.h" +#include "conf/configuredialog.h" + +// libkleopatra +#include <kleo/cryptobackendfactory.h> +#include <kleo/downloadjob.h> +#include <kleo/importjob.h> +#include <kleo/exportjob.h> +#include <kleo/multideletejob.h> +#include <kleo/deletejob.h> +#include <kleo/keylistjob.h> +#include <kleo/dn.h> +#include <kleo/keyfilter.h> +#include <kleo/keyfiltermanager.h> +#include <kleo/hierarchicalkeylistjob.h> +#include <kleo/refreshkeysjob.h> +#include <kleo/cryptoconfig.h> + +#include <ui/progressdialog.h> +#include <ui/progressbar.h> +#include <ui/keyselectiondialog.h> +#include <ui/cryptoconfigdialog.h> + +// GPGME++ +#include <gpgmepp/importresult.h> +#include <gpgmepp/keylistresult.h> +#include <gpgmepp/key.h> + +// KDE +#include <kfiledialog.h> +#include <kprocess.h> +#include <kaction.h> +#include <kapplication.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <dcopclient.h> +#include <ktoolbar.h> +#include <kstatusbar.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <kkeydialog.h> +#include <ktempfile.h> +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kstdaccel.h> + +// Qt +#include <qfontmetrics.h> +#include <qpopupmenu.h> + +// other +#include <algorithm> +#include <assert.h> +#include <kdepimmacros.h> +#include <kinputdialog.h> +namespace { + + class KDE_EXPORT DisplayStrategy : public Kleo::KeyListView::DisplayStrategy{ + public: + ~DisplayStrategy() {} + + virtual QFont keyFont( const GpgME::Key& key, const QFont& font ) const { + const Kleo::KeyFilter* filter = Kleo::KeyFilterManager::instance()->filterMatching( key ); + return filter ? filter->font( font ) : font; + } + virtual QColor keyForeground( const GpgME::Key& key, const QColor& c ) const { + const Kleo::KeyFilter* filter = Kleo::KeyFilterManager::instance()->filterMatching( key ); + if ( filter && filter->fgColor().isValid() ) + return filter->fgColor(); + return c; + } + virtual QColor keyBackground( const GpgME::Key& key, const QColor& c ) const { + const Kleo::KeyFilter* filter = Kleo::KeyFilterManager::instance()->filterMatching( key ); + if ( filter && filter->bgColor().isValid() ) + return filter->bgColor(); + return c; + } + }; + + class KDE_EXPORT ColumnStrategy : public Kleo::KeyListView::ColumnStrategy { + public: + ~ColumnStrategy() {} + + QString title( int col ) const; + QString text( const GpgME::Key & key, int col ) const; + int width( int col, const QFontMetrics & fm ) const; + }; + + QString ColumnStrategy::title( int col ) const { + switch ( col ) { + case 0: return i18n("Subject"); + case 1: return i18n("Issuer"); + case 2: return i18n("Serial"); + default: return QString::null; + } + } + + QString ColumnStrategy::text( const GpgME::Key & key, int col ) const { + switch ( col ) { + case 0: return Kleo::DN( key.userID(0).id() ).prettyDN(); + case 1: return Kleo::DN( key.issuerName() ).prettyDN(); + case 2: return key.issuerSerial() ? QString::fromUtf8( key.issuerSerial() ) : QString::null ; + default: return QString::null; + } + } + + int ColumnStrategy::width( int col, const QFontMetrics & fm ) const { + int factor = -1; + switch ( col ) { + case 0: factor = 6; break; + case 1: factor = 4; break; + default: return -1; + } + return fm.width( title( col ) ) * factor; + } +} // anon namespace + +CertManager::CertManager( bool remote, const QString& query, const QString & import, + QWidget* parent, const char* name, WFlags f ) + : KMainWindow( parent, name, f|WDestructiveClose ), + mCrlView( 0 ), + mDirmngrProc( 0 ), + mHierarchyAnalyser( 0 ), + mLineEditAction( 0 ), + mComboAction( 0 ), + mFindAction( 0 ), + mImportCertFromFileAction( 0 ), + mImportCRLFromFileAction( 0 ), + mNextFindRemote( remote ), + mRemote( remote ), + mDirMngrFound( false ) +{ + readConfig( query.isEmpty() ); + createStatusBar(); + createActions(); + + createGUI(); + setAutoSaveSettings(); + + // Main Window -------------------------------------------------- + mKeyListView = new CertKeyListView( new ColumnStrategy(), new DisplayStrategy(), this, "mKeyListView" ); + mKeyListView->setSelectionMode( QListView::Extended ); + setCentralWidget( mKeyListView ); + + connect( mKeyListView, SIGNAL(doubleClicked(Kleo::KeyListViewItem*,const QPoint&,int)), + SLOT(slotViewDetails(Kleo::KeyListViewItem*)) ); + connect( mKeyListView, SIGNAL(returnPressed(Kleo::KeyListViewItem*)), + SLOT(slotViewDetails(Kleo::KeyListViewItem*)) ); + connect( mKeyListView, SIGNAL(selectionChanged()), + SLOT(slotSelectionChanged()) ); + connect( mKeyListView, SIGNAL(contextMenu(Kleo::KeyListViewItem*, const QPoint&)), + SLOT(slotContextMenu(Kleo::KeyListViewItem*, const QPoint&)) ); + + connect( mKeyListView, SIGNAL(dropped(const KURL::List&) ), + SLOT( slotDropped(const KURL::List&) ) ); + + mLineEditAction->setText(query); + if ( !mRemote && !mNextFindRemote || !query.isEmpty() ) + slotSearch(); + + if ( !import.isEmpty() ) + slotImportCertFromFile( KURL( import ) ); + + slotToggleHierarchicalView( mHierarchicalView ); + updateStatusBarLabels(); + slotSelectionChanged(); // initial state for selection-dependent actions +} + +CertManager::~CertManager() { + writeConfig(); + delete mDirmngrProc; mDirmngrProc = 0; + delete mHierarchyAnalyser; mHierarchyAnalyser = 0; +} + +void CertManager::readConfig( bool noQueryGiven ) { + KConfig config( "kleopatrarc" ); + config.setGroup( "Display Options" ); + mHierarchicalView = config.readBoolEntry( "hierarchicalView", false ); + if ( noQueryGiven ) { + mNextFindRemote = config.readBoolEntry( "startInRemoteMode", false ); + } +} + +void CertManager::writeConfig() { + KConfig config( "kleopatrarc" ); + config.setGroup( "Display Options" ); + config.writeEntry( "hierarchicalView", mKeyListView->hierarchical() ); + config.writeEntry( "startInRemoteMode", mNextFindRemote ); +} + +void CertManager::createStatusBar() { + KStatusBar * bar = statusBar(); + mProgressBar = new Kleo::ProgressBar( bar, "mProgressBar" ); + mProgressBar->reset(); + mProgressBar->setFixedSize( QSize( 100, mProgressBar->height() * 3 / 5 ) ); + bar->addWidget( mProgressBar, 0, true ); + mStatusLabel = new QLabel( bar, "mStatusLabel" ); + bar->addWidget( mStatusLabel, 1, false ); +} + +static inline void connectEnableOperationSignal( QObject * s, QObject * d ) { + QObject::connect( s, SIGNAL(enableOperations(bool)), + d, SLOT(setEnabled(bool)) ); +} + + +void CertManager::createActions() { + KAction * action = 0; + + (void)KStdAction::quit( this, SLOT(close()), actionCollection() ); + + action = KStdAction::redisplay( this, SLOT(slotRedisplay()), actionCollection() ); + // work around the fact that the stdaction has no shortcut + KShortcut reloadShortcut = KStdAccel::shortcut(KStdAccel::Reload); + reloadShortcut.append(KKey(CTRL + Key_R)); + action->setShortcut( reloadShortcut ); + + connectEnableOperationSignal( this, action ); + + action = new KAction( i18n("Stop Operation"), "stop", Key_Escape, + this, SIGNAL(stopOperations()), + actionCollection(), "view_stop_operations" ); + action->setEnabled( false ); + + (void) new KAction( i18n("New Key Pair..."), "filenew", 0, + this, SLOT(newCertificate()), + actionCollection(), "file_new_certificate" ); + + connect( new KToggleAction( i18n("Hierarchical Key List"), 0, + actionCollection(), "view_hierarchical" ), + SIGNAL(toggled(bool)), SLOT(slotToggleHierarchicalView(bool)) ); + + action = new KAction( i18n("Expand All"), 0, CTRL+Key_Period, + this, SLOT(slotExpandAll()), + actionCollection(), "view_expandall" ); + action = new KAction( i18n("Collapse All"), 0, CTRL+Key_Comma, + this, SLOT(slotCollapseAll()), + actionCollection(), "view_collapseall" ); + + (void) new KAction( i18n("Refresh CRLs"), 0, 0, + this, SLOT(slotRefreshKeys()), + actionCollection(), "certificates_refresh_clr" ); + +#ifdef NOT_IMPLEMENTED_ANYWAY + mRevokeCertificateAction = new KAction( i18n("Revoke"), 0, + this, SLOT(revokeCertificate()), + actionCollection(), "edit_revoke_certificate" ); + connectEnableOperationSignal( this, mRevokeCertificateAction ); + + mExtendCertificateAction = new KAction( i18n("Extend"), 0, + this, SLOT(extendCertificate()), + actionCollection(), "edit_extend_certificate" ); + connectEnableOperationSignal( this, mExtendCertificateAction ); +#endif + + mDeleteCertificateAction = new KAction( i18n("Delete"), "editdelete", Key_Delete, + this, SLOT(slotDeleteCertificate()), + actionCollection(), "edit_delete_certificate" ); + connectEnableOperationSignal( this, mDeleteCertificateAction ); + + mValidateCertificateAction = new KAction( i18n("Validate"), "reload", SHIFT + Key_F5, + this, SLOT(slotValidate()), + actionCollection(), "certificates_validate" ); + connectEnableOperationSignal( this, mValidateCertificateAction ); + + mImportCertFromFileAction = new KAction( i18n("Import Certificates..."), 0, + this, SLOT(slotImportCertFromFile()), + actionCollection(), "file_import_certificates" ); + connectEnableOperationSignal( this, mImportCertFromFileAction ); + + mImportCRLFromFileAction = new KAction( i18n("Import CRLs..."), 0, + this, SLOT(importCRLFromFile()), + actionCollection(), "file_import_crls" ); + connectEnableOperationSignal( this, mImportCRLFromFileAction ); + + mExportCertificateAction = new KAction( i18n("Export Certificates..."), "export", 0, + this, SLOT(slotExportCertificate()), + actionCollection(), "file_export_certificate" ); + + mExportSecretKeyAction = new KAction( i18n("Export Secret Key..."), "export", 0, + this, SLOT(slotExportSecretKey()), + actionCollection(), "file_export_secret_keys" ); + connectEnableOperationSignal( this, mExportSecretKeyAction ); + + mViewCertDetailsAction = new KAction( i18n("Certificate Details..."), 0, 0, + this, SLOT(slotViewDetails()), actionCollection(), + "view_certificate_details" ); + mDownloadCertificateAction = new KAction( i18n( "Download"), 0, 0, + this, SLOT(slotDownloadCertificate()), actionCollection(), + "download_certificate" ); + + const QString dirmngr = KStandardDirs::findExe( "gpgsm" ); + mDirMngrFound = !dirmngr.isEmpty(); + + action = new KAction( i18n("Dump CRL Cache..."), 0, + this, SLOT(slotViewCRLs()), + actionCollection(), "crl_dump_crl_cache" ); + action->setEnabled( mDirMngrFound ); // we also need dirmngr for this + + action = new KAction( i18n("Clear CRL Cache..."), 0, + this, SLOT(slotClearCRLs()), + actionCollection(), "crl_clear_crl_cache" ); + action->setEnabled( mDirMngrFound ); // we also need dirmngr for this + + action = new KAction( i18n("GnuPG Log Viewer..."), "pgp-keys", 0, this, + SLOT(slotStartWatchGnuPG()), actionCollection(), "tools_start_kwatchgnupg"); + // disable action if no kwatchgnupg binary is around + if (KStandardDirs::findExe("kwatchgnupg").isEmpty()) action->setEnabled(false); + + (void)new LabelAction( i18n("Search:"), actionCollection(), "label_action" ); + + mLineEditAction = new LineEditAction( QString::null, actionCollection(), this, + SLOT(slotSearch()), + "query_lineedit_action"); + + QStringList lst; + lst << i18n("In Local Certificates") << i18n("In External Certificates"); + mComboAction = new ComboAction( lst, actionCollection(), this, SLOT( slotToggleRemote(int) ), + "location_combo_action", mNextFindRemote? 1 : 0 ); + + mFindAction = new KAction( i18n("Find"), "find", 0, this, SLOT(slotSearch()), + actionCollection(), "find" ); + + KStdAction::keyBindings( this, SLOT(slotEditKeybindings()), actionCollection() ); + KStdAction::preferences( this, SLOT(slotShowConfigurationDialog()), actionCollection() ); + + new KAction( i18n( "Configure &GpgME Backend" ), 0, 0, this, SLOT(slotConfigureGpgME()), + actionCollection(), "configure_gpgme" ); + + createStandardStatusBarAction(); + updateImportActions( true ); +} + +void CertManager::updateImportActions( bool enable ) { + mImportCRLFromFileAction->setEnabled( mDirMngrFound && enable ); + mImportCertFromFileAction->setEnabled( enable ); +} + +void CertManager::slotEditKeybindings() { + KKeyDialog::configure( actionCollection(), true ); +} + +void CertManager::slotShowConfigurationDialog() { + ConfigureDialog dlg( this ); + connect( &dlg, SIGNAL( configCommitted() ), SLOT( slotRepaint() ) ); + dlg.exec(); +} + +void CertManager::slotConfigureGpgME() { + Kleo::CryptoConfig* config = Kleo::CryptoBackendFactory::instance()->config(); + if ( config ) { + Kleo::CryptoConfigDialog dlg( config ); + + int result = dlg.exec(); + + // Forget all data parsed from gpgconf, so that we show updated information + // when reopening the configuration dialog. + config->clear(); + + if ( result == QDialog::Accepted ) + { + // Tell other apps (e.g. kmail) that the gpgconf data might have changed + kapp->dcopClient()->emitDCOPSignal( "KPIM::CryptoConfig", "changed()", QByteArray() ); + } + } +} + +void CertManager::slotRepaint() +{ + mKeyListView->repaintContents(); +} + +void CertManager::slotToggleRemote( int idx ) { + mNextFindRemote = idx != 0; +} + +void CertManager::slotToggleHierarchicalView( bool hier ) { + mHierarchicalView = hier; + mKeyListView->setHierarchical( hier ); + mKeyListView->setRootIsDecorated( hier ); + if ( KAction * act = action("view_expandall") ) + act->setEnabled( hier ); + if ( KAction * act = action("view_collapseall" ) ) + act->setEnabled( hier ); + if ( KToggleAction * act = + static_cast<KToggleAction*>( action("view_hierarchical") ) ) + act->setChecked( hier ); + + if ( hier && !mCurrentQuery.isEmpty() ) + startRedisplay( false ); +} + +void CertManager::slotExpandAll() { + for ( QListViewItemIterator it( mKeyListView ) ; it.current() ; ++it ) + it.current()->setOpen( true ); +} + +void CertManager::slotCollapseAll() { + for ( QListViewItemIterator it( mKeyListView ) ; it.current() ; ++it ) + it.current()->setOpen( false ); +} + +void CertManager::connectJobToStatusBarProgress( Kleo::Job * job, const QString & initialText ) { + assert( mProgressBar ); + if ( !job ) + return; + if ( !initialText.isEmpty() ) + statusBar()->message( initialText ); + connect( job, SIGNAL(progress(const QString&,int,int)), + mProgressBar, SLOT(slotProgress(const QString&,int,int)) ); + connect( job, SIGNAL(done()), mProgressBar, SLOT(reset()) ); + connect( this, SIGNAL(stopOperations()), job, SLOT(slotCancel()) ); + + action("view_stop_operations")->setEnabled( true ); + emit enableOperations( false ); +} + +void CertManager::disconnectJobFromStatusBarProgress( const GpgME::Error & err ) { + updateStatusBarLabels(); + const QString msg = err.isCanceled() ? i18n("Canceled.") + : err ? i18n("Failed.") + : i18n("Done.") ; + statusBar()->message( msg, 4000 ); + + action("view_stop_operations")->setEnabled( false ); + emit enableOperations( true ); + slotSelectionChanged(); +} + +void CertManager::updateStatusBarLabels() { + mKeyListView->flushKeys(); + int total = 0; + for ( QListViewItemIterator it( mKeyListView ) ; it.current() ; ++it ) + ++total; + mStatusLabel->setText( i18n( "%n Key.","%n Keys.", total ) ); +} + +// +// +// Key Listing: +// +// + + +static std::set<std::string> extractKeyFingerprints( const QPtrList<Kleo::KeyListViewItem> & items ) { + std::set<std::string> result; + for ( QPtrListIterator<Kleo::KeyListViewItem> it( items ) ; it.current() ; ++it ) + if ( const char * fpr = it.current()->key().primaryFingerprint() ) + result.insert( fpr ); + return result; +} + +static QStringList stringlistFromSet( const std::set<std::string> & set ) { + // ARGH. This is madness. Shitty Qt containers don't support QStringList( patterns.begin(), patterns.end() ) :/ + QStringList sl; + for ( std::set<std::string>::const_iterator it = set.begin() ; it != set.end() ; ++it ) + // let's make extra sure, maybe someone tries to make Qt not support std::string->QString conversion + sl.push_back( QString::fromLatin1( it->c_str() ) ); + return sl; +} + +void CertManager::slotRefreshKeys() { + const QStringList keys = stringlistFromSet( extractKeyFingerprints( mKeyListView->selectedItems() ) ); + Kleo::RefreshKeysJob * job = Kleo::CryptoBackendFactory::instance()->smime()->refreshKeysJob(); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::Error&)), + this, SLOT(slotRefreshKeysResult(const GpgME::Error&)) ); + + connectJobToStatusBarProgress( job, i18n("Refreshing keys...") ); + if ( const GpgME::Error err = job->start( keys ) ) + slotRefreshKeysResult( err ); +} + +void CertManager::slotRefreshKeysResult( const GpgME::Error & err ) { + disconnectJobFromStatusBarProgress( err ); + if ( err.isCanceled() ) + return; + if ( err ) + KMessageBox::error( this, i18n("An error occurred while trying to refresh " + "keys:\n%1").arg( QString::fromLocal8Bit( err.asString() ) ), + i18n("Refreshing Keys Failed") ); +} + +static void showKeyListError( QWidget * parent, const GpgME::Error & err ) { + assert( err ); + const QString msg = i18n( "<qt><p>An error occurred while fetching " + "the certificates from the backend:</p>" + "<p><b>%1</b></p></qt>" ) + .arg( QString::fromLocal8Bit( err.asString() ) ); + + KMessageBox::error( parent, msg, i18n( "Certificate Listing Failed" ) ); +} + +void CertManager::slotSearch() { + mPreviouslySelectedFingerprints.clear(); + // Clear display + mKeyListView->clear(); + mCurrentQuery = mLineEditAction->text(); + startKeyListing( false, false, mCurrentQuery ); +} + +void CertManager::startRedisplay( bool validate ) { + mPreviouslySelectedFingerprints = extractKeyFingerprints( mKeyListView->selectedItems() ); + if ( mPreviouslySelectedFingerprints.empty() ) + startKeyListing( validate, true, mCurrentQuery ); + else + startKeyListing( validate, true, mPreviouslySelectedFingerprints ); +} + +void CertManager::startKeyListing( bool validating, bool refresh, const std::set<std::string> & patterns ) { + startKeyListing( validating, refresh, stringlistFromSet( patterns ) ); +} + +void CertManager::startKeyListing( bool validating, bool refresh, const QStringList & patterns ) { + mRemote = mNextFindRemote; + mLineEditAction->setEnabled( false ); + mComboAction->setEnabled( false ); + mFindAction->setEnabled( false ); + + Kleo::KeyListJob * job = 0; + if ( !validating && !refresh && mKeyListView->hierarchical() && !patterns.empty() ) + job = new Kleo::HierarchicalKeyListJob( Kleo::CryptoBackendFactory::instance()->smime(), + mRemote, false, validating ); + else + job = Kleo::CryptoBackendFactory::instance()->smime()->keyListJob( mRemote, false, validating ); + assert( job ); + + connect( job, SIGNAL(nextKey(const GpgME::Key&)), + mKeyListView, refresh ? SLOT(slotRefreshKey(const GpgME::Key&)) : SLOT(slotAddKey(const GpgME::Key&)) ); + connect( job, SIGNAL(result(const GpgME::KeyListResult&)), + this, SLOT(slotKeyListResult(const GpgME::KeyListResult&)) ); + + connectJobToStatusBarProgress( job, i18n("Fetching keys...") ); + + const GpgME::Error err = job->start( patterns ) ; + if ( err ) { + showKeyListError( this, err ); + return; + } + mProgressBar->setProgress( 0, 0 ); // enable busy indicator +} + +static void selectKeys( Kleo::KeyListView * lv, const std::set<std::string> & fprs ) { + if ( !lv || fprs.empty() ) + return; + for ( QListViewItemIterator it( lv ) ; it.current() ; ++it ) + if ( Kleo::KeyListViewItem * item = Kleo::lvi_cast<Kleo::KeyListViewItem>( it.current() ) ) { + const char * fpr = item->key().primaryFingerprint(); + item->setSelected( fpr && fprs.find( fpr ) != fprs.end() ); + } +} + +void CertManager::slotKeyListResult( const GpgME::KeyListResult & res ) { + if ( res.error() ) + showKeyListError( this, res.error() ); + else if ( res.isTruncated() ) + KMessageBox::information( this, + i18n("The query result has been truncated.\n" + "Either the local or a remote limit on " + "the maximum number of returned hits has " + "been exceeded.\n" + "You can try to increase the local limit " + "in the configuration dialog, but if one " + "of the configured servers is the limiting " + "factor, you have to refine your search.") ); + + mLineEditAction->setEnabled( true ); + mComboAction->setEnabled( true ); + mFindAction->setEnabled( true ); + + mLineEditAction->focusAll(); + disconnectJobFromStatusBarProgress( res.error() ); + selectKeys( mKeyListView, mPreviouslySelectedFingerprints ); +} + +void CertManager::slotContextMenu(Kleo::KeyListViewItem* item, const QPoint& point) { + if ( !item ) + return; + if ( QPopupMenu * popup = static_cast<QPopupMenu*>(factory()->container("listview_popup",this)) ) + popup->exec( point ); +} + +/** + This slot is invoked when the user selects "New certificate" +*/ +void CertManager::newCertificate() +{ + CertificateWizardImpl wizard( this ); + wizard.exec(); +} + +/** + This slot is invoked when the user selects revoke certificate. + The slot will revoke the selected certificates +*/ +void CertManager::revokeCertificate() +{ + qDebug("Not Yet Implemented"); +} + +/** + This slot is invoked when the user selects extend certificate. + It will send an extension request for the selected certificates +*/ +void CertManager::extendCertificate() +{ + qDebug("Not Yet Implemented"); +} + + +// +// +// Downloading / Importing Certificates +// +// + + +/** + This slot is invoked when the user selects Certificates/Import/From File. +*/ +void CertManager::slotImportCertFromFile() +{ + const QString filter = "application/x-x509-ca-cert application/x-pkcs12 application/pkcs7-mime"; + //const QString filter = QString("*.pem *.der *.p7c *.p12|") + i18n("Certificates (*.pem *.der *.p7c *.p12)"); + slotImportCertFromFile( KFileDialog::getOpenURL( QString::null, filter, this, + i18n( "Select Certificate File" ) ) ); +} + +void CertManager::slotImportCertFromFile( const KURL & certURL ) +{ + if ( !certURL.isValid() ) // empty or malformed + return; + + mPreviouslySelectedFingerprints.clear(); + + // Prevent two simultaneous imports + updateImportActions( false ); + + // Download the cert + KIOext::StoredTransferJob* importJob = KIOext::storedGet( certURL ); + importJob->setWindow( this ); + connect( importJob, SIGNAL(result(KIO::Job*)), SLOT(slotImportResult(KIO::Job*)) ); +} + +void CertManager::slotImportResult( KIO::Job* job ) +{ + if ( job->error() ) { + job->showErrorDialog(); + } else { + KIOext::StoredTransferJob* trJob = static_cast<KIOext::StoredTransferJob *>( job ); + startCertificateImport( trJob->data(), trJob->url().fileName() ); + } + + updateImportActions( true ); +} + +static void showCertificateDownloadError( QWidget * parent, const GpgME::Error & err, const QString& certDisplayName ) { + assert( err ); + const QString msg = i18n( "<qt><p>An error occurred while trying " + "to download the certificate %1:</p>" + "<p><b>%2</b></p></qt>" ) + .arg( certDisplayName ) + .arg( QString::fromLocal8Bit( err.asString() ) ); + + KMessageBox::error( parent, msg, i18n( "Certificate Download Failed" ) ); +} + +void CertManager::slotDownloadCertificate() { + mPreviouslySelectedFingerprints.clear(); + QPtrList<Kleo::KeyListViewItem> items = mKeyListView->selectedItems(); + for ( QPtrListIterator<Kleo::KeyListViewItem> it( items ) ; it.current() ; ++it ) + if ( !it.current()->key().isNull() ) + if ( const char * fpr = it.current()->key().primaryFingerprint() ) + slotStartCertificateDownload( fpr, it.current()->text(0) ); +} + +// Called from slotDownloadCertificate and from the certificate-details widget +void CertManager::slotStartCertificateDownload( const QString& fingerprint, const QString& displayName ) { + if ( fingerprint.isEmpty() ) + return; + + Kleo::DownloadJob * job = + Kleo::CryptoBackendFactory::instance()->smime()->downloadJob( false /* no armor */ ); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::Error&,const QByteArray&)), + SLOT(slotCertificateDownloadResult(const GpgME::Error&,const QByteArray&)) ); + + connectJobToStatusBarProgress( job, i18n("Fetching certificate from server...") ); + + const GpgME::Error err = job->start( fingerprint ); + if ( err ) + showCertificateDownloadError( this, err, displayName ); + else { + mProgressBar->setProgress( 0, 0 ); + mJobsDisplayNameMap.insert( job, displayName ); + } +} + +QString CertManager::displayNameForJob( const Kleo::Job *job ) +{ + JobsDisplayNameMap::iterator it = mJobsDisplayNameMap.find( job ); + QString displayName; + if ( it != mJobsDisplayNameMap.end() ) { + displayName = *it; + mJobsDisplayNameMap.remove( it ); + } else { + kdWarning() << "Job not found in map: " << job << endl; + } + return displayName; +} + +// Don't call directly! +void CertManager::slotCertificateDownloadResult( const GpgME::Error & err, const QByteArray & keyData ) { + + QString displayName = displayNameForJob( static_cast<const Kleo::Job *>( sender() ) ); + + if ( err ) + showCertificateDownloadError( this, err, displayName ); + else + startCertificateImport( keyData, displayName ); + disconnectJobFromStatusBarProgress( err ); +} + +static void showCertificateImportError( QWidget * parent, const GpgME::Error & err, const QString& certDisplayName ) { + assert( err ); + const QString msg = i18n( "<qt><p>An error occurred while trying " + "to import the certificate %1:</p>" + "<p><b>%2</b></p></qt>" ) + .arg( certDisplayName ) + .arg( QString::fromLocal8Bit( err.asString() ) ); + KMessageBox::error( parent, msg, i18n( "Certificate Import Failed" ) ); +} + +void CertManager::startCertificateImport( const QByteArray & keyData, const QString& certDisplayName ) { + Kleo::ImportJob * job = Kleo::CryptoBackendFactory::instance()->smime()->importJob(); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::ImportResult&)), + SLOT(slotCertificateImportResult(const GpgME::ImportResult&)) ); + + connectJobToStatusBarProgress( job, i18n("Importing certificates...") ); + + kdDebug() << "Importing certificate. keyData size:" << keyData.size() << endl; + const GpgME::Error err = job->start( keyData ); + if ( err ) + showCertificateImportError( this, err, certDisplayName ); + else { + mProgressBar->setProgress( 0, 0 ); + mJobsDisplayNameMap.insert( job, certDisplayName ); + } +} + +void CertManager::slotCertificateImportResult( const GpgME::ImportResult & res ) { + QString displayName = displayNameForJob( static_cast<const Kleo::Job *>( sender() ) ); + + if ( res.error().isCanceled() ) { + // do nothing + } else if ( res.error() ) { + showCertificateImportError( this, res.error(), displayName ); + } else { + + const QString normalLine = i18n("<tr><td align=\"right\">%1</td><td>%2</td></tr>"); + const QString boldLine = i18n("<tr><td align=\"right\"><b>%1</b></td><td>%2</td></tr>"); + + QStringList lines; + lines.push_back( normalLine.arg( i18n("Total number processed:"), + QString::number( res.numConsidered() ) ) ); + lines.push_back( normalLine.arg( i18n("Imported:"), + QString::number( res.numImported() ) ) ); + if ( res.newSignatures() ) + lines.push_back( normalLine.arg( i18n("New signatures:"), + QString::number( res.newSignatures() ) ) ); + if ( res.newUserIDs() ) + lines.push_back( normalLine.arg( i18n("New user IDs:"), + QString::number( res.newUserIDs() ) ) ); + if ( res.numKeysWithoutUserID() ) + lines.push_back( normalLine.arg( i18n("Keys without user IDs:"), + QString::number( res.numKeysWithoutUserID() ) ) ); + if ( res.newSubkeys() ) + lines.push_back( normalLine.arg( i18n("New subkeys:"), + QString::number( res.newSubkeys() ) ) ); + if ( res.newRevocations() ) + lines.push_back( boldLine.arg( i18n("Newly revoked:"), + QString::number( res.newRevocations() ) ) ); + if ( res.notImported() ) + lines.push_back( boldLine.arg( i18n("Not imported:"), + QString::number( res.notImported() ) ) ); + if ( res.numUnchanged() ) + lines.push_back( normalLine.arg( i18n("Unchanged:"), + QString::number( res.numUnchanged() ) ) ); + if ( res.numSecretKeysConsidered() ) + lines.push_back( normalLine.arg( i18n("Secret keys processed:"), + QString::number( res.numSecretKeysConsidered() ) ) ); + if ( res.numSecretKeysImported() ) + lines.push_back( normalLine.arg( i18n("Secret keys imported:"), + QString::number( res.numSecretKeysImported() ) ) ); + if ( res.numSecretKeysConsidered() - res.numSecretKeysImported() - res.numSecretKeysUnchanged() > 0 ) + lines.push_back( boldLine.arg( i18n("Secret keys <em>not</em> imported:"), + QString::number( res.numSecretKeysConsidered() + - res.numSecretKeysImported() + - res.numSecretKeysUnchanged() ) ) ); + if ( res.numSecretKeysUnchanged() ) + lines.push_back( normalLine.arg( i18n("Secret keys unchanged:"), + QString::number( res.numSecretKeysUnchanged() ) ) ); + + KMessageBox::information( this, + i18n( "<qt><p>Detailed results of importing %1:</p>" + "<table>%2</table></qt>" ) + .arg( displayName ).arg( lines.join( QString::null ) ), + i18n( "Certificate Import Result" ) ); + + disconnectJobFromStatusBarProgress( res.error() ); + // save the fingerprints of imported certs for later selection: + const std::vector<GpgME::Import> imports = res.imports(); + for ( std::vector<GpgME::Import>::const_iterator it = imports.begin() ; it != imports.end() ; ++it ) + mPreviouslySelectedFingerprints.insert( it->fingerprint() ); + } + importNextURLOrRedisplay(); +} + + + +/** + This slot is called when the dirmngr process that imports a + certificate file exists. +*/ +void CertManager::slotDirmngrExited() { + if ( !mDirmngrProc->normalExit() ) + KMessageBox::error( this, i18n( "The GpgSM process that tried to import the CRL file ended prematurely because of an unexpected error." ), i18n( "Certificate Manager Error" ) ); + else if ( mDirmngrProc->exitStatus() ) + KMessageBox::error( this, i18n( "An error occurred when trying to import the CRL file. The output from GpgSM was:\n%1").arg( mErrorbuffer ), i18n( "Certificate Manager Error" ) ); + else + KMessageBox::information( this, i18n( "CRL file imported successfully." ), i18n( "Certificate Manager Information" ) ); + + delete mDirmngrProc; mDirmngrProc = 0; + if ( !mImportCRLTempFile.isEmpty() ) + QFile::remove( mImportCRLTempFile ); + updateImportActions( true ); +} + +/** + This slot will import CRLs from a file. +*/ +void CertManager::importCRLFromFile() { + QString filter = QString("*.crl *.arl *-crl.der *-arl.der|") + i18n("Certificate Revocation List (*.crl *.arl *-crl.der *-arl.der)"); + KURL url = KFileDialog::getOpenURL( QString::null, + filter, + this, + i18n( "Select CRL File" ) ); + if ( url.isValid() ) { + updateImportActions( false ); + if ( url.isLocalFile() ) { + startImportCRL( url.path(), false ); + updateImportActions( true ); + } else { + KTempFile tempFile; + KURL destURL; + destURL.setPath( tempFile.name() ); + KIO::Job* copyJob = KIO::file_copy( url, destURL, 0600, true, false ); + copyJob->setWindow( this ); + connect( copyJob, SIGNAL( result( KIO::Job * ) ), + SLOT( slotImportCRLJobFinished( KIO::Job * ) ) ); + } + } +} + +void CertManager::slotImportCRLJobFinished( KIO::Job *job ) +{ + KIO::FileCopyJob* fcjob = static_cast<KIO::FileCopyJob*>( job ); + QString tempFilePath = fcjob->destURL().path(); + if ( job->error() ) { + job->showErrorDialog(); + QFile::remove( tempFilePath ); // unlink tempfile + updateImportActions( true ); + return; + } + startImportCRL( tempFilePath, true ); +} + +bool CertManager::connectAndStartDirmngr( const char * slot, const char * processname ) { + assert( slot ); + assert( processname ); + assert( mDirmngrProc ); + mErrorbuffer = QString::null; + connect( mDirmngrProc, SIGNAL(processExited(KProcess*)), slot ); + connect( mDirmngrProc, SIGNAL(receivedStderr(KProcess*,char*,int) ), + this, SLOT(slotStderr(KProcess*,char*,int)) ); + if( !mDirmngrProc->start( KProcess::NotifyOnExit, KProcess::Stderr ) ) { + delete mDirmngrProc; mDirmngrProc = 0; + KMessageBox::error( this, i18n( "Unable to start %1 process. Please check your installation." ).arg( processname ), i18n( "Certificate Manager Error" ) ); + return false; + } + return true; +} + +void CertManager::startImportCRL( const QString& filename, bool isTempFile ) +{ + assert( !mDirmngrProc ); + mImportCRLTempFile = isTempFile ? filename : QString::null; + mDirmngrProc = new KProcess(); + *mDirmngrProc << "gpgsm" << "--call-dirmngr" << "loadcrl" << filename; + if ( !connectAndStartDirmngr( SLOT(slotDirmngrExited()), "gpgsm" ) ) { + updateImportActions( true ); + if ( isTempFile ) + QFile::remove( mImportCRLTempFile ); // unlink tempfile + } +} + +void CertManager::startClearCRLs() { + assert( !mDirmngrProc ); + mDirmngrProc = new KProcess(); + *mDirmngrProc << "dirmngr" << "--flush"; + //*mDirmngrProc << "gpgsm" << "--call-dimngr" << "flush"; // use this once it's implemented! + connectAndStartDirmngr( SLOT(slotClearCRLsResult()), "dirmngr" ); +} + +void CertManager::slotStderr( KProcess*, char* buf, int len ) { + mErrorbuffer += QString::fromLocal8Bit( buf, len ); +} + +/** + This slot will import CRLs from an LDAP server. +*/ +void CertManager::importCRLFromLDAP() +{ + qDebug("Not Yet Implemented"); +} + +void CertManager::slotViewCRLs() { + if ( !mCrlView ) + mCrlView = new CRLView( this ); + + mCrlView->show(); + mCrlView->slotUpdateView(); +} + + +void CertManager::slotClearCRLs() { + startClearCRLs(); +} + +void CertManager::slotClearCRLsResult() { + assert( mDirmngrProc ); + if ( !mDirmngrProc->normalExit() ) + KMessageBox::error( this, i18n( "The DirMngr process that tried to clear the CRL cache ended prematurely because of an unexpected error." ), i18n( "Certificate Manager Error" ) ); + else if ( mDirmngrProc->exitStatus() ) + KMessageBox::error( this, i18n( "An error occurred when trying to clear the CRL cache. The output from DirMngr was:\n%1").arg( mErrorbuffer ), i18n( "Certificate Manager Error" ) ); + else + KMessageBox::information( this, i18n( "CRL cache cleared successfully." ), i18n( "Certificate Manager Information" ) ); + delete mDirmngrProc; mDirmngrProc = 0; +} + +static void showDeleteError( QWidget * parent, const GpgME::Error & err ) { + assert( err ); + const QString msg = i18n("<qt><p>An error occurred while trying to delete " + "the certificates:</p>" + "<p><b>%1</b></p></qt>") + .arg( QString::fromLocal8Bit( err.asString() ) ); + KMessageBox::error( parent, msg, i18n("Certificate Deletion Failed") ); +} + +static bool ByFingerprint( const GpgME::Key & left, const GpgME::Key & right ) { + return qstricmp( left.primaryFingerprint(), right.primaryFingerprint() ) < 0 ; +} + +static bool WithRespectToFingerprints( const GpgME::Key & left, const GpgME::Key & right ) { + return qstricmp( left.primaryFingerprint(), right.primaryFingerprint() ) == 0; +} + +void CertManager::slotDeleteCertificate() { + mItemsToDelete = mKeyListView->selectedItems(); + if ( mItemsToDelete.isEmpty() ) + return; + std::vector<GpgME::Key> keys; + keys.reserve( mItemsToDelete.count() ); + QStringList keyDisplayNames; + for ( QPtrListIterator<Kleo::KeyListViewItem> it( mItemsToDelete ) ; it.current() ; ++it ) + if ( !it.current()->key().isNull() ) { + keys.push_back( it.current()->key() ); + keyDisplayNames.push_back( it.current()->text( 0 ) ); + } + if ( keys.empty() ) + return; + + if ( !mHierarchyAnalyser ) { + mHierarchyAnalyser = new HierarchyAnalyser( this, "mHierarchyAnalyser" ); + Kleo::KeyListJob * job = Kleo::CryptoBackendFactory::instance()->smime()->keyListJob(); + assert( job ); + connect( job, SIGNAL(nextKey(const GpgME::Key&)), + mHierarchyAnalyser, SLOT(slotNextKey(const GpgME::Key&)) ); + connect( job, SIGNAL(result(const GpgME::KeyListResult&)), + this, SLOT(slotDeleteCertificate()) ); + connectJobToStatusBarProgress( job, i18n("Checking key dependencies...") ); + if ( const GpgME::Error error = job->start( QStringList() ) ) { + showKeyListError( this, error ); + delete mHierarchyAnalyser; mHierarchyAnalyser = 0; + } + return; + } else + disconnectJobFromStatusBarProgress( 0 ); + + std::vector<GpgME::Key> keysToDelete = keys; + for ( std::vector<GpgME::Key>::const_iterator it = keys.begin() ; it != keys.end() ; ++it ) + if ( !it->isNull() ) { + const std::vector<GpgME::Key> subjects + = mHierarchyAnalyser->subjectsForIssuerRecursive( it->primaryFingerprint() ); + keysToDelete.insert( keysToDelete.end(), subjects.begin(), subjects.end() ); + } + + std::sort( keysToDelete.begin(), keysToDelete.end(), ByFingerprint ); + keysToDelete.erase( std::unique( keysToDelete.begin(), keysToDelete.end(), + WithRespectToFingerprints ), + keysToDelete.end() ); + + delete mHierarchyAnalyser; mHierarchyAnalyser = 0; + + if ( keysToDelete.size() > keys.size() ) + if ( KMessageBox::warningContinueCancel( this, + i18n("Some or all of the selected " + "certificates are issuers (CA certificates) " + "for other, non-selected certificates.\n" + "Deleting a CA certificate will also delete " + "all certificates issued by it."), + i18n("Deleting CA Certificates") ) + != KMessageBox::Continue ) + return; + + const QString msg = keysToDelete.size() > keys.size() + ? i18n("Do you really want to delete this certificate and the %1 certificates it certified?", + "Do you really want to delete these %n certificates and the %1 certificates they certified?", + keys.size() ).arg( keysToDelete.size() - keys.size() ) + : i18n("Do you really want to delete this certificate?", + "Do you really want to delete these %n certificates?", keys.size() ) ; + + if ( KMessageBox::warningContinueCancelList( this, msg, keyDisplayNames, + i18n( "Delete Certificates" ), + KGuiItem( i18n( "Delete" ), "editdelete" ), + "ConfirmDeleteCert", KMessageBox::Dangerous ) + != KMessageBox::Continue ) + return; + + if ( Kleo::DeleteJob * job = Kleo::CryptoBackendFactory::instance()->smime()->deleteJob() ) + job->slotCancel(); + else { + QString str = keys.size() == 1 + ? i18n("<qt><p>An error occurred while trying to delete " + "the certificate:</p>" + "<p><b>%1</b><p></qt>" ) + : i18n( "<qt><p>An error occurred while trying to delete " + "the certificates:</p>" + "<p><b>%1</b><p></qt>" ); + KMessageBox::error( this, + str.arg( i18n("Operation not supported by the backend.") ), + i18n("Certificate Deletion Failed") ); + } + + mItemsToDelete.clear(); // re-create according to the real selection + for ( std::vector<GpgME::Key>::const_iterator it = keysToDelete.begin() ; it != keysToDelete.end() ; ++it ) + if ( Kleo::KeyListViewItem * item = mKeyListView->itemByFingerprint( it->primaryFingerprint() ) ) + mItemsToDelete.append( item ); + + Kleo::MultiDeleteJob * job = new Kleo::MultiDeleteJob( Kleo::CryptoBackendFactory::instance()->smime() ); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::Error&,const GpgME::Key&)), + SLOT(slotDeleteResult(const GpgME::Error&,const GpgME::Key&)) ); + + connectJobToStatusBarProgress( job, i18n("Deleting keys...") ); + + const GpgME::Error err = job->start( keys, true ); + if ( err ) + showDeleteError( this, err ); + else + mProgressBar->setProgress( 0, 0 ); +} + +void CertManager::slotDeleteResult( const GpgME::Error & err, const GpgME::Key & ) { + if ( err ) + showDeleteError( this, err ); + else { + const int infinity = 100; // infinite loop guard... + mItemsToDelete.setAutoDelete( true ); + for ( int i = 0 ; i < infinity ; ++i ) { + QPtrListIterator<Kleo::KeyListViewItem> it( mItemsToDelete ); + while ( Kleo::KeyListViewItem * cur = it.current() ) { + ++it; + if ( cur->childCount() == 0 ) { + mItemsToDelete.remove( cur ); + } + } + if ( mItemsToDelete.isEmpty() ) + break; + } + mItemsToDelete.setAutoDelete( false ); + Q_ASSERT( mItemsToDelete.isEmpty() ); + mItemsToDelete.clear(); + } + disconnectJobFromStatusBarProgress( err ); +} + +void CertManager::slotViewDetails( Kleo::KeyListViewItem * item ) { + if ( !item || item->key().isNull() ) + return; + + // <UGH> + KDialogBase * dialog = new KDialogBase( this, "dialog", false, i18n("Additional Information for Key"), KDialogBase::Close, KDialogBase::Close ); + + CertificateInfoWidgetImpl * top = new CertificateInfoWidgetImpl( item->key(), isRemote(), dialog ); + dialog->setMainWidget( top ); + // </UGH> + connect( top, SIGNAL(requestCertificateDownload(const QString&, const QString&)), + SLOT(slotStartCertificateDownload(const QString&, const QString&)) ); + dialog->show(); +} + +void CertManager::slotViewDetails() +{ + QPtrList<Kleo::KeyListViewItem> items = mKeyListView->selectedItems(); + if ( items.isEmpty() ) + return; + + // selectedItem() doesn't work in Extended mode. + // But we only want to show the details of one item... + slotViewDetails( items.first() ); +} + +void CertManager::slotSelectionChanged() +{ + mKeyListView->flushKeys(); + bool b = mKeyListView->hasSelection(); + mExportCertificateAction->setEnabled( b ); + mViewCertDetailsAction->setEnabled( b ); + mDeleteCertificateAction->setEnabled( b ); +#ifdef NOT_IMPLEMENTED_ANYWAY + mRevokeCertificateAction->setEnabled( b ); + mExtendCertificateAction->setEnabled( b ); +#endif + mDownloadCertificateAction->setEnabled( b && mRemote ); + mValidateCertificateAction->setEnabled( !mRemote ); +} + +void CertManager::slotExportCertificate() { + QPtrList<Kleo::KeyListViewItem> items = mKeyListView->selectedItems(); + if ( items.isEmpty() ) + return; + + QStringList fingerprints; + for ( QPtrListIterator<Kleo::KeyListViewItem> it( items ) ; it.current() ; ++it ) + if ( !it.current()->key().isNull() ) + if ( const char * fpr = it.current()->key().primaryFingerprint() ) + fingerprints.push_back( fpr ); + + startCertificateExport( fingerprints ); +} + +static void showCertificateExportError( QWidget * parent, const GpgME::Error & err ) { + assert( err ); + const QString msg = i18n("<qt><p>An error occurred while trying to export " + "the certificate:</p>" + "<p><b>%1</b></p></qt>") + .arg( QString::fromLocal8Bit( err.asString() ) ); + KMessageBox::error( parent, msg, i18n("Certificate Export Failed") ); +} + +void CertManager::startCertificateExport( const QStringList & fingerprints ) { + if ( fingerprints.empty() ) + return; + + // we need to use PEM (ascii armoured) format, since DER (binary) + // can't transport more than one certificate *sigh* this is madness :/ + Kleo::ExportJob * job = Kleo::CryptoBackendFactory::instance()->smime()->publicKeyExportJob( true ); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::Error&,const QByteArray&)), + SLOT(slotCertificateExportResult(const GpgME::Error&,const QByteArray&)) ); + + connectJobToStatusBarProgress( job, i18n("Exporting certificate...") ); + + const GpgME::Error err = job->start( fingerprints ); + if ( err ) + showCertificateExportError( this, err ); + else + mProgressBar->setProgress( 0, 0 ); +} + +// return true if we should proceed, false if we should abort +static bool checkOverwrite( const KURL& url, bool& overwrite, QWidget* w ) +{ + if ( KIO::NetAccess::exists( url, false /*dest*/, w ) ) { + if ( KMessageBox::Cancel == + KMessageBox::warningContinueCancel( + w, + i18n( "A file named \"%1\" already exists. " + "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ), + i18n( "Overwrite File?" ), + i18n( "&Overwrite" ) ) ) + return false; + overwrite = true; + } + return true; +} + +void CertManager::slotCertificateExportResult( const GpgME::Error & err, const QByteArray & data ) { + disconnectJobFromStatusBarProgress( err ); + if ( err ) { + showCertificateExportError( this, err ); + return; + } + + kdDebug() << "CertManager::slotCertificateExportResult(): got " << data.size() << " bytes" << endl; + + const QString filter = QString("*.pem|") + i18n("ASCII Armored Certificate Bundles (*.pem)"); + const KURL url = KFileDialog::getOpenURL( QString::null, + filter, + this, + i18n( "Save Certificate" ) ); + if ( !url.isValid() ) + return; + + bool overwrite = false; + if ( !checkOverwrite( url, overwrite, this ) ) + return; + + KIO::Job* uploadJob = KIOext::put( data, url, -1, overwrite, false /*resume*/ ); + uploadJob->setWindow( this ); + connect( uploadJob, SIGNAL( result( KIO::Job* ) ), + this, SLOT( slotUploadResult( KIO::Job* ) ) ); +} + + +void CertManager::slotExportSecretKey() { + Kleo::KeySelectionDialog dlg( i18n("Secret Key Export"), + i18n("Select the secret key to export " + "(<b>Warning: The PKCS#12 format is insecure; " + "exporting secret keys is discouraged</b>):"), + std::vector<GpgME::Key>(), + Kleo::KeySelectionDialog::SecretKeys|Kleo::KeySelectionDialog::SMIMEKeys, + false /* no multiple selection */, + false /* no remember choice box */, + this, "secret key export key selection dialog" ); + //dlg.setHideInvalidKeys( false ); + + if ( dlg.exec() != QDialog::Accepted ) + return; + + startSecretKeyExport( dlg.fingerprint() ); +} + +static void showSecretKeyExportError( QWidget * parent, const GpgME::Error & err ) { + assert( err ); + const QString msg = i18n("<qt><p>An error occurred while trying to export " + "the secret key:</p>" + "<p><b>%1</b></p></qt>") + .arg( QString::fromLocal8Bit( err.asString() ) ); + KMessageBox::error( parent, msg, i18n("Secret-Key Export Failed") ); +} + +void CertManager::startSecretKeyExport( const QString & fingerprint ) { + if ( fingerprint.isEmpty() ) + return; + + // PENDING(marc): let user choose between binary and PEM format? + + // Check if gpgsm supports --p12-charset + Kleo::CryptoConfig* config = Kleo::CryptoBackendFactory::instance()->config(); + QString charset; + if ( config && config->entry( "gpgsm", "Configuration", "p12-charset" ) ) { + // This comes from gnupg's sources, agent/minip12.c + // In fact, any charset supported by iconv would work, but we don't link to iconv directly... + static const char *charsets[] = { + "utf8", + "iso-8859-1", + "iso-8859-15", + "iso-8859-2", + "iso-8859-3", + "iso-8859-4", + "iso-8859-5", + "iso-8859-6", + "iso-8859-7", + "iso-8859-8", + "iso-8859-9", + "koi8-r", + "ibm437", + "ibm850", + "euc-jp", + "big5", + NULL + }; + QStringList charsetList; + for ( const char** c = charsets; *c; ++c ) { + charsetList.append( QString::fromLatin1( *c ) ); + } + + // TODO this selection could be done in a derived KeySelectionDialog which would add a combobox, + // it would be better integrated. + bool ok; + charset = KInputDialog::getItem( i18n("Exporting secret key..."), + i18n("Choose a charset for encoding the pkcs#12 passphrase (utf8 is recommended)"), + charsetList, + 0, false /*editable*/, + &ok, this ); + if ( !ok ) + return; + } + + Kleo::ExportJob * job = Kleo::CryptoBackendFactory::instance()->smime()->secretKeyExportJob( false, charset ); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::Error&,const QByteArray&)), + SLOT(slotSecretKeyExportResult(const GpgME::Error&,const QByteArray&)) ); + + connectJobToStatusBarProgress( job, i18n("Exporting secret key...") ); + + const GpgME::Error err = job->start( fingerprint ); + if ( err ) + showSecretKeyExportError( this, err ); + else + mProgressBar->setProgress( 0, 0 ); +} + +void CertManager::slotSecretKeyExportResult( const GpgME::Error & err, const QByteArray & data ) { + disconnectJobFromStatusBarProgress( err ); + if ( err ) { + showSecretKeyExportError( this, err ); + return; + } + + kdDebug() << "CertManager::slotSecretKeyExportResult(): got " << data.size() << " bytes" << endl; + QString filter = QString("*.p12|") + i18n("PKCS#12 Key Bundle (*.p12)"); + KURL url = KFileDialog::getOpenURL( QString::null, + filter, + this, + i18n( "Save Certificate" ) ); + if ( !url.isValid() ) + return; + + bool overwrite = false; + if ( !checkOverwrite( url, overwrite, this ) ) + return; + + KIO::Job* uploadJob = KIOext::put( data, url, -1, overwrite, false /*resume*/ ); + uploadJob->setWindow( this ); + connect( uploadJob, SIGNAL( result( KIO::Job* ) ), + this, SLOT( slotUploadResult( KIO::Job* ) ) ); +} + +void CertManager::slotUploadResult( KIO::Job* job ) +{ + if ( job->error() ) + job->showErrorDialog(); +} + +void CertManager::slotDropped(const KURL::List& lst) +{ + mURLsToImport = lst; + if ( !lst.empty() ) + importNextURLOrRedisplay(); +} + +void CertManager::importNextURLOrRedisplay() +{ + if ( !mURLsToImport.empty() ) { + // We can only import them one by one, otherwise the jobs would run into each other + KURL url = mURLsToImport.front(); + mURLsToImport.pop_front(); + slotImportCertFromFile( url ); + } else { + if ( isRemote() ) + return; + startKeyListing( false, true, mPreviouslySelectedFingerprints ); + } +} + +void CertManager::slotStartWatchGnuPG() +{ + KProcess certManagerProc; + certManagerProc << "kwatchgnupg"; + + if( !certManagerProc.start( KProcess::DontCare ) ) + KMessageBox::error( this, i18n( "Could not start GnuPG LogViewer (kwatchgnupg). " + "Please check your installation!" ), + i18n( "Kleopatra Error" ) ); +} + +#include "certmanager.moc" |