File indexing completed on 2024-05-26 05:27:50
0001 /* 0002 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 0020 #include "mailtransportresource.h" 0021 #include "facade.h" 0022 #include "facadefactory.h" 0023 #include "resourceconfig.h" 0024 #include "definitions.h" 0025 #include "inspector.h" 0026 #include "store.h" 0027 #include <QDir> 0028 #include <QFileInfo> 0029 #include <QSettings> 0030 #include <QUrl> 0031 #include <KMime/Message> 0032 0033 #include "mailtransport.h" 0034 #include "inspection.h" 0035 #include <synchronizer.h> 0036 #include <log.h> 0037 #include <resourceconfig.h> 0038 #include <mailpreprocessor.h> 0039 #include <adaptorfactoryregistry.h> 0040 0041 #define ENTITY_TYPE_MAIL "mail" 0042 0043 using namespace Sink; 0044 0045 struct Settings { 0046 QString server; 0047 QString username; 0048 QString cacert; 0049 bool testMode; 0050 }; 0051 0052 class MailtransportPreprocessor : public Sink::Preprocessor 0053 { 0054 public: 0055 MailtransportPreprocessor() : Sink::Preprocessor() {} 0056 0057 QByteArray getTargetResource() 0058 { 0059 using namespace Sink::ApplicationDomain; 0060 0061 auto resource = Store::readOne<ApplicationDomain::SinkResource>(Query{}.filter(resourceInstanceIdentifier()).request<ApplicationDomain::SinkResource::Account>()); 0062 if (resource.identifier().isEmpty()) { 0063 SinkWarning() << "Failed to retrieve this resource: " << resourceInstanceIdentifier(); 0064 } 0065 Query query; 0066 query.containsFilter<ApplicationDomain::SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::sent); 0067 query.filter<ApplicationDomain::SinkResource::Account>(resource.getAccount()); 0068 auto targetResource = Store::readOne<ApplicationDomain::SinkResource>(query); 0069 if (targetResource.identifier().isEmpty()) { 0070 SinkWarning() << "Failed to find target resource: " << targetResource.identifier(); 0071 } 0072 return targetResource.identifier(); 0073 } 0074 0075 Result process(Type type, const ApplicationDomain::ApplicationDomainType ¤t, ApplicationDomain::ApplicationDomainType &diff) Q_DECL_OVERRIDE 0076 { 0077 if (type == Preprocessor::Modification) { 0078 using namespace Sink::ApplicationDomain; 0079 if (diff.changedProperties().contains(Mail::Trash::name)) { 0080 //Move back to regular resource 0081 diff.setResource(getTargetResource()); 0082 return {MoveToResource}; 0083 } else if (diff.changedProperties().contains(Mail::Draft::name)) { 0084 //Move back to regular resource 0085 diff.setResource(getTargetResource()); 0086 return {MoveToResource}; 0087 } 0088 } 0089 return {NoAction}; 0090 } 0091 }; 0092 0093 class MailtransportSynchronizer : public Sink::Synchronizer { 0094 public: 0095 MailtransportSynchronizer(const Sink::ResourceContext &resourceContext) 0096 : Sink::Synchronizer(resourceContext), 0097 mResourceInstanceIdentifier(resourceContext.instanceId()) 0098 { 0099 0100 } 0101 0102 KAsync::Job<void> send(const ApplicationDomain::Mail &mail, const Settings &settings) 0103 { 0104 return KAsync::start([=] { 0105 if (!syncStore().readValue(mail.identifier()).isEmpty()) { 0106 SinkLog() << "Mail is already sent: " << mail.identifier(); 0107 return KAsync::null(); 0108 } 0109 emitNotification(Notification::Info, ApplicationDomain::SyncInProgress, "Sending message.", {}, {mail.identifier()}); 0110 const auto data = mail.getMimeMessage(); 0111 auto msg = KMime::Message::Ptr::create(); 0112 msg->setContent(KMime::CRLFtoLF(data)); 0113 msg->parse(); 0114 if (settings.testMode) { 0115 auto subject = msg->subject(true)->asUnicodeString(); 0116 SinkLog() << "I would totally send that mail, but I'm in test mode." << mail.identifier() << subject; 0117 if (!subject.contains("send")) { 0118 return KAsync::error("Failed to send the message."); 0119 } 0120 auto path = resourceStorageLocation(mResourceInstanceIdentifier) + "/test/"; 0121 SinkTrace() << path; 0122 QDir dir; 0123 dir.mkpath(path); 0124 QFile f(path+ mail.identifier()); 0125 f.open(QIODevice::ReadWrite); 0126 f.write("foo"); 0127 f.close(); 0128 } else { 0129 MailTransport::Options options; 0130 if (settings.server.contains("smtps")) { 0131 if (settings.server.contains("465")) { 0132 options |= MailTransport::UseTls; 0133 } else { 0134 options |= MailTransport::UseStarttls; 0135 } 0136 } 0137 0138 SinkLog() << "Sending message " << settings.server << settings.username << "CaCert: " << settings.cacert << "Using tls: " << bool(options & MailTransport::UseTls); 0139 SinkTrace() << "Sending message " << msg; 0140 auto result = MailTransport::sendMessage(msg, settings.server.toUtf8(), settings.username.toUtf8(), secret().toUtf8(), settings.cacert.toUtf8(), options); 0141 if (!result.error) { 0142 SinkWarning() << "Failed to send message: " << mail << "\n" << result.errorMessage; 0143 const auto errorMessage = QString("Failed to send the message: %1").arg(result.errorMessage); 0144 emitNotification(Notification::Warning, ApplicationDomain::SyncError, errorMessage, {}, {mail.identifier()}); 0145 emitNotification(Notification::Warning, ApplicationDomain::TransmissionError, errorMessage, {}, {mail.identifier()}); 0146 return KAsync::error(errorMessage.toUtf8().constData()); 0147 } else { 0148 emitNotification(Notification::Info, ApplicationDomain::SyncSuccess, "Message successfully sent.", {}, {mail.identifier()}); 0149 emitNotification(Notification::Info, ApplicationDomain::TransmissionSuccess, "Message successfully sent.", {}, {mail.identifier()}); 0150 } 0151 } 0152 syncStore().writeValue(mail.identifier(), "sent"); 0153 0154 SinkLog() << "Sent mail, and triggering move to sent mail folder: " << mail.identifier(); 0155 auto modifiedMail = ApplicationDomain::Mail(mResourceInstanceIdentifier, mail.identifier(), mail.revision(), QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create()); 0156 modifiedMail.setSent(true); 0157 0158 auto resource = Store::readOne<ApplicationDomain::SinkResource>(Query{}.filter(mResourceInstanceIdentifier).request<ApplicationDomain::SinkResource::Account>()); 0159 if (resource.identifier().isEmpty()) { 0160 SinkWarning() << "Failed to retrieve target resource: " << mResourceInstanceIdentifier; 0161 } 0162 //Then copy the mail to the target resource 0163 Query query; 0164 query.containsFilter<ApplicationDomain::SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::sent); 0165 query.filter<ApplicationDomain::SinkResource::Account>(resource.getAccount()); 0166 return Store::fetchOne<ApplicationDomain::SinkResource>(query) 0167 .then([this, modifiedMail](const ApplicationDomain::SinkResource &resource) { 0168 //Modify the mail to have the sent property set to true, and move it to the new resource. 0169 modify(modifiedMail, resource.identifier(), true); 0170 }); 0171 }); 0172 } 0173 0174 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE 0175 { 0176 if (!QUrl{mSettings.server}.isValid()) { 0177 return KAsync::error(ApplicationDomain::ConfigurationError, "Invalid server url: " + mSettings.server); 0178 } 0179 return KAsync::start<void>([this]() { 0180 QList<ApplicationDomain::Mail> toSend; 0181 SinkLog() << "Looking for mails to send."; 0182 store().readAll<ApplicationDomain::Mail>([&](const ApplicationDomain::Mail &mail) { 0183 if (!mail.getSent()) { 0184 toSend << mail; 0185 } 0186 }); 0187 SinkLog() << "Found " << toSend.size() << " mails to send"; 0188 auto job = KAsync::null<void>(); 0189 for (const auto &m : toSend) { 0190 job = job.then(send(m, mSettings)); 0191 } 0192 return job; 0193 }); 0194 } 0195 0196 bool canReplay(const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE 0197 { 0198 return true; 0199 } 0200 0201 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 0202 { 0203 if (operation == Sink::Operation_Creation) { 0204 SinkTrace() << "Dispatching message."; 0205 return send(mail, mSettings) 0206 .then(KAsync::value(QByteArray{})); 0207 } else if (operation == Sink::Operation_Removal) { 0208 syncStore().removeValue(mail.identifier(), ""); 0209 } else if (operation == Sink::Operation_Modification) { 0210 } 0211 return KAsync::null<QByteArray>(); 0212 } 0213 0214 public: 0215 QByteArray mResourceInstanceIdentifier; 0216 Settings mSettings; 0217 }; 0218 0219 class MailtransportInspector : public Sink::Inspector { 0220 public: 0221 MailtransportInspector(const Sink::ResourceContext &resourceContext) 0222 : Sink::Inspector(resourceContext) 0223 { 0224 0225 } 0226 0227 protected: 0228 KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE 0229 { 0230 if (domainType == ENTITY_TYPE_MAIL) { 0231 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { 0232 auto path = resourceStorageLocation(mResourceContext.instanceId()) + "/test/" + entityId; 0233 if (QFileInfo::exists(path)) { 0234 return KAsync::null<void>(); 0235 } 0236 return KAsync::error<void>(1, "Couldn't find message: " + path); 0237 } 0238 } 0239 return KAsync::null<void>(); 0240 } 0241 }; 0242 0243 0244 MailtransportResource::MailtransportResource(const Sink::ResourceContext &resourceContext) 0245 : Sink::GenericResource(resourceContext) 0246 { 0247 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); 0248 0249 auto synchronizer = QSharedPointer<MailtransportSynchronizer>::create(resourceContext); 0250 synchronizer->mSettings = {config.value("server").toString(), 0251 config.value("username").toString(), 0252 config.value("cacert").toString(), 0253 config.value("testmode").toBool() 0254 }; 0255 setupSynchronizer(synchronizer); 0256 setupInspector(QSharedPointer<MailtransportInspector>::create(resourceContext)); 0257 0258 setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new MailPropertyExtractor << new MailtransportPreprocessor); 0259 } 0260 0261 MailtransportResourceFactory::MailtransportResourceFactory(QObject *parent) 0262 : Sink::ResourceFactory(parent, {Sink::ApplicationDomain::ResourceCapabilities::Mail::mail, 0263 Sink::ApplicationDomain::ResourceCapabilities::Mail::transport}) 0264 { 0265 0266 } 0267 0268 Sink::Resource *MailtransportResourceFactory::createResource(const Sink::ResourceContext &context) 0269 { 0270 return new MailtransportResource(context); 0271 } 0272 0273 void MailtransportResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) 0274 { 0275 factory.registerFacade<ApplicationDomain::Mail, DefaultFacade<ApplicationDomain::Mail>>(resourceName); 0276 } 0277 0278 void MailtransportResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) 0279 { 0280 registry.registerFactory<Sink::ApplicationDomain::Mail, DomainTypeAdaptorFactory<ApplicationDomain::Mail>>(resourceName); 0281 } 0282 0283 void MailtransportResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) 0284 { 0285 MailtransportResource::removeFromDisk(instanceIdentifier); 0286 }