From 4b11f1b48188993f65a3d039531a1c59b750e189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 29 Apr 2019 16:31:57 +0200 Subject: [PATCH 1/4] Clean up email notification plugin and prepare for improvement --- .../devicepluginmailnotification.cpp | 71 +++------- .../devicepluginmailnotification.json | 125 +----------------- mailnotification/smtpclient.cpp | 79 +++++------ mailnotification/smtpclient.h | 27 ++-- 4 files changed, 77 insertions(+), 225 deletions(-) diff --git a/mailnotification/devicepluginmailnotification.cpp b/mailnotification/devicepluginmailnotification.cpp index f526a274..08fffef7 100644 --- a/mailnotification/devicepluginmailnotification.cpp +++ b/mailnotification/devicepluginmailnotification.cpp @@ -36,11 +36,6 @@ \chapter Supported services - \section2 Google Mail - With the Google Mail Notification you can send a mail with your gmail address to a recipient. The - username is your mail address (e.g. "chuck.norris@gmail.com"). The recipient will receive the notification - from your gmail account. - \section2 Yahoo Mail The Yahoo Mail Notification you can send a mail with your yahoo address to a recipient. The username is your mail address (e.g. "chuck.norris@yahoo.com"). The recipient will receive the notification @@ -80,48 +75,6 @@ DevicePluginMailNotification::~DevicePluginMailNotification() DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Device *device) { - // Google mail - if(device->deviceClassId() == googleMailDeviceClassId) { - SmtpClient *smtpClient = new SmtpClient(this); - smtpClient->setHost("smtp.gmail.com"); - smtpClient->setPort(465); - smtpClient->setUser(device->paramValue(googleMailDeviceUserParamTypeId).toString()); - // TODO: use cryptography to save password not as plain text - smtpClient->setPassword(device->paramValue(googleMailDevicePasswordParamTypeId).toString()); - smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin); - smtpClient->setEncryptionType(SmtpClient::EncryptionTypeSSL); - smtpClient->setSender(device->paramValue(googleMailDeviceUserParamTypeId).toString()); - smtpClient->setRecipient(device->paramValue(googleMailDeviceRecipientParamTypeId).toString()); - - connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished); - connect(smtpClient, &SmtpClient::sendMailFinished, this, &DevicePluginMailNotification::sendMailFinished); - m_clients.insert(smtpClient,device); - - smtpClient->testLogin(); - - return DeviceManager::DeviceSetupStatusAsync; - } - // Yahoo mail - if(device->deviceClassId() == yahooMailDeviceClassId) { - SmtpClient *smtpClient = new SmtpClient(this); - smtpClient->setHost("smtp.mail.yahoo.com"); - smtpClient->setPort(465); - smtpClient->setUser(device->paramValue(yahooMailDeviceUserParamTypeId).toString()); - // TODO: use cryptography to save password not as plain text - smtpClient->setPassword(device->paramValue(yahooMailDevicePasswordParamTypeId).toString()); - smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin); - smtpClient->setEncryptionType(SmtpClient::EncryptionTypeSSL); - smtpClient->setSender(device->paramValue(yahooMailDeviceUserParamTypeId).toString()); - smtpClient->setRecipient(device->paramValue(yahooMailDeviceRecipientParamTypeId).toString()); - - connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished); - connect(smtpClient, &SmtpClient::sendMailFinished, this, &DevicePluginMailNotification::sendMailFinished); - m_clients.insert(smtpClient,device); - - smtpClient->testLogin(); - - return DeviceManager::DeviceSetupStatusAsync; - } // Custom mail if(device->deviceClassId() == customMailDeviceClassId) { SmtpClient *smtpClient = new SmtpClient(this); @@ -133,9 +86,9 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic smtpClient->setPassword(device->paramValue(customMailDeviceCustomPasswordParamTypeId).toString()); if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "PLAIN") { - smtpClient->setAuthMethod(SmtpClient::AuthMethodPlain); + smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodPlain); } else if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "LOGIN") { - smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin); + smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodLogin); } else { return DeviceManager::DeviceSetupStatusFailure; } @@ -166,12 +119,22 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic DeviceManager::DeviceError DevicePluginMailNotification::executeAction(Device *device, const Action &action) { - if(action.actionTypeId() == googleMailSendMailActionTypeId) { - SmtpClient *smtpClient = m_clients.key(device); - smtpClient->sendMail(action.param(googleMailSendMailActionSubjectParamTypeId).value().toString(), action.param(googleMailSendMailActionBodyParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + if (device->deviceClassId() == customMailDeviceClassId) { + if(action.actionTypeId() == customMailNotifyActionTypeId) { + SmtpClient *smtpClient = m_clients.key(device); + if (!smtpClient) { + qCWarning(dcMailNotification()) << "Could not find SMTP client for " << device; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + smtpClient->sendMail(action.param(customMailNotifyActionTitleParamTypeId).value().toString(), + action.param(customMailNotifyActionBodyParamTypeId).value().toString(), + action.id()); + return DeviceManager::DeviceErrorAsync; + } + return DeviceManager::DeviceErrorActionTypeNotFound; } - return DeviceManager::DeviceErrorActionTypeNotFound; + return DeviceManager::DeviceErrorDeviceClassNotFound; } void DevicePluginMailNotification::deviceRemoved(Device *device) diff --git a/mailnotification/devicepluginmailnotification.json b/mailnotification/devicepluginmailnotification.json index c18ed934..1c279939 100644 --- a/mailnotification/devicepluginmailnotification.json +++ b/mailnotification/devicepluginmailnotification.json @@ -12,7 +12,8 @@ "id": "f4844c97-7ca6-4349-904e-ff9749a9fe74", "name": "customMail", "displayName": "Custom mail", - "createMethods": ["user"], + "createMethods": [ "user" ], + "interfaces": [ "notifications" ], "paramTypes": [ { "id": "af30ce7b-fb6b-42f0-889d-20b32f8b8fa4", @@ -75,130 +76,12 @@ "actionTypes": [ { "id": "054613b0-3666-4dad-9252-e0ebca187edc", - "name": "send", + "name": "notify", "displayName": "send mail", "paramTypes": [ { "id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006", - "name": "subject", - "displayName": "subject", - "type": "QString", - "inputType": "TextLine" - }, - { - "id": "aeb6e79e-9862-43e1-9873-cbdce549344a", - "name": "body", - "displayName": "body", - "type": "QString", - "inputType": "TextArea" - } - ] - } - ] - } - ] - }, - { - "name": "google", - "displayName": "Google", - "id": "d02399c5-8e79-40be-8cf6-e00b55bbfe7c", - "deviceClasses": [ - { - "id": "3869884a-1592-4b8f-84a7-994be18ff555", - "name": "googleMail", - "displayName": "Google mail", - "createMethods": ["user"], - "paramTypes": [ - { - "id": "0a4ea3ff-ea76-4a5f-adbb-b8f79faa2156", - "name": "user", - "displayName": "user", - "type": "QString", - "inputType": "Mail" - }, - { - "id": "f63f6486-5340-472a-b37c-be828111725c", - "name": "password", - "displayName": "password", - "type": "QString", - "inputType": "Password" - }, - { - "id": "76697fe2-3393-4ed0-a15d-9d041502cfd3", - "name": "recipient", - "displayName": "recipient", - "type": "QString", - "inputType": "Mail" - } - ], - "actionTypes": [ - { - "id": "054613b0-3666-4dad-9252-e0ebca187edc", - "name": "sendMail", - "displayName": "send mail", - "paramTypes": [ - { - "id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006", - "name": "subject", - "displayName": "subject", - "type": "QString", - "inputType": "TextLine" - }, - { - "id": "aeb6e79e-9862-43e1-9873-cbdce549344a", - "name": "body", - "displayName": "body", - "type": "QString", - "inputType": "TextArea" - } - ] - } - ] - } - ] - }, - { - "name": "yahoo", - "displayName": "Yahoo", - "id": "5eb3e1e9-25a7-4740-806e-5cfc836e4bf5", - "deviceClasses": [ - { - "id": "59409e8f-0c83-414f-abd5-bbbf2758acba", - "name": "yahooMail", - "displayName": "Yahoo mail", - "createMethods": ["user"], - "paramTypes": [ - { - "id": "0a4ea3ff-ea76-4a5f-adbb-b8f79faa2156", - "name": "user", - "displayName": "user", - "type": "QString", - "inputType": "Mail" - }, - { - "id": "f63f6486-5340-472a-b37c-be828111725c", - "name": "password", - "displayName": "password", - "type": "QString", - "inputType": "Password" - }, - { - "id": "76697fe2-3393-4ed0-a15d-9d041502cfd3", - "name": "recipient", - "displayName": "recipient", - "type": "QString", - "inputType": "Mail" - } - ], - "actionTypes": [ - { - "id": "054613b0-3666-4dad-9252-e0ebca187edc", - "name": "send", - "displayName": "send mail", - "paramTypes": [ - { - "id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006", - "name": "subject", + "name": "title", "displayName": "subject", "type": "QString", "inputType": "TextLine" diff --git a/mailnotification/smtpclient.cpp b/mailnotification/smtpclient.cpp index e6e9764f..9901b072 100644 --- a/mailnotification/smtpclient.cpp +++ b/mailnotification/smtpclient.cpp @@ -27,30 +27,23 @@ SmtpClient::SmtpClient(QObject *parent): QObject(parent) { m_socket = new QSslSocket(this); - m_state = InitState; - m_testLogin = false; - connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); - // error because QSslSocket has also a method called error...also QAbstractSocket or QTcpSocket don't work... - //connect(m_socket, &QSslSocket::error, this, &SmtpClient::socketError); connect(m_socket, &QSslSocket::connected, this, &SmtpClient::connected); connect(m_socket, &QSslSocket::readyRead, this, &SmtpClient::readData); connect(m_socket, &QSslSocket::disconnected, this, &SmtpClient::disconnected); - + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); } void SmtpClient::connectToHost() { switch (m_encryptionType) { case EncryptionTypeNone: + case EncryptionTypeTLS: m_socket->connectToHost(m_host, m_port); break; case EncryptionTypeSSL: m_socket->connectToHostEncrypted(m_host, m_port); break; - case EncryptionTypeTLS: - m_socket->connectToHost(m_host,m_port); - break; default: break; } @@ -80,59 +73,68 @@ void SmtpClient::readData() responseLine = m_socket->readLine(); response.append(responseLine); } - responseLine.truncate( 3 ); + responseLine.truncate(3); + + qCDebug(dcMailNotification()) << "<--" << response << "|" << responseLine; switch (m_state) { case InitState: - if(responseLine == "220") { + if (responseLine == "220") { send("EHLO localhost"); - if(m_encryptionType == EncryptionTypeNone) { + + if (m_encryptionType == EncryptionTypeNone) { m_state = AuthentificationState; break; } - if(m_encryptionType == EncryptionTypeSSL) { + + if (m_encryptionType == EncryptionTypeSSL) { m_state = HandShakeState; break; } - if(m_encryptionType == EncryptionTypeTLS) { + + if (m_encryptionType == EncryptionTypeTLS) { m_state = StartTlsState; break; } } break; case HandShakeState: - if(responseLine == "250") { - if(!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { + if (responseLine == "250") { + if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { m_socket->startClientEncryption(); } send("EHLO localhost"); m_state = AuthentificationState; } - if(responseLine == "220") { - if(!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { + + if (responseLine == "220") { + if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { m_socket->startClientEncryption(); } + send("EHLO localhost"); m_state = AuthentificationState; } + break; case StartTlsState: - if(responseLine == "250") { + if (responseLine == "250") { send("STARTTLS"); m_state = HandShakeState; } + break; case AuthentificationState: - if(responseLine == "250") { - if(m_authMethod == AuthMethodLogin) { + if (responseLine == "250") { + if (m_authenticationMethod == AuthenticationMethodLogin) { send("AUTH LOGIN"); m_state = UserState; break; } - if(m_authMethod == AuthMethodPlain) { + if (m_authenticationMethod == AuthenticationMethodPlain) { send("AUTH PLAIN " + QByteArray().append((char) 0).append(m_user).append((char) 0).append(m_password).toBase64()); // if we just want to test the Login, we are almost done here - if(!m_testLogin) { + if (!m_testLogin) { m_state = MailState; } else { m_state = TestLoginFinishedState; @@ -142,16 +144,16 @@ void SmtpClient::readData() } break; case UserState: - if(responseLine == "334") { + if (responseLine == "334") { send(QByteArray().append(m_user).toBase64()); m_state = PasswordState; } break; case PasswordState: - if(responseLine == "334") { + if (responseLine == "334") { send(QByteArray().append(m_password).toBase64()); // if we just want to test the Login, we are almost done here - if(!m_testLogin) { + if (!m_testLogin) { m_state = MailState; } else { m_state = TestLoginFinishedState; @@ -160,7 +162,7 @@ void SmtpClient::readData() } break; case TestLoginFinishedState: - if(responseLine == "235") { + if (responseLine == "235") { emit testLoginFinished(true); } else { emit testLoginFinished(false); @@ -169,38 +171,38 @@ void SmtpClient::readData() m_testLogin = false; break; case MailState: - if(responseLine == "235") { + if (responseLine == "235") { send("MAIL FROM:<" + m_sender + ">"); m_state = RcptState; } break; case RcptState: - if(responseLine == "250") { + if (responseLine == "250") { send("RCPT TO:<" + m_rcpt + ">"); m_state = DataState; } break; case DataState: - if(responseLine == "250") { + if (responseLine == "250") { send("DATA"); m_state = BodyState; } break; case BodyState: - if(responseLine == "354") { + if (responseLine == "354") { send(m_message + "\r\n.\r\n"); m_state = QuitState; } break; case QuitState: - if(responseLine == "250") { + if (responseLine == "250") { emit sendMailFinished(true, m_actionId); send("QUIT"); m_state = CloseState; } break; case CloseState: - if(responseLine == "221") { + if (responseLine == "221") { m_socket->close(); } // some mail server does not recognize the QUIT command...so close the connection either way @@ -208,7 +210,7 @@ void SmtpClient::readData() break; default: // unexpecterd response code received... - if(m_testLogin) { + if (m_testLogin) { emit testLoginFinished(false); m_testLogin = false; m_socket->close(); @@ -256,9 +258,9 @@ void SmtpClient::setEncryptionType(const SmtpClient::EncryptionType &encryptionT m_encryptionType = encryptionType; } -void SmtpClient::setAuthMethod(const SmtpClient::AuthMethod &authMethod) +void SmtpClient::setAuthenticationMethod(const SmtpClient::AuthenticationMethod &authenticationMethod) { - m_authMethod = authMethod; + m_authenticationMethod = authenticationMethod; } void SmtpClient::setUser(const QString &user) @@ -281,13 +283,14 @@ void SmtpClient::setRecipient(const QString &rcpt) m_rcpt = rcpt; } -void SmtpClient::socketError(QAbstractSocket::SocketError error) +void SmtpClient::onSocketError(QAbstractSocket::SocketError error) { - qCWarning(dcMailNotification) << "Mail socket -> " << error << m_socket->errorString(); + qCWarning(dcMailNotification) << "Mail socket error" << error << m_socket->errorString(); } void SmtpClient::send(const QString &data) { + qCDebug(dcMailNotification()) << "-->" << qUtf8Printable(data.toUtf8()); m_socket->write(data.toUtf8() + "\r\n"); m_socket->flush(); } diff --git a/mailnotification/smtpclient.h b/mailnotification/smtpclient.h index 6f7e5ee1..3d0a6a6e 100644 --- a/mailnotification/smtpclient.h +++ b/mailnotification/smtpclient.h @@ -35,10 +35,11 @@ class SmtpClient : public QObject Q_OBJECT public: - enum AuthMethod{ - AuthMethodPlain, - AuthMethodLogin + enum AuthenticationMethod{ + AuthenticationMethodPlain, + AuthenticationMethodLogin }; + Q_ENUM(AuthenticationMethod) enum SendState{ InitState, @@ -55,12 +56,14 @@ public: QuitState, CloseState }; + Q_ENUM(SendState) enum EncryptionType{ EncryptionTypeNone, EncryptionTypeSSL, EncryptionTypeTLS }; + Q_ENUM(EncryptionType) explicit SmtpClient(QObject *parent = 0); @@ -71,22 +74,22 @@ public: void setHost(const QString &host); void setPort(const int &port); void setEncryptionType(const EncryptionType &encryptionType); - void setAuthMethod(const AuthMethod &authMethod); + void setAuthenticationMethod(const AuthenticationMethod &authenticationMethod); void setUser(const QString &user); void setPassword(const QString &password); void setSender(const QString &sender); void setRecipient(const QString &rcpt); - private: - QSslSocket *m_socket; - SendState m_state; - QString m_host; - int m_port; + QSslSocket *m_socket = nullptr; + SendState m_state = InitState; + QString m_host = "127.0.0.1"; + int m_port = 25; + QString m_user; QString m_password; QString m_sender; - AuthMethod m_authMethod; + AuthenticationMethod m_authenticationMethod; EncryptionType m_encryptionType; QString m_rcpt; QString m_subject; @@ -94,14 +97,14 @@ private: QString m_message; ActionId m_actionId; - bool m_testLogin; + bool m_testLogin = false; signals: void sendMailFinished(const bool &success, const ActionId &actionId); void testLoginFinished(const bool &success); private slots: - void socketError(QAbstractSocket::SocketError error); + void onSocketError(QAbstractSocket::SocketError error); void connected(); void disconnected(); void readData(); From 9c3758742f96a60d2241726b9fbdcc452d402f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 18 May 2019 00:35:45 +0200 Subject: [PATCH 2/4] Improve mail notification plugin --- .../devicepluginmailnotification.cpp | 8 +- .../devicepluginmailnotification.json | 12 +- mailnotification/smtpclient.cpp | 218 +++++++++++------- mailnotification/smtpclient.h | 70 ++++-- 4 files changed, 196 insertions(+), 112 deletions(-) diff --git a/mailnotification/devicepluginmailnotification.cpp b/mailnotification/devicepluginmailnotification.cpp index 08fffef7..f5746451 100644 --- a/mailnotification/devicepluginmailnotification.cpp +++ b/mailnotification/devicepluginmailnotification.cpp @@ -148,9 +148,11 @@ void DevicePluginMailNotification::testLoginFinished(const bool &success) { SmtpClient *smtpClient = static_cast(sender()); Device *device = m_clients.value(smtpClient); - if(success) { + if (success) { + qCDebug(dcMailNotification()) << "Email login test successfull"; emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); } else { + qCWarning(dcMailNotification()) << "Email login test failed"; emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); if(m_clients.contains(smtpClient)) { m_clients.remove(smtpClient); @@ -161,9 +163,11 @@ void DevicePluginMailNotification::testLoginFinished(const bool &success) void DevicePluginMailNotification::sendMailFinished(const bool &success, const ActionId &actionId) { - if(success) { + if (success) { + qCDebug(dcMailNotification()) << "Email sent successfully"; emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); } else { + qCWarning(dcMailNotification()) << "Email sending failed"; emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorDeviceNotFound); } } diff --git a/mailnotification/devicepluginmailnotification.json b/mailnotification/devicepluginmailnotification.json index 1c279939..56ad4833 100644 --- a/mailnotification/devicepluginmailnotification.json +++ b/mailnotification/devicepluginmailnotification.json @@ -18,28 +18,28 @@ { "id": "af30ce7b-fb6b-42f0-889d-20b32f8b8fa4", "name": "customSender", - "displayName": "sender mail", + "displayName": "Sender mail", "type": "QString", "inputType": "Mail" }, { "id": "b91d0ecc-6903-4991-ae8d-f36757ce40a7", "name": "customUser", - "displayName": "user", + "displayName": "User", "type": "QString", "inputType": "TextLine" }, { "id": "ac29e643-1d18-4612-8d2f-65fb07a67182", "name": "customPassword", - "displayName": "password", + "displayName": "Password", "type": "QString", "inputType": "Password" }, { "id": "d657f002-9741-42e1-9fef-32eae96dacdb", "name": "customRecipient", - "displayName": "recipient", + "displayName": "Recipient", "type": "QString", "inputType": "Mail" }, @@ -53,9 +53,9 @@ { "id": "56ec204f-2e02-4a17-b9d1-e855e384b689", "name": "port", - "displayName": "port", + "displayName": "Port", "type": "int", - "defaultValue": "465" + "defaultValue": 25 }, { "id": "789b963b-4143-4e21-853c-2612707d726f", diff --git a/mailnotification/smtpclient.cpp b/mailnotification/smtpclient.cpp index 9901b072..8be95602 100644 --- a/mailnotification/smtpclient.cpp +++ b/mailnotification/smtpclient.cpp @@ -23,6 +23,10 @@ #include "smtpclient.h" #include "extern-plugininfo.h" +#include + +Q_LOGGING_CATEGORY(dcSmtpClient, "SmtpClient") + SmtpClient::SmtpClient(QObject *parent): QObject(parent) { @@ -31,6 +35,7 @@ SmtpClient::SmtpClient(QObject *parent): connect(m_socket, &QSslSocket::connected, this, &SmtpClient::connected); connect(m_socket, &QSslSocket::readyRead, this, &SmtpClient::readData); connect(m_socket, &QSslSocket::disconnected, this, &SmtpClient::disconnected); + connect(m_socket, &QSslSocket::encrypted, this, &SmtpClient::onEncrypted); connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); } @@ -44,24 +49,34 @@ void SmtpClient::connectToHost() case EncryptionTypeSSL: m_socket->connectToHostEncrypted(m_host, m_port); break; - default: - break; } } void SmtpClient::testLogin() { - m_socket->close(); m_testLogin = true; + setState(StateInitialize); + m_socket->close(); connectToHost(); } void SmtpClient::connected() { + qCDebug(dcSmtpClient()) << "Connected"; } void SmtpClient::disconnected() { + qCDebug(dcSmtpClient()) << "Disconnected"; + setState(StateIdle); + sendNextMail(); +} + +void SmtpClient::onEncrypted() +{ + qCDebug(dcSmtpClient()) << "Socket encrypted"; + send("EHLO localhost"); + setState(StateAuthentification); } void SmtpClient::readData() @@ -74,94 +89,84 @@ void SmtpClient::readData() response.append(responseLine); } responseLine.truncate(3); - - qCDebug(dcMailNotification()) << "<--" << response << "|" << responseLine; + qCDebug(dcSmtpClient()) << "<--" << response; switch (m_state) { - case InitState: + case StateIdle: + sendNextMail(); + break; + case StateInitialize: if (responseLine == "220") { send("EHLO localhost"); if (m_encryptionType == EncryptionTypeNone) { - m_state = AuthentificationState; + setState(StateAuthentification); break; } if (m_encryptionType == EncryptionTypeSSL) { - m_state = HandShakeState; + setState(StateHandShake); break; } if (m_encryptionType == EncryptionTypeTLS) { - m_state = StartTlsState; + setState(StateStartTls); break; } } break; - case HandShakeState: - if (responseLine == "250") { + case StateHandShake: + if (responseLine == "250" || responseLine == "220") { if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { + qCDebug(dcSmtpClient()) << "Start client encryption..."; m_socket->startClientEncryption(); } - send("EHLO localhost"); - m_state = AuthentificationState; } - - if (responseLine == "220") { - if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { - m_socket->startClientEncryption(); - } - - send("EHLO localhost"); - m_state = AuthentificationState; - } - break; - case StartTlsState: + case StateStartTls: if (responseLine == "250") { send("STARTTLS"); - m_state = HandShakeState; + setState(StateHandShake); } - break; - case AuthentificationState: + case StateAuthentification: if (responseLine == "250") { if (m_authenticationMethod == AuthenticationMethodLogin) { send("AUTH LOGIN"); - m_state = UserState; + setState(StateUser); break; } if (m_authenticationMethod == AuthenticationMethodPlain) { send("AUTH PLAIN " + QByteArray().append((char) 0).append(m_user).append((char) 0).append(m_password).toBase64()); // if we just want to test the Login, we are almost done here if (!m_testLogin) { - m_state = MailState; + setState(StateMail); } else { - m_state = TestLoginFinishedState; + setState(StateTestLoginFinished); } break; } } break; - case UserState: + case StateUser: if (responseLine == "334") { send(QByteArray().append(m_user).toBase64()); - m_state = PasswordState; + setState(StatePassword); } break; - case PasswordState: + case StatePassword: if (responseLine == "334") { send(QByteArray().append(m_password).toBase64()); // if we just want to test the Login, we are almost done here if (!m_testLogin) { - m_state = MailState; + setState(StateMail); } else { - m_state = TestLoginFinishedState; + setState(StateTestLoginFinished); } break; } break; - case TestLoginFinishedState: + case StateTestLoginFinished: if (responseLine == "235") { emit testLoginFinished(true); } else { @@ -170,77 +175,56 @@ void SmtpClient::readData() m_socket->close(); m_testLogin = false; break; - case MailState: + case StateMail: if (responseLine == "235") { send("MAIL FROM:<" + m_sender + ">"); - m_state = RcptState; + setState(StateRcpt); } break; - case RcptState: + case StateRcpt: if (responseLine == "250") { send("RCPT TO:<" + m_rcpt + ">"); - m_state = DataState; + setState(StateData); } break; - case DataState: + case StateData: if (responseLine == "250") { send("DATA"); - m_state = BodyState; + setState(StateBody); } break; - case BodyState: + case StateBody: if (responseLine == "354") { - send(m_message + "\r\n.\r\n"); - m_state = QuitState; + send(m_messageData + "\r\n.\r\n"); + setState(StateQuit); } break; - case QuitState: + case StateQuit: if (responseLine == "250") { - emit sendMailFinished(true, m_actionId); + emit sendMailFinished(true, m_message.actionId); send("QUIT"); - m_state = CloseState; + setState(StateClose); } break; - case CloseState: + case StateClose: if (responseLine == "221") { m_socket->close(); } // some mail server does not recognize the QUIT command...so close the connection either way m_socket->close(); break; - default: - // unexpecterd response code received... - if (m_testLogin) { - emit testLoginFinished(false); - m_testLogin = false; - m_socket->close(); - break; - } - emit sendMailFinished(false, m_actionId); - m_socket->close(); - break; } } -bool SmtpClient::sendMail(const QString &subject, const QString &body, const ActionId &actionId) +void SmtpClient::sendMail(const QString &subject, const QString &body, const ActionId &actionId) { - m_actionId = actionId; + Message message; + message.subject = subject; + message.body = body; + message.actionId = actionId; - // create mail content - m_state = InitState; - m_message.clear(); - - m_message = "To: " + m_rcpt + "\n"; - m_message.append("From: " + m_sender + "\n"); - m_message.append("Subject: [nymea notification] | " + subject + "\n"); - m_message.append(body); - m_message.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "\r\n" ) ); - m_message.replace( QString::fromLatin1( "\r\n.\r\n" ), QString::fromLatin1( "\r\n..\r\n" ) ); - m_message.append("\r\n.\r\n"); - m_socket->close(); - connectToHost(); - - return true; + m_messageQueue.enqueue(message); + sendNextMail(); } void SmtpClient::setHost(const QString &host) @@ -248,7 +232,7 @@ void SmtpClient::setHost(const QString &host) m_host = host; } -void SmtpClient::setPort(const int &port) +void SmtpClient::setPort(const quint16 &port) { m_port = port; } @@ -283,14 +267,88 @@ void SmtpClient::setRecipient(const QString &rcpt) m_rcpt = rcpt; } +QString SmtpClient::createDateString() +{ + return QDateTime::currentDateTime().toString(Qt::RFC2822Date); +} + +void SmtpClient::setState(SmtpClient::State state) +{ + if (m_state == state) + return; + + qCDebug(dcSmtpClient()) << state; + m_state = state; +} + +bool SmtpClient::verifyResponseCode(int responseCode) +{ + if (responseCode >= 500) { + return false; + } + return true; +} + +void SmtpClient::sendNextMail() +{ + // Check if there is a mail left to send + if (m_messageQueue.isEmpty()) + return; + + // Check if busy + if (m_state != StateIdle) { + return; + } + + sendEmailInternally(m_messageQueue.dequeue()); +} + +void SmtpClient::sendEmailInternally(const Message &message) +{ + qCDebug(dcMailNotification()) << "Start sending message" << message.subject << message.body; + + // Initialize data for sending + m_message = message; + m_messageData.clear(); + + // Create plain message content + m_messageData = "To: " + m_rcpt + "\n"; + m_messageData.append("From: " + m_sender + "\n"); + m_messageData.append("Subject: " + message.subject + "\n"); + m_messageData.append("Date: " + createDateString() + "\n"); + m_messageData.append("Content-Type: text/plain; charset=\"UTF-8\"\n"); + m_messageData.append("Content-Transfer-Encoding: quoted-printable\n"); + m_messageData.append("MIME-Version: 1.0\n"); + m_messageData.append("X-Mailer: nymea;\n"); + m_messageData.append(message.body); + m_messageData.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "\r\n" ) ); + m_messageData.replace( QString::fromLatin1( "\r\n.\r\n" ), QString::fromLatin1( "\r\n..\r\n" ) ); + m_messageData.append("\r\n.\r\n"); + + setState(StateInitialize); + + // Make sure the connection starts from the beginning + m_socket->close(); + connectToHost(); +} + void SmtpClient::onSocketError(QAbstractSocket::SocketError error) { - qCWarning(dcMailNotification) << "Mail socket error" << error << m_socket->errorString(); + qCWarning(dcMailNotification()) << "Mail socket error" << error << m_socket->errorString(); + if (m_state != StateIdle) { + if (m_testLogin) { + emit testLoginFinished(false); + } else { + emit sendMailFinished(false, m_message.actionId); + } + m_socket->close(); + setState(StateIdle); + } } void SmtpClient::send(const QString &data) { - qCDebug(dcMailNotification()) << "-->" << qUtf8Printable(data.toUtf8()); + qCDebug(dcSmtpClient()) << "-->" << data; m_socket->write(data.toUtf8() + "\r\n"); m_socket->flush(); } diff --git a/mailnotification/smtpclient.h b/mailnotification/smtpclient.h index 3d0a6a6e..33c20fbd 100644 --- a/mailnotification/smtpclient.h +++ b/mailnotification/smtpclient.h @@ -27,9 +27,20 @@ #include #include #include +#include +#include #include "plugin/deviceplugin.h" +Q_DECLARE_LOGGING_CATEGORY(dcSmtpClient) + +struct Message { + QString subject; + QString body; + ActionId actionId; +}; + + class SmtpClient : public QObject { Q_OBJECT @@ -41,22 +52,23 @@ public: }; Q_ENUM(AuthenticationMethod) - enum SendState{ - InitState, - HandShakeState, - AuthentificationState, - StartTlsState, - UserState, - PasswordState, - TestLoginFinishedState, - MailState, - RcptState, - DataState, - BodyState, - QuitState, - CloseState + enum State{ + StateIdle, + StateInitialize, + StateHandShake, + StateAuthentification, + StateStartTls, + StateUser, + StatePassword, + StateTestLoginFinished, + StateMail, + StateRcpt, + StateData, + StateBody, + StateQuit, + StateClose }; - Q_ENUM(SendState) + Q_ENUM(State) enum EncryptionType{ EncryptionTypeNone, @@ -65,14 +77,14 @@ public: }; Q_ENUM(EncryptionType) - explicit SmtpClient(QObject *parent = 0); + explicit SmtpClient(QObject *parent = nullptr); void connectToHost(); void testLogin(); - bool sendMail(const QString &subject, const QString &body, const ActionId &actionId); + void sendMail(const QString &subject, const QString &body, const ActionId &actionId); void setHost(const QString &host); - void setPort(const int &port); + void setPort(const quint16 &port); void setEncryptionType(const EncryptionType &encryptionType); void setAuthenticationMethod(const AuthenticationMethod &authenticationMethod); void setUser(const QString &user); @@ -82,9 +94,9 @@ public: private: QSslSocket *m_socket = nullptr; - SendState m_state = InitState; + State m_state = StateIdle; QString m_host = "127.0.0.1"; - int m_port = 25; + quint16 m_port = 25; QString m_user; QString m_password; @@ -92,13 +104,22 @@ private: AuthenticationMethod m_authenticationMethod; EncryptionType m_encryptionType; QString m_rcpt; - QString m_subject; - QString m_boy; - QString m_message; - ActionId m_actionId; + + // Created for each message + Message m_message; + QString m_messageData; + + QQueue m_messageQueue; bool m_testLogin = false; + QString createDateString(); + void setState(State state); + bool verifyResponseCode(int responseCode); + + void sendNextMail(); + void sendEmailInternally(const Message &message); + signals: void sendMailFinished(const bool &success, const ActionId &actionId); void testLoginFinished(const bool &success); @@ -107,6 +128,7 @@ private slots: void onSocketError(QAbstractSocket::SocketError error); void connected(); void disconnected(); + void onEncrypted(); void readData(); void send(const QString &data); }; From 8a6f22b04c7f3a336d22cb179d1caf87cbb22bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 23 May 2019 13:37:04 +0200 Subject: [PATCH 3/4] Fix smtp envelop coding for mail notifications --- mailnotification/smtpclient.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mailnotification/smtpclient.cpp b/mailnotification/smtpclient.cpp index 8be95602..d6769aef 100644 --- a/mailnotification/smtpclient.cpp +++ b/mailnotification/smtpclient.cpp @@ -312,17 +312,18 @@ void SmtpClient::sendEmailInternally(const Message &message) m_messageData.clear(); // Create plain message content - m_messageData = "To: " + m_rcpt + "\n"; - m_messageData.append("From: " + m_sender + "\n"); - m_messageData.append("Subject: " + message.subject + "\n"); - m_messageData.append("Date: " + createDateString() + "\n"); - m_messageData.append("Content-Type: text/plain; charset=\"UTF-8\"\n"); - m_messageData.append("Content-Transfer-Encoding: quoted-printable\n"); - m_messageData.append("MIME-Version: 1.0\n"); - m_messageData.append("X-Mailer: nymea;\n"); + m_messageData = "To: " + m_rcpt + "\r\n"; + m_messageData.append("From: " + m_sender + "\r\n"); + m_messageData.append("Subject: " + message.subject + "\r\n"); + m_messageData.append("Date: " + createDateString() + "\r\n"); + m_messageData.append("Content-Type: text/plain; charset=\"UTF-8\"\r\n"); + m_messageData.append("Content-Transfer-Encoding: quoted-printable\r\n"); + m_messageData.append("MIME-Version: 1.0\r\n"); + m_messageData.append("X-Mailer: nymea;\r\n"); + m_messageData.append("\r\n"); m_messageData.append(message.body); - m_messageData.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "\r\n" ) ); - m_messageData.replace( QString::fromLatin1( "\r\n.\r\n" ), QString::fromLatin1( "\r\n..\r\n" ) ); + //m_messageData.replace(QString::fromLatin1("\n"), QString::fromLatin1("\r\n")); + //m_messageData.replace(QString::fromLatin1("\r\n.\r\n"), QString::fromLatin1("\r\n..\r\n")); m_messageData.append("\r\n.\r\n"); setState(StateInitialize); From 7c6880f62d6535af278f3a64bc74e0ff9fbaeb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 23 May 2019 17:09:35 +0200 Subject: [PATCH 4/4] Improve smtp error handling and parsing mechanism and add multiple recipients mechanism --- .../devicepluginmailnotification.cpp | 7 +- mailnotification/smtpclient.cpp | 377 +++++++++++------- mailnotification/smtpclient.h | 17 +- 3 files changed, 250 insertions(+), 151 deletions(-) diff --git a/mailnotification/devicepluginmailnotification.cpp b/mailnotification/devicepluginmailnotification.cpp index f5746451..15fcc3f3 100644 --- a/mailnotification/devicepluginmailnotification.cpp +++ b/mailnotification/devicepluginmailnotification.cpp @@ -79,7 +79,7 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic if(device->deviceClassId() == customMailDeviceClassId) { SmtpClient *smtpClient = new SmtpClient(this); smtpClient->setHost(device->paramValue(customMailDeviceSmtpParamTypeId).toString()); - smtpClient->setPort(device->paramValue(customMailDevicePortParamTypeId).toInt()); + smtpClient->setPort(static_cast(device->paramValue(customMailDevicePortParamTypeId).toUInt())); smtpClient->setUser(device->paramValue(customMailDeviceCustomUserParamTypeId).toString()); // TODO: use cryptography to save password not as plain text @@ -103,7 +103,10 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic return DeviceManager::DeviceSetupStatusFailure; } - smtpClient->setRecipient(device->paramValue(customMailDeviceCustomRecipientParamTypeId).toString()); + QString recipientsString = device->paramValue(customMailDeviceCustomRecipientParamTypeId).toString(); + QStringList recipients = recipientsString.split(","); + + smtpClient->setRecipients(recipients); smtpClient->setSender(device->paramValue(customMailDeviceCustomSenderParamTypeId).toString()); connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished); diff --git a/mailnotification/smtpclient.cpp b/mailnotification/smtpclient.cpp index d6769aef..4465f3b5 100644 --- a/mailnotification/smtpclient.cpp +++ b/mailnotification/smtpclient.cpp @@ -44,6 +44,7 @@ void SmtpClient::connectToHost() switch (m_encryptionType) { case EncryptionTypeNone: case EncryptionTypeTLS: + // Note: the handshake will be done later, not from the beginning m_socket->connectToHost(m_host, m_port); break; case EncryptionTypeSSL: @@ -81,138 +82,23 @@ void SmtpClient::onEncrypted() void SmtpClient::readData() { - QString response; - QString responseLine; - - while(m_socket->canReadLine() && responseLine[3] != ' ') { + while(m_socket->canReadLine()) { + QString responseLine; responseLine = m_socket->readLine(); - response.append(responseLine); - } - responseLine.truncate(3); - qCDebug(dcSmtpClient()) << "<--" << response; - switch (m_state) { - case StateIdle: - sendNextMail(); - break; - case StateInitialize: - if (responseLine == "220") { - send("EHLO localhost"); + qCDebug(dcSmtpClient()) << "<--" << responseLine; - if (m_encryptionType == EncryptionTypeNone) { - setState(StateAuthentification); - break; + bool responseCodeParseSuccess = false; + int responseCode = responseLine.left(3).toInt(&responseCodeParseSuccess); + if (!responseCodeParseSuccess) { + qCWarning(dcSmtpClient()) << "Could not convert status code to a valid integer" << responseLine; + if (m_state != StateIdle) { + handleSmtpFailure(); + continue; } - - if (m_encryptionType == EncryptionTypeSSL) { - setState(StateHandShake); - break; - } - - if (m_encryptionType == EncryptionTypeTLS) { - setState(StateStartTls); - break; - } - } - break; - case StateHandShake: - if (responseLine == "250" || responseLine == "220") { - if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { - qCDebug(dcSmtpClient()) << "Start client encryption..."; - m_socket->startClientEncryption(); - } - } - break; - case StateStartTls: - if (responseLine == "250") { - send("STARTTLS"); - setState(StateHandShake); - } - break; - case StateAuthentification: - if (responseLine == "250") { - if (m_authenticationMethod == AuthenticationMethodLogin) { - send("AUTH LOGIN"); - setState(StateUser); - break; - } - if (m_authenticationMethod == AuthenticationMethodPlain) { - send("AUTH PLAIN " + QByteArray().append((char) 0).append(m_user).append((char) 0).append(m_password).toBase64()); - // if we just want to test the Login, we are almost done here - if (!m_testLogin) { - setState(StateMail); - } else { - setState(StateTestLoginFinished); - } - break; - } - } - break; - case StateUser: - if (responseLine == "334") { - send(QByteArray().append(m_user).toBase64()); - setState(StatePassword); - } - break; - case StatePassword: - if (responseLine == "334") { - send(QByteArray().append(m_password).toBase64()); - // if we just want to test the Login, we are almost done here - if (!m_testLogin) { - setState(StateMail); - } else { - setState(StateTestLoginFinished); - } - break; - } - break; - case StateTestLoginFinished: - if (responseLine == "235") { - emit testLoginFinished(true); } else { - emit testLoginFinished(false); + processServerResponse(responseCode, responseLine); } - m_socket->close(); - m_testLogin = false; - break; - case StateMail: - if (responseLine == "235") { - send("MAIL FROM:<" + m_sender + ">"); - setState(StateRcpt); - } - break; - case StateRcpt: - if (responseLine == "250") { - send("RCPT TO:<" + m_rcpt + ">"); - setState(StateData); - } - break; - case StateData: - if (responseLine == "250") { - send("DATA"); - setState(StateBody); - } - break; - case StateBody: - if (responseLine == "354") { - send(m_messageData + "\r\n.\r\n"); - setState(StateQuit); - } - break; - case StateQuit: - if (responseLine == "250") { - emit sendMailFinished(true, m_message.actionId); - send("QUIT"); - setState(StateClose); - } - break; - case StateClose: - if (responseLine == "221") { - m_socket->close(); - } - // some mail server does not recognize the QUIT command...so close the connection either way - m_socket->close(); - break; } } @@ -262,9 +148,9 @@ void SmtpClient::setSender(const QString &sender) m_sender = sender; } -void SmtpClient::setRecipient(const QString &rcpt) +void SmtpClient::setRecipients(const QStringList &recipients) { - m_rcpt = rcpt; + m_recipients = recipients; } QString SmtpClient::createDateString() @@ -281,12 +167,201 @@ void SmtpClient::setState(SmtpClient::State state) m_state = state; } -bool SmtpClient::verifyResponseCode(int responseCode) +void SmtpClient::processServerResponse(int responseCode, const QString &response) { - if (responseCode >= 500) { - return false; + switch (m_state) { + case StateIdle: + // Check if we have to send an other email, otherwise we are done and remain in idle + sendNextMail(); + break; + case StateInitialize: + if (responseCode == 220) { + send("EHLO localhost"); + + if (m_encryptionType == EncryptionTypeNone) { + setState(StateAuthentification); + break; + } + + if (m_encryptionType == EncryptionTypeSSL) { + setState(StateHandShake); + break; + } + + if (m_encryptionType == EncryptionTypeTLS) { + setState(StateStartTls); + break; + } + } + break; + case StateHandShake: + // Ignore server information messages + if (responseCode == 250) { + break; + } + + // We need a 220 befor continue + if (responseCode == 220) { + if (!m_socket->isEncrypted() && m_encryptionType != EncryptionTypeNone) { + qCDebug(dcSmtpClient()) << "Start client encryption..."; + m_socket->startClientEncryption(); + } + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateStartTls: + // Ignore server information messages until we get a '250 ...' instead of '250-....' + if (responseCode == 250 && response.at(3) != ' ') { + break; + } + if (responseCode == 250) { + send("STARTTLS"); + setState(StateHandShake); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateAuthentification: + // Ignore server information messages until we get a '250 ...' instead of '250-....' + if (responseCode == 250 && response.at(3) != ' ') { + break; + } + + if (responseCode == 250) { + if (m_authenticationMethod == AuthenticationMethodLogin) { + send("AUTH LOGIN"); + setState(StateUser); + break; + } + + if (m_authenticationMethod == AuthenticationMethodPlain) { + send("AUTH PLAIN " + QByteArray().append(static_cast(0)) + .append(m_user) + .append(static_cast(0)) + .append(m_password) + .toBase64()); + + // If we just want to test the Login, we are almost done here + if (!m_testLogin) { + setState(StateMail); + } else { + setState(StateTestLoginFinished); + } + break; + } + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateUser: + if (responseCode == 334) { + send(QByteArray().append(m_user).toBase64()); + setState(StatePassword); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StatePassword: + if (responseCode == 334) { + send(QByteArray().append(m_password).toBase64()); + // if we just want to test the Login, we are almost done here + if (!m_testLogin) { + setState(StateMail); + } else { + setState(StateTestLoginFinished); + } + break; + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateTestLoginFinished: + if (responseCode == 235) { + emit testLoginFinished(true); + } else { + emit testLoginFinished(false); + } + m_socket->close(); + m_testLogin = false; + break; + case StateMail: + if (responseCode == 235) { + send("MAIL FROM:<" + m_sender + ">"); + + // Prepare queue for recipients + m_recipientsQueue.clear(); + qCDebug(dcSmtpClient()) << "Prepare recipients list" << m_recipients; + foreach (const QString &recipient, m_recipients) { + m_recipientsQueue.enqueue(recipient.trimmed()); + } + setState(StateRcpt); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateRcpt: + // Ignore server information messages until we get a '250 ...' instead of '250-....' + if (responseCode == 250 && response.at(3) != ' ') { + break; + } + + if (responseCode == 250) { + send("RCPT TO:<" + m_recipientsQueue.dequeue() + ">"); + + if (m_recipientsQueue.isEmpty()) { + setState(StateData); + } + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateData: + // Ignore server information messages until we get a '250 ...' instead of '250-....' + if (responseCode == 250 && response.at(3) != ' ') { + break; + } + + if (responseCode == 250) { + send("DATA"); + setState(StateBody); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateBody: + if (responseCode == 354) { + send(m_messageData + "\r\n.\r\n"); + setState(StateQuit); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateQuit: + // Ignore server information messages until we get a '250 ...' instead of '250-....' + if (responseCode == 250 && response.at(3) != ' ') { + break; + } + + if (responseCode == 250) { + emit sendMailFinished(true, m_message.actionId); + send("QUIT"); + setState(StateClose); + } else { + handleUnexpectedSmtpCode(responseCode, response); + } + break; + case StateClose: + if (responseCode == 221) { + m_socket->close(); + } else { + qCDebug(dcSmtpClient()) << "The server does not handle the QUIT command. This is ok, we close the socket either way."; + } + + // some mail server does not recognize the QUIT command...so close the connection either way + m_socket->close(); + break; } - return true; } void SmtpClient::sendNextMail() @@ -296,9 +371,8 @@ void SmtpClient::sendNextMail() return; // Check if busy - if (m_state != StateIdle) { + if (m_state != StateIdle) return; - } sendEmailInternally(m_messageQueue.dequeue()); } @@ -312,7 +386,7 @@ void SmtpClient::sendEmailInternally(const Message &message) m_messageData.clear(); // Create plain message content - m_messageData = "To: " + m_rcpt + "\r\n"; + m_messageData = "To: " + m_recipients.join(",") + "\r\n"; m_messageData.append("From: " + m_sender + "\r\n"); m_messageData.append("Subject: " + message.subject + "\r\n"); m_messageData.append("Date: " + createDateString() + "\r\n"); @@ -322,8 +396,6 @@ void SmtpClient::sendEmailInternally(const Message &message) m_messageData.append("X-Mailer: nymea;\r\n"); m_messageData.append("\r\n"); m_messageData.append(message.body); - //m_messageData.replace(QString::fromLatin1("\n"), QString::fromLatin1("\r\n")); - //m_messageData.replace(QString::fromLatin1("\r\n.\r\n"), QString::fromLatin1("\r\n..\r\n")); m_messageData.append("\r\n.\r\n"); setState(StateInitialize); @@ -333,17 +405,34 @@ void SmtpClient::sendEmailInternally(const Message &message) connectToHost(); } +void SmtpClient::handleSmtpFailure() +{ + if (m_testLogin) { + emit testLoginFinished(false); + } else { + emit sendMailFinished(false, m_message.actionId); + } + + // Clean up + m_socket->close(); + m_messageData.clear(); + m_testLogin = false; + + // Set idle state (handles the queue) + setState(StateIdle); +} + +void SmtpClient::handleUnexpectedSmtpCode(int responseCode, const QString &serverMessage) +{ + qCWarning(dcMailNotification()) << "Received unexpected error code from smtp server" << responseCode << serverMessage; + handleSmtpFailure(); +} + void SmtpClient::onSocketError(QAbstractSocket::SocketError error) { qCWarning(dcMailNotification()) << "Mail socket error" << error << m_socket->errorString(); if (m_state != StateIdle) { - if (m_testLogin) { - emit testLoginFinished(false); - } else { - emit sendMailFinished(false, m_message.actionId); - } - m_socket->close(); - setState(StateIdle); + handleSmtpFailure(); } } diff --git a/mailnotification/smtpclient.h b/mailnotification/smtpclient.h index 33c20fbd..21333f6e 100644 --- a/mailnotification/smtpclient.h +++ b/mailnotification/smtpclient.h @@ -23,12 +23,13 @@ #ifndef SMTPCLIENT_H #define SMTPCLIENT_H -#include +#include #include +#include #include #include +#include #include -#include #include "plugin/deviceplugin.h" @@ -90,7 +91,7 @@ public: void setUser(const QString &user); void setPassword(const QString &password); void setSender(const QString &sender); - void setRecipient(const QString &rcpt); + void setRecipients(const QStringList &recipients); private: QSslSocket *m_socket = nullptr; @@ -103,7 +104,8 @@ private: QString m_sender; AuthenticationMethod m_authenticationMethod; EncryptionType m_encryptionType; - QString m_rcpt; + QStringList m_recipients; + QQueue m_recipientsQueue; // Created for each message Message m_message; @@ -115,16 +117,21 @@ private: QString createDateString(); void setState(State state); - bool verifyResponseCode(int responseCode); + + void processServerResponse(int responseCode, const QString &response); void sendNextMail(); void sendEmailInternally(const Message &message); + void handleSmtpFailure(); + void handleUnexpectedSmtpCode(int responseCode, const QString &serverMessage); + signals: void sendMailFinished(const bool &success, const ActionId &actionId); void testLoginFinished(const bool &success); private slots: + void onSocketError(QAbstractSocket::SocketError error); void connected(); void disconnected();