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/certificatewizardimpl.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/certificatewizardimpl.cpp')
-rw-r--r-- | certmanager/certificatewizardimpl.cpp | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/certmanager/certificatewizardimpl.cpp b/certmanager/certificatewizardimpl.cpp new file mode 100644 index 000000000..a88c54d84 --- /dev/null +++ b/certmanager/certificatewizardimpl.cpp @@ -0,0 +1,515 @@ +/* + certificatewizardimpl.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2001,2002,2004 Klar�vdalens 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 "certificatewizardimpl.h" +#include "storedtransferjob.h" + +// libkleopatra +#include <kleo/oidmap.h> +#include <kleo/keygenerationjob.h> +#include <kleo/dn.h> +#include <kleo/cryptobackendfactory.h> + +#include <ui/progressdialog.h> + +// gpgme++ +#include <gpgmepp/keygenerationresult.h> + +// KDE +#include <kabc/stdaddressbook.h> +#include <kabc/addressee.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kdialog.h> +#include <kurlrequester.h> +#include <kdcopservicestarter.h> +#include <dcopclient.h> +#include <kio/job.h> +#include <kio/netaccess.h> + +// Qt +#include <qlineedit.h> +#include <qtextedit.h> +#include <qpushbutton.h> +#include <qcheckbox.h> +#include <qradiobutton.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qcombobox.h> + +#include <assert.h> +#include <dcopref.h> + +static const unsigned int keyLengths[] = { + 1024, 1532, 2048, 3072, 4096 +}; +static const unsigned int numKeyLengths = sizeof keyLengths / sizeof *keyLengths; + +static QString attributeLabel( const QString & attr, bool required ) { + if ( attr.isEmpty() ) + return QString::null; + const QString label = Kleo::DNAttributeMapper::instance()->name2label( attr ); + if ( !label.isEmpty() ) + if ( required ) + return i18n("Format string for the labels in the \"Your Personal Data\" page - required field", + "*%1 (%2):").arg( label, attr ); + else + return i18n("Format string for the labels in the \"Your Personal Data\" page", + "%1 (%2):").arg( label, attr ); + + else if ( required ) + return '*' + attr + ':'; + else + return attr + ':'; +} + +static QString attributeFromKey( QString key ) { + return key.remove( '!' ); +} + +static bool availForMod( const QLineEdit * le ) { + return le && le->isEnabled(); +} + +/* + * Constructs a CertificateWizardImpl which is a child of 'parent', with the + * name 'name' and widget flags set to 'f' + * + * The wizard will by default be modeless, unless you set 'modal' to + * TRUE to construct a modal wizard. + */ +CertificateWizardImpl::CertificateWizardImpl( QWidget* parent, const char* name, bool modal, WFlags fl ) + : CertificateWizard( parent, name, modal, fl ) +{ + // don't allow to go to last page until a key has been generated + setNextEnabled( generatePage, false ); + // setNextEnabled( personalDataPage, false ); // ## disable again once we have a criteria when to enable again + + createPersonalDataPage(); + + // Allow to select remote URLs + storeUR->setMode( KFile::File ); + storeUR->setFilter( "application/pkcs10" ); + connect( storeUR, SIGNAL( urlSelected( const QString& ) ), + this, SLOT( slotURLSelected( const QString& ) ) ); + + const KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" ); + caEmailED->setText( config.readEntry( "CAEmailAddress" ) ); + + connect( this, SIGNAL( helpClicked() ), + this, SLOT( slotHelpClicked() ) ); + connect( insertAddressButton, SIGNAL( clicked() ), + this, SLOT( slotSetValuesFromWhoAmI() ) ); + + for ( unsigned int i = 0 ; i < numKeyLengths ; ++i ) + keyLengthCB->insertItem( i18n("%n bit", "%n bits", keyLengths[i] ) ); +} + +static bool requirementsAreMet( const CertificateWizardImpl::AttrPairList & list ) { + for ( CertificateWizardImpl::AttrPairList::const_iterator it = list.begin() ; + it != list.end() ; ++it ) { + const QLineEdit * le = (*it).second; + if ( !le ) + continue; + const QString key = (*it).first; +#ifndef NDEBUG + kdbgstream s = kdDebug(); +#else + kndbgstream s = kdDebug(); +#endif + s << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\": "; + if ( key.endsWith("!") && le->text().stripWhiteSpace().isEmpty() ) { + s << "required field is empty!" << endl; + return false; + } + s << "ok" << endl; + } + return true; +} + +/* + This slot is called when the user changes the text. + */ +void CertificateWizardImpl::slotEnablePersonalDataPageExit() { + setNextEnabled( personalDataPage, requirementsAreMet( _attrPairList ) ); +} + + +/* + * Destroys the object and frees any allocated resources + */ +CertificateWizardImpl::~CertificateWizardImpl() +{ + // no need to delete child widgets, Qt does it all for us +} + +static const char * oidForAttributeName( const QString & attr ) { + QCString attrUtf8 = attr.utf8(); + for ( unsigned int i = 0 ; i < numOidMaps ; ++i ) + if ( qstricmp( attrUtf8, oidmap[i].name ) == 0 ) + return oidmap[i].oid; + return 0; +} + +/* + * protected slot + */ +void CertificateWizardImpl::slotGenerateCertificate() +{ + // Ask gpgme to generate a key and return it + QString certParms; + certParms += "<GnupgKeyParms format=\"internal\">\n"; + certParms += "Key-Type: RSA\n"; + certParms += QString( "Key-Length: %1\n" ).arg( keyLengths[keyLengthCB->currentItem()] ); + certParms += "Key-Usage: "; + if ( signOnlyCB->isChecked() ) + certParms += "Sign"; + else if ( encryptOnlyCB->isChecked() ) + certParms += "Encrypt"; + else + certParms += "Sign, Encrypt"; + certParms += "\n"; + certParms += "name-dn: "; + + QString email; + QStringList rdns; + for( AttrPairList::const_iterator it = _attrPairList.begin(); it != _attrPairList.end(); ++it ) { + const QString attr = attributeFromKey( (*it).first.upper() ); + const QLineEdit * le = (*it).second; + if ( !le ) + continue; + + const QString value = le->text().stripWhiteSpace(); + if ( value.isEmpty() ) + continue; + + if ( attr == "EMAIL" ) { + // EMAIL is special, since it shouldn't be part of the DN, + // except for non-RFC-conformant CAs that require it to be + // there. + email = value; + if ( !brokenCA->isChecked() ) + continue; + } + + if ( const char * oid = oidForAttributeName( attr ) ) { + // we need to translate the attribute name for the backend: + rdns.push_back( QString::fromUtf8( oid ) + '=' + Kleo::DN::escape( value ) ); + } else { + rdns.push_back( attr + '=' + Kleo::DN::escape( value ) ); + } + } + certParms += rdns.join(","); + if( !email.isEmpty() ) + certParms += "\nname-email: " + email; + certParms += "\n</GnupgKeyParms>\n"; + + kdDebug() << certParms << endl; + + Kleo::KeyGenerationJob * job = + Kleo::CryptoBackendFactory::instance()->smime()->keyGenerationJob(); + assert( job ); + + connect( job, SIGNAL(result(const GpgME::KeyGenerationResult&,const QByteArray&)), + SLOT(slotResult(const GpgME::KeyGenerationResult&,const QByteArray&)) ); + + certificateTE->setText( certParms ); + + const GpgME::Error err = job->start( certParms ); + if ( err ) + KMessageBox::error( this, + i18n( "Could not start certificate generation: %1" ) + .arg( QString::fromLocal8Bit( err.asString() ) ), + i18n( "Certificate Manager Error" ) ); + else { + generatePB->setEnabled( false ); + setBackEnabled( generatePage, false ); + (void)new Kleo::ProgressDialog( job, i18n("Generating key"), this ); + } +} + + +void CertificateWizardImpl::slotResult( const GpgME::KeyGenerationResult & res, + const QByteArray & keyData ) { + //kdDebug() << "keyData.size(): " << keyData.size() << endl; + _keyData = keyData; + + if ( res.error().isCanceled() || res.error() ) { + setNextEnabled( generatePage, false ); + setBackEnabled( generatePage, true ); + setFinishEnabled( finishPage, false ); + generatePB->setEnabled( true ); + if ( !res.error().isCanceled() ) + KMessageBox::error( this, + i18n( "Could not generate certificate: %1" ) + .arg( QString::fromLatin1( res.error().asString() ) ), + i18n( "Certificate Manager Error" ) ); + } else { + // next will stay enabled until the user clicks Generate + // Certificate again + setNextEnabled( generatePage, true ); + setFinishEnabled( finishPage, true ); + } +} + +void CertificateWizardImpl::slotHelpClicked() +{ + kapp->invokeHelp( "newcert" ); +} + +void CertificateWizardImpl::slotSetValuesFromWhoAmI() +{ + const KABC::Addressee a = KABC::StdAddressBook::self( true )->whoAmI(); + if ( a.isEmpty() ) + return; + const KABC::Address adr = a.address(KABC::Address::Work); + + for ( AttrPairList::const_iterator it = _attrPairList.begin() ; + it != _attrPairList.end() ; ++it ) { + QLineEdit * le = (*it).second; + if ( !availForMod( le ) ) + continue; + + const QString attr = attributeFromKey( (*it).first.upper() ); + if ( attr == "CN" ) + le->setText( a.formattedName() ); + else if ( attr == "EMAIL" ) + le->setText( a.preferredEmail() ); + else if ( attr == "O" ) + le->setText( a.organization() ); + else if ( attr == "OU" ) + le->setText( a.custom( "KADDRESSBOOK", "X-Department" ) ); + else if ( attr == "L" ) + le->setText( adr.locality() ); + else if ( attr == "SP" ) + le->setText( adr.region() ); + else if ( attr == "PC" ) + le->setText( adr.postalCode() ); + else if ( attr == "SN" ) + le->setText( a.familyName() ); + else if ( attr == "GN" ) + le->setText( a.givenName() ); + else if ( attr == "T" ) + le->setText( a.title() ); + else if ( attr == "BC" ) + le->setText( a.role() ); // correct mapping? + } +} + +void CertificateWizardImpl::createPersonalDataPage() +{ + QGridLayout* grid = new QGridLayout( edContainer, 2, 1, + KDialog::marginHint(), KDialog::spacingHint() ); + + KConfigGroup config( KGlobal::config(), "CertificateCreationWizard" ); + QStringList attrOrder = config.readListEntry( "DNAttributeOrder" ); + if ( attrOrder.empty() ) + attrOrder << "CN!" << "L" << "OU" << "O!" << "C!" << "EMAIL!"; + int row = 0; + + for ( QStringList::const_iterator it = attrOrder.begin() ; it != attrOrder.end() ; ++it, ++row ) { + const QString key = (*it).stripWhiteSpace().upper(); + const QString attr = attributeFromKey( key ); + if ( attr.isEmpty() ) { + --row; + continue; + } + const QString preset = config.readEntry( attr ); + const QString label = config.readEntry( attr + "_label", + attributeLabel( attr, key.endsWith("!") ) ); + + QLineEdit * le = new QLineEdit( edContainer ); + grid->addWidget( le, row, 1 ); + grid->addWidget( new QLabel( le, label.isEmpty() ? attr : label, edContainer ), row, 0 ); + + le->setText( preset ); + if ( config.entryIsImmutable( attr ) ) + le->setEnabled( false ); + + _attrPairList.append(qMakePair(key, le)); + + connect( le, SIGNAL(textChanged(const QString&)), + SLOT(slotEnablePersonalDataPageExit()) ); + } + + // enable button only if administrator wants to allow it + if (KABC::StdAddressBook::self( true )->whoAmI().isEmpty() || + !config.readBoolEntry("ShowSetWhoAmI", true)) + insertAddressButton->setEnabled( false ); + + slotEnablePersonalDataPageExit(); +} + +bool CertificateWizardImpl::sendToCA() const { + return sendToCARB->isChecked(); +} + +QString CertificateWizardImpl::caEMailAddress() const { + return caEmailED->text().stripWhiteSpace(); +} + +void CertificateWizardImpl::slotURLSelected( const QString& _url ) +{ + KURL url = KURL::fromPathOrURL( _url.stripWhiteSpace() ); +#if ! KDE_IS_VERSION(3,2,90) + // The application/pkcs10 mimetype didn't have a native extension, + // so the filedialog didn't have the checkbox for auto-adding it. + QString fileName = url.fileName(); + int pos = fileName.findRev( '.' ); + if ( pos < 0 ) // no extension + url.setFileName( fileName + ".p10" ); +#endif + storeUR->setURL( url.prettyURL() ); +} + +KURL CertificateWizardImpl::saveFileUrl() const { + return KURL::fromPathOrURL( storeUR->url().stripWhiteSpace() ); +} + +void CertificateWizardImpl::showPage( QWidget * page ) +{ + CertificateWizard::showPage( page ); + if ( page == generatePage ) { + // Initial settings for the generation page: focus the correct lineedit + // and disable the other one + if ( storeInFileRB->isChecked() ) { + storeUR->setEnabled( true ); + caEmailED->setEnabled( false ); + storeUR->setFocus(); + } else { + storeUR->setEnabled( false ); + caEmailED->setEnabled( true ); + caEmailED->setFocus(); + } + } +} + +static const char* const dcopObjectId = "KMailIface"; +/** + Send the new certificate by mail using KMail + */ +void CertificateWizardImpl::sendCertificate( const QString& email, const QByteArray& certificateData ) +{ + QString error; + QCString dcopService; + int result = KDCOPServiceStarter::self()-> + findServiceFor( "DCOP/Mailer", QString::null, + QString::null, &error, &dcopService ); + if ( result != 0 ) { + kdDebug() << "Couldn't connect to KMail\n"; + KMessageBox::error( this, + i18n( "DCOP Communication Error, unable to send certificate using KMail.\n%1" ).arg( error ) ); + return; + } + + QCString dummy; + // OK, so kmail (or kontact) is running. Now ensure the object we want is available. + // [that's not the case when kontact was already running, but kmail not loaded into it... in theory.] + if ( !kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ) { + DCOPRef ref( dcopService, dcopService ); // talk to the KUniqueApplication or its kontact wrapper + DCOPReply reply = ref.call( "load()" ); + if ( reply.isValid() && (bool)reply ) { + Q_ASSERT( kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ); + } else + kdWarning() << "Error loading " << dcopService << endl; + } + + DCOPClient* dcopClient = kapp->dcopClient(); + QByteArray data; + QDataStream arg( data, IO_WriteOnly ); + arg << email; + arg << certificateData; + if( !dcopClient->send( dcopService, dcopObjectId, + "sendCertificate(QString,QByteArray)", data ) ) { + KMessageBox::error( this, + i18n( "DCOP Communication Error, unable to send certificate using KMail." ) ); + return; + } + // All good, close dialog + CertificateWizard::accept(); +} + +// Called when pressing Finish +// We want to do the emailing/uploading first, before closing the dialog, +// in case of errors during the upload. +void CertificateWizardImpl::accept() +{ + if( sendToCA() ) { + // Ask KMail to send this key to the CA. + sendCertificate( caEMailAddress(), _keyData ); + } else { + // Save in file/URL + KURL url = saveFileUrl(); + bool overwrite = false; + if ( KIO::NetAccess::exists( url, false /*dest*/, this ) ) { + if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( + this, + i18n( "A file named \"%1\" already exists. " + "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ), + i18n( "Overwrite File?" ), + i18n( "&Overwrite" ) ) ) + return; + overwrite = true; + } + + KIO::Job* uploadJob = KIOext::put( _keyData, url, -1, overwrite, false /*resume*/ ); + uploadJob->setWindow( this ); + connect( uploadJob, SIGNAL( result( KIO::Job* ) ), + this, SLOT( slotUploadResult( KIO::Job* ) ) ); + // Can't press finish again during the upload + setFinishEnabled( finishPage, false ); + } +} + +/** + This slot is invoked by the KIO job used in newCertificate + to save/upload the certificate, when finished (success or error). +*/ +void CertificateWizardImpl::slotUploadResult( KIO::Job* job ) +{ + if ( job->error() ) { + job->showErrorDialog(); + setFinishEnabled( finishPage, true ); + } else { + // All good, close dialog + CertificateWizard::accept(); + } +} + +#include "certificatewizardimpl.moc" |