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 <%3>", 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 <%3>", 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 }