Messagelib

signjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, [email protected]
3  SPDX-FileCopyrightText: 2009 Leo Franchi <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "job/signjob.h"
9 
10 #include "contentjobbase_p.h"
11 #include "job/protectedheaders.h"
12 #include "utils/util_p.h"
13 
14 #include <Libkleo/Enum>
15 #include <QGpgME/Protocol>
16 #include <QGpgME/SignJob>
17 #include <QVector>
18 
19 #include "messagecomposer_debug.h"
20 #include <kmime/kmime_headers.h>
21 #include <kmime/kmime_message.h>
22 #include <kmime/kmime_content.h>
23 
24 #include <gpgme++/global.h>
25 #include <gpgme++/signingresult.h>
26 #include <gpgme++/encryptionresult.h>
27 #include <sstream>
28 
29 using namespace MessageComposer;
30 
31 class MessageComposer::SignJobPrivate : public ContentJobBasePrivate
32 {
33 public:
34  SignJobPrivate(SignJob *qq)
35  : ContentJobBasePrivate(qq)
36  {
37  }
38 
39  KMime::Content *content = nullptr;
40  KMime::Message *skeletonMessage = nullptr;
41  std::vector<GpgME::Key> signers;
42  Kleo::CryptoMessageFormat format;
43 
44  bool protectedHeaders = true;
45 
46  // copied from messagecomposer.cpp
47  Q_REQUIRED_RESULT bool binaryHint(Kleo::CryptoMessageFormat f)
48  {
49  switch (f) {
50  case Kleo::SMIMEFormat:
51  case Kleo::SMIMEOpaqueFormat:
52  return true;
53  default:
54  case Kleo::OpenPGPMIMEFormat:
55  case Kleo::InlineOpenPGPFormat:
56  return false;
57  }
58  }
59 
60  Q_REQUIRED_RESULT GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
61  {
62  switch (f) {
63  case Kleo::SMIMEOpaqueFormat:
64  return GpgME::NormalSignatureMode;
65  case Kleo::InlineOpenPGPFormat:
66  return GpgME::Clearsigned;
67  default:
68  case Kleo::SMIMEFormat:
69  case Kleo::OpenPGPMIMEFormat:
70  return GpgME::Detached;
71  }
72  }
73 
74  Q_DECLARE_PUBLIC(SignJob)
75 };
76 
77 SignJob::SignJob(QObject *parent)
78  : ContentJobBase(*new SignJobPrivate(this), parent)
79 {
80 }
81 
82 SignJob::~SignJob()
83 {
84 }
85 
86 void SignJob::setContent(KMime::Content *content)
87 {
88  Q_D(SignJob);
89 
90  d->content = content;
91 }
92 
93 void SignJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
94 {
95  Q_D(SignJob);
96 
97  // There *must* be a concrete format set at this point.
98  Q_ASSERT(format == Kleo::OpenPGPMIMEFormat
99  || format == Kleo::InlineOpenPGPFormat
100  || format == Kleo::SMIMEFormat
101  || format == Kleo::SMIMEOpaqueFormat);
102  d->format = format;
103 }
104 
105 void SignJob::setSigningKeys(const std::vector<GpgME::Key> &signers)
106 {
107  Q_D(SignJob);
108 
109  d->signers = signers;
110 }
111 
112 void SignJob::setSkeletonMessage(KMime::Message *skeletonMessage)
113 {
114  Q_D(SignJob);
115 
116  d->skeletonMessage = skeletonMessage;
117 }
118 
119 void SignJob::setProtectedHeaders(bool protectedHeaders)
120 {
121  Q_D(SignJob);
122 
123  d->protectedHeaders = protectedHeaders;
124 }
125 
126 KMime::Content *SignJob::origContent()
127 {
128  Q_D(SignJob);
129 
130  return d->content;
131 }
132 
133 void SignJob::doStart()
134 {
135  Q_D(SignJob);
136  Q_ASSERT(d->resultContent == nullptr); // Not processed before.
137 
138  if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) {
140  pJob->setContent(d->content);
141  pJob->setSkeletonMessage(d->skeletonMessage);
142  pJob->setObvoscate(false);
143  QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) {
144  if (job->error()) {
145  return;
146  }
147  d->content = pJob->content();
148  });
149  appendSubjob(pJob);
150  }
151 
153 }
154 
155 void SignJob::slotResult(KJob *job)
156 {
157  if (error()) {
158  ContentJobBase::slotResult(job);
159  return;
160  }
161  if (subjobs().size() == 2) {
162  auto pjob = static_cast<ProtectedHeadersJob *>(subjobs().last());
163  if (pjob) {
164  auto cjob = qobject_cast<ContentJobBase *>(job);
165  Q_ASSERT(cjob);
166  pjob->setContent(cjob->content());
167  }
168  }
169 
170  ContentJobBase::slotResult(job);
171 }
172 
173 void SignJob::process()
174 {
175  Q_D(SignJob);
176  Q_ASSERT(d->resultContent == nullptr); // Not processed before.
177 
178  // if setContent hasn't been called, we assume that a subjob was added
179  // and we want to use that
180  if (!d->content) {
181  Q_ASSERT(d->subjobContents.size() == 1);
182  d->content = d->subjobContents.constFirst();
183  }
184 
185  //d->resultContent = new KMime::Content;
186 
187  const QGpgME::Protocol *proto = nullptr;
188  if (d->format & Kleo::AnyOpenPGP) {
189  proto = QGpgME::openpgp();
190  } else if (d->format & Kleo::AnySMIME) {
191  proto = QGpgME::smime();
192  }
193 
194  Q_ASSERT(proto);
195 
196  qCDebug(MESSAGECOMPOSER_LOG) << "creating signJob from:" << proto->name() << proto->displayName();
197  std::unique_ptr<QGpgME::SignJob> job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
198  // for now just do the main recipients
199  QByteArray signature;
200 
201  d->content->assemble();
202 
203  // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
204  // according to RfC 2633, 3.1.1 Canonicalization
206  if (d->format & Kleo::InlineOpenPGPFormat) {
207  content = d->content->body();
208  } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
209  // replace "From " and "--" at the beginning of lines
210  // with encoded versions according to RfC 3156, 3
211  // Note: If any line begins with the string "From ", it is strongly
212  // suggested that either the Quoted-Printable or Base64 MIME encoding
213  // be applied.
214  const auto encoding = d->content->contentTransferEncoding()->encoding();
215  if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit)
216  && !d->content->contentType(false)) {
217  QByteArray body = d->content->encodedBody();
218  bool changed = false;
219  QVector<QByteArray> search;
220  search.reserve(3);
221  QVector<QByteArray> replacements;
222  replacements.reserve(3);
223  search << "From "
224  << "from "
225  << "-";
226  replacements << "From=20"
227  << "from=20"
228  << "=2D";
229 
230  if (d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
231  for (int i = 0, total = search.size(); i < total; ++i) {
232  const auto pos = body.indexOf(search[i]);
233  if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
234  changed = true;
235  break;
236  }
237  }
238  if (changed) {
239  d->content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
240  d->content->assemble();
241  body = d->content->encodedBody();
242  }
243  }
244 
245  for (int i = 0; i < search.size(); ++i) {
246  const auto pos = body.indexOf(search[i]);
247  if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
248  changed = true;
249  body.replace(pos, search[i].size(), replacements[i]);
250  }
251  }
252 
253  if (changed) {
254  qCDebug(MESSAGECOMPOSER_LOG) << "Content changed";
255  d->content->setBody(body);
256  d->content->contentTransferEncoding()->setDecoded(false);
257  }
258  }
259 
260  content = KMime::LFtoCRLF(d->content->encodedContent());
261  } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
262  content = d->content->encodedContent();
263  }
264 
265  // FIXME: Make this async
266  GpgME::SigningResult res = job->exec(d->signers,
267  content,
268  d->signingMode(d->format),
269  signature);
270 
271  // exec'ed jobs don't delete themselves
272  job->deleteLater();
273 
274  if (res.error().code()) {
275  qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << res.error().asString();
276  // job->showErrorDialog( globalPart()->parentWidgetForGui() );
277  setError(res.error().code());
278  setErrorText(QString::fromLocal8Bit(res.error().asString()));
279  } else {
280  QByteArray signatureHashAlgo = res.createdSignature(0).hashAlgorithmAsString();
281  d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, signature, d->format, true, signatureHashAlgo);
282  }
283 
284  emitResult();
285 }
void emitResult()
char at(int i) const const
void setError(int errorCode)
bool exec()
KMime::Content * content() const
Get the resulting KMime::Content that the ContentJobBase has generated.
virtual void doStart()
Reimplement to do additional stuff before processing children, such as adding more subjobs...
void setErrorText(const QString &errorText)
int indexOf(char ch, int from) const const
QString fromLocal8Bit(const char *str, int size)
void finished(KJob *job)
Copies headers from skeleton message to content.
QByteArray & replace(int pos, int len, const char *after)
void deleteLater()
void reserve(int size)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool appendSubjob(ContentJobBase *job)
This is meant to be used instead of KCompositeJob::addSubjob(), making it possible to add subjobs fro...
const QList< KJob * > & subjobs() const
Signs the contents of a message.
Definition: signjob.h:30
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
int size() const const
int error() const
The ContentJobBase class.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Sep 20 2020 23:12:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.