1 /*
2   SPDX-FileCopyrightText: 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
3   SPDX-FileCopyrightText: 2009 Leo Franchi <lfranchi@kde.org>
4 
5   SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "job/encryptjob.h"
9 
10 #include "contentjobbase_p.h"
11 #include "job/protectedheadersjob.h"
12 #include "utils/util_p.h"
13 
14 #include <QGpgME/EncryptJob>
15 #include <QGpgME/Protocol>
16 
17 #include "messagecomposer_debug.h"
18 
19 #include <gpgme++/encryptionresult.h>
20 #include <gpgme++/global.h>
21 #include <gpgme++/signingresult.h>
22 #include <sstream>
23 
24 using namespace MessageComposer;
25 
26 class MessageComposer::EncryptJobPrivate : public ContentJobBasePrivate
27 {
28 public:
EncryptJobPrivate(EncryptJob * qq)29     EncryptJobPrivate(EncryptJob *qq)
30         : ContentJobBasePrivate(qq)
31     {
32     }
33 
34     QString gnupgHome;
35     QStringList recipients;
36     std::vector<GpgME::Key> keys;
37     Kleo::CryptoMessageFormat format;
38     KMime::Content *content = nullptr;
39     KMime::Message *skeletonMessage = nullptr;
40 
41     bool protectedHeaders = true;
42     bool protectedHeadersObvoscate = false;
43 
44     // copied from messagecomposer.cpp
binaryHint(Kleo::CryptoMessageFormat f)45     bool binaryHint(Kleo::CryptoMessageFormat f)
46     {
47         switch (f) {
48         case Kleo::SMIMEFormat:
49         case Kleo::SMIMEOpaqueFormat:
50             return true;
51         default:
52         case Kleo::OpenPGPMIMEFormat:
53         case Kleo::InlineOpenPGPFormat:
54             return false;
55         }
56     }
57 
signingMode(Kleo::CryptoMessageFormat f)58     GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
59     {
60         switch (f) {
61         case Kleo::SMIMEOpaqueFormat:
62             return GpgME::NormalSignatureMode;
63         case Kleo::InlineOpenPGPFormat:
64             return GpgME::Clearsigned;
65         default:
66         case Kleo::SMIMEFormat:
67         case Kleo::OpenPGPMIMEFormat:
68             return GpgME::Detached;
69         }
70     }
71 
72     Q_DECLARE_PUBLIC(EncryptJob)
73 };
74 
EncryptJob(QObject * parent)75 EncryptJob::EncryptJob(QObject *parent)
76     : ContentJobBase(*new EncryptJobPrivate(this), parent)
77 {
78 }
79 
~EncryptJob()80 EncryptJob::~EncryptJob()
81 {
82 }
83 
setContent(KMime::Content * content)84 void EncryptJob::setContent(KMime::Content *content)
85 {
86     Q_D(EncryptJob);
87 
88     d->content = content;
89     d->content->assemble();
90 }
91 
setCryptoMessageFormat(Kleo::CryptoMessageFormat format)92 void EncryptJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
93 {
94     Q_D(EncryptJob);
95 
96     d->format = format;
97 }
98 
setEncryptionKeys(const std::vector<GpgME::Key> & keys)99 void EncryptJob::setEncryptionKeys(const std::vector<GpgME::Key> &keys)
100 {
101     Q_D(EncryptJob);
102 
103     d->keys = keys;
104 }
105 
setRecipients(const QStringList & recipients)106 void EncryptJob::setRecipients(const QStringList &recipients)
107 {
108     Q_D(EncryptJob);
109 
110     d->recipients = recipients;
111 }
112 
setSkeletonMessage(KMime::Message * skeletonMessage)113 void EncryptJob::setSkeletonMessage(KMime::Message *skeletonMessage)
114 {
115     Q_D(EncryptJob);
116 
117     d->skeletonMessage = skeletonMessage;
118 }
119 
setProtectedHeaders(bool protectedHeaders)120 void EncryptJob::setProtectedHeaders(bool protectedHeaders)
121 {
122     Q_D(EncryptJob);
123 
124     d->protectedHeaders = protectedHeaders;
125 }
126 
setProtectedHeadersObvoscate(bool protectedHeadersObvoscate)127 void EncryptJob::setProtectedHeadersObvoscate(bool protectedHeadersObvoscate)
128 {
129     Q_D(EncryptJob);
130 
131     d->protectedHeadersObvoscate = protectedHeadersObvoscate;
132 }
133 
setGnupgHome(const QString & path)134 void EncryptJob::setGnupgHome(const QString &path)
135 {
136     Q_D(EncryptJob);
137 
138     d->gnupgHome = path;
139 }
140 
recipients() const141 QStringList EncryptJob::recipients() const
142 {
143     Q_D(const EncryptJob);
144 
145     return d->recipients;
146 }
147 
encryptionKeys() const148 std::vector<GpgME::Key> EncryptJob::encryptionKeys() const
149 {
150     Q_D(const EncryptJob);
151 
152     return d->keys;
153 }
154 
doStart()155 void EncryptJob::doStart()
156 {
157     Q_D(EncryptJob);
158     Q_ASSERT(d->resultContent == nullptr); // Not processed before.
159 
160     if (d->keys.size() == 0) { // should not happen---resolver should have dealt with it earlier
161         qCDebug(MESSAGECOMPOSER_LOG) << "HELP! Encrypt job but have no keys to encrypt with.";
162         return;
163     }
164 
165     // if setContent hasn't been called, we assume that a subjob was added
166     // and we want to use that
167     if (!d->content || !d->content->hasContent()) {
168         if (d->subjobContents.size() == 1) {
169             d->content = d->subjobContents.constFirst();
170         }
171     }
172 
173     if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) {
174         auto pJob = new ProtectedHeadersJob;
175         pJob->setContent(d->content);
176         pJob->setSkeletonMessage(d->skeletonMessage);
177         pJob->setObvoscate(d->protectedHeadersObvoscate);
178         QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) {
179             if (job->error()) {
180                 return;
181             }
182             d->content = pJob->content();
183         });
184         appendSubjob(pJob);
185     }
186 
187     ContentJobBase::doStart();
188 }
189 
slotResult(KJob * job)190 void EncryptJob::slotResult(KJob *job)
191 {
192     // Q_D(EncryptJob);
193     if (error() || job->error()) {
194         ContentJobBase::slotResult(job);
195         return;
196     }
197     if (subjobs().size() == 2) {
198         auto pjob = static_cast<ProtectedHeadersJob *>(subjobs().last());
199         if (pjob) {
200             auto cjob = qobject_cast<ContentJobBase *>(job);
201             Q_ASSERT(cjob);
202             pjob->setContent(cjob->content());
203         }
204     }
205 
206     ContentJobBase::slotResult(job);
207 }
208 
process()209 void EncryptJob::process()
210 {
211     Q_D(EncryptJob);
212 
213     // if setContent hasn't been called, we assume that a subjob was added
214     // and we want to use that
215     if (!d->content || !d->content->hasContent()) {
216         Q_ASSERT(d->subjobContents.size() == 1);
217         d->content = d->subjobContents.constFirst();
218     }
219 
220     const QGpgME::Protocol *proto = nullptr;
221     if (d->format & Kleo::AnyOpenPGP) {
222         proto = QGpgME::openpgp();
223     } else if (d->format & Kleo::AnySMIME) {
224         proto = QGpgME::smime();
225     } else {
226         qCDebug(MESSAGECOMPOSER_LOG) << "HELP! Encrypt job but have protocol to encrypt with.";
227         return;
228     }
229 
230     Q_ASSERT(proto);
231 
232     // for now just do the main recipients
233     QByteArray content;
234     d->content->assemble();
235     if (d->format & Kleo::InlineOpenPGPFormat) {
236         content = d->content->body();
237     } else {
238         content = d->content->encodedContent();
239     }
240 
241     qCDebug(MESSAGECOMPOSER_LOG) << "got backend, starting job";
242     QGpgME::EncryptJob *eJob = proto->encryptJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat);
243 
244     if (!d->gnupgHome.isEmpty()) {
245         QGpgME::Job::context(eJob)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData());
246     }
247 
248     QObject::connect(
249         eJob,
250         &QGpgME::EncryptJob::result,
251         this,
252         [this, d](const GpgME::EncryptionResult &result, const QByteArray &cipherText, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
253             Q_UNUSED(auditLogAsHtml)
254             Q_UNUSED(auditLogError)
255             if (result.error()) {
256                 setError(result.error().code());
257                 setErrorText(QString::fromLocal8Bit(result.error().asString()));
258                 emitResult();
259                 return;
260             }
261             d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, cipherText, d->format, false);
262 
263             emitResult();
264         });
265 
266     const auto error = eJob->start(d->keys, content, true);
267     if (error.code()) {
268         eJob->deleteLater();
269         setError(error.code());
270         setErrorText(QString::fromLocal8Bit(error.asString()));
271         emitResult();
272     }
273 }
274