mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-07-28 04:07:47 +00:00
277 lines
8.9 KiB
C++
277 lines
8.9 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/StringGenerator_System.h"
|
|
#include "src/Helpers/StringParser.h"
|
|
#include "src/Helpers/_CPlugin_Helper.h" // safeReadStringUntil
|
|
#include "src/Helpers/_NPlugin_init.h"
|
|
|
|
#include <base64.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"), getSystemBuildString());
|
|
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
|