File indexing completed on 2024-06-23 05:18:31
0001 /* 0002 SPDX-FileCopyrightText: 2020 Sandro Knauß <sknauss@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "job/autocryptheadersjob.h" 0008 0009 #include "contentjobbase_p.h" 0010 0011 #include "job/singlepartjob.h" 0012 #include "utils/util_p.h" 0013 0014 #include "messagecomposer_debug.h" 0015 0016 #include <QGpgME/ExportJob> 0017 #include <QGpgME/Protocol> 0018 #include <gpgme++/context.h> 0019 0020 #include <KCodecs> 0021 #include <KLocalizedString> 0022 #include <KMime/Content> 0023 #include <KMime/Headers> 0024 0025 #include <QByteArray> 0026 0027 #include <map> 0028 0029 using namespace MessageComposer; 0030 0031 class MessageComposer::AutocryptHeadersJobPrivate : public ContentJobBasePrivate 0032 { 0033 public: 0034 AutocryptHeadersJobPrivate(AutocryptHeadersJob *qq) 0035 : ContentJobBasePrivate(qq) 0036 { 0037 } 0038 0039 ~AutocryptHeadersJobPrivate() override 0040 { 0041 // clean up in case of cancelled job 0042 for (const auto &[key, header] : gossipHeaders) { 0043 delete header; 0044 } 0045 gossipHeaders.clear(); 0046 } 0047 0048 void emitGpgError(const GpgME::Error &error); 0049 void emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint); 0050 void fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata); 0051 void finishOnLastSubJob(); 0052 0053 KMime::Content *content = nullptr; 0054 KMime::Message *skeletonMessage = nullptr; 0055 // used to ensure consistent order based on key order, not random one by async subjobs delivering 0056 std::map<QByteArray, KMime::Headers::Generic *> gossipHeaders; 0057 0058 bool preferEncrypted = false; 0059 int subJobs = 0; 0060 0061 QString gnupgHome; 0062 GpgME::Key recipientKey; 0063 std::vector<GpgME::Key> gossipKeys; 0064 0065 Q_DECLARE_PUBLIC(AutocryptHeadersJob) 0066 }; 0067 0068 void AutocryptHeadersJobPrivate::finishOnLastSubJob() 0069 { 0070 Q_Q(AutocryptHeadersJob); 0071 0072 if (subJobs > 0) { 0073 return; 0074 } 0075 0076 for (const auto &[key, header] : gossipHeaders) { 0077 content->appendHeader(header); 0078 } 0079 gossipHeaders.clear(); 0080 resultContent = content; 0081 0082 q->emitResult(); 0083 } 0084 0085 void AutocryptHeadersJobPrivate::emitGpgError(const GpgME::Error &error) 0086 { 0087 Q_Q(AutocryptHeadersJob); 0088 0089 Q_ASSERT(error); 0090 const QString msg = i18n( 0091 "<p>An error occurred while trying to export " 0092 "the key from the backend:</p>" 0093 "<p><b>%1</b></p>", 0094 QString::fromLocal8Bit(error.asString())); 0095 q->setError(KJob::UserDefinedError); 0096 q->setErrorText(msg); 0097 q->emitResult(); 0098 } 0099 0100 void AutocryptHeadersJobPrivate::emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint) 0101 { 0102 Q_Q(AutocryptHeadersJob); 0103 const QString msg = i18n( 0104 "<p>An error occurred while trying to export " 0105 "the key from the backend:</p>" 0106 "<p><b>No valid key found for user %1 (%2)</b></p>", 0107 QString::fromLatin1(addr), 0108 QString::fromLatin1(fingerprint)); 0109 q->setError(KJob::UserDefinedError); 0110 q->setErrorText(msg); 0111 q->emitResult(); 0112 } 0113 0114 void AutocryptHeadersJobPrivate::fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata) 0115 { 0116 QByteArray parameters = "addr=" + addr + "; "; 0117 if (preferEncrypted) { 0118 parameters += "prefer-encrypt=mutual; "; 0119 } 0120 parameters += "keydata=\n "; 0121 auto encoded = KCodecs::base64Encode(keydata).replace('\n', QByteArray()); 0122 const auto length = encoded.size(); 0123 const auto lineLength = 76; 0124 auto start = 0; 0125 auto column = 1; 0126 while (start < length) { 0127 const auto midLength = std::min<int>(length - start, lineLength - column); 0128 parameters += encoded.mid(start, midLength); 0129 start += midLength; 0130 column += midLength; 0131 if (column >= lineLength) { 0132 parameters += "\n "; 0133 column = 1; 0134 } 0135 } 0136 header->from7BitString(parameters); 0137 } 0138 0139 AutocryptHeadersJob::AutocryptHeadersJob(QObject *parent) 0140 : ContentJobBase(*new AutocryptHeadersJobPrivate(this), parent) 0141 { 0142 } 0143 0144 AutocryptHeadersJob::~AutocryptHeadersJob() = default; 0145 0146 void AutocryptHeadersJob::setContent(KMime::Content *content) 0147 { 0148 Q_D(AutocryptHeadersJob); 0149 0150 d->content = content; 0151 if (content) { 0152 d->content->assemble(); 0153 } 0154 } 0155 0156 void AutocryptHeadersJob::setSkeletonMessage(KMime::Message *skeletonMessage) 0157 { 0158 Q_D(AutocryptHeadersJob); 0159 0160 d->skeletonMessage = skeletonMessage; 0161 } 0162 0163 void AutocryptHeadersJob::setGnupgHome(const QString &path) 0164 { 0165 Q_D(AutocryptHeadersJob); 0166 0167 d->gnupgHome = path; 0168 } 0169 0170 void AutocryptHeadersJob::setSenderKey(const GpgME::Key &key) 0171 { 0172 Q_D(AutocryptHeadersJob); 0173 0174 d->recipientKey = key; 0175 } 0176 0177 void AutocryptHeadersJob::setPreferEncrypted(bool preferEncrypted) 0178 { 0179 Q_D(AutocryptHeadersJob); 0180 0181 d->preferEncrypted = preferEncrypted; 0182 } 0183 0184 void AutocryptHeadersJob::setGossipKeys(const std::vector<GpgME::Key> &gossipKeys) 0185 { 0186 Q_D(AutocryptHeadersJob); 0187 0188 d->gossipKeys = gossipKeys; 0189 } 0190 0191 void AutocryptHeadersJob::process() 0192 { 0193 Q_D(AutocryptHeadersJob); 0194 Q_ASSERT(d->resultContent == nullptr); // Not processed before. 0195 0196 // if setContent hasn't been called, we assume that a subjob was added 0197 // and we want to use that 0198 if (!d->content) { 0199 Q_ASSERT(d->subjobContents.size() == 1); 0200 d->content = d->subjobContents.constFirst(); 0201 } 0202 0203 auto job = QGpgME::openpgp()->publicKeyExportJob(false); 0204 Q_ASSERT(job); 0205 0206 if (!d->gnupgHome.isEmpty()) { 0207 QGpgME::Job::context(job)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData()); 0208 } 0209 if (!d->recipientKey.isNull() && !d->recipientKey.isInvalid()) { 0210 connect(job, &QGpgME::ExportJob::result, this, [this, d](const GpgME::Error &error, const QByteArray &keydata) { 0211 d->subJobs--; 0212 if (AutocryptHeadersJob::error()) { 0213 // When the job already has failed do nothing. 0214 return; 0215 } 0216 if (error) { 0217 d->emitGpgError(error); 0218 return; 0219 } 0220 if (keydata.isEmpty()) { 0221 d->emitNotFoundError(d->skeletonMessage->from()->addresses()[0], d->recipientKey.primaryFingerprint()); 0222 return; 0223 } 0224 0225 auto autocrypt = new KMime::Headers::Generic("Autocrypt"); 0226 d->fillHeaderData(autocrypt, d->skeletonMessage->from()->addresses()[0], d->preferEncrypted, keydata); 0227 0228 d->skeletonMessage->setHeader(autocrypt); 0229 d->skeletonMessage->assemble(); 0230 0231 d->finishOnLastSubJob(); 0232 }); 0233 d->subJobs++; 0234 job->start(QStringList(QString::fromLatin1(d->recipientKey.primaryFingerprint()))); 0235 job->setExportFlags(GpgME::Context::ExportMinimal); 0236 } 0237 0238 const auto keys = d->gossipKeys; 0239 for (const auto &key : keys) { 0240 if (QByteArray(key.primaryFingerprint()) == QByteArray(d->recipientKey.primaryFingerprint())) { 0241 continue; 0242 } 0243 0244 auto gossipJob = QGpgME::openpgp()->publicKeyExportJob(false); 0245 Q_ASSERT(gossipJob); 0246 0247 if (!d->gnupgHome.isEmpty()) { 0248 QGpgME::Job::context(gossipJob)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData()); 0249 } 0250 0251 connect(gossipJob, &QGpgME::ExportJob::result, this, [this, d, key](const GpgME::Error &error, const QByteArray &keydata) { 0252 d->subJobs--; 0253 if (AutocryptHeadersJob::error()) { 0254 // When the job already has failed do nothing. 0255 return; 0256 } 0257 if (error) { 0258 d->emitGpgError(error); 0259 return; 0260 } 0261 if (keydata.isEmpty()) { 0262 d->emitNotFoundError(key.userID(0).email(), key.primaryFingerprint()); 0263 return; 0264 } 0265 0266 auto header = new KMime::Headers::Generic("Autocrypt-Gossip"); 0267 d->fillHeaderData(header, key.userID(0).email(), false, keydata); 0268 0269 d->gossipHeaders.insert({QByteArray(key.primaryFingerprint()), header}); 0270 0271 d->finishOnLastSubJob(); 0272 }); 0273 0274 d->subJobs++; 0275 gossipJob->start(QStringList(QString::fromLatin1(key.primaryFingerprint()))); 0276 gossipJob->setExportFlags(GpgME::Context::ExportMinimal); 0277 } 0278 if (d->subJobs == 0) { 0279 d->resultContent = d->content; 0280 emitResult(); 0281 } 0282 } 0283 0284 #include "moc_autocryptheadersjob.cpp"