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] 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); };