mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 01:24:04 +00:00
[Email] Split N001_Email.ino into N001_data_struct
This commit is contained in:
+1
-424
@@ -24,37 +24,7 @@
|
||||
|
||||
# define NPLUGIN_001_PKT_SZ 256
|
||||
|
||||
# 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,
|
||||
uint16_t timeout);
|
||||
bool NPlugin_001_MTA(WiFiClient & client,
|
||||
const String& aStr,
|
||||
uint16_t aWaitForPattern,
|
||||
uint16_t timeout);
|
||||
bool getNextMailAddress(const String& data,
|
||||
String & address,
|
||||
int index);
|
||||
# include "src/NotifierStructs/N001_data_struct.h"
|
||||
|
||||
|
||||
// The message body is included in event->String1
|
||||
@@ -122,397 +92,4 @@ bool NPlugin_001(NPlugin::Function function, struct EventStruct *event, String&
|
||||
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;
|
||||
bool failFlag = false;
|
||||
|
||||
// Use WiFiClient class to create TCP connections
|
||||
WiFiClient client;
|
||||
|
||||
# ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
|
||||
// See: https://github.com/espressif/arduino-esp32/pull/6676
|
||||
client.setTimeout((notificationsettings.Timeout_ms + 500) / 1000); // in seconds!!!!
|
||||
Client *pClient = &client;
|
||||
pClient->setTimeout(notificationsettings.Timeout_ms);
|
||||
# else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
client.setTimeout(notificationsettings.Timeout_ms); // in msec as it should be!
|
||||
# endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(
|
||||
F("Email: Connecting to %s:%d"),
|
||||
notificationsettings.Server,
|
||||
notificationsettings.Port));
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (!connectClient(client, notificationsettings.Server, notificationsettings.Port, notificationsettings.Timeout_ms)) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLog(LOG_LEVEL_ERROR, strformat(
|
||||
F("Email: Error connecting to %s:%d"),
|
||||
notificationsettings.Server,
|
||||
notificationsettings.Port));
|
||||
}
|
||||
myStatus = false;
|
||||
failFlag = true;
|
||||
} else {
|
||||
uint16_t clientTimeout = notificationsettings.Timeout_ms;
|
||||
|
||||
if ((clientTimeout < NPLUGIN_001_MIN_TM) || (clientTimeout > NPLUGIN_001_MAX_TM)) {
|
||||
clientTimeout = NPLUGIN_001_DEF_TM;
|
||||
}
|
||||
|
||||
String email_address(notificationsettings.Sender);
|
||||
int pos_less = email_address.indexOf('<');
|
||||
String senderName = Settings.getHostname();
|
||||
|
||||
if (pos_less > -1) {
|
||||
senderName = email_address.substring(0, pos_less);
|
||||
removeChar(senderName, '"'); // Remove quotes
|
||||
email_address = email_address.substring(pos_less + 1);
|
||||
removeChar(email_address, '<');
|
||||
removeChar(email_address, '>');
|
||||
email_address.trim();
|
||||
senderName.trim();
|
||||
}
|
||||
|
||||
|
||||
// Use Notify Command's destination email address(s) if provided in Command rules.
|
||||
// Sample Rule: Notify 1, "{email1@domain.com;email2@domain.net}Test email from %sysname%.<br/> How are you?<br/>Have a good day.<br/>"
|
||||
String subAddr;
|
||||
String tmp_ato;
|
||||
int pos_brace1 = aMesg.indexOf('{');
|
||||
int pos_amper = aMesg.indexOf('@');
|
||||
int pos_brace2 = aMesg.indexOf('}');
|
||||
|
||||
if ((pos_brace1 == 0) && (pos_amper > pos_brace1) && (pos_brace2 > pos_amper)) {
|
||||
subAddr = aMesg.substring(pos_brace1 + 1, pos_brace2);
|
||||
subAddr.trim();
|
||||
tmp_ato = subAddr;
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Receiver (ato): %s"), subAddr.c_str()));
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
String subMsg = aMesg.substring(pos_brace2 + 1); // Remove substitute email address from subject line.
|
||||
subMsg.trim();
|
||||
|
||||
if (subMsg.indexOf(',') == 0) {
|
||||
subMsg = subMsg.substring(1); // Remove leading comma.
|
||||
subMsg.trim();
|
||||
}
|
||||
|
||||
if (!subMsg.length()) {
|
||||
subMsg = "ERROR: ESPEasy Notify Rule missing the message text. Please correct the rule.";
|
||||
}
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Message: %s"), subMsg.c_str()));
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
aMesg = subMsg;
|
||||
}
|
||||
else {
|
||||
tmp_ato = notificationsettings.Receiver; // Use plugin's receiver.
|
||||
}
|
||||
|
||||
// Clean up receiver address.
|
||||
tmp_ato.replace(";", ",");
|
||||
tmp_ato.replace(" ", "");
|
||||
|
||||
String mailheader = F(
|
||||
"From: $nodename <$emailfrom>\r\n"
|
||||
"To: $ato\r\n"
|
||||
"Subject: $subject\r\n"
|
||||
"Reply-To: $nodename <$emailfrom>\r\n"
|
||||
"Date: $date\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"
|
||||
);
|
||||
|
||||
|
||||
mailheader.replace(F("$nodename"), senderName);
|
||||
mailheader.replace(F("$emailfrom"), email_address);
|
||||
mailheader.replace(F("$ato"), tmp_ato);
|
||||
mailheader.replace(F("$subject"), aSub);
|
||||
String dateFmtHdr = F("%sysweekday_s%, %sysday_0% %sysmonth_s% %sysyear% %systime% %systzoffset%");
|
||||
mailheader.replace(F("$date"), parseTemplate(dateFmtHdr));
|
||||
mailheader.replace(F("$espeasyversion"), getSystemBuildString());
|
||||
|
||||
// Make sure to replace the char '\r' and not the string "\r"
|
||||
// See: https://github.com/letscontrolit/ESPEasy/issues/4967
|
||||
removeChar(aMesg, '\r');
|
||||
aMesg.replace(String('\n'), F("<br/>")); // re-write line breaks for Content-type: text/html
|
||||
|
||||
// Wait for Client to Start Sending
|
||||
// The MTA Exchange
|
||||
|
||||
if (!failFlag) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Initializing ..."));
|
||||
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_INFO, strformat(F("Email: Max Allowed Timeout is %d secs"), clientTimeout / 1000));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
while (true) { // FIXME TD-er: Use of while here can be useful so you can
|
||||
// exit using break;
|
||||
// However this is way too complex using both a failFlag and break
|
||||
// and not even consistently.
|
||||
if (!NPlugin_001_MTA(client, EMPTY_STRING, 220, clientTimeout)) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Initialization Fail"));
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending EHLO domain"));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
const String astr = strformat(F("EHLO %s"), notificationsettings.Domain);
|
||||
|
||||
if (!NPlugin_001_MTA(
|
||||
client,
|
||||
astr,
|
||||
250,
|
||||
clientTimeout)) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: EHLO Domain Fail"));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Must retrieve SMTP Reply Packet. Data not used, ignored.
|
||||
if (!failFlag) {
|
||||
const unsigned long timer = millis() + clientTimeout;
|
||||
String replyStr;
|
||||
String catStr;
|
||||
|
||||
bool done = false;
|
||||
|
||||
while (client.available() && !done) {
|
||||
if (timeOutReached(timer)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
done = safeReadStringUntil(client, replyStr, '\n', NPLUGIN_001_PKT_SZ);
|
||||
catStr += replyStr;
|
||||
}
|
||||
|
||||
if (!catStr.length()) {
|
||||
catStr = F("Empty!");
|
||||
}
|
||||
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
String log = strformat(F("Email: Packet Rcvd is: > %s <"), catStr.c_str());
|
||||
addLogMove(LOG_LEVEL_DEBUG, log);
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending User/Pass"));
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass, clientTimeout)) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: User/Pass Fail"));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending email Addr"));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
const String astr = strformat(F("MAIL FROM:<%s>"), email_address.c_str());
|
||||
|
||||
if (!NPlugin_001_MTA(client, astr, 250, clientTimeout)) {
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Addr Fail"));
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
bool nextAddressAvailable = true;
|
||||
int i = 0;
|
||||
String emailTo;
|
||||
const String receiver(tmp_ato);
|
||||
|
||||
addLog(LOG_LEVEL_INFO, strformat(F("Email: Receiver(s): %s"), receiver.c_str()));
|
||||
|
||||
if (!getNextMailAddress(receiver, emailTo, i)) {
|
||||
addLog(LOG_LEVEL_ERROR, F("Email: Receiver missing!"));
|
||||
break;
|
||||
}
|
||||
|
||||
while (nextAddressAvailable) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
|
||||
addLog(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo));
|
||||
}
|
||||
|
||||
if (!NPlugin_001_MTA(client, strformat(F("RCPT TO:<%s>"), emailTo.c_str()), 250, clientTimeout)) { break; }
|
||||
++i;
|
||||
nextAddressAvailable = getNextMailAddress(receiver, emailTo, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
if (!NPlugin_001_MTA(client, F("DATA"), 354, clientTimeout)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
if (!NPlugin_001_MTA(client, strformat(F("%s%s\r\n.\r\n"), mailheader.c_str(), aMesg.c_str()), 250, clientTimeout)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Email Sent. Do some final housekeeping, tell server we're leaving.
|
||||
if (!failFlag) {
|
||||
myStatus = true;
|
||||
}
|
||||
|
||||
NPlugin_001_MTA(client, F("QUIT"), 221, clientTimeout); // Sent successfully, close SMTP protocol, ignore failure
|
||||
break;
|
||||
}
|
||||
}
|
||||
client.PR_9453_FLUSH_TO_CLEAR();
|
||||
client.stop();
|
||||
|
||||
if (myStatus == true) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Connection Closed Successfully"));
|
||||
} else {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLogMove(LOG_LEVEL_ERROR, concat(F("Email: Connection Closed With Error. Used header: "), mailheader));
|
||||
}
|
||||
}
|
||||
}
|
||||
return myStatus;
|
||||
}
|
||||
|
||||
bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass, uint16_t timeout)
|
||||
{
|
||||
if (user.isEmpty() || pass.isEmpty()) {
|
||||
// No user/password given.
|
||||
return true;
|
||||
}
|
||||
base64 encoder;
|
||||
|
||||
if (NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout) &&
|
||||
NPlugin_001_MTA(client, encoder.encode(user), 334, timeout) &&
|
||||
NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout)) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern, uint16_t timeout)
|
||||
{
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, aStr);
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (aStr.length()) {
|
||||
client.PR_9453_FLUSH_TO_CLEAR(); // have to send msg to server so flush data first
|
||||
client.println(aStr);
|
||||
}
|
||||
|
||||
// Wait For Response
|
||||
unsigned long timer = millis() + timeout;
|
||||
|
||||
backgroundtasks();
|
||||
|
||||
while (true) { // FIXME TD-er: Why this while loop??? makes no sense as it will only be run once
|
||||
if (timeOutReached(timer)) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLogMove(LOG_LEVEL_ERROR,
|
||||
concat(F("NPlugin_001_MTA: timeout. "), aStr));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
delay(0);
|
||||
|
||||
String line;
|
||||
safeReadStringUntil(client, line, '\n', 1024, timeout);
|
||||
|
||||
// response could be like: '220 domain', '220-domain','220+domain'
|
||||
const String pattern_str_space = strformat(F("%d "), aWaitForPattern);
|
||||
const String pattern_str_minus = strformat(F("%d-"), aWaitForPattern);
|
||||
const String pattern_str_plus = strformat(F("%d+"), aWaitForPattern);
|
||||
|
||||
const bool patternFound = line.indexOf(pattern_str_space) >= 0
|
||||
|| line.indexOf(pattern_str_minus) >= 0
|
||||
|| line.indexOf(pattern_str_plus) >= 0;
|
||||
|
||||
# ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLogMove(LOG_LEVEL_DEBUG, line);
|
||||
}
|
||||
# endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
return patternFound;
|
||||
}
|
||||
|
||||
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 // ifdef USES_N001
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
#include "../NotifierStructs/N001_data_struct.h"
|
||||
|
||||
#include "../DataStructs/ESPEasy_EventStruct.h"
|
||||
#include "../ESPEasyCore/ESPEasy_Log.h"
|
||||
#include "../ESPEasyCore/ESPEasy_backgroundtasks.h"
|
||||
#include "../Globals/Settings.h"
|
||||
#include "../Helpers/ESPEasy_Storage.h"
|
||||
#include "../Helpers/ESPEasy_time_calc.h"
|
||||
#include "../Helpers/Networking.h"
|
||||
#include "../Helpers/StringGenerator_System.h"
|
||||
#include "../Helpers/StringParser.h"
|
||||
#include "../Helpers/_CPlugin_Helper.h" // safeReadStringUntil
|
||||
#include "../Helpers/_NPlugin_init.h"
|
||||
|
||||
|
||||
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;
|
||||
bool failFlag = false;
|
||||
|
||||
// Use WiFiClient class to create TCP connections
|
||||
WiFiClient client;
|
||||
|
||||
#ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
|
||||
// See: https://github.com/espressif/arduino-esp32/pull/6676
|
||||
client.setTimeout((notificationsettings.Timeout_ms + 500) / 1000); // in seconds!!!!
|
||||
Client *pClient = &client;
|
||||
pClient->setTimeout(notificationsettings.Timeout_ms);
|
||||
#else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
client.setTimeout(notificationsettings.Timeout_ms); // in msec as it should be!
|
||||
#endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
|
||||
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(
|
||||
F("Email: Connecting to %s:%d"),
|
||||
notificationsettings.Server,
|
||||
notificationsettings.Port));
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (!connectClient(client, notificationsettings.Server, notificationsettings.Port, notificationsettings.Timeout_ms)) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLog(LOG_LEVEL_ERROR, strformat(
|
||||
F("Email: Error connecting to %s:%d"),
|
||||
notificationsettings.Server,
|
||||
notificationsettings.Port));
|
||||
}
|
||||
myStatus = false;
|
||||
failFlag = true;
|
||||
} else {
|
||||
uint16_t clientTimeout = notificationsettings.Timeout_ms;
|
||||
|
||||
if ((clientTimeout < NPLUGIN_001_MIN_TM) || (clientTimeout > NPLUGIN_001_MAX_TM)) {
|
||||
clientTimeout = NPLUGIN_001_DEF_TM;
|
||||
}
|
||||
|
||||
String email_address(notificationsettings.Sender);
|
||||
int pos_less = email_address.indexOf('<');
|
||||
String senderName = Settings.getHostname();
|
||||
|
||||
if (pos_less > -1) {
|
||||
senderName = email_address.substring(0, pos_less);
|
||||
removeChar(senderName, '"'); // Remove quotes
|
||||
email_address = email_address.substring(pos_less + 1);
|
||||
removeChar(email_address, '<');
|
||||
removeChar(email_address, '>');
|
||||
email_address.trim();
|
||||
senderName.trim();
|
||||
}
|
||||
|
||||
|
||||
// Use Notify Command's destination email address(s) if provided in Command rules.
|
||||
// Sample Rule: Notify 1, "{email1@domain.com;email2@domain.net}Test email from %sysname%.<br/> How are you?<br/>Have a good day.<br/>"
|
||||
String subAddr;
|
||||
String tmp_ato;
|
||||
int pos_brace1 = aMesg.indexOf('{');
|
||||
int pos_amper = aMesg.indexOf('@');
|
||||
int pos_brace2 = aMesg.indexOf('}');
|
||||
|
||||
if ((pos_brace1 == 0) && (pos_amper > pos_brace1) && (pos_brace2 > pos_amper)) {
|
||||
subAddr = aMesg.substring(pos_brace1 + 1, pos_brace2);
|
||||
subAddr.trim();
|
||||
tmp_ato = subAddr;
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Receiver (ato): %s"), subAddr.c_str()));
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
String subMsg = aMesg.substring(pos_brace2 + 1); // Remove substitute email address from subject line.
|
||||
subMsg.trim();
|
||||
|
||||
if (subMsg.indexOf(',') == 0) {
|
||||
subMsg = subMsg.substring(1); // Remove leading comma.
|
||||
subMsg.trim();
|
||||
}
|
||||
|
||||
if (!subMsg.length()) {
|
||||
subMsg = "ERROR: ESPEasy Notify Rule missing the message text. Please correct the rule.";
|
||||
}
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Message: %s"), subMsg.c_str()));
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
aMesg = subMsg;
|
||||
}
|
||||
else {
|
||||
tmp_ato = notificationsettings.Receiver; // Use plugin's receiver.
|
||||
}
|
||||
|
||||
// Clean up receiver address.
|
||||
tmp_ato.replace(";", ",");
|
||||
tmp_ato.replace(" ", "");
|
||||
|
||||
String mailheader = F(
|
||||
"From: $nodename <$emailfrom>\r\n"
|
||||
"To: $ato\r\n"
|
||||
"Subject: $subject\r\n"
|
||||
"Reply-To: $nodename <$emailfrom>\r\n"
|
||||
"Date: $date\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"
|
||||
);
|
||||
|
||||
|
||||
mailheader.replace(F("$nodename"), senderName);
|
||||
mailheader.replace(F("$emailfrom"), email_address);
|
||||
mailheader.replace(F("$ato"), tmp_ato);
|
||||
mailheader.replace(F("$subject"), aSub);
|
||||
String dateFmtHdr = F("%sysweekday_s%, %sysday_0% %sysmonth_s% %sysyear% %systime% %systzoffset%");
|
||||
mailheader.replace(F("$date"), parseTemplate(dateFmtHdr));
|
||||
mailheader.replace(F("$espeasyversion"), getSystemBuildString());
|
||||
|
||||
// Make sure to replace the char '\r' and not the string "\r"
|
||||
// See: https://github.com/letscontrolit/ESPEasy/issues/4967
|
||||
removeChar(aMesg, '\r');
|
||||
aMesg.replace(String('\n'), F("<br/>")); // re-write line breaks for Content-type: text/html
|
||||
|
||||
// Wait for Client to Start Sending
|
||||
// The MTA Exchange
|
||||
|
||||
if (!failFlag) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Initializing ..."));
|
||||
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_INFO, strformat(F("Email: Max Allowed Timeout is %d secs"), clientTimeout / 1000));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
while (true) { // FIXME TD-er: Use of while here can be useful so you can
|
||||
// exit using break;
|
||||
// However this is way too complex using both a failFlag and break
|
||||
// and not even consistently.
|
||||
if (!NPlugin_001_MTA(client, EMPTY_STRING, 220, clientTimeout)) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Initialization Fail"));
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending EHLO domain"));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
const String astr = strformat(F("EHLO %s"), notificationsettings.Domain);
|
||||
|
||||
if (!NPlugin_001_MTA(
|
||||
client,
|
||||
astr,
|
||||
250,
|
||||
clientTimeout)) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: EHLO Domain Fail"));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Must retrieve SMTP Reply Packet. Data not used, ignored.
|
||||
if (!failFlag) {
|
||||
const unsigned long timer = millis() + clientTimeout;
|
||||
String replyStr;
|
||||
String catStr;
|
||||
|
||||
bool done = false;
|
||||
|
||||
while (client.available() && !done) {
|
||||
if (timeOutReached(timer)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
done = safeReadStringUntil(client, replyStr, '\n', NPLUGIN_001_PKT_SZ);
|
||||
catStr += replyStr;
|
||||
}
|
||||
|
||||
if (!catStr.length()) {
|
||||
catStr = F("Empty!");
|
||||
}
|
||||
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
String log = strformat(F("Email: Packet Rcvd is: > %s <"), catStr.c_str());
|
||||
addLogMove(LOG_LEVEL_DEBUG, log);
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending User/Pass"));
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass, clientTimeout)) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: User/Pass Fail"));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Sending email Addr"));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
const String astr = strformat(F("MAIL FROM:<%s>"), email_address.c_str());
|
||||
|
||||
if (!NPlugin_001_MTA(client, astr, 250, clientTimeout)) {
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
addLog(LOG_LEVEL_DEBUG, F("Email: Addr Fail"));
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
bool nextAddressAvailable = true;
|
||||
int i = 0;
|
||||
String emailTo;
|
||||
const String receiver(tmp_ato);
|
||||
|
||||
addLog(LOG_LEVEL_INFO, strformat(F("Email: Receiver(s): %s"), receiver.c_str()));
|
||||
|
||||
if (!getNextMailAddress(receiver, emailTo, i)) {
|
||||
addLog(LOG_LEVEL_ERROR, F("Email: Receiver missing!"));
|
||||
break;
|
||||
}
|
||||
|
||||
while (nextAddressAvailable) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
|
||||
addLog(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo));
|
||||
}
|
||||
|
||||
if (!NPlugin_001_MTA(client, strformat(F("RCPT TO:<%s>"), emailTo.c_str()), 250, clientTimeout)) { break; }
|
||||
++i;
|
||||
nextAddressAvailable = getNextMailAddress(receiver, emailTo, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
if (!NPlugin_001_MTA(client, F("DATA"), 354, clientTimeout)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failFlag) {
|
||||
if (!NPlugin_001_MTA(client, strformat(F("%s%s\r\n.\r\n"), mailheader.c_str(), aMesg.c_str()), 250, clientTimeout)) {
|
||||
failFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Email Sent. Do some final housekeeping, tell server we're leaving.
|
||||
if (!failFlag) {
|
||||
myStatus = true;
|
||||
}
|
||||
|
||||
NPlugin_001_MTA(client, F("QUIT"), 221, clientTimeout); // Sent successfully, close SMTP protocol, ignore failure
|
||||
break;
|
||||
}
|
||||
}
|
||||
client.PR_9453_FLUSH_TO_CLEAR();
|
||||
client.stop();
|
||||
|
||||
if (myStatus == true) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Connection Closed Successfully"));
|
||||
} else {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLogMove(LOG_LEVEL_ERROR, concat(F("Email: Connection Closed With Error. Used header: "), mailheader));
|
||||
}
|
||||
}
|
||||
}
|
||||
return myStatus;
|
||||
}
|
||||
|
||||
bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass, uint16_t timeout)
|
||||
{
|
||||
if (user.isEmpty() || pass.isEmpty()) {
|
||||
// No user/password given.
|
||||
return true;
|
||||
}
|
||||
base64 encoder;
|
||||
|
||||
if (NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout) &&
|
||||
NPlugin_001_MTA(client, encoder.encode(user), 334, timeout) &&
|
||||
NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout)) {
|
||||
addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern, uint16_t timeout)
|
||||
{
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLog(LOG_LEVEL_DEBUG, aStr);
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (aStr.length()) {
|
||||
client.PR_9453_FLUSH_TO_CLEAR(); // have to send msg to server so flush data first
|
||||
client.println(aStr);
|
||||
}
|
||||
|
||||
// Wait For Response
|
||||
unsigned long timer = millis() + timeout;
|
||||
|
||||
backgroundtasks();
|
||||
|
||||
while (true) { // FIXME TD-er: Why this while loop??? makes no sense as it will only be run once
|
||||
if (timeOutReached(timer)) {
|
||||
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
|
||||
addLogMove(LOG_LEVEL_ERROR,
|
||||
concat(F("NPlugin_001_MTA: timeout. "), aStr));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
delay(0);
|
||||
|
||||
String line;
|
||||
safeReadStringUntil(client, line, '\n', 1024, timeout);
|
||||
|
||||
// response could be like: '220 domain', '220-domain','220+domain'
|
||||
const String pattern_str_space = strformat(F("%d "), aWaitForPattern);
|
||||
const String pattern_str_minus = strformat(F("%d-"), aWaitForPattern);
|
||||
const String pattern_str_plus = strformat(F("%d+"), aWaitForPattern);
|
||||
|
||||
const bool patternFound = line.indexOf(pattern_str_space) >= 0
|
||||
|| line.indexOf(pattern_str_minus) >= 0
|
||||
|| line.indexOf(pattern_str_plus) >= 0;
|
||||
|
||||
#ifndef BUILD_NO_DEBUG
|
||||
|
||||
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
|
||||
addLogMove(LOG_LEVEL_DEBUG, line);
|
||||
}
|
||||
#endif // ifndef BUILD_NO_DEBUG
|
||||
|
||||
return patternFound;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef NOTIFIERSTRUCTS_N001_DATA_STRUCT_H
|
||||
#define NOTIFIERSTRUCTS_N001_DATA_STRUCT_H
|
||||
|
||||
#include "../../_Plugin_Helper.h"
|
||||
|
||||
#ifdef USES_N001
|
||||
|
||||
// FIXME TD-er: Must we add a complete inheritance structure like done with pluginTaskData?
|
||||
// We do not have multiple instances of these notifiers, so let's keep it simple.
|
||||
|
||||
|
||||
# define NPLUGIN_001_PKT_SZ 256
|
||||
|
||||
# include <base64.h>
|
||||
|
||||
# include "../DataStructs/NotificationSettingsStruct.h"
|
||||
# include "../Globals/NPlugins.h"
|
||||
|
||||
|
||||
bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings,
|
||||
const String & aSub,
|
||||
String & aMesg);
|
||||
bool NPlugin_001_Auth(WiFiClient & client,
|
||||
const String& user,
|
||||
const String& pass,
|
||||
uint16_t timeout);
|
||||
bool NPlugin_001_MTA(WiFiClient & client,
|
||||
const String& aStr,
|
||||
uint16_t aWaitForPattern,
|
||||
uint16_t timeout);
|
||||
bool getNextMailAddress(const String& data,
|
||||
String & address,
|
||||
int index);
|
||||
|
||||
|
||||
#endif // ifdef USES_N001
|
||||
|
||||
#endif // ifndef NOTIFIERSTRUCTS_N001_DATA_STRUCT_H
|
||||
Reference in New Issue
Block a user