Files
ESPEasy/src/_N001_Email.cpp
T
TD-er 10cca9ca72 [Log] Call addLogMove where log Strings can be moved instead of copied
This is a macro for:
```
#define addLogMove(L, S)  addToLogMove(L, std::move(S))
```

This will then try to move the constructed string to the log buffer.
If it cannot be moved (e.g. log level is not active), the string will be cleared to free up memory.
2022-02-09 22:28:18 +01:00

274 lines
8.8 KiB
C++

#include "ESPEasy_common.h"
#ifdef USES_N001
//#######################################################################################################
//########################### Notification Plugin 001: Email ############################################
//#######################################################################################################
#define NPLUGIN_001
#define NPLUGIN_ID_001 1
#define NPLUGIN_NAME_001 "Email (SMTP)"
#define NPLUGIN_001_TIMEOUT 5000
#include "src/DataStructs/ESPEasy_EventStruct.h"
#include "src/DataStructs/NotificationSettingsStruct.h"
#include "src/ESPEasyCore/ESPEasy_Log.h"
#include "src/ESPEasyCore/ESPEasy_backgroundtasks.h"
#include "src/Globals/NPlugins.h"
#include "src/Globals/Settings.h"
#include "src/Helpers/ESPEasy_Storage.h"
#include "src/Helpers/ESPEasy_time_calc.h"
#include "src/Helpers/Networking.h"
#include "src/Helpers/StringParser.h"
#include "src/Helpers/_CPlugin_Helper.h" // safeReadStringUntil
#include "src/Helpers/_NPlugin_init.h"
// Forward declaration
bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, const String& aSub, String& aMesg);
bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass);
bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, const String &aWaitForPattern);
bool getNextMailAddress(const String& data, String& address, int index);
// The message body is included in event->String1
bool NPlugin_001(NPlugin::Function function, struct EventStruct *event, String& string)
{
bool success = false;
switch (function) {
case NPlugin::Function::NPLUGIN_PROTOCOL_ADD:
{
Notification[++notificationCount].Number = NPLUGIN_ID_001;
Notification[notificationCount].usesMessaging = true;
Notification[notificationCount].usesGPIO = 0;
break;
}
case NPlugin::Function::NPLUGIN_GET_DEVICENAME:
{
string = F(NPLUGIN_NAME_001);
break;
}
// Edwin: NPlugin::Function::NPLUGIN_WRITE seems to be not implemented/not used yet? Disabled because its confusing now.
// case NPlugin::Function::NPLUGIN_WRITE:
// {
// String log;
// String command = parseString(string, 1);
//
// if (command == F("email"))
// {
// MakeNotificationSettings(NotificationSettings);
// LoadNotificationSettings(event->NotificationIndex, (uint8_t*)&NotificationSettings, sizeof(NotificationSettingsStruct));
// NPlugin_001_send(NotificationSettings.Domain, NotificationSettings.Receiver, NotificationSettings.Sender, NotificationSettings.Subject, NotificationSettings.Body, NotificationSettings.Server, NotificationSettings.Port);
// success = true;
// }
// break;
// }
case NPlugin::Function::NPLUGIN_NOTIFY:
{
MakeNotificationSettings(NotificationSettings);
LoadNotificationSettings(event->NotificationIndex, (uint8_t*)&NotificationSettings, sizeof(NotificationSettingsStruct));
NotificationSettings.validate();
String subject = NotificationSettings.Subject;
String body;
if (event->String1.length() > 0)
body = event->String1;
else
body = NotificationSettings.Body;
subject = parseTemplate(subject);
body = parseTemplate(body);
NPlugin_001_send(NotificationSettings, subject, body);
success = true;
break;
}
default:
break;
}
return success;
}
bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, const String& aSub, String& aMesg)
{
// String& aDomain , String aTo, String aFrom, String aSub, String aMesg, String aHost, int aPort)
bool myStatus = false;
// Use WiFiClient class to create TCP connections
WiFiClient client;
client.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT);
String aHost = notificationsettings.Server;
if (loglevelActiveFor(LOG_LEVEL_DEBUG))
addLog(LOG_LEVEL_DEBUG, String(F("EMAIL: Connecting to ")) + aHost + notificationsettings.Port);
if (!connectClient(client, aHost.c_str(), notificationsettings.Port, CONTROLLER_CLIENTTIMEOUT_DFLT)) {
if (loglevelActiveFor(LOG_LEVEL_ERROR))
addLog(LOG_LEVEL_ERROR, String(F("EMAIL: Error connecting to ")) + aHost + notificationsettings.Port);
myStatus = false;
}else {
String mailheader = F(
"From: $nodename <$emailfrom>\r\n"
"To: $ato\r\n"
"Subject: $subject\r\n"
"Reply-To: $nodename <$emailfrom>\r\n"
"MIME-VERSION: 1.0\r\n"
"Content-type: text/html; charset=UTF-8\r\n"
"X-Mailer: EspEasy v$espeasyversion\r\n\r\n"
);
String email_address = notificationsettings.Sender;
int pos_less = email_address.indexOf('<');
if (pos_less == -1) {
// No email address markup
mailheader.replace(F("$nodename"), Settings.getHostname());
mailheader.replace(F("$emailfrom"), notificationsettings.Sender);
} else {
String senderName = email_address.substring(0, pos_less);
senderName.replace(F("\""), EMPTY_STRING); // Remove quotes
String address = email_address.substring(pos_less + 1);
address.replace(F("<"), EMPTY_STRING);
address.replace(F(">"), EMPTY_STRING);
address.trim();
senderName.trim();
mailheader.replace(F("$nodename"), senderName);
mailheader.replace(F("$emailfrom"), address);
}
mailheader.replace(F("$nodename"), Settings.getHostname());
mailheader.replace(F("$emailfrom"), notificationsettings.Sender);
mailheader.replace(F("$ato"), notificationsettings.Receiver);
mailheader.replace(F("$subject"), aSub);
mailheader.replace(F("$espeasyversion"), String(BUILD));
aMesg.replace(F("\r"), F("<br/>")); // re-write line breaks for Content-type: text/html
// Wait for Client to Start Sending
// The MTA Exchange
while (true) {
if (!NPlugin_001_MTA(client, EMPTY_STRING, F("220 "))) break;
if (!NPlugin_001_MTA(client, String(F("EHLO ")) + notificationsettings.Domain, F("250 "))) break;
if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass)) break;
if (!NPlugin_001_MTA(client, String(F("MAIL FROM:<")) + notificationsettings.Sender + ">", F("250 "))) break;
bool nextAddressAvailable = true;
int i = 0;
String emailTo;
if (!getNextMailAddress(notificationsettings.Receiver, emailTo, i)) {
addLog(LOG_LEVEL_ERROR, F("Email: No recipient given"));
break;
}
while (nextAddressAvailable) {
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
String log = F("Email: To ");
log += emailTo;
addLogMove(LOG_LEVEL_INFO, log);
}
if (!NPlugin_001_MTA(client, String(F("RCPT TO:<")) + emailTo + ">", F("250 "))) break;
++i;
nextAddressAvailable = getNextMailAddress(notificationsettings.Receiver, emailTo, i);
}
if (!NPlugin_001_MTA(client, F("DATA"), F("354 "))) break;
if (!NPlugin_001_MTA(client, mailheader + aMesg + F("\r\n.\r\n"), F("250 "))) break;
myStatus = true;
break;
}
client.flush();
client.stop();
if (myStatus == true) {
addLog(LOG_LEVEL_INFO, F("EMAIL: Connection Closed Successfully"));
}else {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = F("EMAIL: Connection Closed With Error. Used header: ");
log += mailheader;
addLogMove(LOG_LEVEL_ERROR, log);
}
}
}
return myStatus;
}
bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass)
{
if (user.isEmpty() || pass.isEmpty()) {
// No user/password given.
return true;
}
if (!NPlugin_001_MTA(client, String(F("AUTH LOGIN")), F("334 "))) return false;
base64 encoder;
String auth;
auth = encoder.encode(user);
if (!NPlugin_001_MTA(client, auth, F("334 "))) return false;
auth = encoder.encode(pass);
if (!NPlugin_001_MTA(client, auth, F("235 "))) return false;
return true;
}
bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, const String &aWaitForPattern)
{
if (loglevelActiveFor(LOG_LEVEL_DEBUG))
addLog(LOG_LEVEL_DEBUG, aStr);
if (aStr.length()) client.println(aStr);
// Wait For Response
unsigned long timer = millis() + NPLUGIN_001_TIMEOUT;
backgroundtasks();
while (true) {
if (timeOutReached(timer)) {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = F("NPlugin_001_MTA: timeout. ");
log += aStr;
addLogMove(LOG_LEVEL_ERROR, log);
}
return false;
}
delay(0);
// String line = client.readStringUntil('\n');
String line;
safeReadStringUntil(client, line, '\n');
const bool patternFound = line.indexOf(aWaitForPattern) >= 0;
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG))
addLogMove(LOG_LEVEL_DEBUG, line);
#endif
if (patternFound) {
return true;
}
}
return false;
}
bool getNextMailAddress(const String& data, String& address, int index)
{
int found = 0;
int strIndex[] = { 0, -1 };
const int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == ',' || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
if (found > index) {
address = data.substring(strIndex[0], strIndex[1]);
return true;
}
return false;
}
#endif