File indexing completed on 2022-08-04 16:24:07

0001 /*
0002     SPDX-FileCopyrightText: 2009, 2010, 2012, 2013, 2014, 2015, 2016 Rolf Eike Beer <kde@opensource.sf-tec.de>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "caff.h"
0007 #include "caff_p.h"
0008 
0009 #include "kgpg_general_debug.h"
0010 #include "kgpginterface.h"
0011 #include "kgpgsettings.h"
0012 #include "core/KGpgKeyNode.h"
0013 #include "transactions/kgpgdeluid.h"
0014 #include "transactions/kgpgencrypt.h"
0015 #include "transactions/kgpgexport.h"
0016 #include "transactions/kgpgimport.h"
0017 #include "transactions/kgpgsignuid.h"
0018 #include "gpgproc.h"
0019 
0020 #include <KLocalizedString>
0021 #include <KMessageBox>
0022 
0023 
0024 #include <QDesktopServices>
0025 #include <QDir>
0026 #include <QFileInfo>
0027 #include <QTemporaryDir>
0028 
0029 KGpgCaffPrivate::KGpgCaffPrivate(KGpgCaff *parent, const KGpgSignableNode::List &ids, const QStringList &signers,
0030         const KGpgCaff::OperationFlags flags, const KGpgSignTransactionHelper::carefulCheck checklevel)
0031     : QObject(parent),
0032     q_ptr(parent),
0033     m_signers(signers),
0034     m_flags(flags),
0035     m_checklevel(checklevel),
0036     m_gpgVersion(GPGProc::gpgVersion(GPGProc::gpgVersionString(QString()))),
0037     m_allids(ids)
0038 {
0039     const QString gpgCfg = KGpgSettings::gpgConfigPath();
0040     const QString secring = KgpgInterface::getGpgSetting(QLatin1String( "secret-keyring" ), gpgCfg);
0041 
0042     QFileInfo fn(gpgCfg);
0043     if (!secring.isEmpty()) {
0044         m_secringfile = secring;
0045     } else {
0046         fn.setFile(fn.dir(), QLatin1String("secring.gpg"));
0047         m_secringfile = QDir::toNativeSeparators(fn.absoluteFilePath());
0048     }
0049 
0050     fn.setFile(fn.dir(), QLatin1String("private-keys-v1.d"));
0051     m_secringdir = QDir::toNativeSeparators(fn.absoluteFilePath());
0052 }
0053 
0054 KGpgCaffPrivate::~KGpgCaffPrivate()
0055 {
0056 }
0057 
0058 void
0059 KGpgCaffPrivate::reexportKey(const KGpgSignableNode *key)
0060 {
0061     Q_Q(KGpgCaff);
0062 
0063     Q_ASSERT(m_tempdir.isNull());
0064 
0065     // find out if the given id can be used for encryption
0066     const KGpgKeyNode *k;
0067     if (key->getType() & KgpgCore::ITYPE_PAIR)
0068         k = key->toKeyNode();
0069     else
0070         k = key->getParentKeyNode()->toKeyNode();
0071 
0072     // skip if not
0073     if (!k->canEncrypt()) {
0074         m_noEncIds << key;
0075         m_allids.removeFirst();
0076         checkNextLoop();
0077         return;
0078     }
0079 
0080     m_tempdir.reset(new QTemporaryDir());
0081 
0082     if (m_gpgVersion >= 0x20100) {
0083         /* see https://lists.gnupg.org/pipermail/gnupg-devel/2014-December/029296.html */
0084         QFile seclink(m_secringdir);
0085 
0086         if (!seclink.link(m_tempdir->path() + QLatin1String("private-keys-v1.d"))) {
0087             KMessageBox::error(qobject_cast<QWidget *>(q->parent()),
0088                     i18n("This function is not available on this system. The symbolic link to the private GnuPG keys cannot be created."));
0089             return;
0090         }
0091     }
0092 
0093     // export all keys necessary for signing
0094     QStringList exportkeys(m_signers);
0095     exportkeys << key->getKeyNode()->getId();
0096 
0097     KGpgImport *imp = new KGpgImport(this);
0098 
0099     QStringList expOptions(QLatin1String( "--export-options" ));
0100     expOptions << QLatin1String( "export-clean,export-attribute" );
0101     KGpgExport *exp = new KGpgExport(this, exportkeys, expOptions);
0102     exp->setOutputTransaction(imp);
0103 
0104     imp->setGnuPGHome(m_tempdir->path());
0105 
0106     connect(imp, &KGpgImport::done, this, &KGpgCaffPrivate::slotReimportDone);
0107     imp->start();
0108 }
0109 
0110 void
0111 KGpgCaffPrivate::slotReimportDone(int result)
0112 {
0113     KGpgImport *imp = qobject_cast<KGpgImport *>(sender());
0114 
0115     if (result != KGpgTransaction::TS_OK) {
0116         abortOperation(result);
0117     } else {
0118         if (imp->getImportedIds(0x1).count() != 1 + m_signers.count()) {
0119             abortOperation(-1);
0120         } else {
0121             KGpgSignUid *signuid = new KGpgSignUid(this, m_signers.first(), m_allids.first(), false, m_checklevel);
0122             signuid->setGnuPGHome(m_tempdir->path());
0123             if (m_gpgVersion < 0x20100)
0124                 signuid->setSecringFile(m_secringfile);
0125             connect(signuid, &KGpgSignUid::done, this, &KGpgCaffPrivate::slotSigningFinished);
0126 
0127             signuid->start();
0128         }
0129     }
0130 
0131     sender()->deleteLater();
0132 }
0133 
0134 void
0135 KGpgCaffPrivate::abortOperation(int result)
0136 {
0137     Q_Q(KGpgCaff);
0138 
0139     qCDebug(KGPG_LOG_GENERAL) << "transaction" << sender() << "failed, result" << result;
0140     m_tempdir.reset();
0141 
0142     Q_EMIT q->aborted();
0143 }
0144 
0145 void
0146 KGpgCaffPrivate::checkNextLoop()
0147 {
0148     Q_Q(KGpgCaff);
0149 
0150     m_tempdir.reset();
0151 
0152     if (m_allids.isEmpty()) {
0153         if (!m_noEncIds.isEmpty()) {
0154             QStringList ids;
0155 
0156             for (const KGpgSignableNode *nd : qAsConst(m_noEncIds))
0157                 if (nd->getEmail().isEmpty())
0158                     ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid",
0159                             "%1: %2", nd->getId(), nd->getNameComment());
0160                 else
0161                     ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid, %3 is the email address of the uid",
0162                             "%1: %2 &lt;%3&gt;", nd->getId(), nd->getNameComment(), nd->getEmail());
0163 
0164             KMessageBox::detailedSorry(qobject_cast<QWidget *>(q->parent()),
0165                     i18np("No mail was sent for the following user id because it belongs to a key without encryption capability:",
0166                             "No mail was sent for the following user ids because they belong to keys without encryption capability:",
0167                             m_noEncIds.count()),
0168                     ids.join(QLatin1String("\n")));
0169         }
0170 
0171         if (!m_alreadyIds.isEmpty()) {
0172             QStringList ids;
0173 
0174             for (const KGpgSignableNode *nd : qAsConst(m_alreadyIds))
0175                 if (nd->getEmail().isEmpty())
0176                     ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid",
0177                             "%1: %2", nd->getId(), nd->getNameComment());
0178                 else
0179                     ids << i18nc("%1 is the key id, %2 is the name and comment of the key or uid, %3 is the email address of the uid",
0180                             "%1: %2 &lt;%3&gt;", nd->getId(), nd->getNameComment(), nd->getEmail());
0181 
0182             KMessageBox::detailedSorry(qobject_cast<QWidget *>(q->parent()),
0183                     i18np("No mail was sent for the following user id because it was already signed:",
0184                             "No mail was sent for the following user ids because they were already signed:",
0185                             m_alreadyIds.count()),
0186                     ids.join(QLatin1String("\n")));
0187         }
0188 
0189         Q_EMIT q->done();
0190     } else {
0191         reexportKey(m_allids.first());
0192     }
0193 }
0194 
0195 void
0196 KGpgCaffPrivate::slotSigningFinished(int result)
0197 {
0198     sender()->deleteLater();
0199 
0200     if (result != KGpgTransaction::TS_OK) {
0201         if ((result == KGpgSignTransactionHelper::TS_ALREADY_SIGNED) && (m_flags & KGpgCaff::IgnoreAlreadySigned)) {
0202             m_alreadyIds << m_allids.takeFirst();
0203             checkNextLoop();
0204         } else {
0205             abortOperation(result);
0206         }
0207         return;
0208     }
0209 
0210     const KGpgSignableNode *uid = m_allids.first();
0211 
0212     // if there is no email address we can't send this out anyway, so don't bother.
0213     // could be improved: if this is the only selected uid from this key go and select
0214     // a proper mail address to send this to
0215     if (uid->getEmail().isEmpty()) {
0216         m_allids.removeFirst();
0217         checkNextLoop();
0218     }
0219 
0220     const KGpgKeyNode *key = uid->getKeyNode();
0221 
0222     int uidnum;
0223 
0224     if (uid == key) {
0225         uidnum = -1;
0226     } else {
0227         uidnum = -uid->getId().toInt();
0228     }
0229 
0230     KGpgDelUid::RemoveMode removeMode;
0231     switch (KGpgSettings::mailUats()) {
0232     case 0:
0233         removeMode = KGpgDelUid::RemoveWithEmail;
0234         break;
0235     case 1:
0236         if (uid == key) {
0237             removeMode = KGpgDelUid::RemoveWithEmail;
0238         } else {
0239             // check if this is the first uid with email address
0240             const KGpgSignableNode *otherUid;
0241             int index = 1;
0242             removeMode = KGpgDelUid::RemoveAllOther;
0243 
0244             while ( (otherUid = key->getUid(index++)) != nullptr) {
0245                 if (otherUid == uid) {
0246                     removeMode = KGpgDelUid::RemoveWithEmail;
0247                     break;
0248                 }
0249                 if (!otherUid->getEmail().isEmpty())
0250                     break;
0251             }
0252         }
0253         break;
0254     case 2:
0255         removeMode = KGpgDelUid::RemoveAllOther;
0256         break;
0257     default:
0258         Q_ASSERT(0);
0259         return;
0260     }
0261 
0262     KGpgDelUid *deluid = new KGpgDelUid(this, key, uidnum, removeMode);
0263 
0264     deluid->setGnuPGHome(m_tempdir->path());
0265 
0266     connect(deluid, &KGpgDelUid::done, this, &KGpgCaffPrivate::slotDelUidFinished);
0267 
0268     deluid->start();
0269 }
0270 
0271 void
0272 KGpgCaffPrivate::slotDelUidFinished(int result)
0273 {
0274     sender()->deleteLater();
0275 
0276     const KGpgSignableNode *uid = m_allids.first();
0277     const KGpgKeyNode *key = uid->getKeyNode();
0278 
0279     if (result != KGpgTransaction::TS_OK) {
0280         // it's no error if we tried to delete all other ids but there is no other id
0281         if ((uid != key) || (result != KGpgDelUid::TS_NO_SUCH_UID)) {
0282             abortOperation(result);
0283             return;
0284         }
0285     }
0286 
0287     QStringList expOptions(QLatin1String( "--export-options" ));
0288     expOptions << QLatin1String( "export-attribute" );
0289 
0290     KGpgExport *exp = new KGpgExport(this, QStringList(key->getId()), expOptions);
0291 
0292     exp->setGnuPGHome(m_tempdir->path());
0293 
0294     connect(exp, &KGpgExport::done, this, &KGpgCaffPrivate::slotExportFinished);
0295 
0296     exp->start();
0297 }
0298 
0299 void
0300 KGpgCaffPrivate::slotExportFinished(int result)
0301 {
0302     sender()->deleteLater();
0303 
0304     if (result != KGpgTransaction::TS_OK) {
0305         abortOperation(result);
0306         return;
0307     }
0308 
0309     const KGpgSignableNode *uid = m_allids.first();
0310     const KGpgKeyNode *key = uid->getKeyNode();
0311 
0312     KGpgExport *exp = qobject_cast<KGpgExport *>(sender());
0313     Q_ASSERT(exp != nullptr);
0314 
0315     QString body = KGpgSettings::emailTemplate();
0316     body.replace(QLatin1Char( '%' ) + i18nc("Email template placeholder for key id", "KEYID") + QLatin1Char( '%' ), key->getId());
0317     body.replace(QLatin1Char( '%' ) + i18nc("Email template placeholder for key id", "UIDNAME") + QLatin1Char( '%' ), uid->getNameComment());
0318 
0319     body += QLatin1Char( '\n' ) + QLatin1String( exp->getOutputData() );
0320 
0321     KGpgEncrypt *enc = new KGpgEncrypt(this, QStringList(key->getId()), body, KGpgEncrypt::AsciiArmored | KGpgEncrypt::AllowUntrustedEncryption);
0322 
0323     // Set the home directory to make sure custom encrypt options
0324     // as well as the "always encrypt to" setting are not honored.
0325     enc->setGnuPGHome(m_tempdir->path());
0326 
0327     connect(enc, &KGpgEncrypt::done, this, &KGpgCaffPrivate::slotTextEncrypted);
0328 
0329     enc->start();
0330 }
0331 
0332 void
0333 KGpgCaffPrivate::slotTextEncrypted(int result)
0334 {
0335     sender()->deleteLater();
0336 
0337     switch (result) {
0338     case KGpgTransaction::TS_OK: {
0339         KGpgEncrypt *enc = qobject_cast<KGpgEncrypt *>(sender());
0340         Q_ASSERT(enc != nullptr);
0341 
0342         const QString text = enc->encryptedText().join(QLatin1String("\n"));
0343 
0344         const KGpgSignableNode *uid = m_allids.takeFirst();
0345 
0346         const QString email = uid->getEmail();
0347         const QString keyid = uid->getKeyNode()->getId();
0348 
0349         QDesktopServices::openUrl(QUrl(QLatin1String("mailto:") + email +
0350                 QLatin1String("?subject=") + i18nc("%1 is 64 bit key id (in hex), text is used as email subject", "Your key %1", keyid) +
0351                 QLatin1String("&body=") + text));
0352         break;
0353         }
0354     default:
0355         abortOperation(result);
0356         break;
0357     case KGpgTransaction::TS_USER_ABORTED:
0358         m_allids.clear();
0359         break;
0360     }
0361 
0362     checkNextLoop();
0363 }
0364 
0365 KGpgCaff::KGpgCaff(QObject *parent, const KGpgSignableNode::List &ids, const QStringList &signids,
0366         const int checklevel, const OperationFlags flags)
0367     : QObject(parent),
0368     d_ptr(new KGpgCaffPrivate(this, ids, signids, flags, static_cast<KGpgSignTransactionHelper::carefulCheck>(checklevel)))
0369 {
0370 }
0371 
0372 void
0373 KGpgCaff::run()
0374 {
0375     Q_D(KGpgCaff);
0376 
0377     d->reexportKey(d->m_allids.first());
0378 }