File indexing completed on 2024-05-12 05:10:48
0001 /* 0002 SPDX-FileCopyrightText: 1998 Barry D Benowitz <b.benowitz@telesciences.com> 0003 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org> 0004 SPDX-FileCopyrightText: 2009 Allen Winter <winter@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "mailclient_p.h" 0010 0011 #include "akonadi-calendar-version.h" 0012 0013 #include <Akonadi/Collection> 0014 0015 #include <KCalUtils/IncidenceFormatter> 0016 #include <KCalendarCore/Attendee> 0017 #include <KCalendarCore/Incidence> 0018 #include <KEmailAddress> 0019 #include <KIdentityManagementCore/Identity> 0020 #include <MessageComposer/ContactPreference> 0021 0022 #include <Akonadi/MessageQueueJob> 0023 #include <MailTransport/Transport> 0024 #include <MailTransport/TransportManager> 0025 0026 #include <KMime/Headers> 0027 0028 #include <QGpgME/ExportJob> 0029 #include <QGpgME/ImportJob> 0030 #include <QGpgME/Protocol> 0031 #include <gpgme++/context.h> 0032 #include <gpgme++/importresult.h> 0033 0034 #include <MessageComposer/Composer> 0035 #include <MessageComposer/GlobalPart> 0036 #include <MessageComposer/InfoPart> 0037 #include <MessageComposer/ItipPart> 0038 #include <MessageComposer/KeyResolver> 0039 #include <MessageComposer/MessageComposerSettings> 0040 #include <MessageComposer/Util> 0041 #include <MessageCore/AutocryptStorage> 0042 0043 #include <Libkleo/Enum> 0044 #include <Libkleo/ExpiryChecker> 0045 #include <Libkleo/ExpiryCheckerSettings> 0046 0047 #include "akonadicalendar_debug.h" 0048 #include <KJob> 0049 #include <KLocalizedString> 0050 #include <KMessageBox> 0051 0052 #include <QTemporaryDir> 0053 0054 using namespace Akonadi; 0055 0056 // Crypto-related helpers 0057 static Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays() 0058 { 0059 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) { 0060 return Kleo::chrono::days{-1}; 0061 } 0062 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays(); 0063 return Kleo::chrono::days{qMax(1, num)}; 0064 } 0065 0066 static Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays() 0067 { 0068 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) { 0069 return Kleo::chrono::days{-1}; 0070 } 0071 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays(); 0072 return Kleo::chrono::days{qMax(1, num)}; 0073 } 0074 0075 static Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays() 0076 { 0077 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) { 0078 return Kleo::chrono::days{-1}; 0079 } 0080 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays(); 0081 return Kleo::chrono::days{qMax(1, num)}; 0082 } 0083 0084 static Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays() 0085 { 0086 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) { 0087 return Kleo::chrono::days{-1}; 0088 } 0089 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays(); 0090 return Kleo::chrono::days{qMax(1, num)}; 0091 } 0092 0093 static bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity) 0094 { 0095 if (identity.encryptionOverride()) { 0096 return identity.warnNotSign(); 0097 } 0098 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned(); 0099 } 0100 0101 static bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity) 0102 { 0103 if (identity.encryptionOverride()) { 0104 return identity.warnNotEncrypt(); 0105 } 0106 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted(); 0107 } 0108 0109 static QStringList extractEmailAndNormalize(const QString &email) 0110 { 0111 const QStringList splittedEmail = KEmailAddress::splitAddressList(email); 0112 QStringList normalizedEmail; 0113 normalizedEmail.reserve(splittedEmail.count()); 0114 for (const QString &email : splittedEmail) { 0115 const QString str = KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(email)); 0116 normalizedEmail << str; 0117 } 0118 return normalizedEmail; 0119 } 0120 0121 void MailClient::setAkonadiLookupEnabled(bool enabled) 0122 { 0123 mAkonadiLookupEnabled = enabled; 0124 } 0125 0126 std::optional<MessageComposer::ContactPreference> MailClient::contactPreference(const QString &address) 0127 { 0128 Q_UNUSED(address); 0129 return {}; 0130 } 0131 0132 void MailClient::populateKeyResolverContactsPreferences(Kleo::KeyResolver &keyResolver, const QStringList &addresses) 0133 { 0134 for (const auto &address : addresses) { 0135 if (const auto &pref = contactPreference(address); pref.has_value()) { 0136 keyResolver.setContactPreferences(address, *pref); 0137 } 0138 } 0139 } 0140 0141 static bool populateKeyResolverEncryptionKeys(Kleo::KeyResolver &keyResolver, const KIdentityManagementCore::Identity &identity) 0142 { 0143 QStringList encryptToSelfKeys; 0144 if (!identity.pgpEncryptionKey().isEmpty()) { 0145 encryptToSelfKeys.push_back(QString::fromLatin1(identity.pgpEncryptionKey())); 0146 } 0147 if (!identity.smimeEncryptionKey().isEmpty()) { 0148 encryptToSelfKeys.push_back(QString::fromLatin1(identity.smimeEncryptionKey())); 0149 } 0150 if (const auto result = keyResolver.setEncryptToSelfKeys(encryptToSelfKeys); result != Kleo::Ok) { 0151 qCWarning(AKONADICALENDAR_LOG) << "KeyResolver - failed to set encrypto-to-self keys, result:" << result; 0152 return false; 0153 } 0154 0155 return true; 0156 } 0157 0158 static bool populateKeyResolverSigningKeys(Kleo::KeyResolver &keyResolver, const KIdentityManagementCore::Identity &identity) 0159 { 0160 QStringList signingKeys; 0161 if (!identity.pgpSigningKey().isEmpty()) { 0162 signingKeys.push_back(QString::fromLatin1(identity.pgpSigningKey())); 0163 } 0164 if (!identity.smimeSigningKey().isEmpty()) { 0165 signingKeys.push_back(QString::fromLatin1(identity.smimeSigningKey())); 0166 } 0167 qCDebug(AKONADICALENDAR_LOG) << "Settings signing keys:" << signingKeys; 0168 if (const auto result = keyResolver.setSigningKeys(signingKeys); result != Kleo::Ok) { 0169 qCWarning(AKONADICALENDAR_LOG) << "KeyResolver - failed to set signing keys, result:" << result; 0170 return false; 0171 } 0172 0173 return true; 0174 } 0175 0176 std::vector<std::unique_ptr<MessageComposer::Composer>> 0177 MailClient::buildComposers(const KCalendarCore::IncidenceBase::Ptr &incidence, const KIdentityManagementCore::Identity &identity, const MessageData &msg) 0178 { 0179 // TODO: Those should be set based on whether the user selects "Sign" or "Encrypt" options 0180 // in the incidence editor (similar to the "Sign" and "Encrypt" actions in KMail composer). 0181 // Right now we do NOT have those actions in the UI, so we keep the values to false so that 0182 // signing/encryption depends purely on current identity and attendee signing and encryption 0183 // preferences. 0184 bool signSomething = msg.sign; 0185 const bool doSignCompletely = msg.sign; 0186 bool encryptSomething = msg.encrypt; 0187 const bool doEncryptCompletely = msg.encrypt; 0188 0189 std::unique_ptr<ITIPHandlerDialogDelegate> dialogDelegate( 0190 mFactory->createITIPHanderDialogDelegate(qSharedPointerCast<KCalendarCore::Incidence>(incidence), KCalendarCore::iTIPMethod::iTIPNoMethod)); 0191 0192 auto expiryChecker = std::make_shared<Kleo::ExpiryChecker>(Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(), 0193 encryptKeyNearExpiryWarningThresholdInDays(), 0194 encryptRootCertNearExpiryWarningThresholdInDays(), 0195 encryptChainCertNearExpiryWarningThresholdInDays()}); 0196 Kleo::KeyResolver keyResolver(/* encToSelf */ true, showKeyApprovalDialog(), identity.pgpAutoEncrypt(), Kleo::AutoFormat, expiryChecker); 0197 0198 const auto recipients = msg.to + msg.cc; 0199 populateKeyResolverContactsPreferences(keyResolver, recipients); 0200 0201 keyResolver.setAkonadiLookupEnabled(mAkonadiLookupEnabled); 0202 keyResolver.setAutocryptEnabled(identity.autocryptEnabled()); 0203 keyResolver.setPrimaryRecipients(recipients); 0204 if (msg.bccMe) { 0205 keyResolver.setSecondaryRecipients({msg.from}); 0206 } 0207 0208 if (!populateKeyResolverEncryptionKeys(keyResolver, identity)) { 0209 return {}; 0210 } 0211 if (!populateKeyResolverSigningKeys(keyResolver, identity)) { 0212 return {}; 0213 } 0214 0215 bool result = true; 0216 bool canceled = false; 0217 bool signAttachments = false; 0218 bool encryptAttachments = false; 0219 0220 signSomething = determineWhetherToSign(doSignCompletely, &keyResolver, dialogDelegate.get(), identity, signSomething, signAttachments, result, canceled); 0221 if (!result) { 0222 qCDebug(AKONADICALENDAR_LOG) << "KeyResolver failed to resolve signing keys - " << (canceled ? "operation canceled" : "an error occured"); 0223 return {}; 0224 } 0225 0226 encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, 0227 &keyResolver, 0228 dialogDelegate.get(), 0229 identity, 0230 encryptSomething, 0231 signSomething, 0232 encryptAttachments, 0233 result, 0234 canceled); 0235 if (!result) { 0236 qCDebug(AKONADICALENDAR_LOG) << "KeyResolver failed to resolve encryption keys - " << (canceled ? "operation canceled" : "an error occured"); 0237 return {}; 0238 } 0239 0240 std::vector<std::unique_ptr<MessageComposer::Composer>> composers; 0241 0242 if (!signSomething && !encryptSomething) { 0243 auto &composer = composers.emplace_back(std::make_unique<MessageComposer::Composer>()); 0244 const auto preferredCrypto = Kleo::stringToCryptoMessageFormat(identity.preferredCryptoMessageFormat()); 0245 if (preferredCrypto & Kleo::OpenPGPMIMEFormat) { 0246 composer->setAutocryptEnabled(identity.autocryptEnabled()); 0247 if (keyResolver.encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) { 0248 composer->setSenderEncryptionKey(keyResolver.encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]); 0249 } 0250 } 0251 return composers; 0252 } 0253 0254 canceled = false; 0255 const Kleo::Result kpgpResult = keyResolver.resolveAllKeys(signSomething, encryptSomething); 0256 if (kpgpResult == Kleo::Canceled || canceled) { 0257 qCDebug(AKONADICALENDAR_LOG) << "resolveAllKeys: one key resolution canceled by user"; 0258 return {}; 0259 } else if (kpgpResult != Kleo::Ok) { 0260 // TODO handle failure 0261 qCDebug(AKONADICALENDAR_LOG) << "resolveAllKeys: failed to resolve keys! oh noes"; 0262 return {}; 0263 } 0264 0265 if (encryptSomething || signSomething) { 0266 for (auto concreteFormat : {Kleo::OpenPGPMIMEFormat, Kleo::SMIMEFormat, Kleo::SMIMEOpaqueFormat, Kleo::InlineOpenPGPFormat}) { 0267 const auto encData = keyResolver.encryptionItems(concreteFormat); 0268 if (encData.empty()) { 0269 continue; 0270 } 0271 0272 if (!(concreteFormat & Kleo::AutoFormat)) { 0273 continue; 0274 } 0275 0276 auto composer = std::make_unique<MessageComposer::Composer>(); 0277 0278 if (encryptSomething || identity.autocryptEnabled()) { 0279 QList<QPair<QStringList, std::vector<GpgME::Key>>> data; 0280 data.reserve(encData.size()); 0281 for (const auto &info : encData) { 0282 data.push_back(qMakePair(info.recipients, info.keys)); 0283 qCDebug(AKONADICALENDAR_LOG) << "Resolved keys for:" << info.recipients; 0284 } 0285 composer->setEncryptionKeys(data); 0286 if (concreteFormat & Kleo::OpenPGPMIMEFormat && identity.autocryptEnabled()) { 0287 composer->setAutocryptEnabled(true); 0288 composer->setSenderEncryptionKey(keyResolver.encryptToSelfKeysFor(concreteFormat)[0]); 0289 QTemporaryDir dir; 0290 bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver.useAutocrypt()); 0291 if (specialGnupgHome) { 0292 dir.setAutoRemove(false); 0293 composer->setGnupgHome(dir.path()); 0294 } 0295 } 0296 } 0297 0298 if (signSomething) { 0299 // find signing keys for this format 0300 composer->setSigningKeys(keyResolver.signingKeys(concreteFormat)); 0301 } 0302 0303 composer->setMessageCryptoFormat(concreteFormat); 0304 composer->setSignAndEncrypt(signSomething, encryptSomething); 0305 0306 composers.push_back(std::move(composer)); 0307 } 0308 } else { 0309 composers.emplace_back(std::make_unique<MessageComposer::Composer>()); 0310 } 0311 0312 return composers; 0313 } 0314 0315 void MailClient::queueMessage(const MailTransport::Transport *transport, 0316 const MessageComposer::Composer *composer, 0317 const KCalendarCore::IncidenceBase::Ptr &incidence, 0318 const KIdentityManagementCore::Identity &identity, 0319 const MessageData &msg, 0320 const KMime::Message::Ptr &message) 0321 { 0322 Akonadi::MessageQueueJob *qjob = mFactory->createMessageQueueJob(incidence, identity, this); 0323 qjob->setMessage(message); 0324 0325 if (identity.disabledFcc()) { 0326 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete); 0327 } else { 0328 const Akonadi::Collection sentCollection(identity.fcc().toLongLong()); 0329 if (sentCollection.isValid()) { 0330 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection); 0331 qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection); 0332 } else { 0333 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection); 0334 } 0335 } 0336 0337 qjob->transportAttribute().setTransportId(transport->id()); 0338 0339 if (transport && transport->specifySenderOverwriteAddress()) { 0340 qjob->addressAttribute().setFrom( 0341 KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress()))); 0342 } else { 0343 qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(composer->infoPart()->from()))); 0344 } 0345 0346 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(composer->infoPart()->to())); 0347 qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(composer->infoPart()->cc())); 0348 if (msg.bccMe) { 0349 qjob->addressAttribute().setBcc({qjob->addressAttribute().from()}); 0350 } 0351 0352 message->assemble(); 0353 connect(qjob, &KJob::finished, this, &MailClient::handleQueueJobFinished); 0354 qjob->start(); 0355 } 0356 0357 MailClient::MailClient(ITIPHandlerComponentFactory *factory, QObject *parent) 0358 : QObject(parent) 0359 , mFactory(factory ? factory : new ITIPHandlerComponentFactory(this)) 0360 { 0361 } 0362 0363 MailClient::~MailClient() = default; 0364 0365 void MailClient::mailAttendees(const KCalendarCore::IncidenceBase::Ptr &incidence, 0366 const KIdentityManagementCore::Identity &identity, 0367 KCalendarCore::iTIPMethod method, 0368 bool bccMe, 0369 const QString &attachment, 0370 const QString &mailTransport, 0371 MailPrivacyFlags mailPrivacy) 0372 { 0373 Q_ASSERT(incidence); 0374 KCalendarCore::Attendee::List attendees = incidence->attendees(); 0375 if (attendees.isEmpty()) { 0376 qCWarning(AKONADICALENDAR_LOG) << "There are no attendees to e-mail"; 0377 Q_EMIT finished(ResultNoAttendees, i18n("There are no attendees to e-mail")); 0378 return; 0379 } 0380 0381 MessageData msg; 0382 msg.bccMe = bccMe; 0383 msg.attachment = attachment; 0384 msg.from = incidence->organizer().fullName(); 0385 msg.method = KCalendarCore::ScheduleMessage::methodName(method); 0386 0387 const QString organizerEmail = incidence->organizer().email(); 0388 0389 const int numberOfAttendees = attendees.count(); 0390 for (int i = 0; i < numberOfAttendees; ++i) { 0391 const KCalendarCore::Attendee a = attendees.at(i); 0392 0393 const QString email = a.email(); 0394 if (email.isEmpty()) { 0395 continue; 0396 } 0397 0398 // In case we (as one of our identities) are the organizer we are sending 0399 // this mail. We could also have added ourselves as an attendee, in which 0400 // case we don't want to send ourselves a notification mail. 0401 if (organizerEmail == email) { 0402 continue; 0403 } 0404 0405 // Optional Participants and Non-Participants are copied on the email 0406 if (a.role() == KCalendarCore::Attendee::OptParticipant || a.role() == KCalendarCore::Attendee::NonParticipant) { 0407 msg.cc.push_back(a.email()); 0408 } else { 0409 msg.to.push_back(a.email()); 0410 } 0411 } 0412 if (msg.cc.isEmpty() && msg.to.isEmpty()) { 0413 // Not really to be called a groupware meeting, eh 0414 qCWarning(AKONADICALENDAR_LOG) << "There are really no attendees to e-mail"; 0415 Q_EMIT finished(ResultReallyNoAttendees, i18n("There are no attendees to e-mail")); 0416 return; 0417 } 0418 0419 QString subject; 0420 if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) { 0421 KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>(); 0422 msg.subject = inc->summary(); 0423 } else { 0424 msg.subject = i18n("Free Busy Object"); 0425 } 0426 0427 msg.body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence); 0428 msg.sign = (mailPrivacy & MailPrivacySign) == MailPrivacySign; 0429 msg.encrypt = (mailPrivacy & MailPrivacyEncrypt) == MailPrivacyEncrypt; 0430 0431 send(incidence, identity, msg, mailTransport); 0432 } 0433 0434 void MailClient::mailOrganizer(const KCalendarCore::IncidenceBase::Ptr &incidence, 0435 const KIdentityManagementCore::Identity &identity, 0436 const QString &from, 0437 KCalendarCore::iTIPMethod method, 0438 bool bccMe, 0439 const QString &attachment, 0440 const QString &sub, 0441 const QString &mailTransport, 0442 MailPrivacyFlags mailPrivacy) 0443 { 0444 MessageData msg; 0445 msg.from = from; 0446 msg.to.push_back(incidence->organizer().fullName()); 0447 msg.bccMe = bccMe; 0448 msg.subject = sub; 0449 msg.attachment = attachment; 0450 msg.method = KCalendarCore::ScheduleMessage::methodName(method); 0451 0452 if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) { 0453 KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>(); 0454 if (msg.subject.isEmpty()) { 0455 msg.subject = inc->summary(); 0456 } 0457 } else { 0458 msg.subject = i18n("Free Busy Message"); 0459 } 0460 0461 msg.body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence); 0462 msg.sign = (mailPrivacy & MailPrivacySign) == MailPrivacySign; 0463 msg.encrypt = (mailPrivacy & MailPrivacyEncrypt) == MailPrivacyEncrypt; 0464 0465 send(incidence, identity, msg, mailTransport); 0466 } 0467 0468 void MailClient::mailTo(const KCalendarCore::IncidenceBase::Ptr &incidence, 0469 const KIdentityManagementCore::Identity &identity, 0470 const QString &from, 0471 KCalendarCore::iTIPMethod method, 0472 bool bccMe, 0473 const QString &recipients, 0474 const QString &attachment, 0475 const QString &mailTransport, 0476 MailPrivacyFlags mailPrivacy) 0477 { 0478 MessageData msg; 0479 msg.to = extractEmailAndNormalize(recipients); 0480 msg.from = from; 0481 msg.bccMe = bccMe; 0482 msg.attachment = attachment; 0483 msg.method = KCalendarCore::ScheduleMessage::methodName(method); 0484 0485 if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) { 0486 KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>(); 0487 msg.subject = inc->summary(); 0488 } else { 0489 msg.subject = i18n("Free Busy Message"); 0490 } 0491 0492 msg.body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence); 0493 msg.sign = (mailPrivacy & MailPrivacySign) == MailPrivacySign; 0494 msg.encrypt = (mailPrivacy & MailPrivacyEncrypt) == MailPrivacyEncrypt; 0495 0496 send(incidence, identity, msg, mailTransport); 0497 } 0498 0499 void MailClient::populateComposer(MessageComposer::Composer *composer, const MessageData &msg) 0500 { 0501 // gather config values 0502 KConfig config(QStringLiteral("kmail2rc")); 0503 KConfigGroup configGroup(&config, QStringLiteral("Invitations")); 0504 const bool outlookConformInvitation = configGroup.readEntry("LegacyBodyInvites", 0505 #ifdef KDEPIM_ENTERPRISE_BUILD 0506 true 0507 #else 0508 false 0509 #endif 0510 ); 0511 0512 auto *globalPart = composer->globalPart(); 0513 globalPart->setGuiEnabled(false); 0514 0515 auto *infoPart = composer->infoPart(); 0516 infoPart->setCc(msg.cc); 0517 infoPart->setTo(msg.to); 0518 infoPart->setFrom(msg.from); 0519 if (msg.bccMe) { 0520 infoPart->setBcc({msg.from}); 0521 } 0522 infoPart->setSubject(msg.subject); 0523 0524 // Set User-Agent 0525 auto *header = new KMime::Headers::Generic("User-Agent"); 0526 header->fromUnicodeString(QStringLiteral("KOrganizer %1").arg(QStringLiteral(AKONADI_CALENDAR_VERSION)), "utf-8"); 0527 KMime::Headers::Base::List extras; 0528 extras.push_back(header); 0529 infoPart->setExtraHeaders(extras); 0530 0531 auto *itipPart = composer->itipPart(); 0532 itipPart->setOutlookConformInvitation(outlookConformInvitation); 0533 itipPart->setInvitationBody(msg.body); 0534 itipPart->setInvitation(msg.attachment); 0535 itipPart->setMethod(msg.method); 0536 } 0537 0538 bool MailClient::addKeysToContext(const QString &gnupgHome, 0539 const QList<QPair<QStringList, std::vector<GpgME::Key>>> &data, 0540 const std::map<QByteArray, QString> &autocryptMap) 0541 { 0542 bool needSpecialContext = false; 0543 0544 for (const auto &p : data) { 0545 for (const auto &k : p.second) { 0546 const auto it = autocryptMap.find(k.primaryFingerprint()); 0547 if (it != autocryptMap.end()) { 0548 needSpecialContext = true; 0549 break; 0550 } 0551 } 0552 if (needSpecialContext) { 0553 break; 0554 } 0555 } 0556 0557 if (!needSpecialContext) { 0558 return false; 0559 } 0560 const QGpgME::Protocol *proto(QGpgME::openpgp()); 0561 0562 const auto storage = MessageCore::AutocryptStorage::self(); 0563 QEventLoop loop; 0564 int runningJobs = 0; 0565 for (const auto &p : data) { 0566 for (const auto &k : p.second) { 0567 const auto it = autocryptMap.find(k.primaryFingerprint()); 0568 if (it == autocryptMap.end()) { 0569 qCDebug(AKONADICALENDAR_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import"; 0570 auto exportJob = proto->publicKeyExportJob(false); 0571 connect(exportJob, 0572 &QGpgME::ExportJob::result, 0573 exportJob, 0574 [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result, 0575 const QByteArray &keyData, 0576 const QString &auditLogAsHtml, 0577 const GpgME::Error &auditLogError) { 0578 Q_UNUSED(auditLogAsHtml); 0579 Q_UNUSED(auditLogError); 0580 if (result) { 0581 qCWarning(AKONADICALENDAR_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString(); 0582 --runningJobs; 0583 if (runningJobs < 1) { 0584 loop.quit(); 0585 } 0586 } 0587 0588 auto importJob = proto->importJob(); 0589 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData()); 0590 importJob->exec(keyData); 0591 importJob->deleteLater(); 0592 --runningJobs; 0593 if (runningJobs < 1) { 0594 loop.quit(); 0595 } 0596 }); 0597 QStringList patterns; 0598 patterns << QString::fromUtf8(k.primaryFingerprint()); 0599 runningJobs++; 0600 exportJob->start(patterns); 0601 exportJob->setExportFlags(GpgME::Context::ExportMinimal); 0602 } else { 0603 qCDebug(AKONADICALENDAR_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage"; 0604 const auto recipient = storage->getRecipient(it->second.toUtf8()); 0605 auto key = recipient->gpgKey(); 0606 auto keydata = recipient->gpgKeydata(); 0607 if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) { 0608 qCDebug(AKONADICALENDAR_LOG) << "Using gossipkey"; 0609 keydata = recipient->gossipKeydata(); 0610 } 0611 auto importJob = proto->importJob(); 0612 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData()); 0613 const auto result = importJob->exec(keydata); 0614 importJob->deleteLater(); 0615 } 0616 } 0617 } 0618 loop.exec(); 0619 return true; 0620 } 0621 0622 void MailClient::send(const KCalendarCore::IncidenceBase::Ptr &incidence, 0623 const KIdentityManagementCore::Identity &identity, 0624 const MessageData &_msg, 0625 const QString &mailTransport) 0626 { 0627 if (!MailTransport::TransportManager::self()->showTransportCreationDialog(nullptr, MailTransport::TransportManager::IfNoTransportExists)) { 0628 qCritical() << "Error while creating transport"; 0629 Q_EMIT finished(ResultErrorCreatingTransport, i18n("Error while creating transport")); 0630 return; 0631 } 0632 0633 MessageData msg = _msg; 0634 0635 // We must have a recipients list for most MUAs. Thus, if the 'to' list 0636 // is empty simply use the 'from' address as the recipient. 0637 if (msg.to.isEmpty()) { 0638 msg.to.push_back(msg.from); 0639 } 0640 qCDebug(AKONADICALENDAR_LOG) << "\nFrom:" << msg.from << "\nTo:" << msg.to << "\nCC:" << msg.cc << "\nSubject:" << msg.subject << "\nBody: \n" 0641 << msg.body << "\nAttachment:\n" 0642 << msg.attachment << "\nmailTransport: " << mailTransport << "\nidentity:" << identity.identityName(); 0643 0644 MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportByName(mailTransport); 0645 if (!transport) { 0646 qCritical() << "Error fetching transport; mailTransport" << mailTransport << MailTransport::TransportManager::self()->defaultTransportName(); 0647 Q_EMIT finished(ResultErrorFetchingTransport, i18n("Error fetching transport. Unable to send invitations")); 0648 return; 0649 } 0650 0651 auto composers = buildComposers(incidence, identity, msg); 0652 for (auto &composerPtr : composers) { 0653 populateComposer(composerPtr.get(), msg); 0654 auto *composer = composerPtr.release(); 0655 QObject::connect(composer, &MessageComposer::Composer::result, this, [this, transport, composer, incidence, identity, msg]() { 0656 for (const auto &message : composer->resultMessages()) { 0657 queueMessage(transport, composer, incidence, identity, msg, message); 0658 } 0659 composer->deleteLater(); 0660 }); 0661 composer->start(); 0662 } 0663 } 0664 0665 void MailClient::handleQueueJobFinished(KJob *job) 0666 { 0667 if (job->error()) { 0668 qCritical() << "Error queueing message:" << job->errorText(); 0669 Q_EMIT finished(ResultQueueJobError, i18n("Error queuing message in outbox: %1", job->errorText())); 0670 } else { 0671 Q_EMIT finished(ResultSuccess, QString()); 0672 } 0673 } 0674 0675 bool MailClient::determineWhetherToSign(bool doSignCompletely, 0676 Kleo::KeyResolver *keyResolver, 0677 ITIPHandlerDialogDelegate *dialogDelegate, 0678 const KIdentityManagementCore::Identity &identity, 0679 bool signSomething, 0680 bool &signAttachments, 0681 bool &result, 0682 bool &canceled) 0683 { 0684 bool sign = false; 0685 switch (keyResolver->checkSigningPreferences(signSomething)) { 0686 case Kleo::DoIt: 0687 if (!signSomething) { 0688 signAttachments = true; 0689 return true; 0690 } 0691 sign = true; 0692 break; 0693 case Kleo::DontDoIt: 0694 sign = false; 0695 break; 0696 case Kleo::AskOpportunistic: 0697 assert(0); 0698 case Kleo::Ask: { 0699 // the user wants to be asked or has to be asked 0700 const QString msg = i18n( 0701 "Examination of the recipient's signing preferences " 0702 "yielded that you be asked whether or not to sign " 0703 "this message.\n" 0704 "Sign this message?"); 0705 switch (dialogDelegate->warningTwoActionsCancel(msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) { 0706 case ITIPHandlerDialogDelegate::CancelAction: 0707 result = false; 0708 canceled = true; 0709 return false; 0710 case ITIPHandlerDialogDelegate::PrimaryAction: 0711 signAttachments = true; 0712 return true; 0713 case ITIPHandlerDialogDelegate::SecondaryAction: 0714 signAttachments = false; 0715 return false; 0716 default: 0717 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0718 return false; 0719 } 0720 break; 0721 } 0722 case Kleo::Conflict: { 0723 // warn the user that there are conflicting signing preferences 0724 const QString msg = i18n( 0725 "There are conflicting signing preferences " 0726 "for these recipients.\n" 0727 "Sign this message?"); 0728 switch (dialogDelegate->warningTwoActionsCancel(msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) { 0729 case ITIPHandlerDialogDelegate::CancelAction: 0730 result = false; 0731 canceled = true; 0732 return false; 0733 case ITIPHandlerDialogDelegate::PrimaryAction: 0734 signAttachments = true; 0735 return true; 0736 case ITIPHandlerDialogDelegate::SecondaryAction: 0737 signAttachments = false; 0738 return false; 0739 default: 0740 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0741 return false; 0742 } 0743 break; 0744 } 0745 case Kleo::Impossible: { 0746 const QString msg = i18n( 0747 "You have requested to sign this message, " 0748 "but no valid signing keys have been configured " 0749 "for this identity."); 0750 if (dialogDelegate->warningContinueCancel(msg, i18n("Send Unsigned?"), KGuiItem(i18n("Send &Unsigned"))) == KMessageBox::Cancel) { 0751 result = false; 0752 return false; 0753 } else { 0754 signAttachments = false; 0755 return false; 0756 } 0757 } 0758 } 0759 0760 if (!sign || !doSignCompletely) { 0761 if (cryptoWarningUnsigned(identity)) { 0762 const QString msg = sign && !doSignCompletely ? i18n( 0763 "Some parts of this message will not be signed.\n" 0764 "Sending only partially signed messages might violate site policy.\n" 0765 "Sign all parts instead?") // oh, I hate this... 0766 : i18n( 0767 "This message will not be signed.\n" 0768 "Sending unsigned message might violate site policy.\n" 0769 "Sign message instead?"); // oh, I hate this... 0770 const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign"); 0771 switch (dialogDelegate->warningTwoActionsCancel(msg, i18n("Unsigned-Message Warning"), KGuiItem(buttonText), KGuiItem(i18n("Send &As Is")))) { 0772 case ITIPHandlerDialogDelegate::CancelAction: 0773 result = false; 0774 canceled = true; 0775 return false; 0776 case ITIPHandlerDialogDelegate::PrimaryAction: 0777 signAttachments = true; 0778 return true; 0779 case ITIPHandlerDialogDelegate::SecondaryAction: 0780 return sign || doSignCompletely; 0781 default: 0782 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0783 return false; 0784 } 0785 } 0786 } 0787 return sign || doSignCompletely; 0788 } 0789 0790 bool MailClient::determineWhetherToEncrypt(bool doEncryptCompletely, 0791 Kleo::KeyResolver *keyResolver, 0792 ITIPHandlerDialogDelegate *dialogDelegate, 0793 const KIdentityManagementCore::Identity &identity, 0794 bool encryptSomething, 0795 bool signSomething, 0796 bool &encryptAttachments, 0797 bool &result, 0798 bool &canceled) 0799 { 0800 bool encrypt = false; 0801 bool opportunistic = false; 0802 0803 const auto encryptionPrefs = keyResolver->checkEncryptionPreferences(encryptSomething); 0804 qDebug() << "DetermineWhetherToEncrypt:" << encryptionPrefs; 0805 0806 switch (encryptionPrefs) { 0807 case Kleo::DoIt: 0808 if (!encryptSomething) { 0809 encryptAttachments = true; 0810 return true; 0811 } 0812 encrypt = true; 0813 break; 0814 case Kleo::DontDoIt: 0815 encrypt = false; 0816 break; 0817 case Kleo::AskOpportunistic: 0818 opportunistic = true; 0819 // fall through... 0820 [[fallthrough]]; 0821 case Kleo::Ask: { 0822 // the user wants to be asked or has to be asked 0823 const QString msg = opportunistic ? i18n( 0824 "Valid trusted encryption keys were found for all recipients.\n" 0825 "Encrypt this message?") 0826 : i18n( 0827 "Examination of the recipient's encryption preferences " 0828 "yielded that you be asked whether or not to encrypt " 0829 "this message.\n" 0830 "Encrypt this message?"); 0831 switch (dialogDelegate->warningTwoActionsCancel(msg, 0832 i18n("Encrypt Message?"), 0833 KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")), 0834 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) { 0835 case ITIPHandlerDialogDelegate::CancelAction: 0836 result = false; 0837 canceled = true; 0838 return false; 0839 case ITIPHandlerDialogDelegate::PrimaryAction: 0840 encryptAttachments = true; 0841 return true; 0842 case ITIPHandlerDialogDelegate::SecondaryAction: 0843 encryptAttachments = false; 0844 return false; 0845 default: 0846 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0847 return false; 0848 } 0849 break; 0850 } 0851 case Kleo::Conflict: { 0852 // warn the user that there are conflicting encryption preferences 0853 const QString msg = i18n( 0854 "There are conflicting encryption preferences " 0855 "for these recipients.\n" 0856 "Encrypt this message?"); 0857 switch (dialogDelegate->warningTwoActionsCancel(msg, i18n("Encrypt Message?"), KGuiItem(i18n("&Encrypt")), KGuiItem(i18n("Do &Not Encrypt")))) { 0858 case ITIPHandlerDialogDelegate::CancelAction: 0859 result = false; 0860 canceled = true; 0861 return false; 0862 case ITIPHandlerDialogDelegate::PrimaryAction: 0863 encryptAttachments = true; 0864 return true; 0865 case ITIPHandlerDialogDelegate::SecondaryAction: 0866 encryptAttachments = false; 0867 return false; 0868 default: 0869 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0870 return false; 0871 } 0872 break; 0873 } 0874 case Kleo::Impossible: { 0875 const QString msg = i18n( 0876 "You have requested to encrypt this message, " 0877 "and to encrypt a copy to yourself, " 0878 "but no valid trusted encryption keys have been " 0879 "configured for this identity."); 0880 if (dialogDelegate->warningContinueCancel(msg, i18n("Send Unencrypted?"), KGuiItem(i18n("Send &Unencrypted"))) 0881 == ITIPHandlerDialogDelegate::CancelAction) { 0882 result = false; 0883 return false; 0884 } else { 0885 encryptAttachments = false; 0886 return false; 0887 } 0888 } 0889 } 0890 0891 if (!encrypt || !doEncryptCompletely) { 0892 if (cryptoWarningUnencrypted(identity)) { 0893 const QString msg = !doEncryptCompletely ? i18n( 0894 "Some parts of this message will not be encrypted.\n" 0895 "Sending only partially encrypted messages might violate " 0896 "site policy and/or leak sensitive information.\n" 0897 "Encrypt all parts instead?") // oh, I hate this... 0898 : i18n( 0899 "This message will not be encrypted.\n" 0900 "Sending unencrypted messages might violate site policy and/or " 0901 "leak sensitive information.\n" 0902 "Encrypt messages instead?"); // oh, I hate this... 0903 const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt"); 0904 switch (dialogDelegate->warningTwoActionsCancel(msg, 0905 i18n("Unencrypted Message Warning"), 0906 KGuiItem(buttonText), 0907 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) { 0908 case ITIPHandlerDialogDelegate::CancelAction: 0909 result = false; 0910 canceled = true; 0911 return false; 0912 case ITIPHandlerDialogDelegate::PrimaryAction: 0913 encryptAttachments = true; 0914 return true; 0915 case ITIPHandlerDialogDelegate::SecondaryAction: 0916 return encrypt || doEncryptCompletely; 0917 default: 0918 qCWarning(AKONADICALENDAR_LOG) << "Unhandled MessageBox response"; 0919 return false; 0920 } 0921 } 0922 } 0923 0924 return encrypt || doEncryptCompletely; 0925 } 0926 0927 bool MailClient::showKeyApprovalDialog() const 0928 { 0929 return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval(); 0930 } 0931 0932 #include "moc_mailclient_p.cpp"