Merge PR #109: Update mail notifications plugin

This commit is contained in:
Jenkins 2019-06-19 23:52:34 +02:00
commit 097b794549
4 changed files with 432 additions and 396 deletions

View File

@ -36,11 +36,6 @@
\chapter Supported services \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 \section2 Yahoo Mail
The Yahoo Mail Notification you can send a mail with your yahoo address to a recipient. The username 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 is your mail address (e.g. "chuck.norris@yahoo.com"). The recipient will receive the notification
@ -80,62 +75,20 @@ DevicePluginMailNotification::~DevicePluginMailNotification()
DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Device *device) 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 // Custom mail
if(device->deviceClassId() == customMailDeviceClassId) { if(device->deviceClassId() == customMailDeviceClassId) {
SmtpClient *smtpClient = new SmtpClient(this); SmtpClient *smtpClient = new SmtpClient(this);
smtpClient->setHost(device->paramValue(customMailDeviceSmtpParamTypeId).toString()); smtpClient->setHost(device->paramValue(customMailDeviceSmtpParamTypeId).toString());
smtpClient->setPort(device->paramValue(customMailDevicePortParamTypeId).toInt()); smtpClient->setPort(static_cast<quint16>(device->paramValue(customMailDevicePortParamTypeId).toUInt()));
smtpClient->setUser(device->paramValue(customMailDeviceCustomUserParamTypeId).toString()); smtpClient->setUser(device->paramValue(customMailDeviceCustomUserParamTypeId).toString());
// TODO: use cryptography to save password not as plain text // TODO: use cryptography to save password not as plain text
smtpClient->setPassword(device->paramValue(customMailDeviceCustomPasswordParamTypeId).toString()); smtpClient->setPassword(device->paramValue(customMailDeviceCustomPasswordParamTypeId).toString());
if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "PLAIN") { if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "PLAIN") {
smtpClient->setAuthMethod(SmtpClient::AuthMethodPlain); smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodPlain);
} else if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "LOGIN") { } else if(device->paramValue(customMailDeviceAuthenticationParamTypeId).toString() == "LOGIN") {
smtpClient->setAuthMethod(SmtpClient::AuthMethodLogin); smtpClient->setAuthenticationMethod(SmtpClient::AuthenticationMethodLogin);
} else { } else {
return DeviceManager::DeviceSetupStatusFailure; return DeviceManager::DeviceSetupStatusFailure;
} }
@ -150,7 +103,10 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic
return DeviceManager::DeviceSetupStatusFailure; 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()); smtpClient->setSender(device->paramValue(customMailDeviceCustomSenderParamTypeId).toString());
connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished); connect(smtpClient, &SmtpClient::testLoginFinished, this, &DevicePluginMailNotification::testLoginFinished);
@ -166,12 +122,22 @@ DeviceManager::DeviceSetupStatus DevicePluginMailNotification::setupDevice(Devic
DeviceManager::DeviceError DevicePluginMailNotification::executeAction(Device *device, const Action &action) DeviceManager::DeviceError DevicePluginMailNotification::executeAction(Device *device, const Action &action)
{ {
if(action.actionTypeId() == googleMailSendMailActionTypeId) { if (device->deviceClassId() == customMailDeviceClassId) {
SmtpClient *smtpClient = m_clients.key(device); if(action.actionTypeId() == customMailNotifyActionTypeId) {
smtpClient->sendMail(action.param(googleMailSendMailActionSubjectParamTypeId).value().toString(), action.param(googleMailSendMailActionBodyParamTypeId).value().toString(), action.id()); SmtpClient *smtpClient = m_clients.key(device);
return DeviceManager::DeviceErrorAsync; 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) void DevicePluginMailNotification::deviceRemoved(Device *device)
@ -185,9 +151,11 @@ void DevicePluginMailNotification::testLoginFinished(const bool &success)
{ {
SmtpClient *smtpClient = static_cast<SmtpClient*>(sender()); SmtpClient *smtpClient = static_cast<SmtpClient*>(sender());
Device *device = m_clients.value(smtpClient); Device *device = m_clients.value(smtpClient);
if(success) { if (success) {
qCDebug(dcMailNotification()) << "Email login test successfull";
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
} else { } else {
qCWarning(dcMailNotification()) << "Email login test failed";
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
if(m_clients.contains(smtpClient)) { if(m_clients.contains(smtpClient)) {
m_clients.remove(smtpClient); m_clients.remove(smtpClient);
@ -198,9 +166,11 @@ void DevicePluginMailNotification::testLoginFinished(const bool &success)
void DevicePluginMailNotification::sendMailFinished(const bool &success, const ActionId &actionId) void DevicePluginMailNotification::sendMailFinished(const bool &success, const ActionId &actionId)
{ {
if(success) { if (success) {
qCDebug(dcMailNotification()) << "Email sent successfully";
emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError);
} else { } else {
qCWarning(dcMailNotification()) << "Email sending failed";
emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorDeviceNotFound); emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorDeviceNotFound);
} }
} }

View File

@ -12,33 +12,34 @@
"id": "f4844c97-7ca6-4349-904e-ff9749a9fe74", "id": "f4844c97-7ca6-4349-904e-ff9749a9fe74",
"name": "customMail", "name": "customMail",
"displayName": "Custom mail", "displayName": "Custom mail",
"createMethods": ["user"], "createMethods": [ "user" ],
"interfaces": [ "notifications" ],
"paramTypes": [ "paramTypes": [
{ {
"id": "af30ce7b-fb6b-42f0-889d-20b32f8b8fa4", "id": "af30ce7b-fb6b-42f0-889d-20b32f8b8fa4",
"name": "customSender", "name": "customSender",
"displayName": "sender mail", "displayName": "Sender mail",
"type": "QString", "type": "QString",
"inputType": "Mail" "inputType": "Mail"
}, },
{ {
"id": "b91d0ecc-6903-4991-ae8d-f36757ce40a7", "id": "b91d0ecc-6903-4991-ae8d-f36757ce40a7",
"name": "customUser", "name": "customUser",
"displayName": "user", "displayName": "User",
"type": "QString", "type": "QString",
"inputType": "TextLine" "inputType": "TextLine"
}, },
{ {
"id": "ac29e643-1d18-4612-8d2f-65fb07a67182", "id": "ac29e643-1d18-4612-8d2f-65fb07a67182",
"name": "customPassword", "name": "customPassword",
"displayName": "password", "displayName": "Password",
"type": "QString", "type": "QString",
"inputType": "Password" "inputType": "Password"
}, },
{ {
"id": "d657f002-9741-42e1-9fef-32eae96dacdb", "id": "d657f002-9741-42e1-9fef-32eae96dacdb",
"name": "customRecipient", "name": "customRecipient",
"displayName": "recipient", "displayName": "Recipient",
"type": "QString", "type": "QString",
"inputType": "Mail" "inputType": "Mail"
}, },
@ -52,9 +53,9 @@
{ {
"id": "56ec204f-2e02-4a17-b9d1-e855e384b689", "id": "56ec204f-2e02-4a17-b9d1-e855e384b689",
"name": "port", "name": "port",
"displayName": "port", "displayName": "Port",
"type": "int", "type": "int",
"defaultValue": "465" "defaultValue": 25
}, },
{ {
"id": "789b963b-4143-4e21-853c-2612707d726f", "id": "789b963b-4143-4e21-853c-2612707d726f",
@ -75,130 +76,12 @@
"actionTypes": [ "actionTypes": [
{ {
"id": "054613b0-3666-4dad-9252-e0ebca187edc", "id": "054613b0-3666-4dad-9252-e0ebca187edc",
"name": "send", "name": "notify",
"displayName": "send mail", "displayName": "send mail",
"paramTypes": [ "paramTypes": [
{ {
"id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006", "id": "2047e0f4-3d34-4214-bc8f-9ab741ae6006",
"name": "subject", "name": "title",
"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",
"displayName": "subject", "displayName": "subject",
"type": "QString", "type": "QString",
"inputType": "TextLine" "inputType": "TextLine"

View File

@ -23,222 +23,94 @@
#include "smtpclient.h" #include "smtpclient.h"
#include "extern-plugininfo.h" #include "extern-plugininfo.h"
#include <QDateTime>
Q_LOGGING_CATEGORY(dcSmtpClient, "SmtpClient")
SmtpClient::SmtpClient(QObject *parent): SmtpClient::SmtpClient(QObject *parent):
QObject(parent) QObject(parent)
{ {
m_socket = new QSslSocket(this); 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::connected, this, &SmtpClient::connected);
connect(m_socket, &QSslSocket::readyRead, this, &SmtpClient::readData); connect(m_socket, &QSslSocket::readyRead, this, &SmtpClient::readData);
connect(m_socket, &QSslSocket::disconnected, this, &SmtpClient::disconnected); 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)));
} }
void SmtpClient::connectToHost() void SmtpClient::connectToHost()
{ {
switch (m_encryptionType) { switch (m_encryptionType) {
case EncryptionTypeNone: case EncryptionTypeNone:
case EncryptionTypeTLS:
// Note: the handshake will be done later, not from the beginning
m_socket->connectToHost(m_host, m_port); m_socket->connectToHost(m_host, m_port);
break; break;
case EncryptionTypeSSL: case EncryptionTypeSSL:
m_socket->connectToHostEncrypted(m_host, m_port); m_socket->connectToHostEncrypted(m_host, m_port);
break; break;
case EncryptionTypeTLS:
m_socket->connectToHost(m_host,m_port);
break;
default:
break;
} }
} }
void SmtpClient::testLogin() void SmtpClient::testLogin()
{ {
m_socket->close();
m_testLogin = true; m_testLogin = true;
setState(StateInitialize);
m_socket->close();
connectToHost(); connectToHost();
} }
void SmtpClient::connected() void SmtpClient::connected()
{ {
qCDebug(dcSmtpClient()) << "Connected";
} }
void SmtpClient::disconnected() void SmtpClient::disconnected()
{ {
qCDebug(dcSmtpClient()) << "Disconnected";
setState(StateIdle);
sendNextMail();
}
void SmtpClient::onEncrypted()
{
qCDebug(dcSmtpClient()) << "Socket encrypted";
send("EHLO localhost");
setState(StateAuthentification);
} }
void SmtpClient::readData() void SmtpClient::readData()
{ {
QString response; while(m_socket->canReadLine()) {
QString responseLine; QString responseLine;
while(m_socket->canReadLine() && responseLine[3] != ' ') {
responseLine = m_socket->readLine(); responseLine = m_socket->readLine();
response.append(responseLine);
}
responseLine.truncate( 3 );
switch (m_state) { qCDebug(dcSmtpClient()) << "<--" << responseLine;
case InitState:
if(responseLine == "220") { bool responseCodeParseSuccess = false;
send("EHLO localhost"); int responseCode = responseLine.left(3).toInt(&responseCodeParseSuccess);
if(m_encryptionType == EncryptionTypeNone) { if (!responseCodeParseSuccess) {
m_state = AuthentificationState; qCWarning(dcSmtpClient()) << "Could not convert status code to a valid integer" << responseLine;
break; if (m_state != StateIdle) {
handleSmtpFailure();
continue;
} }
if(m_encryptionType == EncryptionTypeSSL) {
m_state = HandShakeState;
break;
}
if(m_encryptionType == EncryptionTypeTLS) {
m_state = StartTlsState;
break;
}
}
break;
case HandShakeState:
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) {
m_socket->startClientEncryption();
}
send("EHLO localhost");
m_state = AuthentificationState;
}
break;
case StartTlsState:
if(responseLine == "250") {
send("STARTTLS");
m_state = HandShakeState;
}
break;
case AuthentificationState:
if(responseLine == "250") {
if(m_authMethod == AuthMethodLogin) {
send("AUTH LOGIN");
m_state = UserState;
break;
}
if(m_authMethod == AuthMethodPlain) {
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;
} else {
m_state = TestLoginFinishedState;
}
break;
}
}
break;
case UserState:
if(responseLine == "334") {
send(QByteArray().append(m_user).toBase64());
m_state = PasswordState;
}
break;
case PasswordState:
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;
} else {
m_state = TestLoginFinishedState;
}
break;
}
break;
case TestLoginFinishedState:
if(responseLine == "235") {
emit testLoginFinished(true);
} else { } else {
emit testLoginFinished(false); processServerResponse(responseCode, responseLine);
} }
m_socket->close();
m_testLogin = false;
break;
case MailState:
if(responseLine == "235") {
send("MAIL FROM:<" + m_sender + ">");
m_state = RcptState;
}
break;
case RcptState:
if(responseLine == "250") {
send("RCPT TO:<" + m_rcpt + ">");
m_state = DataState;
}
break;
case DataState:
if(responseLine == "250") {
send("DATA");
m_state = BodyState;
}
break;
case BodyState:
if(responseLine == "354") {
send(m_message + "\r\n.\r\n");
m_state = QuitState;
}
break;
case QuitState:
if(responseLine == "250") {
emit sendMailFinished(true, m_actionId);
send("QUIT");
m_state = CloseState;
}
break;
case CloseState:
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_messageQueue.enqueue(message);
m_state = InitState; sendNextMail();
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;
} }
void SmtpClient::setHost(const QString &host) void SmtpClient::setHost(const QString &host)
@ -246,7 +118,7 @@ void SmtpClient::setHost(const QString &host)
m_host = host; m_host = host;
} }
void SmtpClient::setPort(const int &port) void SmtpClient::setPort(const quint16 &port)
{ {
m_port = port; m_port = port;
} }
@ -256,9 +128,9 @@ void SmtpClient::setEncryptionType(const SmtpClient::EncryptionType &encryptionT
m_encryptionType = encryptionType; 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) void SmtpClient::setUser(const QString &user)
@ -276,18 +148,297 @@ void SmtpClient::setSender(const QString &sender)
m_sender = sender; m_sender = sender;
} }
void SmtpClient::setRecipient(const QString &rcpt) void SmtpClient::setRecipients(const QStringList &recipients)
{ {
m_rcpt = rcpt; m_recipients = recipients;
} }
void SmtpClient::socketError(QAbstractSocket::SocketError error) QString SmtpClient::createDateString()
{ {
qCWarning(dcMailNotification) << "Mail socket -> " << error << m_socket->errorString(); return QDateTime::currentDateTime().toString(Qt::RFC2822Date);
}
void SmtpClient::setState(SmtpClient::State state)
{
if (m_state == state)
return;
qCDebug(dcSmtpClient()) << state;
m_state = state;
}
void SmtpClient::processServerResponse(int responseCode, const QString &response)
{
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<char>(0))
.append(m_user)
.append(static_cast<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;
}
} 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;
}
}
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_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");
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.append("\r\n.\r\n");
setState(StateInitialize);
// Make sure the connection starts from the beginning
m_socket->close();
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) {
handleSmtpFailure();
}
} }
void SmtpClient::send(const QString &data) void SmtpClient::send(const QString &data)
{ {
qCDebug(dcSmtpClient()) << "-->" << data;
m_socket->write(data.toUtf8() + "\r\n"); m_socket->write(data.toUtf8() + "\r\n");
m_socket->flush(); m_socket->flush();
} }

View File

@ -23,87 +23,119 @@
#ifndef SMTPCLIENT_H #ifndef SMTPCLIENT_H
#define SMTPCLIENT_H #define SMTPCLIENT_H
#include <QObject> #include <QQueue>
#include <QDebug> #include <QDebug>
#include <QObject>
#include <QTcpSocket> #include <QTcpSocket>
#include <QSslSocket> #include <QSslSocket>
#include <QStringList>
#include <QLoggingCategory>
#include "plugin/deviceplugin.h" #include "plugin/deviceplugin.h"
Q_DECLARE_LOGGING_CATEGORY(dcSmtpClient)
struct Message {
QString subject;
QString body;
ActionId actionId;
};
class SmtpClient : public QObject class SmtpClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum AuthMethod{ enum AuthenticationMethod{
AuthMethodPlain, AuthenticationMethodPlain,
AuthMethodLogin AuthenticationMethodLogin
}; };
Q_ENUM(AuthenticationMethod)
enum SendState{ enum State{
InitState, StateIdle,
HandShakeState, StateInitialize,
AuthentificationState, StateHandShake,
StartTlsState, StateAuthentification,
UserState, StateStartTls,
PasswordState, StateUser,
TestLoginFinishedState, StatePassword,
MailState, StateTestLoginFinished,
RcptState, StateMail,
DataState, StateRcpt,
BodyState, StateData,
QuitState, StateBody,
CloseState StateQuit,
StateClose
}; };
Q_ENUM(State)
enum EncryptionType{ enum EncryptionType{
EncryptionTypeNone, EncryptionTypeNone,
EncryptionTypeSSL, EncryptionTypeSSL,
EncryptionTypeTLS EncryptionTypeTLS
}; };
Q_ENUM(EncryptionType)
explicit SmtpClient(QObject *parent = 0); explicit SmtpClient(QObject *parent = nullptr);
void connectToHost(); void connectToHost();
void testLogin(); 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 setHost(const QString &host);
void setPort(const int &port); void setPort(const quint16 &port);
void setEncryptionType(const EncryptionType &encryptionType); void setEncryptionType(const EncryptionType &encryptionType);
void setAuthMethod(const AuthMethod &authMethod); void setAuthenticationMethod(const AuthenticationMethod &authenticationMethod);
void setUser(const QString &user); void setUser(const QString &user);
void setPassword(const QString &password); void setPassword(const QString &password);
void setSender(const QString &sender); void setSender(const QString &sender);
void setRecipient(const QString &rcpt); void setRecipients(const QStringList &recipients);
private: private:
QSslSocket *m_socket; QSslSocket *m_socket = nullptr;
SendState m_state; State m_state = StateIdle;
QString m_host; QString m_host = "127.0.0.1";
int m_port; quint16 m_port = 25;
QString m_user; QString m_user;
QString m_password; QString m_password;
QString m_sender; QString m_sender;
AuthMethod m_authMethod; AuthenticationMethod m_authenticationMethod;
EncryptionType m_encryptionType; EncryptionType m_encryptionType;
QString m_rcpt; QStringList m_recipients;
QString m_subject; QQueue<QString> m_recipientsQueue;
QString m_boy;
QString m_message;
ActionId m_actionId;
bool m_testLogin; // Created for each message
Message m_message;
QString m_messageData;
QQueue<Message> m_messageQueue;
bool m_testLogin = false;
QString createDateString();
void setState(State state);
void processServerResponse(int responseCode, const QString &response);
void sendNextMail();
void sendEmailInternally(const Message &message);
void handleSmtpFailure();
void handleUnexpectedSmtpCode(int responseCode, const QString &serverMessage);
signals: signals:
void sendMailFinished(const bool &success, const ActionId &actionId); void sendMailFinished(const bool &success, const ActionId &actionId);
void testLoginFinished(const bool &success); void testLoginFinished(const bool &success);
private slots: private slots:
void socketError(QAbstractSocket::SocketError error);
void onSocketError(QAbstractSocket::SocketError error);
void connected(); void connected();
void disconnected(); void disconnected();
void onEncrypted();
void readData(); void readData();
void send(const QString &data); void send(const QString &data);
}; };