Skip to content

Commit

Permalink
[Email] Fix issues with different mail server versions
Browse files Browse the repository at this point in the history
  • Loading branch information
TD-er committed Sep 23, 2024
1 parent 875782c commit 87bca8f
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 100 deletions.
224 changes: 125 additions & 99 deletions src/_N001_Email.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings,
bool NPlugin_001_Auth(WiFiClient & client,
const String& user,
const String& pass,
uint16_t timeout);
uint16_t timeout);
bool NPlugin_001_MTA(WiFiClient & client,
const String& aStr,
uint16_t aWaitForPattern,
Expand Down Expand Up @@ -134,14 +134,15 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
# ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS

// See: https://github.com/espressif/arduino-esp32/pull/6676
client.setTimeout((CONTROLLER_CLIENTTIMEOUT_MAX + 500) / 1000); // in seconds!!!!
client.setTimeout((notificationsettings.Timeout_ms + 500) / 1000); // in seconds!!!!
Client *pClient = &client;
pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX);
pClient->setTimeout(notificationsettings.Timeout_ms);
# else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
client.setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX); // in msec as it should be!
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"),
Expand All @@ -150,7 +151,7 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
}
# endif // ifndef BUILD_NO_DEBUG

if (!connectClient(client, notificationsettings.Server, notificationsettings.Port, CONTROLLER_CLIENTTIMEOUT_DFLT)) {
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"),
Expand All @@ -160,19 +161,9 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
myStatus = false;
failFlag = true;
} else {
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"
);
uint16_t clientTimeout = notificationsettings.Timeout_ms;

uint16_t clientTimeout = notificationsettings.Timeout * 1000; // Convert to mS.
if (clientTimeout < NPLUGIN_001_MIN_TM || clientTimeout > NPLUGIN_001_MAX_TM) {
if ((clientTimeout < NPLUGIN_001_MIN_TM) || (clientTimeout > NPLUGIN_001_MAX_TM)) {
clientTimeout = NPLUGIN_001_DEF_TM;
}

Expand All @@ -193,45 +184,62 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co

// 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;
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

String subMsg = aMesg.substring(pos_brace2+1); // Remove substitute email address from subject line.
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.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.";
}
}

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
aMesg = subMsg;

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.
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);
Expand All @@ -252,73 +260,86 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
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
addLog(LOG_LEVEL_INFO, strformat(F("Email: Max Allowed Timeout is %d secs"), clientTimeout / 1000));
# endif // ifndef BUILD_NO_DEBUG

while (true) {
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
# endif // ifndef BUILD_NO_DEBUG
failFlag = true;
break;
}

if (!failFlag) {
# ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
addLog(LOG_LEVEL_DEBUG, F("Email: Sending EHLO domain"));
}
# endif
if (!NPlugin_001_MTA(client, strformat(F("EHLO %s"), notificationsettings.Domain), 250, clientTimeout)) {
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
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
addLog(LOG_LEVEL_DEBUG, F("Email: EHLO Domain Fail"));
}
# endif
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.
// Must retrieve SMTP Reply Packet. Data not used, ignored.
if (!failFlag) {
unsigned long timeout = millis();
const unsigned long timer = millis() + clientTimeout;
String replyStr;
String catStr = "";
while (client.available()) {
if (millis() > timeout + clientTimeout) {
String catStr;

bool done = false;

while (client.available() && !done) {
if (timeOutReached(timer)) {
failFlag = true;
break;
}
safeReadStringUntil(client, replyStr, '\n', NPLUGIN_001_PKT_SZ, clientTimeout);
done = safeReadStringUntil(client, replyStr, '\n', NPLUGIN_001_PKT_SZ);
catStr += replyStr;
}

if(!catStr.length()) {
catStr = "Empty!";
if (!catStr.length()) {
catStr = F("Empty!");
}

# ifndef BUILD_NO_DEBUG

if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Packet Rcvd is: > %s <"),catStr.c_str()));
String log = strformat(F("Email: Packet Rcvd is: > %s <"), catStr.c_str());
addLogMove(LOG_LEVEL_DEBUG, log);
}
# endif
# 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
# endif // ifndef BUILD_NO_DEBUG

if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass, clientTimeout)) {
# ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
addLog(LOG_LEVEL_DEBUG, F("Email: User/Pass Fail"));
}
# endif

addLog(LOG_LEVEL_DEBUG, F("Email: User/Pass Fail"));
# endif // ifndef BUILD_NO_DEBUG
failFlag = true;
break;
}
Expand All @@ -327,35 +348,35 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
if (!failFlag) {
# ifndef BUILD_NO_DEBUG
addLog(LOG_LEVEL_DEBUG, F("Email: Sending email Addr"));
# endif
if (!NPlugin_001_MTA(client, strformat(F("MAIL FROM:<%s>"), email_address.c_str()), 250, clientTimeout)) {
# 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
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
addLog(LOG_LEVEL_DEBUG, F("Email: Addr Fail"));
}
# endif
addLog(LOG_LEVEL_DEBUG, F("Email: Addr Fail"));
# endif // ifndef BUILD_NO_DEBUG
failFlag = true;
break;
}
}

if (!failFlag) {
bool nextAddressAvailable = true;
int i = 0;
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()));
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)) {
addLogMove(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo));
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; }
Expand Down Expand Up @@ -387,7 +408,7 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, co
break;
}
}
client.flush();
client.PR_9453_FLUSH_TO_CLEAR();
client.stop();

if (myStatus == true) {
Expand All @@ -409,36 +430,35 @@ bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass
}
base64 encoder;

bool mta1 = NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout);
bool mta2 = NPlugin_001_MTA(client, encoder.encode(user), 334, timeout);
bool mta3 = NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout);

if (mta1 && mta2 && mta3) {
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 (mta1 && mta2 && mta3);

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

client.flush();

if (aStr.length()) { client.println(aStr); }
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();

const String aWaitForPattern_str = strformat(F("%d "), aWaitForPattern);
while (true) {
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,
Expand All @@ -450,13 +470,19 @@ bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPa
delay(0);

String line;
safeReadStringUntil(client, line, '\n', NPLUGIN_001_PKT_SZ, timeout);
safeReadStringUntil(client, line, '\n', 1024, timeout);

line.replace("-", " "); // Must Remove optional dash from MTA response code.
// 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(aWaitForPattern_str) >= 0;
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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/src/DataStructs/NotificationSettingsStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct NotificationSettingsStruct
int8_t Pin2;
char User[49];
char Pass[33];
unsigned int Timeout;
unsigned int Timeout_ms;
//its safe to extend this struct, up to 4096 bytes, default values in config are 0
};

Expand Down

0 comments on commit 87bca8f

Please sign in to comment.