/* 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 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // GPGME++ #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // other #include #include #include #include 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( 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 extractKeyFingerprints( const QPtrList & items ) { std::set result; for ( QPtrListIterator it( items ) ; it.current() ; ++it ) if ( const char * fpr = it.current()->key().primaryFingerprint() ) result.insert( fpr ); return result; } static QStringList stringlistFromSet( const std::set & set ) { // ARGH. This is madness. Shitty Qt containers don't support QStringList( patterns.begin(), patterns.end() ) :/ QStringList sl; for ( std::set::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( "

An error occurred while fetching " "the certificates from the backend:

" "

%1

" ) .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 & 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 & fprs ) { if ( !lv || fprs.empty() ) return; for ( QListViewItemIterator it( lv ) ; it.current() ; ++it ) if ( Kleo::KeyListViewItem * item = Kleo::lvi_cast( 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(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( 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( "

An error occurred while trying " "to download the certificate %1:

" "

%2

" ) .arg( certDisplayName ) .arg( QString::fromLocal8Bit( err.asString() ) ); KMessageBox::error( parent, msg, i18n( "Certificate Download Failed" ) ); } void CertManager::slotDownloadCertificate() { mPreviouslySelectedFingerprints.clear(); QPtrList items = mKeyListView->selectedItems(); for ( QPtrListIterator 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( 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( "

An error occurred while trying " "to import the certificate %1:

" "

%2

" ) .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( sender() ) ); if ( res.error().isCanceled() ) { // do nothing } else if ( res.error() ) { showCertificateImportError( this, res.error(), displayName ); } else { const QString normalLine = i18n("%1%2"); const QString boldLine = i18n("%1%2"); 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 not 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( "

Detailed results of importing %1:

" "%2
" ) .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 imports = res.imports(); for ( std::vector::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( 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("

An error occurred while trying to delete " "the certificates:

" "

%1

") .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 keys; keys.reserve( mItemsToDelete.count() ); QStringList keyDisplayNames; for ( QPtrListIterator 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 keysToDelete = keys; for ( std::vector::const_iterator it = keys.begin() ; it != keys.end() ; ++it ) if ( !it->isNull() ) { const std::vector 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("

An error occurred while trying to delete " "the certificate:

" "

%1

" ) : i18n( "

An error occurred while trying to delete " "the certificates:

" "

%1

" ); 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::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 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; // 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 ); // connect( top, SIGNAL(requestCertificateDownload(const QString&, const QString&)), SLOT(slotStartCertificateDownload(const QString&, const QString&)) ); dialog->show(); } void CertManager::slotViewDetails() { QPtrList 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 items = mKeyListView->selectedItems(); if ( items.isEmpty() ) return; QStringList fingerprints; for ( QPtrListIterator 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("

An error occurred while trying to export " "the certificate:

" "

%1

") .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 " "(Warning: The PKCS#12 format is insecure; " "exporting secret keys is discouraged):"), std::vector(), 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("

An error occurred while trying to export " "the secret key:

" "

%1

") .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"