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"