Commit e0479d24 authored by Volker Krause's avatar Volker Krause
Browse files

Remove support for email alarms

That is, sending an email with receivers and content controlled by the iCal
alarm. This is almost as dangerous as procedure alarms in the context of
shared calendars, as it allows an external party to send emails from your
system.

Email alarms are now treated just like regular alarms.

KAlarm still implements this functionality in combination with safe local
calendars, in case someone really can't live without it.
parent 19c882f1
Pipeline #89343 passed with stage
in 14 minutes and 3 seconds
......@@ -58,7 +58,6 @@ set(KDEPIM_LIB_SOVERSION "5")
set(AKONADINOTES_LIB_VERSION "5.18.40")
set(QT_REQUIRED_VERSION "5.15.2")
option(KDEPIM_ENTERPRISE_BUILD "Enable features specific to the enterprise branch, which are normally disabled. Also, it disables many components not needed for Kontact such as the Kolab client." FALSE)
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED DBus Gui Widgets Test UiTools)
find_package(KUserFeedback 1.0.0 CONFIG) # Needs Provider::describeDataSources()
......@@ -77,7 +76,6 @@ set(AKONADI_SEARCH_VERSION "5.18.40")
# Find KF5 package
find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED)
......@@ -111,7 +109,6 @@ find_package(KF5CalendarCore ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5AkonadiContact ${AKONADI_CONTACT_VERSION} CONFIG REQUIRED)
find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5AkonadiMime ${AKONADI_MIMELIB_VERSION} CONFIG REQUIRED)
find_package(KF5CalendarUtils ${CALENDARUTILS_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Ldap ${KLDAP_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5AkonadiCalendar ${AKONADICALENDAR_LIB_VERSION} CONFIG REQUIRED)
......@@ -133,7 +130,6 @@ endif()
set(KDEPIM_HAVE_X11 ${X11_FOUND})
configure_file(config-korganizer.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-korganizer.h)
configure_file(korgac/config-enterprise.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-enterprise.h)
include_directories(${korganizer_SOURCE_DIR} ${korganizer_BINARY_DIR} ${korgac_SOURCE_DIR} ${korgac_BINARY_DIR})
configure_file(korganizer-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/korganizer-version.h @ONLY)
......
......@@ -1298,8 +1298,7 @@ automatically.</para></listitem>
<para>Use the <guibutton>Configure</guibutton> button to open the
<guilabel>Edit existing reminder</guilabel> dialog. Using this dialog, you can choose
how many minutes, hours or days before or after the events start or end you want to be reminded, you can set
repeating intervals for your reminders, and create special reminders that display text, send emails or play
sounds.</para>
repeating intervals for your reminders, and create special reminders that display text or play sounds.</para>
</listitem>
</varlistentry>
......
......@@ -36,7 +36,6 @@ target_sources(korgac PRIVATE
korgacmain.cpp
alarmdialog.cpp
alarmdockwindow.cpp
mailclient.cpp
koalarmclient.cpp
${korgac_SRCS}
)
......@@ -58,15 +57,11 @@ endif()
target_link_libraries(korgac
KF5::AkonadiCalendar
KF5::AkonadiMime
KF5::CalendarSupport
KF5::IncidenceEditor
KF5::AkonadiCore
KF5::CalendarCore
KF5::CalendarUtils
KF5::IdentityManagement
KF5::MailTransportAkonadi
KF5::Codecs
KF5::DBusAddons
Phonon::phonon4qt5
KF5::Notifications
......
......@@ -12,25 +12,18 @@
#include "config-korganizer.h"
#include "koalarmclient_debug.h"
#include "korganizer_interface.h"
#include "mailclient.h"
#include "dbusproperties.h" // DBUS-generated
#include "notifications_interface.h" // DBUS-generated
#include <CalendarSupport/IdentityManager>
#include <CalendarSupport/IncidenceViewer>
#include <CalendarSupport/KCalPrefs>
#include <CalendarSupport/Utils>
#include <KCalUtils/IncidenceFormatter>
#include <KIdentityManagement/Identity>
#include <IncidenceEditor/IncidenceDialog>
#include <IncidenceEditor/IncidenceDialogFactory>
#include <MailTransport/TransportManager>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
......@@ -277,8 +270,6 @@ AlarmDialog::AlarmDialog(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *par
connect(mUser2Button, &QPushButton::clicked, this, &AlarmDialog::slotUser2);
connect(mUser3Button, &QPushButton::clicked, this, &AlarmDialog::slotUser3);
mIdentityManager = new CalendarSupport::IdentityManager;
QDBusConnection dbusConn = QDBusConnection::sessionBus();
if (dbusConn.interface()->isServiceRegistered(QString::fromLatin1(s_fdo_notifications_service))) {
auto propsIface = new OrgFreedesktopDBusPropertiesInterface(QString::fromLatin1(s_fdo_notifications_service),
......@@ -292,7 +283,6 @@ AlarmDialog::AlarmDialog(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *par
AlarmDialog::~AlarmDialog()
{
mIncidenceTree->clear();
delete mIdentityManager;
}
ReminderTreeItem *AlarmDialog::searchByItem(const Akonadi::Item &incidence)
......@@ -637,7 +627,7 @@ void AlarmDialog::eventNotification()
Alarm::List::ConstIterator ait;
for (ait = alarms.constBegin(); ait != alarms.constEnd(); ++ait) {
Alarm::Ptr alarm = *ait;
// we intentionally ignore Alarm::Procedure here, as that is insecure in the presence of shared calendars
// we intentionally ignore Alarm::Procedure and Alarm::Email here, as that is insecure in the presence of shared calendars
// FIXME: Check whether this should be done for all multiple alarms
if (alarm->type() == Alarm::Audio) {
beeped = true;
......@@ -645,59 +635,6 @@ void AlarmDialog::eventNotification()
player->setParent(this);
connect(player, &Phonon::MediaObject::finished, player, &Phonon::MediaObject::deleteLater);
player->play();
} else if (alarm->type() == Alarm::Email) {
QString from = CalendarSupport::KCalPrefs::instance()->email();
KIdentityManagement::Identity id = mIdentityManager->identityForAddress(from);
QString to;
if (alarm->mailAddresses().isEmpty()) {
to = from;
} else {
const Person::List addresses = alarm->mailAddresses();
QStringList add;
add.reserve(addresses.count());
Person::List::ConstIterator end(addresses.constEnd());
for (Person::List::ConstIterator it = addresses.constBegin(); it != end; ++it) {
add << (*it).fullName();
}
to = add.join(QLatin1String(", "));
}
QString subject;
Akonadi::Item parentItem = mCalendar->item(alarm->parentUid());
Incidence::Ptr parent = CalendarSupport::incidence(parentItem);
if (alarm->mailSubject().isEmpty()) {
if (parent->summary().isEmpty()) {
subject = i18nc("@title", "Reminder");
} else {
subject = i18nc("@title", "Reminder: %1", cleanSummary(parent->summary()));
}
} else {
subject = i18nc("@title", "Reminder: %1", alarm->mailSubject());
}
QString body = KCalUtils::IncidenceFormatter::mailBodyStr(parent.staticCast<IncidenceBase>());
if (!alarm->mailText().isEmpty()) {
body += QLatin1Char('\n') + alarm->mailText();
}
// TODO: support attachments
KOrg::MailClient mailer;
const bool sendStatus =
mailer
.send(id, from, to, QString(), subject, body, true, false, QString(), MailTransport::TransportManager::self()->defaultTransportName());
if (!sendStatus) {
KNotification::event(QStringLiteral("mailremindersent"),
QString(),
i18nc("@info email subject, error message",
"<warning>Failed to send the Email reminder for %1. %2</warning>",
subject,
mailer.errorMsg()),
QStringLiteral("korgac"),
nullptr,
KNotification::CloseOnTimeout,
QStringLiteral("korgac"));
}
}
}
}
......
......@@ -14,11 +14,6 @@
class ReminderTreeItem;
namespace KIdentityManagement
{
class IdentityManager;
}
namespace Akonadi
{
class Item;
......@@ -121,7 +116,6 @@ private:
Akonadi::ETMCalendar::Ptr mCalendar;
QTreeWidget *mIncidenceTree = nullptr;
CalendarSupport::IncidenceViewer *mDetailView = nullptr;
KIdentityManagement::IdentityManager *mIdentityManager = nullptr;
QRect mRect;
QSpinBox *mSuspendSpin = nullptr;
......
#cmakedefine KDEPIM_ENTERPRISE_BUILD 1
/*
SPDX-FileCopyrightText: 1998 Barry D Benowitz <b.benowitz@telesciences.com>
SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2009 Allen Winter <winter@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
*/
#include <config-enterprise.h>
#include "koalarmclient_debug.h"
#include "korganizer-version.h"
#include "mailclient.h"
#include <QElapsedTimer>
#include <Akonadi/Collection>
#include <KCalendarCore/Attendee>
#include <KCalendarCore/Incidence>
#include <KCalUtils/IncidenceFormatter>
#include <KMime/Message>
#include <KIdentityManagement/Identity>
#include <KEmailAddress>
#include <MailTransport/Transport>
#include <MailTransport/TransportManager>
#include <MailTransportAkonadi/MessageQueueJob>
#include <KLocalizedString>
#include <KProtocolManager>
using namespace KOrg;
MailClient::MailClient()
: QObject()
{
}
MailClient::~MailClient()
{
}
QString const MailClient::errorMsg()
{
return errorString;
}
bool MailClient::mailAttendees(const KCalendarCore::IncidenceBase::Ptr &incidence,
const KIdentityManagement::Identity &identity,
bool bccMe,
const QString &attachment,
const QString &mailTransport)
{
const KCalendarCore::Attendee::List attendees = incidence->attendees();
if (attendees.isEmpty()) {
errorString = i18n("No attendees specified");
qCWarning(KOALARMCLIENT_LOG) << "There are no attendees to e-mail";
return false;
}
const QString from = incidence->organizer().fullName();
const QString organizerEmail = incidence->organizer().email();
QStringList toList;
QStringList ccList;
for (const auto &a : attendees) {
const QString email = a.email();
if (email.isEmpty()) {
continue;
}
// In case we (as one of our identities) are the organizer we are sending
// this mail. We could also have added ourselves as an attendee, in which
// case we don't want to send ourselves a notification mail.
if (organizerEmail == email) {
continue;
}
// Build a nice address for this attendee including the CN.
QString tname;
QString temail;
const QString username = KEmailAddress::quoteNameIfNecessary(a.name());
// ignore the return value from extractEmailAddressAndName() because
// it will always be false since tusername does not contain "@domain".
KEmailAddress::extractEmailAddressAndName(username, temail /*byref*/, tname /*byref*/);
tname += QLatin1String(" <") + email + QLatin1Char('>');
// Optional Participants and Non-Participants are copied on the email
if (a.role() == KCalendarCore::Attendee::OptParticipant || a.role() == KCalendarCore::Attendee::NonParticipant) {
ccList << tname;
} else {
toList << tname;
}
}
if (toList.isEmpty() && ccList.isEmpty()) {
// Not really to be called a groupware meeting, eh
qCDebug(KOALARMCLIENT_LOG) << "There are really no attendees to e-mail";
errorString = i18n("No attendees specified");
return false;
}
QString to;
if (!toList.isEmpty()) {
to = toList.join(QLatin1String(", "));
}
QString cc;
if (!ccList.isEmpty()) {
cc = ccList.join(QLatin1String(", "));
}
QString subject;
if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) {
const KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>();
subject = inc->summary();
} else {
subject = i18n("Free Busy Object");
}
const QString body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence);
return send(identity, from, to, cc, subject, body, false, bccMe, attachment, mailTransport);
}
bool MailClient::mailOrganizer(const KCalendarCore::IncidenceBase::Ptr &incidence,
const KIdentityManagement::Identity &identity,
const QString &from,
bool bccMe,
const QString &attachment,
const QString &sub,
const QString &mailTransport)
{
const QString to = incidence->organizer().fullName();
QString subject = sub;
if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) {
const KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>();
if (subject.isEmpty()) {
subject = inc->summary();
}
} else {
subject = i18n("Free Busy Message");
}
const QString body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence);
return send(identity, from, to, QString(), subject, body, false, bccMe, attachment, mailTransport);
}
bool MailClient::mailTo(const KCalendarCore::IncidenceBase::Ptr &incidence,
const KIdentityManagement::Identity &identity,
const QString &from,
bool bccMe,
const QString &recipients,
const QString &attachment,
const QString &mailTransport)
{
QString subject;
if (incidence->type() != KCalendarCore::Incidence::TypeFreeBusy) {
KCalendarCore::Incidence::Ptr inc = incidence.staticCast<KCalendarCore::Incidence>();
subject = inc->summary();
} else {
subject = i18n("Free Busy Message");
}
const QString body = KCalUtils::IncidenceFormatter::mailBodyStr(incidence);
return send(identity, from, recipients, QString(), subject, body, false, bccMe, attachment, mailTransport);
}
QStringList extractEmailAndNormalize(const QString &email)
{
const QStringList splittedEmail = KEmailAddress::splitAddressList(email);
QStringList normalizedEmail;
normalizedEmail.reserve(splittedEmail.count());
for (const QString &email : splittedEmail) {
const QString str = KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(email));
normalizedEmail << str;
}
return normalizedEmail;
}
bool MailClient::send(const KIdentityManagement::Identity &identity,
const QString &from,
const QString &_to,
const QString &cc,
const QString &subject,
const QString &body,
bool hidden,
bool bccMe,
const QString &attachment,
const QString &mailTransport)
{
Q_UNUSED(hidden)
if (!MailTransport::TransportManager::self()->showTransportCreationDialog(nullptr, MailTransport::TransportManager::IfNoTransportExists)) {
errorString = i18n("Unable to start the transport manager");
return false;
}
// We must have a recipients list for most MUAs. Thus, if the 'to' list
// is empty simply use the 'from' address as the recipient.
QString to = _to;
if (to.isEmpty()) {
to = from;
}
qCDebug(KOALARMCLIENT_LOG) << "\nFrom:" << from << "\nTo:" << to << "\nCC:" << cc << "\nSubject:" << subject << "\nBody: \n"
<< body << "\nAttachment:\n"
<< attachment << "\nmailTransport: " << mailTransport;
QElapsedTimer timer;
timer.start();
MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportByName(mailTransport);
if (!transport) {
transport = MailTransport::TransportManager::self()->transportById(MailTransport::TransportManager::self()->defaultTransportId(), false);
}
if (!transport) {
// TODO: we need better error handling. Currently korganizer says "Error sending invitation".
// Using a boolean for errors isn't granular enough.
qCCritical(KOALARMCLIENT_LOG) << "Error fetching transport; mailTransport" << mailTransport
<< MailTransport::TransportManager::self()->defaultTransportName();
errorString = i18n("Unable to determine a mail transport");
return false;
}
const int transportId = transport->id();
// gather config values
KConfig config(QStringLiteral("kmail2rc"));
KConfigGroup configGroup(&config, QStringLiteral("Invitations"));
const bool outlookConformInvitation = configGroup.readEntry("LegacyBodyInvites",
#ifdef KDEPIM_ENTERPRISE_BUILD
true
#else
false
#endif
);
// Now build the message we like to send. The message KMime::Message::Ptr instance
// will be the root message that has 2 additional message. The body itself and
// the attached cal.ics calendar file.
KMime::Message::Ptr message = KMime::Message::Ptr(new KMime::Message);
message->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
message->contentTransferEncoding()->setDecoded(true);
// Set the headers
message->userAgent()->fromUnicodeString(KProtocolManager::userAgentForApplication(QStringLiteral("KOrganizer"), QStringLiteral(KORGANIZER_VERSION)),
"utf-8");
message->from()->fromUnicodeString(from, "utf-8");
message->to()->fromUnicodeString(to, "utf-8");
message->cc()->fromUnicodeString(cc, "utf-8");
if (bccMe) {
message->bcc()->fromUnicodeString(from, "utf-8"); // from==me, right?
}
message->date()->setDateTime(QDateTime::currentDateTime());
message->subject()->fromUnicodeString(subject, "utf-8");
if (outlookConformInvitation) {
message->contentType()->setMimeType("text/calendar");
message->contentType()->setCharset("utf-8");
message->contentType()->setName(QStringLiteral("cal.ics"), "utf-8");
message->contentType()->setParameter(QStringLiteral("method"), QStringLiteral("request"));
if (!attachment.isEmpty()) {
auto disposition = new KMime::Headers::ContentDisposition;
disposition->setDisposition(KMime::Headers::CDinline);
message->setHeader(disposition);
message->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
message->setBody(KMime::CRLFtoLF(attachment.toUtf8()));
}
} else {
// We need to set following 4 lines by hand else KMime::Content::addContent
// will create a new Content instance for us to attach the main message
// what we don't need cause we already have the main message instance where
// 2 additional messages are attached.
KMime::Headers::ContentType *ct = message->contentType();
ct->setMimeType("multipart/mixed");
ct->setBoundary(KMime::multiPartBoundary());
ct->setCategory(KMime::Headers::CCcontainer);
// Set the first multipart, the body message.
auto bodyMessage = new KMime::Content;
auto bodyDisposition = new KMime::Headers::ContentDisposition;
bodyDisposition->setDisposition(KMime::Headers::CDinline);
bodyMessage->contentType()->setMimeType("text/plain");
bodyMessage->contentType()->setCharset("utf-8");
bodyMessage->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
bodyMessage->setBody(KMime::CRLFtoLF(body.toUtf8()));
bodyMessage->setHeader(bodyDisposition);
message->addContent(bodyMessage);
// Set the second multipart, the attachment.
if (!attachment.isEmpty()) {
auto attachMessage = new KMime::Content;
auto attachDisposition = new KMime::Headers::ContentDisposition;
attachDisposition->setDisposition(KMime::Headers::CDattachment);
attachMessage->contentType()->setMimeType("text/calendar");
attachMessage->contentType()->setCharset("utf-8");
attachMessage->contentType()->setName(QStringLiteral("cal.ics"), "utf-8");
attachMessage->contentType()->setParameter(QStringLiteral("method"), QStringLiteral("request"));
attachMessage->setHeader(attachDisposition);
attachMessage->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
attachMessage->setBody(KMime::CRLFtoLF(attachment.toUtf8()));
message->addContent(attachMessage);
}
}
// Job done, attach the both multiparts and assemble the message.
message->assemble();
// Put the newly created item in the MessageQueueJob.
auto qjob = new MailTransport::MessageQueueJob(this);
qjob->transportAttribute().setTransportId(transportId);
if (identity.disabledFcc()) {
qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::Delete);
} else {
const Akonadi::Collection sentCollection(identity.fcc().toLongLong());
if (sentCollection.isValid()) {
qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToCollection);
qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
} else {
qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection);
}
}
if (transport->specifySenderOverwriteAddress()) {
qjob->addressAttribute().setFrom(
KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress())));
} else {
qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(from)));
}
if (!to.isEmpty()) {
qjob->addressAttribute().setTo(extractEmailAndNormalize(to));
}
if (!cc.isEmpty()) {
qjob->addressAttribute().setCc(extractEmailAndNormalize(cc));
}
if (bccMe) {
qjob->addressAttribute().setBcc(extractEmailAndNormalize(from));
}
qjob->setMessage(message);
if (!qjob->exec()) {
qCDebug(KOALARMCLIENT_LOG) << "Error queuing message in outbox:" << qjob->errorText();
errorString = i18n("Unable to queue the message in the outbox");
return false;
}
// Everything done successful now.
qCDebug(KOALARMCLIENT_LOG) << "Send mail finished. Time elapsed in ms:" << timer.elapsed();
return true;
}
/*
SPDX-FileCopyrightText: 1998 Barry D Benowitz <b.benowitz@telesciences.com>
SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2009 Allen Winter <winter@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
*/
#pragma once
#include <KCalendarCore/IncidenceBase>
#include <QObject>
/**
* MailClient is in kdepimlibs/akonadi/calendar now, but it's private API and I don't
* feel like making public right now, hence this copy.
*
* Theres probably non calendaring-specific APIs to send e-mails, so I'd like to keep
* MailClient private in kdepimlibs.
*/
namespace KIdentityManagement
{
class Identity;
}
namespace KOrg
{
class MailClient : public QObject
{
Q_OBJECT
public:
MailClient();
~MailClient();
/**
* Return the error description string.
* Empty if there are no errors.
*/
Q_REQUIRED_RESULT const QString errorMsg();
Q_REQUIRED_RESULT bool mailAttendees(const KCalendarCore::IncidenceBase::Ptr &,
const KIdentityManagement::Identity &identity,
bool bccMe,
const QString &attachment = QString(),
const QString &mailTransport = QString());
Q_REQUIRED_RESULT bool mailOrganizer(const KCalendarCore::IncidenceBase::Ptr &,
const KIdentityManagement::Identity &identity,
const QString &from,
bool bccMe,
const QString &attachment = QString(),
const QString &sub = QString(),
const QString &mailTransport = QString());