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