From 44e761df47dffa29f1e10fd6fd630be4b7093037 Mon Sep 17 00:00:00 2001
From: Ray
Date: Sun, 23 Jun 2024 20:38:25 -0400
Subject: [PATCH 01/61] support for https station (per PR 260) and new feature
of remote otc station
---
OpenSprinkler.cpp | 111 ++++++++++++++++++++++++++++++---------
OpenSprinkler.h | 24 ++++++---
defines.h | 14 +++--
opensprinkler_server.cpp | 2 +-
4 files changed, 111 insertions(+), 40 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 89dd2012..10e9af12 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -469,7 +469,7 @@ byte OpenSprinkler::start_network() {
useEth = false;
}
- if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=32) {
+ if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=DEFAULT_OTC_TOKEN_LENGTH) {
otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE);
DEBUG_PRINTLN(F("Started OTF with remote connection"));
} else {
@@ -1714,8 +1714,12 @@ void OpenSprinkler::switch_special_station(byte sid, byte value, uint16_t dur) {
switch_rfstation((RFStationData *)pdata->sped, value);
break;
- case STN_TYPE_REMOTE:
- switch_remotestation((RemoteStationData *)pdata->sped, value, dur);
+ case STN_TYPE_REMOTE_IP:
+ switch_remotestation((RemoteIPStationData *)pdata->sped, value, dur);
+ break;
+
+ case STN_TYPE_REMOTE_OTC:
+ switch_remotestation((RemoteOTCStationData *)pdata->sped, value, dur);
break;
case STN_TYPE_GPIO:
@@ -1723,8 +1727,13 @@ void OpenSprinkler::switch_special_station(byte sid, byte value, uint16_t dur) {
break;
case STN_TYPE_HTTP:
- switch_httpstation((HTTPStationData *)pdata->sped, value);
+ switch_httpstation((HTTPStationData *)pdata->sped, value, false);
break;
+
+ case STN_TYPE_HTTPS:
+ switch_httpstation((HTTPStationData *)pdata->sped, value, true);
+ break;
+
}
}
}
@@ -1861,19 +1870,33 @@ void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) {
/** Callback function for switching remote station */
void remote_http_callback(char* buffer) {
-/*
+
DEBUG_PRINTLN(buffer);
-*/
+
}
-int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) {
+int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), bool usessl, uint16_t timeout) {
#if defined(ARDUINO)
- Client *client;
+ Client *client = NULL;
#if defined(ESP8266)
- WiFiClient wifiClient;
- client = &wifiClient;
+ if(usessl) {
+ static WiFiClientSecure _c;
+ _c.setInsecure();
+ bool mfln = _c.probeMaxFragmentLength(server, port, 512);
+ DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no");
+ if (mfln) {
+ _c.setBufferSizes(512, 512);
+ } else {
+ _c.setBufferSizes(2048, 2048);
+ }
+ client = &_c;
+ } else {
+ static WiFiClient _c;
+ //client = new WiFiClient();
+ client = &_c;
+ }
#else
EthernetClient etherClient;
client = ðerClient;
@@ -1962,12 +1985,13 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
#endif
ether_buffer[pos]=0; // properly end buffer with 0
client->stop();
+ //delete client;
if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN;
if(callback) callback(ether_buffer);
return HTTP_RQT_SUCCESS;
}
-int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) {
+int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), bool usessl, uint16_t timeout) {
char server[20];
byte ip[4];
ip[0] = ip4>>24;
@@ -1975,25 +1999,25 @@ int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, vo
ip[2] = (ip4>>8)&0xff;
ip[3] = ip4&0xff;
sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
- return send_http_request(server, port, p, callback, timeout);
+ return send_http_request(server, port, p, callback, usessl, timeout);
}
-int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) {
+int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), bool usessl, uint16_t timeout) {
char * server = strtok(server_with_port, ":");
char * port = strtok(NULL, ":");
- return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout);
+ return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, usessl, timeout);
}
-/** Switch remote station
+/** Switch remote IP station
* This function takes a remote station code,
* parses it into remote IP, port, station index,
* and makes a HTTP GET request.
* The remote controller is assumed to have the same
* password as the main controller
*/
-void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur) {
- RemoteStationData copy;
- memcpy((char*)©, (char*)data, sizeof(RemoteStationData));
+void OpenSprinkler::switch_remotestation(RemoteIPStationData *data, bool turnon, uint16_t dur) {
+ RemoteIPStationData copy;
+ memcpy((char*)©, (char*)data, sizeof(RemoteIPStationData));
uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip));
uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port));
@@ -2004,8 +2028,6 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, u
ip[2] = (ip4>>8)&0xff;
ip[3] = ip4&0xff;
- // use tmp_buffer starting at a later location
- // because remote station data is loaded at the beginning
char *p = tmp_buffer;
BufferFiller bf = p;
// if turning on the zone and duration is defined, give duration as the timer value
@@ -2032,11 +2054,48 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, u
send_http_request(server, port, p, remote_http_callback);
}
-/** Switch http station
- * This function takes an http station code,
+/** Switch remote OTC station
+ * This function takes a remote station code,
+ * parses it into OTC token and station index,
+ * and makes a HTTPS GET request.
+ * The remote controller is assumed to have the same
+ * password as the main controller
+ */
+void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon, uint16_t dur) {
+ RemoteOTCStationData copy;
+ memcpy((char*)©, (char*)data, sizeof(RemoteOTCStationData));
+ copy.token[sizeof(copy.token)-1] = 0; // ensure the string ends properly
+ DEBUG_PRINTLN((char*)copy.token);
+ DEBUG_PRINTLN((int)hex2ulong(copy.sid, sizeof(copy.sid)));
+ char *p = tmp_buffer;
+ BufferFiller bf = p;
+ // if turning on the zone and duration is defined, give duration as the timer value
+ // otherwise:
+ // if autorefresh is defined, we give a fixed duration each time, and auto refresh will renew it periodically
+ // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent
+ uint16_t timer = 0;
+ if(turnon) {
+ if(dur>0) {
+ timer = dur;
+ } else {
+ timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800;
+ }
+ }
+ bf.emit_p(PSTR("GET /forward/v1/$S/cm?pw=$O&sid=$D&en=$D&t=$D"),
+ copy.token,
+ SOPT_PASSWORD,
+ (int)hex2ulong(copy.sid, sizeof(copy.sid)),
+ turnon, timer);
+ bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $S\r\n\r\n"), DEFAULT_OTC_SERVER_APP);
+
+ send_http_request(DEFAULT_OTC_SERVER_APP, DEFAULT_OTC_PORT_APP, p, remote_http_callback, true);
+}
+
+/** Switch http(s) station
+ * This function takes an http(s) station code,
* parses it into a server name and two HTTP GET requests.
*/
-void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) {
+void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon, bool usessl) {
HTTPStationData copy;
// make a copy of the HTTP station data and work with it
@@ -2054,7 +2113,7 @@ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) {
bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server);
- send_http_request(server, atoi(port), p, remote_http_callback);
+ send_http_request(server, atoi(port), p, remote_http_callback, usessl);
}
/** Prepare factory reset */
@@ -2138,7 +2197,7 @@ void OpenSprinkler::factory_reset() {
void OpenSprinkler::parse_otc_config() {
char server[MAX_SOPTS_SIZE+1] = {0};
char token[MAX_SOPTS_SIZE+1] = {0};
- int port = DEFAULT_OTC_PORT;
+ int port = DEFAULT_OTC_PORT_DEV;
int en = 0;
char *config = tmp_buffer;
@@ -2152,7 +2211,7 @@ void OpenSprinkler::parse_otc_config() {
otc.en = en;
otc.token = String(token);
otc.server = String(server);
- otc.port = 80;
+ otc.port = port;
}
#endif
diff --git a/OpenSprinkler.h b/OpenSprinkler.h
index c57c5844..76a35a57 100644
--- a/OpenSprinkler.h
+++ b/OpenSprinkler.h
@@ -144,13 +144,19 @@ struct RFStationData {
byte timing[4];
};
-/** Remote station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */
-struct RemoteStationData {
+/** Remote IP station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */
+struct RemoteIPStationData {
byte ip[8];
byte port[4];
byte sid[2];
};
+/** Remote OTC station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */
+struct RemoteOTCStationData {
+ byte token[DEFAULT_OTC_TOKEN_LENGTH+1];
+ byte sid[2];
+};
+
/** GPIO station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */
struct GPIOStationData {
byte pin[2];
@@ -283,10 +289,11 @@ class OpenSprinkler {
static void attribs_load(); // load and repackage attrib bits (backward compatibility)
static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections
static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station
- static void switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur=0); // switch remote station
+ static void switch_remotestation(RemoteIPStationData *data, bool turnon, uint16_t dur=0); // switch remote IP station
+ static void switch_remotestation(RemoteOTCStationData *data, bool turnon, uint16_t dur=0); // switch remote OTC station
static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station
- static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station
-
+ static void switch_httpstation(HTTPStationData *data, bool turnon, bool usessl=false); // switch http station
+
// -- options and data storeage
static void nvdata_load();
static void nvdata_save();
@@ -322,9 +329,10 @@ class OpenSprinkler {
static void clear_all_station_bits(); // clear all station bits
static void apply_all_station_bits(); // apply all station bits (activate/deactive values)
- static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000);
- static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000);
- static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000);
+ static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000);
+ static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000);
+ static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000);
+
// -- LCD functions
#if defined(ARDUINO) // LCD functions for Arduino
#if defined(ESP8266)
diff --git a/defines.h b/defines.h
index ac2c2309..6c28d406 100755
--- a/defines.h
+++ b/defines.h
@@ -24,7 +24,7 @@
#ifndef _DEFINES_H
#define _DEFINES_H
-//#define ENABLE_DEBUG // enable serial debug
+#define ENABLE_DEBUG // enable serial debug
typedef unsigned char byte;
typedef unsigned long ulong;
@@ -36,7 +36,7 @@ typedef unsigned long ulong;
// if this number is different from the one stored in non-volatile memory
// a device reset will be automatically triggered
-#define OS_FW_MINOR 5 // Firmware minor version
+#define OS_FW_MINOR 6 // Firmware minor version
/** Hardware version base numbers */
#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler
@@ -61,10 +61,11 @@ typedef unsigned long ulong;
/** Station macro defines */
#define STN_TYPE_STANDARD 0x00 // standard solenoid station
#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station
-#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station
+#define STN_TYPE_REMOTE_IP 0x02 // Remote OpenSprinkler station (by IP)
#define STN_TYPE_GPIO 0x03 // direct GPIO station
#define STN_TYPE_HTTP 0x04 // HTTP station
#define STN_TYPE_HTTPS 0x05 // HTTPS station
+#define STN_TYPE_REMOTE_OTC 0x06 // Remote OpenSprinkler station (by OTC)
#define STN_TYPE_OTHER 0xFF
/** Notification macro defines */
@@ -144,8 +145,11 @@ typedef unsigned long ulong;
#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js"
#define DEFAULT_WEATHER_URL "weather.opensprinkler.com"
#define DEFAULT_IFTTT_URL "maker.ifttt.com"
-#define DEFAULT_OTC_SERVER "ws.cloud.openthings.io"
-#define DEFAULT_OTC_PORT 80
+#define DEFAULT_OTC_SERVER_DEV "ws.cloud.openthings.io"
+#define DEFAULT_OTC_PORT_DEV 80
+#define DEFAULT_OTC_SERVER_APP "cloud.openthings.io"
+#define DEFAULT_OTC_PORT_APP 443
+#define DEFAULT_OTC_TOKEN_LENGTH 32
#define DEFAULT_DEVICE_NAME "My OpenSprinkler"
#define DEFAULT_EMPTY_STRING ""
diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp
index 5ee33472..57510782 100644
--- a/opensprinkler_server.cpp
+++ b/opensprinkler_server.cpp
@@ -600,7 +600,7 @@ void server_change_stations(OTF_PARAMS_DEF) {
if (!found || activeState > 1) {
handle_return(HTML_DATA_OUTOFBOUND);
}
- } else if (tmp_buffer[0] == STN_TYPE_HTTP) {
+ } else if (tmp_buffer[0] == STN_TYPE_HTTP || tmp_buffer[0] == STN_TYPE_HTTPS) {
#if !defined(ESP8266)
urlDecode(tmp_buffer + 1);
#endif
From 55fe4f23a35f2d82562a767be9cbb349f29026a8 Mon Sep 17 00:00:00 2001
From: Ray
Date: Mon, 24 Jun 2024 15:22:04 -0400
Subject: [PATCH 02/61] getting https to work for pi/linux
---
OpenSprinkler.cpp | 37 ++++----
build.sh | 6 +-
etherport.cpp | 213 +++++++++++++++++++++++++++++++++++++++++-----
etherport.h | 29 ++++++-
4 files changed, 240 insertions(+), 45 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 10e9af12..8d8475f9 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -1882,24 +1882,21 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
Client *client = NULL;
#if defined(ESP8266)
if(usessl) {
- static WiFiClientSecure _c;
- _c.setInsecure();
- bool mfln = _c.probeMaxFragmentLength(server, port, 512);
+ WiFiClientSecure *_c = new WiFiClientSecure();
+ _c->setInsecure();
+ bool mfln = _c->probeMaxFragmentLength(server, port, 512);
DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no");
if (mfln) {
- _c.setBufferSizes(512, 512);
+ _c->setBufferSizes(512, 512);
} else {
- _c.setBufferSizes(2048, 2048);
+ _c->setBufferSizes(2048, 2048);
}
- client = &_c;
+ client = _c;
} else {
- static WiFiClient _c;
- //client = new WiFiClient();
- client = &_c;
+ client = new WiFiClient();
}
#else
- EthernetClient etherClient;
- client = ðerClient;
+ client = new EthernetClient();
#endif
#define HTTP_CONNECT_NTRIES 3
@@ -1919,21 +1916,25 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
if(tries==HTTP_CONNECT_NTRIES) {
DEBUG_PRINTLN(F("failed."));
client->stop();
+ delete client;
return HTTP_RQT_CONNECT_ERR;
}
#else
+ EthernetClient *client = NULL;
+
+ if (usessl) {
+ client = new EthernetClientSsl();
+ } else {
+ client = new EthernetClient();
+ }
- EthernetClient etherClient;
- EthernetClient *client = ðerClient;
- struct hostent *host;
DEBUG_PRINT(server);
DEBUG_PRINT(":");
DEBUG_PRINTLN(port);
- host = gethostbyname(server);
- if (!host) { return HTTP_RQT_CONNECT_ERR; }
- if(!client->connect((uint8_t*)host->h_addr, port)) {
+ if(!client->connect(server, port)) {
DEBUG_PRINT(F("failed."));
client->stop();
+ delete client;
return HTTP_RQT_CONNECT_ERR;
}
@@ -1985,7 +1986,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
#endif
ether_buffer[pos]=0; // properly end buffer with 0
client->stop();
- //delete client;
+ delete client;
if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN;
if(callback) callback(ether_buffer);
return HTTP_RQT_SUCCESS;
diff --git a/build.sh b/build.sh
index ae257c8d..fe9854be 100755
--- a/build.sh
+++ b/build.sh
@@ -13,13 +13,15 @@ echo "Building OpenSprinkler..."
if [ "$1" == "demo" ]; then
echo "Installing required libraries..."
apt-get install -y libmosquitto-dev
+ apt-get install -y libssl-dev
echo "Compiling demo firmware..."
- g++ -o OpenSprinkler -DDEMO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto
+ g++ -o OpenSprinkler -DDEMO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto -l crypto -lssl
elif [ "$1" == "osbo" ]; then
echo "Installing required libraries..."
apt-get install -y libmosquitto-dev
+ apt-get install -y libssl-dev
echo "Compiling osbo firmware..."
- g++ -o OpenSprinkler -DOSBO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto
+ g++ -o OpenSprinkler -DOSBO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto -l crypto -lssl
else
echo "Installing required libraries..."
apt-get update
diff --git a/etherport.cpp b/etherport.cpp
index a2cea4ad..858b496a 100644
--- a/etherport.cpp
+++ b/etherport.cpp
@@ -33,11 +33,19 @@
#include
#include
#include
+#include
#include
#include
#include
#include "defines.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
EthernetServer::EthernetServer(uint16_t port)
: m_port(port), m_sock(0)
{
@@ -91,28 +99,25 @@ bool EthernetServer::begin()
}
// This function blocks until we get a client connected.
-// It will timeout after 50ms and return a blank client.
+// It will timeout after 5ms and return a blank client.
// If it succeeds it will return an EthernetClient.
EthernetClient EthernetServer::available()
{
- fd_set sock_set;
- FD_ZERO(&sock_set);
- FD_SET(m_sock, &sock_set);
- struct timeval timeout;
- timeout.tv_sec = 0;
- timeout.tv_usec = 50 * 1000; // 50ms
+ struct pollfd fds;
+ memset(&fds, 0, sizeof(fds));
+ fds.fd = m_sock;
+ fds.events = POLLIN;
+ int timeout = 5; // reduce this timeout for less blocking time
- select(m_sock + 1, &sock_set, NULL, NULL, &timeout);
- if (FD_ISSET(m_sock, &sock_set))
+ int rc = poll(&fds, 1, timeout);
+ if (rc > 0)
{
int client_sock = 0;
- struct sockaddr_in6 cli_addr;
- unsigned int clilen = sizeof(cli_addr);
- if ((client_sock = accept(m_sock, (struct sockaddr *) &cli_addr, &clilen)) <= 0)
- return EthernetClient(0);
+ if ((client_sock = accept(m_sock, NULL, NULL)) <= 0)
+ return EthernetClient();
return EthernetClient(client_sock);
}
- return EthernetClient(0);
+ return EthernetClient();
}
EthernetClient::EthernetClient()
@@ -130,18 +135,32 @@ EthernetClient::~EthernetClient()
stop();
}
-int EthernetClient::connect(uint8_t ip[4], uint16_t port)
+int EthernetClient::connect(const char* server, uint16_t port)
{
if (m_sock)
return 0;
+
+ struct hostent *host = gethostbyname(server);
+ if (!host) {
+ DEBUG_PRINTLN("Error: DNS look up failed\n");
+ return 0;
+ }
+
struct sockaddr_in sin = {0};
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
- sin.sin_addr.s_addr = *(uint32_t*) (ip);
+ sin.sin_addr.s_addr = *(uint32_t*) (host->h_addr);
m_sock = socket(AF_INET, SOCK_STREAM, 0);
+
+ struct timeval timeout;
+ timeout.tv_sec = 2;
+ timeout.tv_usec = 0;
+ setsockopt (m_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
+ setsockopt (m_sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+
if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
{
- DEBUG_PRINTLN("error connecting to server");
+ DEBUG_PRINTLN("Error: connecting to the server");
return 0;
}
m_connected = true;
@@ -178,6 +197,150 @@ EthernetClient::operator bool()
// If an error occurs or a timeout happens, we set the disconnect flag on the socket
// and return 0;
int EthernetClient::read(uint8_t *buf, size_t size)
+{
+ struct pollfd fds;
+ memset(&fds, 0, sizeof(fds));
+ fds.fd = m_sock;
+ fds.events = POLLIN;
+ int timeout = 3000;
+
+ int rc = poll(&fds, 1, timeout);
+ if (rc > 0)
+ {
+ rc = recv(m_sock, buf, size, 0);
+ if (errno == EWOULDBLOCK)
+ return 0;
+
+ if (rc <= 0) // socket closed
+ m_connected = false;
+ return rc;
+ }
+ if (rc < 0)
+ m_connected = false;
+ return 0;
+}
+
+size_t EthernetClient::write(const uint8_t *buf, size_t size)
+{
+ return ::send(m_sock, buf, size, MSG_NOSIGNAL);
+}
+
+/**
+ * SSL Client
+*/
+
+EthernetClientSsl::EthernetClientSsl()
+ : m_sock(0), m_connected(false)
+{
+}
+
+EthernetClientSsl::EthernetClientSsl(int sock)
+ : m_sock(sock), m_connected(true)
+{
+}
+
+EthernetClientSsl::~EthernetClientSsl()
+{
+ stop();
+}
+
+static bool sslInit;
+//static BIO* certbio;
+static SSL_CTX* ctx;
+
+/**
+ * https://github.com/angstyloop/c-web/blob/main/openssl-fetch-example.c
+*/
+int EthernetClientSsl::connect(const char* server, uint16_t port)
+{
+ if (m_sock)
+ return 0;
+
+ struct hostent *host = gethostbyname(server);
+ if (!host) {
+ DEBUG_PRINTLN("Error: DNS look up failed\n");
+ return 0;
+ }
+
+ if (!sslInit) {
+ OpenSSL_add_all_algorithms();
+ //ERR_load_BIO_strings();
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ //BIO* certbio = BIO_new(BIO_s_file());
+ if (SSL_library_init() < 0) {
+ DEBUG_PRINTLN("Error: could not initialize the OpenSSL library.\n");
+ return 0;
+ }
+ const SSL_METHOD* method = SSLv23_client_method();
+ ctx = SSL_CTX_new(method);
+ if (!ctx) {
+ DEBUG_PRINTLN("Error: unable to create SSL context.\n");
+ return 0;
+ }
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); //Accept all certs
+ sslInit = true;
+ }
+
+ // Create a new SSL session. This does not connect the socket.
+ ssl = SSL_new(ctx);
+
+ struct sockaddr_in sin = {0};
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ sin.sin_addr.s_addr = *(uint32_t*) (host->h_addr);
+ m_sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
+ {
+ DEBUG_PRINTLN("Error: connecting to the server failed");
+ return 0;
+ }
+ SSL_set_fd(ssl, m_sock);
+ SSL_set_tlsext_host_name(ssl, server); // set correct host name
+ if (SSL_connect(ssl) < 1) {
+ close(m_sock);
+ m_sock = 0;
+ DEBUG_PRINTLN("Error: Could not build an SSL session");
+ return 0;
+ }
+ m_connected = true;
+ return 1;
+}
+
+bool EthernetClientSsl::connected()
+{
+ if (!m_sock || !ssl)
+ return false;
+ int error = 0;
+ socklen_t len = sizeof(error);
+ int retval = getsockopt(m_sock, SOL_SOCKET, SO_ERROR, &error, &len);
+ return (retval == 0) && m_connected;
+}
+
+void EthernetClientSsl::stop()
+{
+ if (m_sock) {
+ close(m_sock);
+ m_sock = 0;
+ }
+ if (ssl) {
+ SSL_free(ssl);
+ ssl = NULL;
+ }
+ m_connected = false;
+}
+
+EthernetClientSsl::operator bool()
+{
+ return m_sock != 0 && ssl != NULL;
+}
+
+// read data from the client into the buffer provided
+// This function will block until either data is received OR a timeout happens.
+// If an error occurs or a timeout happens, we set the disconnect flag on the socket
+// and return 0;
+int EthernetClientSsl::read(uint8_t *buf, size_t size)
{
fd_set sock_set;
FD_ZERO(&sock_set);
@@ -186,10 +349,14 @@ int EthernetClient::read(uint8_t *buf, size_t size)
timeout.tv_sec = 3;
timeout.tv_usec = 0;
- select(m_sock + 1, &sock_set, NULL, NULL, &timeout);
- if (FD_ISSET(m_sock, &sock_set))
+ //select(m_sock + 1, &sock_set, NULL, NULL, &timeout);
+ //if (FD_ISSET(m_sock, &sock_set))
+ size_t pending = SSL_pending(ssl);
+ if (pending > 0)
{
- int retval = ::read(m_sock, buf, size);
+ if (size > pending)
+ size = pending;
+ int retval = SSL_read(ssl, buf, size);
if (retval <= 0) // socket closed
m_connected = false;
return retval;
@@ -198,9 +365,9 @@ int EthernetClient::read(uint8_t *buf, size_t size)
return 0;
}
-size_t EthernetClient::write(const uint8_t *buf, size_t size)
+size_t EthernetClientSsl::write(const uint8_t *buf, size_t size)
{
- return ::send(m_sock, buf, size, MSG_NOSIGNAL);
+ return SSL_write(ssl, buf, size);
+ //return ::send(m_sock, buf, size, MSG_NOSIGNAL);
}
-
#endif
diff --git a/etherport.h b/etherport.h
index a0788c8b..20e75787 100644
--- a/etherport.h
+++ b/etherport.h
@@ -27,11 +27,14 @@
#if defined(ARDUINO)
-#else // headers for RPI/BBB
+#else // headers for RPI/BBB/Linux
#include
#include
#include
+#include
+#include
+#include "defines.h"
#ifdef __APPLE__
#define MSG_NOSIGNAL SO_NOSIGPIPE
@@ -44,7 +47,28 @@ class EthernetClient {
EthernetClient();
EthernetClient(int sock);
~EthernetClient();
- int connect(uint8_t ip[4], uint16_t port);
+ int connect(const char *server, uint16_t port);
+ bool connected();
+ void stop();
+ int read(uint8_t *buf, size_t size);
+ size_t write(const uint8_t *buf, size_t size);
+ operator bool();
+ int GetSocket()
+ {
+ return m_sock;
+ }
+private:
+ int m_sock = 0;
+ bool m_connected;
+ friend class EthernetServer;
+};
+
+class EthernetClientSsl : EthernetClient {
+public:
+ EthernetClientSsl();
+ EthernetClientSsl(int sock);
+ ~EthernetClientSsl();
+ int connect(const char *server, uint16_t port);
bool connected();
void stop();
int read(uint8_t *buf, size_t size);
@@ -56,6 +80,7 @@ class EthernetClient {
}
private:
int m_sock;
+ SSL* ssl;
bool m_connected;
friend class EthernetServer;
};
From f0b1e2e3a765f363ece72d14aec6926149882db1 Mon Sep 17 00:00:00 2001
From: "Nathan P."
Date: Wed, 26 Jun 2024 13:44:18 -0400
Subject: [PATCH 03/61] Initial support for Arduino email notifications
---
OpenSprinkler.cpp | 7 ++-
defines.h | 3 +-
main.cpp | 125 +++++++++++++++++++++++++++++++++++----
opensprinkler_server.cpp | 12 +++-
platformio.ini | 33 +++--------
5 files changed, 137 insertions(+), 43 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 10e9af12..7781b59e 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -154,7 +154,7 @@ const char iopt_json_names[] PROGMEM =
"dns3\0"
"dns4\0"
"sar\0\0"
- "ife\0\0"
+ "nfe\0\0"
"sn1t\0"
"sn1o\0"
"sn2t\0"
@@ -224,7 +224,7 @@ const char iopt_prompts[] PROGMEM =
"DNS server.ip3: "
"DNS server.ip4: "
"Special Refresh?"
- "IFTTT Enable: "
+ "Notif Enable: "
"Sensor 1 type: "
"Normally open? "
"Sensor 2 type: "
@@ -368,7 +368,7 @@ byte OpenSprinkler::iopts[] = {
8,
8,
0, // special station auto refresh
- 0, // ifttt enable bits
+ 0, // notif enable bits
0, // sensor 1 type (see SENSOR_TYPE macro defines)
1, // sensor 1 option. 0: normally closed; 1: normally open. default 1.
0, // sensor 2 type
@@ -399,6 +399,7 @@ const char *OpenSprinkler::sopts[] = {
DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS
DEFAULT_DEVICE_NAME,
DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL
+ DEFAULT_EMPTY_STRING, // SOPT_EMAIL_OPTS
};
/** Weekday strings (stored in PROGMEM to reduce RAM usage) */
diff --git a/defines.h b/defines.h
index 6c28d406..bf088f29 100755
--- a/defines.h
+++ b/defines.h
@@ -234,7 +234,7 @@ enum {
IOPT_DNS_IP3,
IOPT_DNS_IP4,
IOPT_SPE_AUTO_REFRESH,
- IOPT_IFTTT_ENABLE,
+ IOPT_NOTIF_ENABLE,
IOPT_SENSOR1_TYPE,
IOPT_SENSOR1_OPTION,
IOPT_SENSOR2_TYPE,
@@ -265,6 +265,7 @@ enum {
SOPT_OTC_OPTS,
SOPT_DEVICE_NAME,
SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel
+ SOPT_EMAIL_OPTS,
NUM_SOPTS // total number of string options
};
diff --git a/main.cpp b/main.cpp
index 81113530..95fd8ba6 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,6 +30,10 @@
#include "opensprinkler_server.h"
#include "mqtt.h"
#include "main.h"
+#include "EmailSender.h"
+
+#define str(s) #s
+#define xstr(s) str(s)
#if defined(ARDUINO)
#if defined(ESP8266)
@@ -89,6 +93,9 @@ float flow_last_gpm=0;
uint32_t reboot_timer = 0;
+//boolean to delay reboot notification until wifi is reconnected
+bool delayed = false;
+
void flow_poll() {
#if defined(ESP8266)
if(os.hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2
@@ -441,6 +448,14 @@ void do_loop()
os.status.mas2= os.iopts[IOPT_MASTER_STATION_2];
time_os_t curr_time = os.now_tz();
+ //handle delayed reboot notification until wifi connection
+ if(delayed){
+ if(WiFi.status() == WL_CONNECTED){
+ push_message(NOTIFY_REBOOT);
+ delayed = false;
+ }
+ }
+
// ====== Process Ethernet packets ======
#if defined(ARDUINO) // Process Ethernet packets for Arduino
#if defined(ESP8266)
@@ -1371,10 +1386,44 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
char* postval = tmp_buffer;
uint32_t volume;
- bool ifttt_enabled = os.iopts[IOPT_IFTTT_ENABLE]&type;
+ //define email variables
+ char host[50 + 1] = {0};
+ char username[32 + 1] = {0};
+ char password[32 + 1] = {0};
+ char recipient[32 + 1] = {0};
+ int port = 465;
+ int enabled = 0;
+
+ //pull email variables
+ os.sopt_load(SOPT_EMAIL_OPTS, postval);
+ if (*postval != 0) {
+ sscanf(
+ postval,
+ "\"en\":%d,\"host\":\"%" xstr(32) "[^\"]\",\"port\":%d,\"user\":\"%" xstr(50) "[^\"]\",\"pass\":\"%" xstr(32) "[^\"]\",\"recipient\":\"%" xstr(32) "[^\"]\"",
+ &enabled, host, &port, username, password, recipient
+ );
+ }
+ //assign email variables necessary
+ EMailSender emailSend(username, password);
+ EMailSender::EMailMessage message;
+
+ //check if ifttt key exists
+ bool ifttt_enabled;
+ if(SOPT_IFTTT_KEY != 0){
+ ifttt_enabled = false;
+ }else{
+ ifttt_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
+ }
+
+ bool email_enabled;
+ if(!enabled){
+ email_enabled = false;
+ }else{
+ email_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
+ }
// check if this type of event is enabled for push notification
- if (!ifttt_enabled && !os.mqtt.enabled())
+ if (!ifttt_enabled && !os.mqtt.enabled() && !email_enabled)
return;
if (ifttt_enabled) {
@@ -1388,6 +1437,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
payload[0] = 0;
}
+ if (email_enabled) {
+ strcpy_P(postval, PSTR("On site ["));
+ os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval));
+ strcat_P(postval, PSTR("], "));
+ emailSend.setSMTPServer(strdup(host));
+ emailSend.setSMTPPort(port);
+ }
+
switch(type) {
case NOTIFY_STATION_ON:
@@ -1396,7 +1453,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(payload, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval);
}
- // todo: add IFTTT support for this event as well.
+ // todo: add IFTTT and email support for this event as well.
// currently no support due to the number of events exceeds 8 so need to use more than 1 byte
break;
@@ -1410,8 +1467,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d}"), (int)fval);
}
}
- if (ifttt_enabled) {
- strcat_P(postval, PSTR("station ["));
+ if (ifttt_enabled || email_enabled) {
+ strcat_P(postval, PSTR("Station ["));
os.get_station_name(lval, postval+strlen(postval));
strcat_P(postval, PSTR("] closed. It ran for "));
sprintf_P(postval+strlen(postval), PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60);
@@ -1420,11 +1477,15 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(postval+strlen(postval), PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100);
}
}
+ if (email_enabled) {
+ message.subject = "Station Off";
+ message.message = postval;
+ }
break;
case NOTIFY_PROGRAM_SCHED:
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
if (sval) strcat_P(postval, PSTR("manually scheduled "));
else strcat_P(postval, PSTR("automatically scheduled "));
strcat_P(postval, PSTR("Program "));
@@ -1435,6 +1496,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
}
sprintf_P(postval+strlen(postval), PSTR(" with %d%% water level."), (int)fval);
}
+ if (email_enabled) {
+ message.subject = "Program Scheduled";
+ message.message = postval;
+ }
break;
case NOTIFY_SENSOR1:
@@ -1443,10 +1508,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcpy_P(topic, PSTR("opensprinkler/sensor1"));
sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval);
}
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
strcat_P(postval, PSTR("sensor 1 "));
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
+ if (email_enabled) {
+ message.subject = "Sensor 1 Notification";
+ message.message = postval;
+ }
break;
case NOTIFY_SENSOR2:
@@ -1455,10 +1524,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcpy_P(topic, PSTR("opensprinkler/sensor2"));
sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval);
}
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
strcat_P(postval, PSTR("sensor 2 "));
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
+ if (email_enabled) {
+ message.subject = "Sensor 2 Notification";
+ message.message = postval;
+ }
break;
case NOTIFY_RAINDELAY:
@@ -1467,10 +1540,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcpy_P(topic, PSTR("opensprinkler/raindelay"));
sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval);
}
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
strcat_P(postval, PSTR("rain delay "));
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
+ if (email_enabled) {
+ message.subject = "Rain Delay";
+ message.message = postval;
+ }
break;
case NOTIFY_FLOWSENSOR:
@@ -1482,14 +1559,18 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcpy_P(topic, PSTR("opensprinkler/sensor/flow"));
sprintf_P(payload, PSTR("{\"count\":%u,\"volume\":%d.%02d}"), lval, (int)volume/100, (int)volume%100);
}
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
sprintf_P(postval+strlen(postval), PSTR("Flow count: %u, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100);
}
+ if (email_enabled) {
+ message.subject = "Flow Sensor Notification";
+ message.message = postval;
+ }
break;
case NOTIFY_WEATHER_UPDATE:
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
if(lval>0) {
strcat_P(postval, PSTR("external IP updated: "));
byte ip[4] = {(byte)((lval>>24)&0xFF),
@@ -1502,15 +1583,23 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(postval+strlen(postval), PSTR("water level updated: %d%%."), (int)fval);
}
}
+ if (email_enabled) {
+ message.subject = "Weather Update";
+ message.message = postval;
+ }
break;
case NOTIFY_REBOOT:
+ if(!delayed){
+ delayed = true;
+ return;
+ }
if (os.mqtt.enabled()) {
strcpy_P(topic, PSTR("opensprinkler/system"));
strcpy_P(payload, PSTR("{\"state\":\"started\"}"));
}
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
#if defined(ARDUINO)
strcat_P(postval, PSTR("rebooted. Device IP: "));
#if defined(ESP8266)
@@ -1534,6 +1623,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcat_P(postval, PSTR("process restarted."));
#endif
}
+ if (email_enabled) {
+ message.subject = "Reboot Notification";
+ message.message = postval;
+ }
break;
}
@@ -1554,6 +1647,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, remote_http_callback);
}
+
+ if(email_enabled){
+ EMailSender::Response resp = emailSend.send(recipient, message);
+ // DEBUG_PRINTLN(F("Sending Status:"));
+ // DEBUG_PRINTLN(resp.status);
+ // DEBUG_PRINTLN(resp.code);
+ // DEBUG_PRINTLN(resp.desc);
+ }
}
// ================================
diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp
index 57510782..e3688f20 100644
--- a/opensprinkler_server.cpp
+++ b/opensprinkler_server.cpp
@@ -1135,13 +1135,14 @@ void server_json_controller_main(OTF_PARAMS_DEF) {
bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","),
+ bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"email\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","),
SOPT_LOCATION,
SOPT_JAVASCRIPTURL,
SOPT_WEATHERURL,
SOPT_WEATHER_OPTS,
SOPT_IFTTT_KEY,
SOPT_MQTT_OPTS,
+ SOPT_EMAIL_OPTS,
strlen(wt_rawData)==0?"{}":wt_rawData,
wt_errCode,
SOPT_DEVICE_NAME);
@@ -1476,6 +1477,15 @@ void server_change_options(OTF_PARAMS_DEF)
os.status.req_mqtt_restart = true;
}
+ keyfound = 0;
+ if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("email"), true, &keyfound)) {
+ urlDecode(tmp_buffer);
+ os.sopt_save(SOPT_EMAIL_OPTS, tmp_buffer);
+ } else if (keyfound) {
+ tmp_buffer[0]=0;
+ os.sopt_save(SOPT_EMAIL_OPTS, tmp_buffer);
+ }
+
if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("dname"), true)) {
urlDecode(tmp_buffer);
strReplace(tmp_buffer, '\"', '\'');
diff --git a/platformio.ini b/platformio.ini
index 681d87a4..8e68b6be 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -8,9 +8,6 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
-; adapt to the already existing folder structure.
-; usually there's src/ and include/ folders.
-; redirect them both.
[platformio]
src_dir = .
include_dir = .
@@ -20,32 +17,16 @@ platform = espressif8266@4.2.1
board = d1_mini
framework = arduino
lib_ldf_mode = deep
-lib_deps =
- sui77/rc-switch @ ^2.6.3
- https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip
- knolleary/PubSubClient @ ^2.8
- https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.1.0
-; ignore html2raw.cpp source file for firmware compilation (external helper program)
+lib_deps =
+ sui77/rc-switch @ ^2.6.3
+ https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip
+ knolleary/PubSubClient @ ^2.8
+ https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.1.0
+ xreef/EMailSender@^3.0.14
build_src_filter = +<*> -
upload_speed = 460800
-monitor_speed=115200
+monitor_speed = 115200
board_build.flash_mode = dio
board_build.ldscript = eagle.flash.4m2m.ld
board_build.f_cpu = 160000000L
board_build.f_flash = 80000000L
-
-;[env:sanguino_atmega1284p]
-;platform = atmelavr
-;board = ATmega1284P
-;board_build.f_cpu = 16000000L
-;board_build.variant = sanguino
-;framework = arduino
-;lib_ldf_mode = deep
-;lib_deps =
-; https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip
-; knolleary/PubSubClient @ ^2.8
-; greiman/SdFat @ 1.0.7
-; Wire
-;build_src_filter = +<*> -
-;monitor_speed=115200
-
From f565cc475dcbf2986cbfc7e70676b62506af6c10 Mon Sep 17 00:00:00 2001
From: Ray
Date: Thu, 27 Jun 2024 14:54:22 -0400
Subject: [PATCH 04/61] fixing issues for ospi https
---
OpenSprinkler.cpp | 16 +++++-----
etherport.cpp | 68 ++++++++++++++++++----------------------
etherport.h | 44 +++++++++++---------------
opensprinkler_server.cpp | 8 +++--
4 files changed, 61 insertions(+), 75 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 8d8475f9..2e30437e 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -1945,7 +1945,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
if(client->connected()) {
client->write((uint8_t *)p, len);
} else {
- DEBUG_PRINTLN(F("clint no longer connected"));
+ DEBUG_PRINTLN(F("client no longer connected"));
}
memset(ether_buffer, 0, ETHER_BUFFER_SIZE);
uint32_t stoptime = millis()+timeout;
@@ -1973,16 +1973,18 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char*
}
}
#else
- while(client->connected()) {
+ /*while(client->connected()) {
int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE);
- if (len<=0) continue;
+ if (len==0) continue;
pos+=len;
if(millis()>stoptime) {
DEBUG_PRINTLN(F("host timeout occured"));
//return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far
break;
}
- }
+ }*/
+ int n = client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE);
+ pos+=n;
#endif
ether_buffer[pos]=0; // properly end buffer with 0
client->stop();
@@ -2066,8 +2068,6 @@ void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon
RemoteOTCStationData copy;
memcpy((char*)©, (char*)data, sizeof(RemoteOTCStationData));
copy.token[sizeof(copy.token)-1] = 0; // ensure the string ends properly
- DEBUG_PRINTLN((char*)copy.token);
- DEBUG_PRINTLN((int)hex2ulong(copy.sid, sizeof(copy.sid)));
char *p = tmp_buffer;
BufferFiller bf = p;
// if turning on the zone and duration is defined, give duration as the timer value
@@ -2087,9 +2087,9 @@ void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon
SOPT_PASSWORD,
(int)hex2ulong(copy.sid, sizeof(copy.sid)),
turnon, timer);
- bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $S\r\n\r\n"), DEFAULT_OTC_SERVER_APP);
+ bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $S\r\nConnection:close\r\n\r\n"), DEFAULT_OTC_SERVER_APP);
- send_http_request(DEFAULT_OTC_SERVER_APP, DEFAULT_OTC_PORT_APP, p, remote_http_callback, true);
+ int x = send_http_request(DEFAULT_OTC_SERVER_APP, DEFAULT_OTC_PORT_APP, p, remote_http_callback, true);
}
/** Switch http(s) station
diff --git a/etherport.cpp b/etherport.cpp
index 858b496a..6e8db0e9 100644
--- a/etherport.cpp
+++ b/etherport.cpp
@@ -228,14 +228,15 @@ size_t EthernetClient::write(const uint8_t *buf, size_t size)
/**
* SSL Client
*/
+static SSL_CTX* ctx = NULL;
EthernetClientSsl::EthernetClientSsl()
- : m_sock(0), m_connected(false)
+ : EthernetClient()
{
}
EthernetClientSsl::EthernetClientSsl(int sock)
- : m_sock(sock), m_connected(true)
+ : EthernetClient(sock)
{
}
@@ -244,10 +245,6 @@ EthernetClientSsl::~EthernetClientSsl()
stop();
}
-static bool sslInit;
-//static BIO* certbio;
-static SSL_CTX* ctx;
-
/**
* https://github.com/angstyloop/c-web/blob/main/openssl-fetch-example.c
*/
@@ -256,18 +253,14 @@ int EthernetClientSsl::connect(const char* server, uint16_t port)
if (m_sock)
return 0;
- struct hostent *host = gethostbyname(server);
- if (!host) {
- DEBUG_PRINTLN("Error: DNS look up failed\n");
- return 0;
- }
-
+ static bool sslInit = false;
if (!sslInit) {
OpenSSL_add_all_algorithms();
//ERR_load_BIO_strings();
ERR_load_crypto_strings();
SSL_load_error_strings();
//BIO* certbio = BIO_new(BIO_s_file());
+ //BIO* outbio = BIO_new_fp(stdout, BIO_NOCLOSE);
if (SSL_library_init() < 0) {
DEBUG_PRINTLN("Error: could not initialize the OpenSSL library.\n");
return 0;
@@ -278,30 +271,36 @@ int EthernetClientSsl::connect(const char* server, uint16_t port)
DEBUG_PRINTLN("Error: unable to create SSL context.\n");
return 0;
}
- SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); //Accept all certs
sslInit = true;
}
- // Create a new SSL session. This does not connect the socket.
- ssl = SSL_new(ctx);
+ struct hostent *host = gethostbyname(server);
+ if (!host) {
+ DEBUG_PRINTLN("Error: DNS look up failed\n");
+ return 0;
+ }
+ // Create a new SSL session. This does not connect the socket.
+ ssl = SSL_new(ctx);
struct sockaddr_in sin = {0};
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
- sin.sin_addr.s_addr = *(uint32_t*) (host->h_addr);
+ sin.sin_addr.s_addr = *(long*) (host->h_addr);
m_sock = socket(AF_INET, SOCK_STREAM, 0);
- if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
+ if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(struct sockaddr)) < 0)
{
DEBUG_PRINTLN("Error: connecting to the server failed");
return 0;
}
SSL_set_fd(ssl, m_sock);
SSL_set_tlsext_host_name(ssl, server); // set correct host name
+
if (SSL_connect(ssl) < 1) {
close(m_sock);
m_sock = 0;
- DEBUG_PRINTLN("Error: Could not build an SSL session");
+ DEBUG_PRINTLN("Error: Could not build an SSL session");
return 0;
}
m_connected = true;
@@ -342,27 +341,20 @@ EthernetClientSsl::operator bool()
// and return 0;
int EthernetClientSsl::read(uint8_t *buf, size_t size)
{
- fd_set sock_set;
- FD_ZERO(&sock_set);
- FD_SET(m_sock, &sock_set);
- struct timeval timeout;
- timeout.tv_sec = 3;
- timeout.tv_usec = 0;
-
- //select(m_sock + 1, &sock_set, NULL, NULL, &timeout);
- //if (FD_ISSET(m_sock, &sock_set))
- size_t pending = SSL_pending(ssl);
- if (pending > 0)
- {
- if (size > pending)
- size = pending;
- int retval = SSL_read(ssl, buf, size);
- if (retval <= 0) // socket closed
- m_connected = false;
- return retval;
+ /*int retval = SSL_read(ssl, buf, size);
+ if (retval < 0) // socket closed
+ m_connected = false;
+ return retval;*/
+ int n=0;
+ for (;;) {
+ if ((n = SSL_read(ssl, buf, size)) < 0) {
+ DEBUG_PRINTLN("ERROR reading from socket.");
+ break;
+ }
+ if (!n) break;
+ printf("%s", buf);
}
- m_connected = false;
- return 0;
+ return n;
}
size_t EthernetClientSsl::write(const uint8_t *buf, size_t size)
diff --git a/etherport.h b/etherport.h
index 20e75787..767d8ff3 100644
--- a/etherport.h
+++ b/etherport.h
@@ -47,42 +47,34 @@ class EthernetClient {
EthernetClient();
EthernetClient(int sock);
~EthernetClient();
- int connect(const char *server, uint16_t port);
- bool connected();
- void stop();
- int read(uint8_t *buf, size_t size);
- size_t write(const uint8_t *buf, size_t size);
- operator bool();
- int GetSocket()
- {
+ virtual int connect(const char *server, uint16_t port);
+ virtual bool connected();
+ virtual void stop();
+ virtual int read(uint8_t *buf, size_t size);
+ virtual size_t write(const uint8_t *buf, size_t size);
+ virtual operator bool();
+ virtual int GetSocket() {
return m_sock;
}
-private:
+protected:
int m_sock = 0;
bool m_connected;
friend class EthernetServer;
};
-class EthernetClientSsl : EthernetClient {
+class EthernetClientSsl : public EthernetClient {
public:
EthernetClientSsl();
EthernetClientSsl(int sock);
~EthernetClientSsl();
- int connect(const char *server, uint16_t port);
- bool connected();
- void stop();
- int read(uint8_t *buf, size_t size);
- size_t write(const uint8_t *buf, size_t size);
- operator bool();
- int GetSocket()
- {
- return m_sock;
- }
-private:
- int m_sock;
+ virtual int connect(const char *server, uint16_t port);
+ virtual bool connected();
+ virtual void stop();
+ virtual int read(uint8_t *buf, size_t size);
+ virtual size_t write(const uint8_t *buf, size_t size);
+ virtual operator bool();
+protected:
SSL* ssl;
- bool m_connected;
- friend class EthernetServer;
};
class EthernetServer {
@@ -90,8 +82,8 @@ class EthernetServer {
EthernetServer(uint16_t port);
~EthernetServer();
- bool begin();
- EthernetClient available();
+ virtual bool begin();
+ virtual EthernetClient available();
private:
uint16_t m_port;
int m_sock;
diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp
index 57510782..8bb226ff 100644
--- a/opensprinkler_server.cpp
+++ b/opensprinkler_server.cpp
@@ -586,9 +586,9 @@ void server_change_stations(OTF_PARAMS_DEF) {
tmp_buffer[0]-='0';
tmp_buffer[STATION_SPECIAL_DATA_SIZE] = 0;
- // only process GPIO and HTTP stations for OS 2.3, above, and OSPi
+ DEBUG_PRINTF("%d\n", tmp_buffer[0]);
if(tmp_buffer[0] == STN_TYPE_GPIO) {
- // check that pin does not clash with OSPi pins
+ // check that pin does not clash with free pins
byte gpio = (tmp_buffer[1] - '0') * 10 + tmp_buffer[2] - '0';
byte activeState = tmp_buffer[3] - '0';
@@ -600,10 +600,12 @@ void server_change_stations(OTF_PARAMS_DEF) {
if (!found || activeState > 1) {
handle_return(HTML_DATA_OUTOFBOUND);
}
- } else if (tmp_buffer[0] == STN_TYPE_HTTP || tmp_buffer[0] == STN_TYPE_HTTPS) {
+ } else if ((tmp_buffer[0] == STN_TYPE_HTTP) || (tmp_buffer[0] == STN_TYPE_HTTPS) || (tmp_buffer[0] == STN_TYPE_REMOTE_OTC)) {
+ DEBUG_PRINTLN(tmp_buffer+1);
#if !defined(ESP8266)
urlDecode(tmp_buffer + 1);
#endif
+ DEBUG_PRINTLN(tmp_buffer+1);
if (strlen(tmp_buffer+1) > sizeof(HTTPStationData)) {
handle_return(HTML_DATA_OUTOFBOUND);
}
From b185c1bc866e43e3724fd796ca6d410085478bae Mon Sep 17 00:00:00 2001
From: Nathan P
Date: Thu, 27 Jun 2024 15:55:54 -0400
Subject: [PATCH 05/61] Initial support for Raspberry Pi email notifications.
---
build.sh | 2 +-
main.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 60 insertions(+), 20 deletions(-)
diff --git a/build.sh b/build.sh
index ae257c8d..44591633 100755
--- a/build.sh
+++ b/build.sh
@@ -45,7 +45,7 @@ else
fi
echo "Compiling ospi firmware..."
- g++ -o OpenSprinkler -DOSPI $USEGPIO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto $GPIOLIB
+ g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL -fpermissive -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp smtp.c -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB
fi
if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then
diff --git a/main.cpp b/main.cpp
index 95fd8ba6..57ccd7fb 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,7 +30,14 @@
#include "opensprinkler_server.h"
#include "mqtt.h"
#include "main.h"
-#include "EmailSender.h"
+#if defined(ARUDINO)
+ #include "EmailSender.h"
+#else
+ #include "smtp.h"
+ #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_TLS
+ #define MAIL_FLAGS (SMTP_DEBUG | SMTP_NO_CERT_VERIFY)
+ #define MAIL_AUTH SMTP_AUTH_PLAIN
+#endif
#define str(s) #s
#define xstr(s) str(s)
@@ -449,12 +456,14 @@ void do_loop()
time_os_t curr_time = os.now_tz();
//handle delayed reboot notification until wifi connection
- if(delayed){
- if(WiFi.status() == WL_CONNECTED){
- push_message(NOTIFY_REBOOT);
- delayed = false;
+ #if defined(ARDUINO)
+ if(delayed){
+ if(WiFi.status() == WL_CONNECTED){
+ push_message(NOTIFY_REBOOT);
+ delayed = false;
+ }
}
- }
+ #endif
// ====== Process Ethernet packets ======
#if defined(ARDUINO) // Process Ethernet packets for Arduino
@@ -1403,9 +1412,19 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
&enabled, host, &port, username, password, recipient
);
}
+
//assign email variables necessary
- EMailSender emailSend(username, password);
- EMailSender::EMailMessage message;
+ #if defined(ARDUINO)
+ EMailSender emailSend(username, password);
+ EMailSender::EMailMessage message;
+ #else
+ struct smtp *smtp;
+ int rc;
+ struct {
+ String subject;
+ String message;
+ }message;
+ #endif
//check if ifttt key exists
bool ifttt_enabled;
@@ -1441,8 +1460,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcpy_P(postval, PSTR("On site ["));
os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval));
strcat_P(postval, PSTR("], "));
- emailSend.setSMTPServer(strdup(host));
- emailSend.setSMTPPort(port);
+ #if defined(ARDUINO)
+ emailSend.setSMTPServer(strdup(host));
+ emailSend.setSMTPPort(port);
+ #endif
}
switch(type) {
@@ -1590,10 +1611,12 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
break;
case NOTIFY_REBOOT:
- if(!delayed){
- delayed = true;
- return;
- }
+ #if defined(ARUDINO)
+ if(!delayed){
+ delayed = true;
+ return;
+ }
+ #endif
if (os.mqtt.enabled()) {
strcpy_P(topic, PSTR("opensprinkler/system"));
@@ -1649,11 +1672,28 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
}
if(email_enabled){
- EMailSender::Response resp = emailSend.send(recipient, message);
- // DEBUG_PRINTLN(F("Sending Status:"));
- // DEBUG_PRINTLN(resp.status);
- // DEBUG_PRINTLN(resp.code);
- // DEBUG_PRINTLN(resp.desc);
+ #if defined(ARDUINO)
+ EMailSender::Response resp = emailSend.send(recipient, message);
+ // DEBUG_PRINTLN(F("Sending Status:"));
+ // DEBUG_PRINTLN(resp.status);
+ // DEBUG_PRINTLN(resp.code);
+ // DEBUG_PRINTLN(resp.desc);
+ #else
+ char piSubject[message.subject.length() + 1];
+ char piMessage[message.message.length() + 1];
+ String sPort = to_string(port);
+ char piPort[sPort.length() + 1];
+ strcpy(piSubject, message.subject.c_str());
+ strcpy(piMessage, message.message.c_str());
+ strcpy(piPort, sPort.c_str());
+ rc = smtp_open(host, piPort, MAIL_CONNECTION_SECURITY, MAIL_FLAGS, NULL, &smtp);
+ rc = smtp_auth(smtp, MAIL_AUTH, username, password);
+ rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, username, "OpenSprinkler");
+ rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, recipient, "User");
+ rc = smtp_header_add(smtp, "Subject", piSubject);
+ rc = smtp_mail(smtp, piMessage);
+ rc = smtp_close(smtp);
+ #endif
}
}
From c5a5fcc81c1ebb81e83b8936ade169435aecdff9 Mon Sep 17 00:00:00 2001
From: Nathan P
Date: Thu, 27 Jun 2024 15:58:46 -0400
Subject: [PATCH 06/61] add missing smtp.c and .h files
---
smtp.c | 3528 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
smtp.h | 635 ++++++++++
2 files changed, 4163 insertions(+)
create mode 100644 smtp.c
create mode 100644 smtp.h
diff --git a/smtp.c b/smtp.c
new file mode 100644
index 00000000..40e77b49
--- /dev/null
+++ b/smtp.c
@@ -0,0 +1,3528 @@
+/**
+ * @file
+ * @brief SMTP client library.
+ * @author James Humphrey (mail@somnisoft.com)
+ * @version 1.00
+ *
+ * This SMTP client library allows the user to send emails to an SMTP server.
+ * The user can include custom headers and MIME attachments.
+ *
+ * This software has been placed into the public domain using CC0.
+ */
+
+/**
+ * @mainpage smtp-client
+ *
+ * This section contains documentation generated directly from the source
+ * code.
+ *
+ * To view the repository details, visit the main smtp-client page at
+ *
+ * www.somnisoft.com/smtp-client
+ * .
+ */
+
+#if defined(_WIN32) || defined(WIN32)
+# define SMTP_IS_WINDOWS
+#endif /* SMTP_IS_WINDOWS */
+
+#ifdef SMTP_IS_WINDOWS
+# include
+# include
+#else /* POSIX */
+# include
+# include
+# include
+# include
+# include
+#endif /* SMTP_IS_WINDOWS */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef SMTP_OPENSSL
+# include
+# include
+# include
+# include
+# include
+#endif /* SMTP_OPENSSL */
+
+/**
+ * Get access to the @ref smtp_result_code and @ref smtp_command definitions.
+ */
+#define SMTP_INTERNAL_DEFINE
+
+#include "smtp.h"
+
+/*
+ * The SMTP_TEST converts some library routines into special test seams which
+ * allows the test program to control whether they fail. For example, we can
+ * control when malloc() fails under certain conditions with an out of
+ * memory condition.
+ */
+#ifdef SMTP_TEST
+/**
+ * Declare extern linkage on some functions so we can redefine their behavior
+ * in the external test suite.
+ */
+# define SMTP_LINKAGE extern
+# include "../test/seams.h"
+#else /* !(SMTP_TEST) */
+/**
+ * When not testing, all functions should have static linkage except for those
+ * in the header.
+ */
+# define SMTP_LINKAGE static
+#endif /* SMTP_TEST */
+
+/**
+ * Increment the read buffer size by this amount if the delimiter
+ * has not been found.
+ */
+#define SMTP_GETDELIM_READ_SZ 1000
+
+/**
+ * Stores source and destination email addresses.
+ */
+struct smtp_address{
+ /**
+ * Email address without any special formatting.
+ *
+ * For example: mail@example.com
+ */
+ char *email;
+
+ /**
+ * Description of the email address.
+ */
+ char *name;
+
+ /**
+ * Specify from, to, cc, bcc.
+ */
+ enum smtp_address_type type;
+
+ /**
+ * Padding structure to align.
+ */
+ char pad[4];
+};
+
+/**
+ * Attachment data which gets placed in the MIME email section.
+ */
+struct smtp_attachment{
+ /**
+ * File name of the attachment.
+ */
+ char *name;
+
+ /**
+ * Base64-encoded file data.
+ */
+ char *b64_data;
+};
+
+/**
+ * List of email headers to send before the mail body.
+ */
+struct smtp_header{
+ /**
+ * Header name which will get sorted alphabetically in the header list.
+ */
+ char *key;
+
+ /**
+ * Content of the corresponding header key.
+ */
+ char *value;
+};
+
+/**
+ * Main data structure that holds the SMTP client context.
+ */
+struct smtp{
+ /**
+ * Bitwise list of flags controlling the behavior of this SMTP client.
+ */
+ enum smtp_flag flags;
+
+ /**
+ * Standard network socket connection.
+ */
+ int sock;
+
+ /**
+ * Read buffer and line parsing structure.
+ */
+ struct str_getdelimfd gdfd;
+
+ /**
+ * List of headers to print before the mail body.
+ */
+ struct smtp_header *header_list;
+
+ /**
+ * Number of headers in header_list.
+ */
+ size_t num_headers;
+
+ /**
+ * List of from, to, cc, and bcc email addresses.
+ */
+ struct smtp_address *address_list;
+
+ /**
+ * Number of addresses in address_list.
+ */
+ size_t num_address;
+
+ /**
+ * List of attachments to send.
+ */
+ struct smtp_attachment *attachment_list;
+
+ /**
+ * Number of attachments in attachment_list.
+ */
+ size_t num_attachment;
+
+ /**
+ * Timeout in seconds to wait before returning with an error.
+ *
+ * This applies to both writing to and reading from a network socket.
+ */
+ long timeout_sec;
+
+ /**
+ * Status code indicating success/failure.
+ *
+ * This code gets returned by most of the header functions.
+ */
+ enum smtp_status_code status_code;
+
+ /**
+ * Indicates if this context has an active TLS connection.
+ * - Set to 0 if TLS connection inactive.
+ * - Set to 1 if TLS connection currently active.
+ */
+ int tls_on;
+
+ /**
+ * Path to certificate file if using self-signed or untrusted certificate
+ * not in the default key store.
+ */
+ const char *cafile;
+
+#ifdef SMTP_OPENSSL
+ /**
+ * OpenSSL TLS object.
+ */
+ SSL *tls;
+
+ /**
+ * OpenSSL TLS context.
+ */
+ SSL_CTX *tls_ctx;
+
+ /**
+ * OpenSSL TLS I/O abstraction.
+ */
+ BIO *tls_bio;
+#endif /* SMTP_OPENSSL */
+};
+
+/**
+ * Check if adding a size_t value will cause a wrap.
+ *
+ * @param[in] a Add this value with @p b.
+ * @param[in] b Add this value with @p a.
+ * @param[out] result Save the addition to this buffer. Does not
+ * perform the addition if set to NULL.
+ * @retval 1 Value wrapped.
+ * @retval 0 Value did not wrap.
+ */
+SMTP_LINKAGE int
+smtp_si_add_size_t(const size_t a,
+ const size_t b,
+ size_t *const result){
+ int wraps;
+
+#ifdef SMTP_TEST
+ if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_add_size_t_ctr)){
+ return 1;
+ }
+#endif /* SMTP_TEST */
+
+ if(SIZE_MAX - a < b){
+ wraps = 1;
+ }
+ else{
+ wraps = 0;
+ }
+ if(result){
+ *result = a + b;
+ }
+ return wraps;
+}
+
+/**
+ * Check if subtracting a size_t value will cause wrap.
+ *
+ * @param[in] a Subtract this value by @p b.
+ * @param[in] b Subtract this value from @p a.
+ * @param[out] result Save the subtraction to this buffer. Does not
+ * perform the subtraction if set to NULL.
+ * @retval 1 Value wrapped.
+ * @retval 0 Value did not wrap.
+ */
+SMTP_LINKAGE int
+smtp_si_sub_size_t(const size_t a,
+ const size_t b,
+ size_t *const result){
+ int wraps;
+
+#ifdef SMTP_TEST
+ if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_sub_size_t_ctr)){
+ return 1;
+ }
+#endif /* SMTP_TEST */
+
+ if(a < b){
+ wraps = 1;
+ }
+ else{
+ wraps = 0;
+ }
+ if(result){
+ *result = a - b;
+ }
+ return wraps;
+}
+
+/**
+ * Check if multiplying a size_t value will cause a wrap.
+ *
+ * @param[in] a Multiply this value with @p b.
+ * @param[in] b Multiply this value with @p a.
+ * @param[out] result Save the multiplication to this buffer. Does not
+ * perform the multiplication if set to NULL.
+ * @retval 1 Value wrapped.
+ * @retval 0 Value did not wrap.
+ */
+SMTP_LINKAGE int
+smtp_si_mul_size_t(const size_t a,
+ const size_t b,
+ size_t *const result){
+ int wraps;
+
+#ifdef SMTP_TEST
+ if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_si_mul_size_t_ctr)){
+ return 1;
+ }
+#endif /* SMTP_TEST */
+
+ if(b != 0 && a > SIZE_MAX / b){
+ wraps = 1;
+ }
+ else{
+ wraps = 0;
+ }
+ if(result){
+ *result = a * b;
+ }
+ return wraps;
+}
+
+/**
+ * Wait until more data has been made available on the socket read end.
+ *
+ * @param[in] smtp SMTP client context.
+ * @retval SMTP_STATUS_OK If data available to read on the socket.
+ * @retval SMTP_STATUS_RECV If the connection times out before any data
+ * appears on the socket.
+ */
+static enum smtp_status_code
+smtp_str_getdelimfd_read_timeout(struct smtp *const smtp){
+ fd_set readfds;
+ struct timeval timeout;
+ int sel_rc;
+
+ FD_ZERO(&readfds);
+ FD_SET(smtp->sock, &readfds);
+ timeout.tv_sec = smtp->timeout_sec;
+ timeout.tv_usec = 0;
+ sel_rc = select(smtp->sock + 1, &readfds, NULL, NULL, &timeout);
+ if(sel_rc < 1){
+ return smtp_status_code_set(smtp, SMTP_STATUS_RECV);
+ }
+ return SMTP_STATUS_OK;
+}
+
+/**
+ * This function gets called by the @ref smtp_str_getdelimfd interface when it
+ * needs to read in more data.
+ *
+ * It reads using either the plain socket connection if encryption not
+ * enabled, or it reads using OpenSSL if it has an active TLS connection.
+ *
+ * @param[in] gdfd See @ref str_getdelimfd.
+ * @param[out] buf Pointer to buffer for storing bytes read.
+ * @param[in] count Maximum number of bytes to try reading.
+ * @retval >=0 Number of bytes read.
+ * @retval -1 Failed to read from the socket.
+ */
+static long
+smtp_str_getdelimfd_read(struct str_getdelimfd *const gdfd,
+ void *buf,
+ size_t count){
+ struct smtp *smtp;
+ long bytes_read;
+
+ smtp = gdfd->user_data;
+
+ if(smtp_str_getdelimfd_read_timeout(smtp) != SMTP_STATUS_OK){
+ return -1;
+ }
+
+ bytes_read = 0;
+ if(smtp->tls_on){
+#ifdef SMTP_OPENSSL
+ do{
+ /*
+ * Count will never have a value greater than SMTP_GETDELIM_READ_SZ,
+ * so we can safely convert this to an int.
+ */
+ bytes_read = SSL_read(smtp->tls, buf, (int)count);
+ } while(bytes_read <= 0 && BIO_should_retry(smtp->tls_bio));
+#endif /* SMTP_OPENSSL */
+ }
+ else{
+ bytes_read = recv(smtp->sock, buf, count, 0);
+ }
+ return bytes_read;
+}
+
+/**
+ * Find and return the location of the delimiter character in the
+ * search buffer.
+ *
+ * This function gets used by the main socket parsing function which
+ * continually reads from the socket and expands the buffer until it
+ * encounters the expected delimiter. This function provides the logic
+ * to check for the delimiter character in order to simplify the code
+ * in the main parse function.
+ *
+ * @param[in] buf Search buffer used to find the delimiter.
+ * @param[in] buf_len Number of bytes to search for in buf.
+ * @param[in] delim The delimiter to search for in buf.
+ * @param[out] delim_pos If delimiter found in buf, return the delimiter
+ * position in this parameter.
+ * @retval 1 If the delimiter character found.
+ * @retval 0 If the delimiter character not found.
+ */
+static int
+smtp_str_getdelimfd_search_delim(const char *const buf,
+ size_t buf_len,
+ int delim,
+ size_t *const delim_pos){
+ size_t i;
+
+ *delim_pos = 0;
+ for(i = 0; i < buf_len; i++){
+ if(buf[i] == delim){
+ *delim_pos = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Set the internal line buffer to the number of bytes specified.
+ *
+ * @param[in] gdfd See @ref str_getdelimfd.
+ * @param[in] copy_len Number of bytes to copy to the internal line buffer.
+ * @retval 0 Successfully allocated and copied data over to the new
+ * line buffer.
+ * @retval -1 Failed to allocate memory for the new line buffer.
+ */
+SMTP_LINKAGE int
+smtp_str_getdelimfd_set_line_and_buf(struct str_getdelimfd *const gdfd,
+ size_t copy_len){
+ size_t copy_len_inc;
+ size_t nbytes_to_shift;
+ size_t new_buf_len;
+
+ if(gdfd->line){
+ free(gdfd->line);
+ gdfd->line = NULL;
+ }
+
+ if(smtp_si_add_size_t(copy_len, 1, ©_len_inc) ||
+ smtp_si_add_size_t((size_t)gdfd->_buf, copy_len_inc, NULL) ||
+ smtp_si_sub_size_t(gdfd->_buf_len, copy_len, &nbytes_to_shift) ||
+ (gdfd->line = calloc(1, copy_len_inc)) == NULL){
+ return -1;
+ }
+ memcpy(gdfd->line, gdfd->_buf, copy_len);
+ gdfd->line_len = copy_len;
+ memmove(gdfd->_buf, gdfd->_buf + copy_len_inc, nbytes_to_shift);
+ if(smtp_si_sub_size_t(nbytes_to_shift, 1, &new_buf_len) == 0){
+ gdfd->_buf_len = new_buf_len;
+ }
+ return 0;
+}
+
+/**
+ * Free memory in the @ref str_getdelimfd data structure.
+ *
+ * @param[in] gdfd Frees memory stored in this socket parsing structure.
+ */
+SMTP_LINKAGE void
+smtp_str_getdelimfd_free(struct str_getdelimfd *const gdfd){
+ free(gdfd->_buf);
+ free(gdfd->line);
+ gdfd->_buf = NULL;
+ gdfd->_bufsz = 0;
+ gdfd->_buf_len = 0;
+ gdfd->line = NULL;
+ gdfd->line_len = 0;
+}
+
+/**
+ * Free the @ref str_getdelimfd and return the @ref STRING_GETDELIMFD_ERROR
+ * error code.
+ *
+ * @param[in] gdfd See @ref str_getdelimfd.
+ * @return See @ref str_getdelim_retcode.
+ */
+static enum str_getdelim_retcode
+smtp_str_getdelimfd_throw_error(struct str_getdelimfd *const gdfd){
+ smtp_str_getdelimfd_free(gdfd);
+ return STRING_GETDELIMFD_ERROR;
+}
+
+/**
+ * Read and parse a delimited string using a custom socket read function.
+ *
+ * This interface handles all of the logic for expanding the buffer,
+ * parsing the delimiter in the buffer, and returning each "line"
+ * to the caller for handling.
+ *
+ * @param[in] gdfd See @ref str_getdelimfd.
+ * @return See @ref str_getdelim_retcode.
+ */
+SMTP_LINKAGE enum str_getdelim_retcode
+smtp_str_getdelimfd(struct str_getdelimfd *const gdfd){
+ size_t delim_pos;
+ long bytes_read;
+ void *read_buf_ptr;
+ char *buf_new;
+ size_t buf_sz_remaining;
+ size_t buf_sz_new;
+
+ if(gdfd->getdelimfd_read == NULL){
+ return STRING_GETDELIMFD_ERROR;
+ }
+
+ bytes_read = -1;
+
+ while(1){
+ if(smtp_str_getdelimfd_search_delim(gdfd->_buf,
+ gdfd->_buf_len,
+ gdfd->delim,
+ &delim_pos)){
+ if(smtp_str_getdelimfd_set_line_and_buf(gdfd, delim_pos) < 0){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ return STRING_GETDELIMFD_NEXT;
+ }else if(bytes_read == 0){
+ if(smtp_str_getdelimfd_set_line_and_buf(gdfd, gdfd->_buf_len) < 0){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ return STRING_GETDELIMFD_DONE;
+ }
+
+ if(smtp_si_sub_size_t(gdfd->_bufsz, gdfd->_buf_len, &buf_sz_remaining)){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+
+ if(buf_sz_remaining < SMTP_GETDELIM_READ_SZ){
+ if(smtp_si_add_size_t(buf_sz_remaining,
+ SMTP_GETDELIM_READ_SZ,
+ &buf_sz_new)){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ buf_new = realloc(gdfd->_buf, buf_sz_new);
+ if(buf_new == NULL){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ gdfd->_buf = buf_new;
+ gdfd->_bufsz = buf_sz_new;
+ }
+
+ if(smtp_si_add_size_t((size_t)gdfd->_buf, gdfd->_buf_len, NULL)){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ read_buf_ptr = gdfd->_buf + gdfd->_buf_len;
+ bytes_read = (*gdfd->getdelimfd_read)(gdfd,
+ read_buf_ptr,
+ SMTP_GETDELIM_READ_SZ);
+ if(bytes_read < 0 ||
+ smtp_si_add_size_t(gdfd->_buf_len,
+ (size_t)bytes_read,
+ &gdfd->_buf_len)){
+ return smtp_str_getdelimfd_throw_error(gdfd);
+ }
+ }
+}
+
+/**
+ * Copy a string and get the pointer to the end of the copied buffer.
+ *
+ * This function behaves similar to POSIX stpcpy(), useful for
+ * concatenating multiple strings onto a buffer. It always adds a
+ * null-terminated byte at the end of the string.
+ *
+ * @param[in] s1 Destination buffer.
+ * @param[in] s2 Null-terminated source string to copy to @p s1.
+ * @return Pointer to location in @p s1 after the last copied byte.
+ */
+SMTP_LINKAGE char *
+smtp_stpcpy(char *s1,
+ const char *s2){
+ size_t i;
+
+ i = 0;
+ do{
+ s1[i] = s2[i];
+ } while(s2[i++] != '\0');
+ return &s1[i-1];
+}
+
+/**
+ * Reallocate memory with unsigned wrapping checks.
+ *
+ * @param[in] ptr Existing allocation buffer, or NULL when allocating a
+ * new buffer.
+ * @param[in] nmemb Number of elements to allocate.
+ * @param[in] size Size of each element in @p nmemb.
+ * @retval void* Pointer to a reallocated buffer containing
+ * @p nmemb * @p size bytes.
+ * @retval NULL Failed to reallocate memory.
+ */
+SMTP_LINKAGE void *
+smtp_reallocarray(void *ptr,
+ size_t nmemb,
+ size_t size){
+ void *alloc;
+ size_t size_mul;
+
+ if(smtp_si_mul_size_t(nmemb, size, &size_mul)){
+ alloc = NULL;
+ errno = ENOMEM;
+ }
+ else{
+ alloc = realloc(ptr, size_mul);
+ }
+ return alloc;
+}
+
+/**
+ * Copy a string into a new dynamically allocated buffer.
+ *
+ * Returns a dynamically allocated string, with the same contents as the
+ * input string. The caller must free the returned string when finished.
+ *
+ * @param[in] s String to duplicate.
+ * @retval char* Pointer to a new dynamically allocated string duplicated
+ * from @p s.
+ * @retval NULL Failed to allocate memory for the new duplicate string.
+ */
+SMTP_LINKAGE char *
+smtp_strdup(const char *s){
+ char *dup;
+ size_t dup_len;
+ size_t slen;
+
+ slen = strlen(s);
+ if(smtp_si_add_size_t(slen, 1, &dup_len)){
+ dup = NULL;
+ errno = ENOMEM;
+ }
+ else if((dup = malloc(dup_len)) != NULL){
+ memcpy(dup, s, dup_len);
+ }
+ return dup;
+}
+
+/**
+ * Search for all substrings in a string and replace each instance with a
+ * replacement string.
+ *
+ * @param[in] search Substring to search for in @p s.
+ * @param[in] replace Replace each instance of the search string with this.
+ * @param[in] s Null-terminated string to search and replace.
+ * @retval char* A dynamically allocated string with the replaced instances
+ * as described above. The caller must free the allocated
+ * memory when finished.
+ * @retval NULL Memory allocation failure.
+ */
+SMTP_LINKAGE char *
+smtp_str_replace(const char *const search,
+ const char *const replace,
+ const char *const s){
+ size_t search_len;
+ size_t replace_len;
+ size_t replace_len_inc;
+ size_t slen;
+ size_t slen_inc;
+ size_t s_idx;
+ size_t snew_len;
+ size_t snew_len_inc;
+ size_t snew_sz;
+ size_t snew_sz_dup;
+ size_t snew_sz_plus_slen;
+ size_t snew_replace_len_inc;
+ char *snew;
+ char *stmp;
+
+ search_len = strlen(search);
+ replace_len = strlen(replace);
+ slen = strlen(s);
+ s_idx = 0;
+ snew = NULL;
+ snew_len = 0;
+ snew_sz = 0;
+
+ if(smtp_si_add_size_t(replace_len, 1, &replace_len_inc) ||
+ smtp_si_add_size_t(slen, 1, &slen_inc)){
+ return NULL;
+ }
+
+ if(s[0] == '\0'){
+ return smtp_strdup("");
+ }
+ else if(search_len < 1){
+ return smtp_strdup(s);
+ }
+
+ while(s[s_idx]){
+ if(smtp_si_add_size_t(snew_len, 1, &snew_len_inc) ||
+ smtp_si_add_size_t(snew_sz, snew_sz, &snew_sz_dup) ||
+ smtp_si_add_size_t(snew_sz, slen, &snew_sz_plus_slen)){
+ free(snew);
+ return NULL;
+ }
+
+ if(strncmp(&s[s_idx], search, search_len) == 0){
+ if(smtp_si_add_size_t(snew_len, replace_len_inc, &snew_replace_len_inc)){
+ free(snew);
+ return NULL;
+ }
+ if(snew_replace_len_inc >= snew_sz){
+ /* snew_sz += snew_sz + slen + replace_len + 1 */
+ if(smtp_si_add_size_t(snew_sz_dup, slen_inc, &snew_sz) ||
+ smtp_si_add_size_t(snew_sz, replace_len, &snew_sz) ||
+ (stmp = realloc(snew, snew_sz)) == NULL){
+ free(snew);
+ return NULL;
+ }
+ snew = stmp;
+ }
+ memcpy(&snew[snew_len], replace, replace_len);
+ snew_len += replace_len;
+ s_idx += search_len;
+ }
+ else{
+ if(snew_len_inc >= snew_sz){
+ /* snew_sz += snew_sz + slen + snew_len + 1 */
+ if(smtp_si_add_size_t(snew_sz_dup, slen, &snew_sz) ||
+ smtp_si_add_size_t(snew_sz, snew_len_inc, &snew_sz) ||
+ (stmp = realloc(snew, snew_sz)) == NULL){
+ free(snew);
+ return NULL;
+ }
+ snew = stmp;
+ }
+ snew[snew_len] = s[s_idx];
+ s_idx += 1;
+ snew_len = snew_len_inc;
+ }
+ }
+ snew[snew_len] = '\0';
+
+ return snew;
+}
+
+/**
+ * Lookup table used to encode data into base64.
+ *
+ * Base64 encoding takes six bits of data and encodes those bits using this
+ * table. Since 2^6 = 64, this array has 64 entries which maps directly from
+ * the 6 bit value into the corresponding array value.
+ */
+static char g_base64_encode_table[] = {
+ 'A','B','C','D','E','F','G','H','I','J',
+ 'K','L','M','N','O','P','Q','R','S','T',
+ 'U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j',
+ 'k','l','m','n','o','p','q','r','s','t',
+ 'u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '+','/'
+};
+
+/**
+ * Encode a single block of binary data into base64.
+ *
+ * @param[in] buf Buffer with data to encode.
+ * @param[in] buf_block_sz Number of bytes in buf to encode (min 1, max 3).
+ * @param[out] b64 Pointer to buffer with at least 4 bytes for
+ * storing the base64 encoded result.
+ */
+static void
+smtp_base64_encode_block(const char *const buf,
+ size_t buf_block_sz,
+ char *const b64){
+ unsigned char inb[3] = {0};
+ unsigned char in_idx[4] = {0};
+ char outb[5] = {'=', '=', '=', '=', '\0'};
+ size_t i;
+
+ memcpy(inb, buf, buf_block_sz);
+
+ in_idx[0] = ((inb[0] >> 2)) & 0x3F;
+ in_idx[1] = ((inb[0] << 4) | ((inb[1] >> 4) & 0xF)) & 0x3F;
+ in_idx[2] = ((inb[1] << 2) | ((inb[2] >> 6) & 0x3)) & 0x3F;
+ in_idx[3] = ((inb[2] )) & 0x3F;
+ for(i = 0; i < 4; i++){
+ if(i < buf_block_sz + 1){
+ outb[i] = g_base64_encode_table[in_idx[i]];
+ }
+ b64[i] = outb[i];
+ }
+}
+
+/**
+ * Encode binary data into a base64 string.
+ *
+ * @param[in] buf Binary data to encode in base64.
+ * @param[in] buflen Number of bytes in the @p buf parameter, or -1 if
+ * null-terminated.
+ * @retval char* Dynamically allocated base64 encoded string. The caller
+ * must free this string when finished.
+ * @retval NULL Memory allocation failure.
+ */
+SMTP_LINKAGE char *
+smtp_base64_encode(const char *const buf,
+ size_t buflen){
+ char *b64;
+ size_t b64_sz;
+ size_t buf_i;
+ size_t b64_i;
+ size_t remaining_block_sz;
+ size_t buf_block_sz;
+
+ if(buflen == SIZE_MAX){
+ buflen = strlen(buf);
+ }
+
+ /*
+ * base64 size expands by 33%
+ * +1 to round integer division up
+ * +2 for '=' padding
+ * +1 null terminator
+ */
+ if(smtp_si_mul_size_t(buflen, 4, NULL)){
+ return NULL;
+ }
+ b64_sz = (4 * buflen / 3) + 1 + 2 + 1;
+ if((b64 = calloc(1, b64_sz)) == NULL){
+ return NULL;
+ }
+
+ if(buflen == 0){
+ return b64;
+ }
+
+ buf_i = 0;
+ b64_i = 0;
+ remaining_block_sz = buflen;
+ while(remaining_block_sz > 0){
+ if(remaining_block_sz >= 3){
+ buf_block_sz = 3;
+ }
+ else{
+ buf_block_sz = remaining_block_sz;
+ }
+
+ smtp_base64_encode_block(&buf[buf_i], buf_block_sz, &b64[b64_i]);
+
+ /*
+ * Do not need to check for wrapping because these values restricted to
+ * range of b64_sz, which has already been checked for wrapping above.
+ */
+ buf_i += 3;
+ b64_i += 4;
+
+ remaining_block_sz -= buf_block_sz;
+ }
+
+ return b64;
+}
+
+#ifdef SMTP_OPENSSL
+/**
+ * Lookup table used to decode base64 data.
+ *
+ * For base64 encoding, every six bits have been encoded using only the ASCII
+ * characters from @ref g_base64_encode_table. This table has entries which
+ * allow the reversal of that process. It has 128 entries which map over to
+ * the index value from the encoding table. If an indexing result ends up
+ * with -1 during the decoding process, then that indicates an invalid base64
+ * character in the encoded data.
+ */
+static signed char
+g_base64_decode_table[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1,
+ 62, /* + */
+ -1, -1, -1,
+ 63, /* / */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
+ -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, /* A - J */
+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, /* K - T */
+ 20, 21, 22, 23, 24, 25, /* U - Z */
+ -1, -1, -1, -1, -1, -1,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, /* a - j */
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, /* k - t */
+ 46, 47, 48, 49, 50, 51, /* u - z */
+ -1, -1, -1, -1, -1
+};
+
+/**
+ * Decodes a base64 block of up to four bytes at a time.
+ *
+ * @param[in] buf Buffer containing bytes to decode.
+ * @param[out] decode Buffer for storing base64 decoded bytes.
+ * @retval >0 Length of the decoded block.
+ * @retval 0 If the block contains invalid base64 data.
+ */
+static size_t
+smtp_base64_decode_block(const unsigned char *const buf,
+ unsigned char *const decode){
+ size_t decode_block_len;
+ size_t i;
+ signed char decode_table[4];
+ unsigned char outb[3];
+
+ decode_block_len = 0;
+ for(i = 0; i < 4; i++){
+ if(buf[i] == '='){
+ decode_table[i] = 0;
+ continue;
+ }
+ decode_table[i] = g_base64_decode_table[buf[i]];
+ if(decode_table[i] < 0){
+ return 0;
+ }
+ }
+
+ outb[0] = ((decode_table[0] << 2) & 0xFC) | ((decode_table[1] >> 4) & 0x03);
+ outb[1] = ((decode_table[1] << 4) & 0xF0) | ((decode_table[2] >> 2) & 0x0F);
+ outb[2] = ((decode_table[2] << 6) & 0xC0) | ((decode_table[3] ) & 0x3F);
+
+ decode[0] = outb[0];
+ decode_block_len += 1;
+
+ if(buf[2] == '='){
+ decode[1] = '\0';
+ }
+ else{
+ decode[1] = outb[1];
+ decode_block_len += 1;
+ }
+
+ if(buf[3] == '='){
+ decode[2] = '\0';
+ }
+ else{
+ decode[2] = outb[2];
+ decode_block_len += 1;
+ }
+
+ return decode_block_len;
+}
+
+/**
+ * Decode a base64 string.
+ *
+ * The decode parameter will get dynamically allocated by this function
+ * if it successfully completes. Therefore, the caller must free the decode
+ * parameter after use.
+ *
+ * @param[in] buf Null-terminated base64 string.
+ * @param[out] decode Pointer to buffer which will get dynamically allocated
+ * and will contain the decoded binary data. This parameter
+ * will get set to NULL if the memory allocation fails.
+ * @retval >=0 Length of the data stored in the decode parameter.
+ * @retval -1 Memory allocation failure or invalid base64 byte sequences.
+ */
+SMTP_LINKAGE size_t
+smtp_base64_decode(const char *const buf,
+ unsigned char **decode){
+ size_t buf_len;
+ size_t buf_len_inc;
+ size_t buf_i;
+ unsigned char *b64_decode;
+ size_t decode_len;
+ size_t decode_block_len;
+
+ *decode = NULL;
+
+ buf_len = strlen(buf);
+ if(buf_len % 4 != 0){
+ return SIZE_MAX;
+ }
+
+ if(smtp_si_add_size_t(buf_len, 1, &buf_len_inc) ||
+ (b64_decode = calloc(1, buf_len_inc)) == NULL){
+ return SIZE_MAX;
+ }
+
+ decode_len = 0;
+ for(buf_i = 0; buf_i < buf_len; buf_i += 4){
+ decode_block_len = smtp_base64_decode_block(
+ (const unsigned char*)&buf[buf_i],
+ &b64_decode[decode_len]);
+ if(decode_block_len == 0){
+ free(b64_decode);
+ return SIZE_MAX;
+ }
+ decode_len += decode_block_len;
+ }
+ *decode = b64_decode;
+ return decode_len;
+}
+
+/**
+ * Convert binary data to lowercase hexadecimal representation.
+ *
+ * @param[in] s Buffer containing binary data to convert.
+ * @param[in] slen Number of bytes in @p s.
+ * @retval char* Dynamically allocated string consisting of a hexadecimal
+ * representation of binary data in @p s. The caller must free
+ * this memory when finished.
+ * @retval NULL Memory allocation or encoding error.
+ */
+SMTP_LINKAGE char *
+smtp_bin2hex(const unsigned char *const s,
+ size_t slen){
+ char *snew;
+ size_t alloc_sz;
+ size_t i;
+ size_t j;
+ unsigned hex;
+ int rc;
+
+ /* alloc_sz = slen * 2 + 1 */
+ if(smtp_si_mul_size_t(slen, 2, &alloc_sz) ||
+ smtp_si_add_size_t(alloc_sz, 1, &alloc_sz)){
+ return NULL;
+ }
+ if((snew = malloc(alloc_sz)) == NULL){
+ return NULL;
+ }
+
+ j = 0;
+ for(i = 0; i < slen; i++){
+ hex = s[i];
+ rc = sprintf(&snew[j], "%02x", hex);
+ if(rc < 0 || (size_t)rc >= 3){
+ free(snew);
+ return NULL;
+ }
+ j += 2;
+ }
+ snew[j] = '\0';
+
+ return snew;
+}
+#endif /* SMTP_OPENSSL */
+
+/**
+ * Get the length in bytes of a UTF-8 character.
+ *
+ * This consists of a very simple check and assumes the user provides a valid
+ * UTF-8 byte sequence. It gets the length from the first byte in the sequence
+ * and does not validate any other bytes in the character sequence or any other
+ * bits in the first byte of the character sequence.
+ *
+ * @param[in] c The first byte in a valid UTF-8 character sequence.
+ * @retval >0 Number of bytes for the current UTF-8 character sequence.
+ * @retval -1 Invalid byte sequence.
+ */
+SMTP_LINKAGE size_t
+smtp_utf8_charlen(char c){
+ unsigned char uc;
+
+ uc = (unsigned char)c;
+ if((uc & 0x80) == 0){ /* 0XXXXXXX */
+ return 1;
+ }
+ else if((uc & 0xE0) == 0xC0){ /* 110XXXXX */
+ return 2;
+ }
+ else if((uc & 0xF0) == 0xE0){ /* 1110XXXX */
+ return 3;
+ }
+ else if((uc & 0xF8) == 0xF0){ /* 11110XXX */
+ return 4;
+ }
+ else{ /* invalid */
+ return 0;
+ }
+}
+
+/**
+ * Check if a string contains non-ASCII UTF-8 characters.
+ *
+ * Uses the simple algorithm from @ref smtp_utf8_charlen to check for
+ * non-ASCII UTF-8 characters.
+ *
+ * @param[in] s UTF-8 string.
+ * @retval 1 String contains non-ASCII UTF-8 characters.
+ * @retval 0 String contains only ASCII characters.
+ */
+SMTP_LINKAGE int
+smtp_str_has_nonascii_utf8(const char *const s){
+ size_t i;
+ size_t charlen;
+
+ for(i = 0; s[i]; i++){
+ charlen = smtp_utf8_charlen(s[i]);
+ if(charlen != 1){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Get the number of bytes in a UTF-8 string, or a shorter count if
+ * the string exceeds a maximum specified length.
+ *
+ * See @p maxlen for more information on multi-byte parsing.
+ *
+ * @param[in] s Null-terminated UTF-8 string.
+ * @param[in] maxlen Do not check more than @p maxlen bytes of string @p s
+ * except if in the middle of a multi-byte character.
+ * @retval strlen(s) If length of s has less bytes than maxlen or the same
+ * number of bytes as maxlen. See @p maxlen for more details.
+ * @retval maxlen If length of s has more bytes than maxlen.
+ * @retval -1 If @p s contains an invalid UTF-8 byte sequence.
+ */
+SMTP_LINKAGE size_t
+smtp_strnlen_utf8(const char *s,
+ size_t maxlen){
+ size_t i;
+ size_t utf8_i;
+ size_t utf8_len;
+
+ for(i = 0; *s && i < maxlen; i += utf8_len){
+ utf8_len = smtp_utf8_charlen(*s);
+ if(utf8_len == 0){
+ return SIZE_MAX;
+ }
+
+ for(utf8_i = 0; utf8_i < utf8_len; utf8_i++){
+ if(!*s){
+ return SIZE_MAX;
+ }
+ s += 1;
+ }
+ }
+ return i;
+}
+
+/**
+ * Get the offset of the next whitespace block to process folding.
+ *
+ * If a string does not have whitespace before @p maxlen, then the index
+ * will get returned past @p maxlen. Also returns the index of NULL character
+ * if that fits within the next block. The caller must check for the NULL
+ * index to indicate the last block. It will skip past any leading whitespace
+ * even if that means going over maxlen.
+ *
+ * Examples:
+ * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 1/2/8/9/10/13) -> 8
+ * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 14/15) -> 13
+ * @ref smtp_fold_whitespace_get_offset ("Subject: Test WS", 17/18) -> 16
+ *
+ * @param[in] s String to get offset from.
+ * @param[in] maxlen Number of bytes for each line in the string (soft limit).
+ * @return Index in @p s.
+ */
+SMTP_LINKAGE size_t
+smtp_fold_whitespace_get_offset(const char *const s,
+ unsigned int maxlen){
+ size_t i;
+ size_t offset_i;
+
+ i = 0;
+ offset_i = 0;
+
+ while(s[i] == ' ' || s[i] == '\t'){
+ i += 1;
+ }
+
+ while(s[i]){
+ if(s[i] == ' ' || s[i] == '\t'){
+ do{
+ i += 1;
+ } while(s[i] == ' ' || s[i] == '\t');
+ i -= 1;
+ if(i < maxlen || !offset_i){
+ offset_i = i;
+ }
+ else{
+ break;
+ }
+ }
+ i += 1;
+ }
+
+ if(!offset_i || i < maxlen){
+ offset_i = i;
+ }
+
+ return offset_i;
+}
+
+/**
+ * Email header lines should have no more than 78 characters and must
+ * not be more than 998 characters.
+ */
+#define SMTP_LINE_MAX 78
+
+/**
+ * Fold a line at whitespace characters.
+ *
+ * This function tries to keep the total number of characters per line under
+ * @p maxlen, but does not guarantee this. For really long text with no
+ * whitespace, the line will still extend beyond @p maxlen and possibly
+ * beyond the RFC limit as defined in @ref SMTP_LINE_MAX. This is by design
+ * and intended to keep the algorithm simpler to implement. Users sending
+ * long headers with no space characters should not assume that will work,
+ * but modern email systems may correctly process those headers anyways.
+ *
+ * Lines get folded by adding a [CR][LF] and then two space characters on the
+ * beginning of the next line. For example, this Subject line:
+ *
+ * Subject: Email[WS][WS]Header
+ *
+ * Would get folded like this (assuming a small @p maxlen):
+ *
+ * Subject: Email[WS][CR][LF]
+ * [WS][WS]Header
+ *
+ * @param[in] s String to fold.
+ * @param[in] maxlen Number of bytes for each line in the string (soft limit).
+ * The minimum value of this parameter is 3 and it will get
+ * forced to 3 if the provided value is less.
+ * @retval char* Pointer to an allocated string with the contents split into
+ * separate lines. The caller must free this memory when done.
+ * @retval NULL Memory allocation failed.
+ */
+SMTP_LINKAGE char *
+smtp_fold_whitespace(const char *const s,
+ unsigned int maxlen){
+ const char *const SMTP_LINE_FOLD_STR = "\r\n ";
+ size_t end_slen;
+ size_t s_i;
+ size_t buf_i;
+ size_t bufsz;
+ size_t ws_offset;
+ char *buf;
+ char *buf_new;
+
+ if(maxlen < 3){
+ maxlen = 3;
+ }
+
+ end_slen = strlen(SMTP_LINE_FOLD_STR);
+
+ s_i = 0;
+ buf_i = 0;
+ bufsz = 0;
+ buf = NULL;
+
+ while(1){
+ ws_offset = smtp_fold_whitespace_get_offset(&s[s_i], maxlen - 2);
+
+ /* bufsz += ws_offset + end_slen + 1 */
+ if(smtp_si_add_size_t(bufsz, ws_offset, &bufsz) ||
+ smtp_si_add_size_t(bufsz, end_slen, &bufsz) ||
+ smtp_si_add_size_t(bufsz, 1, &bufsz) ||
+ (buf_new = realloc(buf, bufsz)) == NULL){
+ free(buf);
+ return NULL;
+ }
+ buf = buf_new;
+ memcpy(&buf[buf_i], &s[s_i], ws_offset);
+ buf[buf_i + ws_offset] = '\0';
+
+ if(s[s_i + ws_offset] == '\0'){
+ break;
+ }
+
+ buf_i += ws_offset;
+ strcat(&buf[buf_i], SMTP_LINE_FOLD_STR);
+ buf_i += end_slen;
+
+ /* WS */
+ s_i += ws_offset + 1;
+ }
+ return buf;
+}
+
+/**
+ * Splits a string into smaller chunks separated by a terminating string.
+ *
+ * @param[in] s The string to chunk.
+ * @param[in] chunklen Number of bytes for each chunk in the string.
+ * @param[in] end Terminating string placed at the end of each chunk.
+ * @retval char* Pointer to an allocated string with the contents split into
+ * separate chunks. The caller must free this memory when done.
+ * @retval NULL Memory allocation failure.
+ */
+SMTP_LINKAGE char *
+smtp_chunk_split(const char *const s,
+ size_t chunklen,
+ const char *const end){
+ char *snew;
+ size_t bodylen;
+ size_t bodylen_inc;
+ size_t endlen;
+ size_t endlen_inc;
+ size_t snewlen;
+ size_t chunk_i;
+ size_t snew_i;
+ size_t body_i;
+ size_t body_copy_len;
+
+ if(chunklen < 1){
+ errno = EINVAL;
+ return NULL;
+ }
+
+ bodylen = strlen(s);
+ endlen = strlen(end);
+
+ if(bodylen < 1){
+ return smtp_strdup(end);
+ }
+
+ /*
+ * \0
+ * snewlen = bodylen + (endlen + 1) * (bodylen / chunklen + 1) + 1
+ */
+ if(smtp_si_add_size_t(endlen, 1, &endlen_inc) ||
+ smtp_si_add_size_t(bodylen, 1, &bodylen_inc) ||
+ smtp_si_mul_size_t(endlen_inc, bodylen / chunklen + 1, &snewlen) ||
+ smtp_si_add_size_t(snewlen, bodylen_inc, &snewlen) ||
+ (snew = calloc(1, snewlen)) == NULL){
+ return NULL;
+ }
+
+ body_i = 0;
+ snew_i = 0;
+ for(chunk_i = 0; chunk_i < bodylen / chunklen + 1; chunk_i++){
+ body_copy_len = smtp_strnlen_utf8(&s[body_i], chunklen);
+ if(body_copy_len == SIZE_MAX){
+ free(snew);
+ return NULL;
+ }
+ memcpy(&snew[snew_i], &s[body_i], body_copy_len);
+ snew_i += body_copy_len;
+ if(s[body_i] == '\0'){
+ snew_i += 1;
+ }
+ body_i += body_copy_len;
+
+ if(endlen > 0){
+ memcpy(&snew[snew_i], end, endlen);
+ }
+ snew_i += endlen;
+ }
+
+ return snew;
+}
+
+/**
+ * Read the entire contents of a file stream and store the data into a
+ * dynamically allocated buffer.
+ *
+ * @param[in] stream File stream already opened by the caller.
+ * @param[out] bytes_read Number of bytes stored in the return buffer.
+ * @retval char* A dynamically allocated buffer which contains the entire
+ * contents of @p stream. The caller must free this memory
+ * when done.
+ * @retval NULL Memory allocation or file read error.
+ */
+SMTP_LINKAGE char *
+smtp_ffile_get_contents(FILE *stream,
+ size_t *bytes_read){
+ char *read_buf;
+ size_t bufsz;
+ size_t bufsz_inc;
+ char *new_buf;
+ size_t bytes_read_loop;
+ const size_t BUFSZ_INCREMENT = 512;
+
+ read_buf = NULL;
+ bufsz = 0;
+
+ if(bytes_read){
+ *bytes_read = 0;
+ }
+
+ do{
+ if(smtp_si_add_size_t(bufsz, BUFSZ_INCREMENT, &bufsz_inc) ||
+ (new_buf = realloc(read_buf, bufsz_inc)) == NULL){
+ free(read_buf);
+ return NULL;
+ }
+ read_buf = new_buf;
+ bufsz = bufsz_inc;
+
+ bytes_read_loop = fread(&read_buf[bufsz - BUFSZ_INCREMENT],
+ sizeof(char),
+ BUFSZ_INCREMENT,
+ stream);
+ if(bytes_read){
+ *bytes_read += bytes_read_loop;
+ }
+ if(ferror(stream)){
+ free(read_buf);
+ return NULL;
+ }
+ } while(!feof(stream));
+
+ return read_buf;
+}
+
+/**
+ * Read the entire contents of a file from a given path, and store the data
+ * into a dynamically allocated buffer.
+ *
+ * @param[in] filename Path of file to open and read from.
+ * @param[out] bytes_read Number of bytes stored in the return buffer.
+ * @retval char* A dynamically allocated buffer which has the contents of
+ * the file at @p filename. The caller must free this memory when
+ * done.
+ * @retval NULL Memory allocation or file read error.
+ */
+SMTP_LINKAGE char *
+smtp_file_get_contents(const char *const filename,
+ size_t *bytes_read){
+ FILE *fp;
+ char *read_buf;
+
+ if((fp = fopen(filename, "rb")) == NULL){
+ return NULL;
+ }
+
+ read_buf = smtp_ffile_get_contents(fp, bytes_read);
+
+ if(fclose(fp) == EOF){
+ free(read_buf);
+ read_buf = NULL;
+ }
+
+ return read_buf;
+}
+
+/**
+ * Parse a server response line into the @ref smtp_command data structure.
+ *
+ * @param[in] line Server response string.
+ * @param[out] cmd Structure containing the server response data broken up
+ * into its separate components.
+ * @return See @ref smtp_result_code.
+ */
+SMTP_LINKAGE int
+smtp_parse_cmd_line(char *const line,
+ struct smtp_command *const cmd){
+ char *ep;
+ char code_str[4];
+ size_t line_len;
+ unsigned long int ulcode;
+
+ line_len = strlen(line);
+ if(line_len < 5){
+ cmd->code = SMTP_INTERNAL_ERROR;
+ cmd->more = 0;
+ cmd->text = line;
+ return cmd->code;
+ }
+
+ cmd->text = &line[4];
+
+ memcpy(code_str, line, 3);
+ code_str[3] = '\0';
+ ulcode = strtoul(code_str, &ep, 10);
+ if(*ep != '\0' || ulcode > SMTP_BEGIN_MAIL){
+ cmd->code = SMTP_INTERNAL_ERROR;
+ }
+ else{
+ cmd->code = (enum smtp_result_code)ulcode;
+ }
+
+ if(line[3] == '-'){
+ cmd->more = 1;
+ }
+ else{
+ cmd->more = 0;
+ }
+ return cmd->code;
+}
+
+/**
+ * Prints communication between the client and server to stderr only if
+ * the debug flag has been set.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] prefix Print this prefix before the main debug line text.
+ * @param[in] str Debug text to print out.
+ */
+static void
+smtp_puts_dbg(struct smtp *const smtp,
+ const char *const prefix,
+ const char *const str){
+ char *sdup;
+ size_t i;
+
+ if(smtp->flags & SMTP_DEBUG){
+ if((sdup = smtp_strdup(str)) == NULL){
+ return;
+ }
+
+ /* Remove carriage return and newline when printing to stderr. */
+ for(i = 0; sdup[i]; i++){
+ if(sdup[i] == '\r' || sdup[i] == '\n'){
+ sdup[i] = ' ';
+ }
+ }
+
+ if(fprintf(stderr, "[smtp %s]: %s\n", prefix, sdup) < 0){
+ /* Do not care if this fails. */
+ }
+ free(sdup);
+ }
+}
+
+/**
+ * Read a server response line.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref str_getdelim_retcode.
+ */
+static enum str_getdelim_retcode
+smtp_getline(struct smtp *const smtp){
+ enum str_getdelim_retcode rc;
+
+ errno = 0;
+ rc = smtp_str_getdelimfd(&smtp->gdfd);
+ if(errno == ENOMEM){
+ smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ return rc;
+ }
+ else if(rc == STRING_GETDELIMFD_ERROR){
+ smtp_status_code_set(smtp, SMTP_STATUS_RECV);
+ return STRING_GETDELIMFD_ERROR;
+ }
+
+ if(smtp->gdfd.line_len > 0){
+ /* Remove the carriage-return character ('\r'). */
+ smtp->gdfd.line[smtp->gdfd.line_len - 1] = '\0';
+ smtp_puts_dbg(smtp, "Server", smtp->gdfd.line);
+ }
+ return rc;
+}
+
+/**
+ * Loop through all of the server response lines until the last line, and
+ * then return the status code from the last response line.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_result_code.
+ */
+static int
+smtp_read_and_parse_code(struct smtp *const smtp){
+ struct smtp_command cmd;
+ enum str_getdelim_retcode rc;
+
+ do{
+ rc = smtp_getline(smtp);
+ if(rc == STRING_GETDELIMFD_ERROR){
+ return SMTP_INTERNAL_ERROR;
+ }
+
+ smtp_parse_cmd_line(smtp->gdfd.line, &cmd);
+ }while (rc != STRING_GETDELIMFD_DONE && cmd.more);
+
+ return cmd.code;
+}
+
+/**
+ * Send data to the SMTP server.
+ *
+ * Writes a buffer of length len into either the unencrypted TCP socket or
+ * the TLS encrypted socket, depending on the current underlying mode of
+ * the socket.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] buf Data to send to the SMTP server.
+ * @param[in] len Number of bytes in buf.
+ * @return See @ref smtp_status_code.
+ */
+SMTP_LINKAGE enum smtp_status_code
+smtp_write(struct smtp *const smtp,
+ const char *const buf,
+ size_t len){
+ size_t bytes_to_send;
+ long bytes_sent;
+ const char *buf_offset;
+ int ssl_bytes_to_send;
+
+ smtp_puts_dbg(smtp, "Client", buf);
+
+ bytes_to_send = len;
+ buf_offset = buf;
+ while(bytes_to_send){
+ if(bytes_to_send > INT_MAX){
+ return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
+ }
+
+ if(smtp->tls_on){
+#ifdef SMTP_OPENSSL
+ /* bytes_to_send <= INT_MAX */
+ ssl_bytes_to_send = (int)bytes_to_send;
+ bytes_sent = SSL_write(smtp->tls, buf_offset, ssl_bytes_to_send);
+ if(bytes_sent <= 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
+ }
+#else /* !(SMTP_OPENSSL) */
+ /* unreachable */
+ bytes_sent = 0;
+ (void)ssl_bytes_to_send;
+#endif /* SMTP_OPENSSL */
+ }
+ else{
+ bytes_sent = send(smtp->sock, buf_offset, bytes_to_send, 0);
+ if(bytes_sent < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_SEND);
+ }
+ }
+ bytes_to_send -= (size_t)bytes_sent;
+ buf_offset += bytes_sent;
+ }
+
+ return smtp->status_code;
+}
+
+/**
+ * Send a null-terminated string to the SMTP server.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] s Null-terminated string to send to the SMTP server.
+ * @return See @ref smtp_status_code and @ref smtp_write.
+ */
+static enum smtp_status_code
+smtp_puts(struct smtp *const smtp,
+ const char *const s){
+ return smtp_write(smtp, s, strlen(s));
+}
+
+/**
+ * Same as @ref smtp_puts except this function also appends the line
+ * terminating carriage return and newline bytes at the end of the string.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] s Null-terminated string to send to the SMTP server.
+ * @return See @ref smtp_status_code and @ref smtp_puts.
+ */
+static enum smtp_status_code
+smtp_puts_terminate(struct smtp *const smtp,
+ const char *const s){
+ enum smtp_status_code rc;
+ char *line;
+ char *concat;
+ size_t slen;
+ size_t allocsz;
+
+ slen = strlen(s);
+ if(smtp_si_add_size_t(slen, 3, &allocsz) ||
+ (line = malloc(allocsz)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ concat = smtp_stpcpy(line, s);
+ smtp_stpcpy(concat, "\r\n");
+ rc = smtp_puts(smtp, line);
+ free(line);
+ return rc;
+}
+
+/**
+ * Connect to the server using a standard TCP socket.
+ *
+ * This function handles the server name lookup to get an IP address
+ * for the server, and then to connect to that IP using a normal TCP
+ * connection.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] server Mail server name or IP address.
+ * @param[in] port Mail server port number.
+ * @retval 0 Successfully connected to server.
+ * @retval -1 Failed to connect to server.
+ */
+static int
+smtp_connect(struct smtp *const smtp,
+ const char *const server,
+ const char *const port){
+ struct addrinfo hints;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+
+ /*
+ * Windows requires initializing the socket library before we call any
+ * socket functions.
+ */
+#ifdef SMTP_IS_WINDOWS
+ /* Windows global network socket data structure. */
+ WSADATA wsa_data;
+ if(WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0){
+ return -1;
+ }
+#endif /* SMTP_IS_WINDOWS */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if(getaddrinfo(server, port, &hints, &res0) != 0){
+ return -1;
+ }
+
+ for(res = res0; res; res = res->ai_next){
+ smtp->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if(smtp->sock < 0){
+ continue;
+ }
+
+ if(connect(smtp->sock, res->ai_addr, res->ai_addrlen) < 0){
+#ifdef SMTP_IS_WINDOWS
+ closesocket(smtp->sock);
+#else /* POSIX */
+ close(smtp->sock);
+#endif /* SMTP_IS_WINDOWS */
+ smtp->sock = -1;
+ }
+ else{
+ break;
+ }
+ }
+
+ freeaddrinfo(res0);
+ if(smtp->sock < 0){
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef SMTP_OPENSSL
+/**
+ * Initialize the TLS library and establish a TLS handshake with the server
+ * over the existing socket connection.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] server Server name or IP address.
+ * @retval 0 Successfully established a TLS connection with the server.
+ * @retval -1 Failed to establish a TLS connection with the server.
+ */
+static int
+smtp_tls_init(struct smtp *const smtp,
+ const char *const server){
+ X509 *X509_cert_peer;
+
+ /* Do not need to check the return value since this always returns 1. */
+ SSL_library_init();
+
+ SSL_load_error_strings();
+ /*ERR_load_BIO_strings();*/
+ OpenSSL_add_all_algorithms();
+
+ if((smtp->tls_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL){
+ return -1;
+ }
+
+ /* Disable SSLv2, SSLv3, and TLSv1.0. */
+ SSL_CTX_set_options(smtp->tls_ctx,
+ SSL_OP_NO_SSLv2 |
+ SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1);
+
+ SSL_CTX_set_mode(smtp->tls_ctx, SSL_MODE_AUTO_RETRY);
+ if((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
+ SSL_CTX_set_verify(smtp->tls_ctx, SSL_VERIFY_PEER, NULL);
+ }
+
+ /*
+ * Set the path to the user-provided CA file or use the default cert paths
+ * if not provided.
+ */
+ if(smtp->cafile){
+ if(SSL_CTX_load_verify_locations(smtp->tls_ctx, smtp->cafile, NULL) != 1){
+ SSL_CTX_free(smtp->tls_ctx);
+ return -1;
+ }
+ }
+ else{
+ X509_STORE_set_default_paths(SSL_CTX_get_cert_store(smtp->tls_ctx));
+ if(ERR_peek_error() != 0){
+ SSL_CTX_free(smtp->tls_ctx);
+ return -1;
+ }
+ }
+
+ if((smtp->tls = SSL_new(smtp->tls_ctx)) == NULL){
+ SSL_CTX_free(smtp->tls_ctx);
+ return -1;
+ }
+
+ if((smtp->tls_bio = BIO_new_socket(smtp->sock, 0)) == NULL){
+ SSL_CTX_free(smtp->tls_ctx);
+ SSL_free(smtp->tls);
+ return -1;
+ }
+
+ SSL_set_bio(smtp->tls, smtp->tls_bio, smtp->tls_bio);
+ SSL_set_connect_state(smtp->tls);
+ if(SSL_connect(smtp->tls) != 1){
+ SSL_CTX_free(smtp->tls_ctx);
+ SSL_free(smtp->tls);
+ return -1;
+ }
+
+ if(SSL_do_handshake(smtp->tls) != 1){
+ SSL_CTX_free(smtp->tls_ctx);
+ SSL_free(smtp->tls);
+ return -1;
+ }
+
+ /* Verify matching subject in certificate. */
+ if((smtp->flags & SMTP_NO_CERT_VERIFY) == 0){
+ if((X509_cert_peer = SSL_get_peer_certificate(smtp->tls)) == NULL){
+ SSL_CTX_free(smtp->tls_ctx);
+ SSL_free(smtp->tls);
+ return -1;
+ }
+ if(X509_check_host(X509_cert_peer, server, 0, 0, NULL) != 1){
+ SSL_CTX_free(smtp->tls_ctx);
+ SSL_free(smtp->tls);
+ return -1;
+ }
+ X509_free(X509_cert_peer);
+ }
+
+ smtp->tls_on = 1;
+ return 0;
+}
+#endif /* SMTP_OPENSSL */
+
+/**
+ * Send the EHLO command and parse through the responses.
+ *
+ * Ignores all of the server extensions that get returned. If a server
+ * doesn't support an extension we need, then we should receive an error
+ * later on when we try to use that extension.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_ehlo(struct smtp *const smtp){
+ if(smtp_puts(smtp, "EHLO smtp\r\n") == SMTP_STATUS_OK){
+ smtp_read_and_parse_code(smtp);
+ }
+ return smtp->status_code;
+}
+
+/**
+ * Authenticate using the PLAIN method.
+ *
+ * 1. Set the text to the following format: "\0\0",
+ * or as shown in the format string: "\0%s\0%s", email, password.
+ * 2. Base64 encode the text from (1).
+ * 3. Send the constructed auth text from (2) to the server:
+ * "AUTH PLAIN ".
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] user SMTP account user name.
+ * @param[in] pass SMTP account password.
+ * @retval 0 Successfully authenticated.
+ * @retval -1 Failed to authenticate.
+ */
+static int
+smtp_auth_plain(struct smtp *const smtp,
+ const char *const user,
+ const char *const pass){
+ size_t user_len;
+ size_t pass_len;
+ char *login_str;
+ size_t login_len;
+ char *login_b64;
+ size_t login_b64_len;
+ char *login_send;
+ char *concat;
+
+ /* (1) */
+ user_len = strlen(user);
+ pass_len = strlen(pass);
+ /* login_len = 1 + user_len + 1 + pass_len */
+ if(smtp_si_add_size_t(user_len, pass_len, &login_len) ||
+ smtp_si_add_size_t(login_len, 2, &login_len) ||
+ (login_str = malloc(login_len)) == NULL){
+ return -1;
+ }
+ login_str[0] = '\0';
+ memcpy(&login_str[1], user, user_len);
+ login_str[1 + user_len] = '\0';
+ memcpy(&login_str[1 + user_len + 1], pass, pass_len);
+
+ /* (2) */
+ login_b64 = smtp_base64_encode(login_str, login_len);
+ free(login_str);
+ if(login_b64 == NULL){
+ return -1;
+ }
+
+ /* (3) */
+ login_b64_len = strlen(login_b64);
+ if(smtp_si_add_size_t(login_b64_len, 14, &login_b64_len) ||
+ (login_send = malloc(login_b64_len)) == NULL){
+ free(login_b64);
+ return -1;
+ }
+ concat = smtp_stpcpy(login_send, "AUTH PLAIN ");
+ concat = smtp_stpcpy(concat, login_b64);
+ smtp_stpcpy(concat, "\r\n");
+
+ free(login_b64);
+ smtp_puts(smtp, login_send);
+ free(login_send);
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return -1;
+ }
+
+ if(smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS){
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * Authenticate using the LOGIN method.
+ *
+ * 1. Base64 encode the user name.
+ * 2. Send string from (1) as part of the login:
+ * "AUTH LOGIN ".
+ * 3. Base64 encode the password and send that by itself on a separate
+ * line: "".
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] user SMTP account user name.
+ * @param[in] pass SMTP account password.
+ * @retval 0 Successfully authenticated.
+ * @retval -1 Failed to authenticate.
+ */
+static int
+smtp_auth_login(struct smtp *const smtp,
+ const char *const user,
+ const char *const pass){
+ char *b64_user;
+ char *b64_pass;
+ size_t b64_user_len;
+ char *login_str;
+ char *concat;
+
+ /* (1) */
+ if((b64_user = smtp_base64_encode(user, SIZE_MAX)) == NULL){
+ return -1;
+ }
+
+ /* (2) */
+ b64_user_len = strlen(b64_user);
+ if(smtp_si_add_size_t(b64_user_len, 14, &b64_user_len) ||
+ (login_str = malloc(b64_user_len)) == NULL){
+ free(b64_user);
+ return -1;
+ }
+ concat = smtp_stpcpy(login_str, "AUTH LOGIN ");
+ concat = smtp_stpcpy(concat, b64_user);
+ smtp_stpcpy(concat, "\r\n");
+
+ free(b64_user);
+ smtp_puts(smtp, login_str);
+ free(login_str);
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return -1;
+ }
+
+ if(smtp_read_and_parse_code(smtp) != SMTP_AUTH_CONTINUE){
+ return -1;
+ }
+
+ /* (3) */
+ if((b64_pass = smtp_base64_encode(pass, SIZE_MAX)) == NULL){
+ return -1;
+ }
+ smtp_puts_terminate(smtp, b64_pass);
+ free(b64_pass);
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return -1;
+ }
+
+ if(smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS){
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef SMTP_OPENSSL
+/**
+ * Authenticate using the CRAM-MD5 method.
+ *
+ * 1. Send "AUTH CRAM-MD5" to the server.
+ * 2. Decode the base64 challenge response from the server.
+ * 3. Do an MD5 HMAC on (2) using the account password as the key.
+ * 4. Convert the binary data in (3) to lowercase hex characters.
+ * 5. Construct the string: " <(4)>".
+ * 6. Encode (5) into base64 format.
+ * 7. Send the final string from (6) to the server and check the response.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] user SMTP account user name.
+ * @param[in] pass SMTP account password.
+ * @retval 0 Successfully authenticated.
+ * @retval -1 Failed to authenticate.
+ */
+static int
+smtp_auth_cram_md5(struct smtp *const smtp,
+ const char *const user,
+ const char *const pass){
+ struct smtp_command cmd;
+ unsigned char *challenge_decoded;
+ size_t challenge_decoded_len;
+ const char *key;
+ int key_len;
+ unsigned char hmac[EVP_MAX_MD_SIZE];
+ unsigned int hmac_len;
+ unsigned char *hmac_ret;
+ char *challenge_hex;
+ size_t user_len;
+ size_t challenge_hex_len;
+ char *auth_concat;
+ char *concat;
+ size_t auth_concat_len;
+ char *b64_auth;
+
+ /* (1) */
+ if(smtp_puts(smtp, "AUTH CRAM-MD5\r\n") != SMTP_STATUS_OK){
+ return -1;
+ }
+ if(smtp_getline(smtp) == STRING_GETDELIMFD_ERROR){
+ return -1;
+ }
+ if(smtp_parse_cmd_line(smtp->gdfd.line, &cmd) != SMTP_AUTH_CONTINUE){
+ return -1;
+ }
+
+ /* (2) */
+ challenge_decoded_len = smtp_base64_decode(cmd.text,
+ &challenge_decoded);
+ if(challenge_decoded_len == SIZE_MAX){
+ return -1;
+ }
+
+ /* (3) */
+ key = pass;
+ key_len = (int)strlen(pass);/*********************cast*/
+ hmac_ret = HMAC(EVP_md5(),
+ key,
+ key_len,
+ challenge_decoded,
+ challenge_decoded_len,
+ hmac,
+ &hmac_len);
+ free(challenge_decoded);
+ if(hmac_ret == NULL){
+ return -1;
+ }
+
+ /* (4) */
+ challenge_hex = smtp_bin2hex(hmac, hmac_len);
+ if(challenge_hex == NULL){
+ return -1;
+ }
+
+ /* (5) */
+ user_len = strlen(user);
+ challenge_hex_len = strlen(challenge_hex);
+ /* auth_concat_len = user_len + 1 + challenge_hex_len + 1 */
+ if(smtp_si_add_size_t(user_len, challenge_hex_len, &auth_concat_len) ||
+ smtp_si_add_size_t(auth_concat_len, 2, &auth_concat_len) ||
+ (auth_concat = malloc(auth_concat_len)) == NULL){
+ free(challenge_hex);
+ return -1;
+ }
+ concat = smtp_stpcpy(auth_concat, user);
+ concat = smtp_stpcpy(concat, " ");
+ smtp_stpcpy(concat, challenge_hex);
+ free(challenge_hex);
+
+ /* (6) */
+ b64_auth = smtp_base64_encode(auth_concat, auth_concat_len - 1);
+ free(auth_concat);
+ if(b64_auth == NULL){
+ return -1;
+ }
+
+ /* (7) */
+ smtp_puts_terminate(smtp, b64_auth);
+ free(b64_auth);
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return -1;
+ }
+
+ if(smtp_read_and_parse_code(smtp) != SMTP_AUTH_SUCCESS){
+ return -1;
+ }
+ return 0;
+}
+#endif /* SMTP_OPENSSL */
+
+/**
+ * Set the timeout for the next socket read operation.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] seconds Timeout in seconds.
+ */
+static void
+smtp_set_read_timeout(struct smtp *const smtp,
+ long seconds){
+ smtp->timeout_sec = seconds;
+}
+
+/**
+ * Perform a handshake with the SMTP server which includes optionally
+ * setting up TLS and sending the EHLO greeting.
+ *
+ * At this point, the client has already connected to the SMTP server
+ * through its socket connection. In this function, the client will:
+ * 1. Optionally convert the connection to TLS (SMTP_SECURITY_TLS).
+ * 2. Read the initial server greeting.
+ * 3. Send an EHLO to the server.
+ * 4. Optionally initiate STARTTLS and resend the EHLO
+ * (SMTP_SECURITY_STARTTLS).
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] server Server name or IP address.
+ * @param[in] connection_security See @ref smtp_connection_security.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_initiate_handshake(struct smtp *const smtp,
+ const char *const server,
+ enum smtp_connection_security connection_security){
+ /* Eliminate unused warnings if not using SMTP_OPENSSL. */
+ (void)server;
+ (void)connection_security;
+
+ /* (1) */
+#ifdef SMTP_OPENSSL
+ if(connection_security == SMTP_SECURITY_TLS){
+ if(smtp_tls_init(smtp, server) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
+ }
+ }
+#endif /* SMTP_OPENSSL */
+
+ /* (2) */
+ /* Get initial 220 message - 5 minute timeout. */
+ smtp_set_read_timeout(smtp, 60 * 5);
+ if(smtp_getline(smtp) == STRING_GETDELIMFD_ERROR){
+ return smtp->status_code;
+ }
+
+ /* (3) */
+ if(smtp_ehlo(smtp) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ /* (4) */
+#ifdef SMTP_OPENSSL
+ if(connection_security == SMTP_SECURITY_STARTTLS){
+ if(smtp_puts(smtp, "STARTTLS\r\n") != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ if(smtp_read_and_parse_code(smtp) != SMTP_READY){
+ return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
+ }
+ if(smtp_tls_init(smtp, server) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_HANDSHAKE);
+ }
+ if(smtp_ehlo(smtp) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ }
+#endif /* SMTP_OPENSSL */
+
+ return smtp->status_code;
+}
+
+/**
+ Maximum size of an RFC 2822 date string.
+
+ @verbatim
+ Thu, 21 May 1998 05:33:29 -0700
+ 12345678901234567890123456789012
+ 10 20 30 32 (bytes)
+ @endverbatim
+
+ Add more bytes to the 32 maximum size to silence compiler warning on the
+ computed UTF offset.
+ */
+#define SMTP_DATE_MAX_SZ (32 + 15)
+
+/**
+ * Convert the time into an RFC 2822 formatted string.
+ *
+ * Example date format:
+ * Thu, 21 May 1998 05:33:29 -0700
+ *
+ * @param[out] date Buffer that has at least SMTP_DATE_MAX_SZ bytes.
+ * @retval 0 Successfully copied the current date to the buffer.
+ * @retval -1 Failed to establish the current date or an output
+ * format error occurred.
+ */
+SMTP_LINKAGE int
+smtp_date_rfc_2822(char *const date){
+ time_t t;
+ time_t t_local;
+ time_t t_utc;
+ struct tm tm_local;
+ struct tm tm_utc;
+ long offset_utc;
+ double diff_local_utc;
+ int rc;
+
+ const char weekday_abbreviation[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+
+ const char month_abbreviation[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ if((t = time(NULL)) == (time_t)(-1)){
+ return -1;
+ }
+
+#ifdef SMTP_IS_WINDOWS
+ if(localtime_s(&tm_local, &t) ||
+ gmtime_s(&tm_utc, &t)){
+ return -1;
+ }
+#else /* POSIX */
+ /* Not defined if system does not have localtime_r or gmtime_r. */
+# ifdef SMTP_TIME_NO_REENTRANT
+ struct tm *tm;
+
+ /* localtime() not thread-safe. */
+ if((tm = localtime(&t)) == NULL){
+ return -1;
+ }
+ memcpy(&tm_local, tm, sizeof(tm_local));
+
+ /* gmtime() not thread-safe. */
+ if((tm = gmtime(&t)) == NULL){
+ return -1;
+ }
+ memcpy(&tm_utc, tm, sizeof(tm_utc));
+# else /* Reentrant versions: localtime_r() and gmtime_r(). */
+ if(localtime_r(&t, &tm_local) == NULL ||
+ gmtime_r(&t, &tm_utc) == NULL){
+ return -1;
+ }
+# endif /* SMTP_TIME_NO_REENTRANT */
+#endif /* SMTP_IS_WINDOWS */
+
+ if((t_local = mktime(&tm_local)) == (time_t)(-1)){
+ return -1;
+ }
+
+ if((t_utc = mktime(&tm_utc)) == (time_t)(-1)){
+ return -1;
+ }
+
+ /*
+ * After computing the offset, it will contain a maximum of 4 digits.
+ * For example, PST time zone will have an offset of -800 which will get
+ * formatted as -0800 in the sprintf call below.
+ */
+ diff_local_utc = difftime(t_local, t_utc);
+ offset_utc = (long)diff_local_utc;
+ offset_utc = offset_utc / 60 / 60 * 100;
+
+ rc = sprintf(date,
+ "%s, %02d %s %d %02d:%02d:%02d %0+5ld",
+ weekday_abbreviation[tm_local.tm_wday],
+ tm_local.tm_mday,
+ month_abbreviation[tm_local.tm_mon],
+ tm_local.tm_year + 1900,
+ tm_local.tm_hour,
+ tm_local.tm_min,
+ tm_local.tm_sec, /* 0 - 60 (leap second) */
+ offset_utc);
+
+ if(rc + 1 != SMTP_DATE_MAX_SZ - 15){ /* See @ref SMTP_DATE_MAX_SZ for -5. */
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Search function used by bsearch, allowing the caller to check for
+ * headers with existing keys.
+ *
+ * @param v1 String to search for in the list.
+ * @param v2 The @ref smtp_header to compare.
+ * @retval 0 If the keys match.
+ * @retval !0 If the keys do not match.
+ */
+static int
+smtp_header_cmp_key(const void *const v1,
+ const void *const v2){
+ const char *key;
+ const struct smtp_header *header2;
+
+ key = v1;
+ header2 = v2;
+ return strcmp(key, header2->key);
+}
+
+/**
+ * Determine if the header key has already been defined in this context.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] key Header key value to search for.
+ * @retval 1 If the header already exists in this context.
+ * @retval 0 If the header does not exist in this context.
+ */
+static int
+smtp_header_exists(const struct smtp *const smtp,
+ const char *const key){
+ if(bsearch(key,
+ smtp->header_list,
+ smtp->num_headers,
+ sizeof(*smtp->header_list),
+ smtp_header_cmp_key) == NULL){
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * Minimum length of buffer required to hold the MIME boundary test:
+ * mimeXXXXXXXXXX
+ * 123456789012345
+ * 1 10 15 bytes
+ */
+#define SMTP_MIME_BOUNDARY_LEN 15
+
+/**
+ * Generate the MIME boundary text field and store it in a user-supplied
+ * buffer.
+ *
+ * For example:
+ * mimeXXXXXXXXXX
+ * where each X gets set to a pseudo-random uppercase ASCII character.
+ *
+ * This uses a simple pseudo-random number generator since we only care
+ * about preventing accidental boundary collisions.
+ *
+ * @param[out] boundary Buffer that has at least SMTP_MIME_BOUNDARY_LEN bytes.
+ */
+static void
+smtp_gen_mime_boundary(char *const boundary){
+ size_t i;
+ unsigned int seed;
+
+ seed = (unsigned int)time(NULL);
+ srand(seed);
+
+ strcpy(boundary, "mime");
+ for(i = 4; i < SMTP_MIME_BOUNDARY_LEN - 1; i++){
+ /* Modulo bias okay since we only need to prevent accidental collision. */
+ boundary[i] = rand() % 26 + 'A';
+ }
+ boundary[SMTP_MIME_BOUNDARY_LEN - 1] = '\0';
+}
+
+/**
+ * Print the MIME header and the MIME section containing the email body.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] boundary MIME boundary text.
+ * @param[in] body_dd Email body with double dots added at the
+ * beginning of each line.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_mime_header_and_body(struct smtp *const smtp,
+ const char *const boundary,
+ const char *const body_dd){
+ /* Buffer size for the static MIME text used below. */
+ const size_t MIME_TEXT_BUFSZ = 1000;
+ size_t data_double_dot_len;
+ char *data_header_and_body;
+ char *concat;
+
+ data_double_dot_len = strlen(body_dd);
+ if(smtp_si_add_size_t(data_double_dot_len,
+ MIME_TEXT_BUFSZ,
+ &data_double_dot_len) ||
+ (data_header_and_body = malloc(data_double_dot_len)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ concat = smtp_stpcpy(data_header_and_body,
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: multipart/mixed; boundary=");
+ concat = smtp_stpcpy(concat,
+ boundary);
+ concat = smtp_stpcpy(concat,
+ "\r\n"
+ "\r\n"
+ "Multipart MIME message.\r\n"
+ "--");
+ concat = smtp_stpcpy(concat,
+ boundary);
+ concat = smtp_stpcpy(concat,
+ "\r\n"
+ "Content-Type: text/plain; charset=\"UTF-8\"\r\n"
+ "\r\n");
+ concat = smtp_stpcpy(concat,
+ body_dd);
+ smtp_stpcpy(concat,
+ "\r\n"
+ "\r\n");
+
+ smtp_puts(smtp, data_header_and_body);
+ free(data_header_and_body);
+ return smtp->status_code;
+}
+
+/**
+ * Print a MIME section containing an attachment.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] boundary MIME boundary text.
+ * @param[in] attachment See @ref smtp_attachment.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_mime_attachment(struct smtp *const smtp,
+ const char *const boundary,
+ const struct smtp_attachment *const attachment){
+ /* Buffer size for the static MIME text used below. */
+ const size_t MIME_TEXT_BUFSZ = 1000;
+ size_t name_len;
+ size_t b64_data_len;
+ size_t bufsz;
+ char *mime_attach_text;
+ char *concat;
+
+ name_len = strlen(attachment->name);
+ b64_data_len = strlen(attachment->b64_data);
+ /*
+ * bufsz = SMTP_MIME_BOUNDARY_LEN + name_len + b64_data_len + MIME_TEXT_BUFSZ
+ */
+ if(smtp_si_add_size_t(name_len, b64_data_len, &bufsz) ||
+ smtp_si_add_size_t(bufsz,
+ SMTP_MIME_BOUNDARY_LEN + MIME_TEXT_BUFSZ,
+ &bufsz) ||
+ (mime_attach_text = malloc(bufsz)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ concat = smtp_stpcpy(mime_attach_text,
+ "--");
+ concat = smtp_stpcpy(concat,
+ boundary);
+ concat = smtp_stpcpy(concat,
+ "\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Disposition: attachment; filename=\"");
+ concat = smtp_stpcpy(concat,
+ attachment->name);
+ concat = smtp_stpcpy(concat,
+ "\"\r\n"
+ "Content-Transfer-Encoding: base64\r\n"
+ "\r\n");
+ concat = smtp_stpcpy(concat,
+ attachment->b64_data);
+ smtp_stpcpy(concat,
+ "\r\n"
+ "\r\n");
+ smtp_puts(smtp, mime_attach_text);
+ free(mime_attach_text);
+ return smtp->status_code;
+}
+
+/**
+ * Prints double hyphen on both sides of the MIME boundary which indicates
+ * the end of the MIME sections.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] boundary MIME boundary text.
+ * @return See @ref smtp_status_code and @ref smtp_puts.
+ */
+static enum smtp_status_code
+smtp_print_mime_end(struct smtp *const smtp,
+ const char *const boundary){
+ char *concat;
+ char mime_end[2 + SMTP_MIME_BOUNDARY_LEN + 4 + 1];
+
+ concat = smtp_stpcpy(mime_end, "--");
+ concat = smtp_stpcpy(concat, boundary);
+ smtp_stpcpy(concat, "--\r\n");
+ return smtp_puts(smtp, mime_end);
+}
+
+/**
+ * Send the main email body to the SMTP server.
+ *
+ * This includes the MIME sections for the email body and attachments.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] body_dd Email body with double dots added at the
+ * beginning of each line.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_mime_email(struct smtp *const smtp,
+ const char *const body_dd){
+ char boundary[SMTP_MIME_BOUNDARY_LEN];
+ size_t i;
+ struct smtp_attachment *attachment;
+
+ smtp_gen_mime_boundary(boundary);
+
+ if(smtp_print_mime_header_and_body(smtp, boundary, body_dd) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ for(i = 0; i < smtp->num_attachment; i++){
+ attachment = &smtp->attachment_list[i];
+ if(smtp_print_mime_attachment(smtp,
+ boundary,
+ attachment) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ }
+
+ return smtp_print_mime_end(smtp, boundary);
+}
+
+/**
+ * Print the email data provided by the user without MIME formatting.
+ *
+ * @param[in,out] smtp SMTP client context.
+ * @param[in] body_dd Email body with double dots added at the
+ * beginning of each line.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_nomime_email(struct smtp *const smtp,
+ const char *const body_dd){
+ return smtp_puts_terminate(smtp, body_dd);
+}
+
+/**
+ * Send the email body to the mail server.
+ *
+ * @param[in,out] smtp SMTP client context.
+ * @param[in] body Email body text.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_email(struct smtp *const smtp,
+ const char *const body){
+ char *body_double_dot;
+
+ /*
+ * Insert an extra dot for each line that begins with a dot. This will
+ * prevent data in the body parameter from prematurely ending the DATA
+ * segment.
+ */
+ if((body_double_dot = smtp_str_replace("\n.", "\n..", body)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ if(smtp_header_exists(smtp, "Content-Type")){
+ smtp_print_nomime_email(smtp, body_double_dot);
+ }
+ else{
+ smtp_print_mime_email(smtp, body_double_dot);
+ }
+ free(body_double_dot);
+ return smtp->status_code;
+}
+
+/**
+ * Convert a header into an RFC 5322 formatted string and send it to the
+ * SMTP server.
+ *
+ * This will adding proper line wrapping and indentation for long
+ * header lines.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] header See @ref smtp_header.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_print_header(struct smtp *const smtp,
+ const struct smtp_header *const header){
+ size_t key_len;
+ size_t value_len;
+ size_t concat_len;
+ char *header_concat;
+ char *concat;
+ char *header_fmt;
+
+ if(header->value == NULL){
+ return smtp->status_code;
+ }
+
+ key_len = strlen(header->key);
+ value_len = strlen(header->value);
+
+ /* concat_len = key_len + 2 + value_len + 1 */
+ if(smtp_si_add_size_t(key_len, value_len, &concat_len) ||
+ smtp_si_add_size_t(concat_len, 3, &concat_len) ||
+ (header_concat = malloc(concat_len)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ concat = smtp_stpcpy(header_concat, header->key);
+ concat = smtp_stpcpy(concat, ": ");
+ smtp_stpcpy(concat, header->value);
+
+ header_fmt = smtp_fold_whitespace(header_concat, SMTP_LINE_MAX);
+ free(header_concat);
+ if(header_fmt == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ smtp_puts_terminate(smtp, header_fmt);
+ free(header_fmt);
+ return smtp->status_code;
+}
+
+/**
+ * Take a FROM, TO, and CC address and add it into the email header list.
+ *
+ * The following example shows what the final header might look like when
+ * the client sends an email to two CC addresses:
+ * Cc: mail1\@example.com, mail2\@example.com
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] address_type See @ref smtp_address_type.
+ * @param[in] key Header key value, for example, To From Cc.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_append_address_to_header(struct smtp *const smtp,
+ enum smtp_address_type address_type,
+ const char *const key){
+ size_t i;
+ size_t num_address_in_header;
+ size_t header_value_sz;
+ size_t name_slen;
+ size_t email_slen;
+ size_t concat_len;
+ struct smtp_address *address;
+ char *header_value;
+ char *header_value_new;
+ char *concat;
+
+ num_address_in_header = 0;
+ header_value_sz = 0;
+ header_value = NULL;
+ concat_len = 0;
+ for(i = 0; i < smtp->num_address; i++){
+ address = &smtp->address_list[i];
+ if(address->type == address_type){
+ name_slen = 0;
+ if(address->name){
+ name_slen = strlen(address->name);
+ }
+ email_slen = strlen(address->email);
+
+ /*
+ * ', "' NAME '" <' EMAIL > \0
+ * header_value_sz += 3 + name_slen + 3 + email_slen + 1 + 1
+ */
+ if(smtp_si_add_size_t(header_value_sz, name_slen, &header_value_sz) ||
+ smtp_si_add_size_t(header_value_sz, email_slen, &header_value_sz) ||
+ smtp_si_add_size_t(header_value_sz, 3 + 3 + 1 + 1, &header_value_sz)||
+ (header_value_new = realloc(header_value,
+ header_value_sz)) == NULL){
+ free(header_value);
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ header_value = header_value_new;
+ concat = header_value + concat_len;
+ if(num_address_in_header > 0){
+ concat = smtp_stpcpy(concat, ", ");
+ }
+
+ if(name_slen){
+ concat = smtp_stpcpy(concat, "\"");
+ concat = smtp_stpcpy(concat, address->name);
+ concat = smtp_stpcpy(concat, "\" ");
+ }
+ concat = smtp_stpcpy(concat, "<");
+ concat = smtp_stpcpy(concat, address->email);
+ concat = smtp_stpcpy(concat, ">");
+ num_address_in_header += 1;
+ concat_len = (size_t)(concat - header_value);
+ }
+ }
+
+ if(header_value){
+ smtp_header_add(smtp, key, header_value);
+ free(header_value);
+ }
+ return smtp->status_code;
+}
+
+/**
+ * Send envelope MAIL FROM or RCPT TO header address.
+ *
+ * Examples:
+ * MAIL FROM:
+ * RCPT TO:
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] header Either "MAIL FROM" or "RCPT TO".
+ * @param[in] address See @ref smtp_address -> email field.
+ * @return See @ref smtp_status_code.
+ */
+static enum smtp_status_code
+smtp_mail_envelope_header(struct smtp *const smtp,
+ const char *const header,
+ const struct smtp_address *const address){
+ const char *const SMTPUTF8 = " SMTPUTF8";
+ const size_t SMTPUTF8_LEN = strlen(SMTPUTF8);
+ size_t email_len;
+ size_t bufsz;
+ char *envelope_address;
+ char *concat;
+ const char *smtputf8_opt;
+
+ email_len = strlen(address->email);
+
+ /* bufsz = 14 + email_len + SMTPUTF8_LEN + 1 */
+ if(smtp_si_add_size_t(email_len, SMTPUTF8_LEN + 14 + 1, &bufsz) ||
+ (envelope_address = malloc(bufsz)) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ smtputf8_opt = "";
+ if(smtp_str_has_nonascii_utf8(address->email)){
+ smtputf8_opt = SMTPUTF8;
+ }
+
+ concat = smtp_stpcpy(envelope_address, header);
+ concat = smtp_stpcpy(concat, ":<");
+ concat = smtp_stpcpy(concat, address->email);
+ concat = smtp_stpcpy(concat, ">");
+ concat = smtp_stpcpy(concat, smtputf8_opt);
+ smtp_stpcpy(concat, "\r\n");
+ smtp_puts(smtp, envelope_address);
+ free(envelope_address);
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ smtp_read_and_parse_code(smtp);
+ return smtp->status_code;
+}
+
+/**
+ * Comparison function for qsort which sorts headers alphabetically based
+ * on the key.
+ *
+ * @param[in] v1 The first @ref smtp_header to compare.
+ * @param[in] v2 The second @ref smtp_header to compare.
+ * @retval 0 If the keys match.
+ * @retval !0 If the keys do not match.
+ */
+static int
+smtp_header_cmp(const void *v1,
+ const void *v2){
+ const struct smtp_header *header1;
+ const struct smtp_header *header2;
+
+ header1 = v1;
+ header2 = v2;
+ return strcmp(header1->key, header2->key);
+}
+
+/**
+ * Validate characters in the email header key.
+ *
+ * Must consist only of printable US-ASCII characters except colon.
+ *
+ * @param[in] key Header key to validate.
+ * @retval 0 Successful validation.
+ * @retval -1 Failed to validate.
+ */
+SMTP_LINKAGE int
+smtp_header_key_validate(const char *const key){
+ unsigned char uc;
+ size_t i;
+ size_t keylen;
+
+ keylen = strlen(key);
+ if(keylen < 1){
+ return -1;
+ }
+
+ for(i = 0; i < keylen; i++){
+ uc = (unsigned char)key[i];
+ if(uc <= ' ' || uc > 126 || uc == ':'){
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Validate characters in the email header contents.
+ *
+ * Must consist only of printable character, space, or horizontal tab.
+ *
+ * @param[in] value Header value to validate.
+ * @retval 0 Successful validation.
+ * @retval -1 Failed to validate.
+ */
+SMTP_LINKAGE int
+smtp_header_value_validate(const char *const value){
+ size_t i;
+ unsigned char uc;
+
+ for(i = 0; value[i]; i++){
+ uc = (unsigned char)value[i];
+ if((uc < ' ' || uc > 126) &&
+ uc != '\t' &&
+ uc < 0x80){ /* Allow UTF-8 byte sequence. */
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Validate characters in the email address.
+ *
+ * The email address must consist only of printable characters excluding
+ * the angle brackets (<) and (>).
+ *
+ * @param[in] email The email address of the party.
+ * @retval 0 Successful validation.
+ * @retval -1 Failed to validate.
+ */
+SMTP_LINKAGE int
+smtp_address_validate_email(const char *const email){
+ size_t i;
+ unsigned char uc;
+
+ for(i = 0; email[i]; i++){
+ uc = (unsigned char)email[i];
+ if(uc <= ' ' || uc == 127 ||
+ uc == '<' || uc == '>'){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Validate characters in the email name.
+ *
+ * Email user name must consist only of printable characters, excluding the
+ * double quote character.
+ *
+ * @param[in] name Email name to validate.
+ * @retval 0 Successful validation.
+ * @retval -1 Failed to validate.
+ */
+SMTP_LINKAGE int
+smtp_address_validate_name(const char *const name){
+ size_t i;
+ unsigned char uc;
+
+ for(i = 0; name[i]; i++){
+ uc = (unsigned char)name[i];
+ if(uc < ' ' || uc == 127 || uc == '\"'){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Validate characters in the attachment file name.
+ *
+ * Must consist only of printable characters or the space character ( ), and
+ * excluding the quote characters (') and (").
+ *
+ * @param[in] name Filename of the attachment shown to recipients.
+ * @retval 0 Successful validation.
+ * @retval -1 Failed to validate.
+ */
+SMTP_LINKAGE int
+smtp_attachment_validate_name(const char *const name){
+ size_t i;
+ unsigned char uc;
+
+ for(i = 0; name[i]; i++){
+ uc = (unsigned char)name[i];
+ if(uc < ' ' || uc == 127 ||
+ uc == '\'' || uc == '\"'){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Special flag value for the SMTP context used to determine if the initial
+ * memory allocation failed to create the context.
+ */
+#define SMTP_FLAG_INVALID_MEMORY (enum smtp_flag)(0xFFFFFFFF)
+
+/**
+ * This error structure used for the single error case where we cannot
+ * initially allocate memory. This makes it easier to propagate any
+ * error codes when calling the other header functions because the
+ * caller will always get a valid SMTP structure returned.
+ */
+static struct smtp
+g_smtp_error = {
+ SMTP_FLAG_INVALID_MEMORY, /* flags */
+ 0, /* sock */
+ { /* gdfd */
+ NULL, /* _buf */
+ 0, /* _bufsz */
+ 0, /* _buf_len */
+ NULL, /* line */
+ 0, /* line_len */
+ NULL, /* getdelimfd_read */
+ NULL, /* user_data */
+ 0, /* delim */
+ {0} /* pad */
+ }, /* gdfd */
+ NULL, /* header_list */
+ 0, /* num_headers */
+ NULL, /* address_list */
+ 0, /* num_address */
+ NULL, /* attachment_list */
+ 0, /* num_attachment */
+ 0, /* timeout_sec */
+ SMTP_STATUS_NOMEM, /* smtp_status_code status_code */
+ 0, /* tls_on */
+ NULL /* cafile */
+#ifdef SMTP_OPENSSL
+ ,
+ NULL, /* tls */
+ NULL, /* tls_ctx */
+ NULL /* tls_bio */
+#endif /* SMTP_OPENSSL */
+};
+
+enum smtp_status_code
+smtp_open(const char *const server,
+ const char *const port,
+ enum smtp_connection_security connection_security,
+ enum smtp_flag flags,
+ const char *const cafile,
+ struct smtp **smtp){
+ struct smtp *snew;
+
+ if((snew = calloc(1, sizeof(**smtp))) == NULL){
+ *smtp = &g_smtp_error;
+ return smtp_status_code_get(*smtp);
+ }
+ *smtp = snew;
+
+ snew->flags = flags;
+ snew->sock = -1;
+ snew->gdfd.delim = '\n';
+ snew->gdfd.getdelimfd_read = smtp_str_getdelimfd_read;
+ snew->gdfd.user_data = snew;
+ snew->cafile = cafile;
+
+#ifndef SMTP_IS_WINDOWS
+ signal(SIGPIPE, SIG_IGN);
+#endif /* !(SMTP_IS_WINDOWS) */
+
+ if(smtp_connect(snew, server, port) < 0){
+ return smtp_status_code_set(*smtp, SMTP_STATUS_CONNECT);
+ }
+
+ if(smtp_initiate_handshake(snew,
+ server,
+ connection_security) != SMTP_STATUS_OK){
+ return smtp_status_code_set(*smtp, SMTP_STATUS_HANDSHAKE);
+ }
+
+ return snew->status_code;
+}
+
+enum smtp_status_code
+smtp_auth(struct smtp *const smtp,
+ enum smtp_authentication_method auth_method,
+ const char *const user,
+ const char *const pass){
+ int auth_rc;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ if(auth_method == SMTP_AUTH_PLAIN){
+ auth_rc = smtp_auth_plain(smtp, user, pass);
+ }
+ else if(auth_method == SMTP_AUTH_LOGIN){
+ auth_rc = smtp_auth_login(smtp, user, pass);
+ }
+#ifdef SMTP_OPENSSL
+ else if(auth_method == SMTP_AUTH_CRAM_MD5){
+ auth_rc = smtp_auth_cram_md5(smtp, user, pass);
+ }
+#endif /* SMTP_OPENSSL */
+ else if(auth_method == SMTP_AUTH_NONE){
+ auth_rc = 0;
+ }
+ else{
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(auth_rc < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_AUTH);
+ }
+
+ return smtp->status_code;
+}
+
+enum smtp_status_code
+smtp_mail(struct smtp *const smtp,
+ const char *const body){
+ size_t i;
+ int has_mail_from;
+ struct smtp_address *address;
+ char date[SMTP_DATE_MAX_SZ];
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ /* MAIL timeout 5 minutes. */
+ smtp_set_read_timeout(smtp, 60 * 5);
+ has_mail_from = 0;
+ for(i = 0; i < smtp->num_address; i++){
+ address = &smtp->address_list[i];
+ if(address->type == SMTP_ADDRESS_FROM){
+ if(smtp_mail_envelope_header(smtp,
+ "MAIL FROM",
+ address) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ has_mail_from = 1;
+ break;
+ }
+ }
+
+ if(!has_mail_from){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ /* RCPT timeout 5 minutes. */
+ smtp_set_read_timeout(smtp, 60 * 5);
+
+ for(i = 0; i < smtp->num_address; i++){
+ address = &smtp->address_list[i];
+ if(address->type != SMTP_ADDRESS_FROM){
+ if(smtp_mail_envelope_header(smtp,
+ "RCPT TO",
+ address) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ }
+ }
+
+ /* DATA timeout 2 minutes. */
+ smtp_set_read_timeout(smtp, 60 * 2);
+
+ if(smtp_puts(smtp, "DATA\r\n") != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ /* 354 response to DATA must get returned before we can send the message. */
+ if(smtp_read_and_parse_code(smtp) != SMTP_BEGIN_MAIL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE);
+ }
+
+ if(!smtp_header_exists(smtp, "Date")){
+ if(smtp_date_rfc_2822(date) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_DATE);
+ }
+ if(smtp_header_add(smtp, "Date", date) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ }
+
+ /* DATA block timeout 3 minutes. */
+ smtp_set_read_timeout(smtp, 60 * 3);
+
+ if(smtp_append_address_to_header(smtp,
+ SMTP_ADDRESS_FROM,
+ "From") != SMTP_STATUS_OK ||
+ smtp_append_address_to_header(smtp,
+ SMTP_ADDRESS_TO,
+ "To") != SMTP_STATUS_OK ||
+ smtp_append_address_to_header(smtp,
+ SMTP_ADDRESS_CC,
+ "Cc") != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ for(i = 0; i < smtp->num_headers; i++){
+ if(smtp_print_header(smtp, &smtp->header_list[i]) != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+ }
+
+ if(smtp_print_email(smtp, body)){
+ return smtp->status_code;
+ }
+
+ /* End of DATA segment. */
+ if(smtp_puts(smtp, ".\r\n") != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ /* DATA termination timeout 250 return code - 10 minutes. */
+ smtp_set_read_timeout(smtp, 60 * 10);
+ if(smtp_read_and_parse_code(smtp) != SMTP_DONE){
+ return smtp_status_code_set(smtp, SMTP_STATUS_SERVER_RESPONSE);
+ }
+
+ return smtp->status_code;
+}
+
+enum smtp_status_code
+smtp_close(struct smtp *smtp){
+ enum smtp_status_code status_code;
+
+ status_code = smtp->status_code;
+
+ if(smtp->flags == SMTP_FLAG_INVALID_MEMORY){
+ return status_code;
+ }
+
+ if(smtp->sock != -1){
+ /*
+ * Do not error out if this fails because we still need to free the
+ * SMTP client resources.
+ */
+ smtp->status_code = SMTP_STATUS_OK;
+ smtp_puts(smtp, "QUIT\r\n");
+
+#ifdef SMTP_OPENSSL
+ if(smtp->tls_on){
+ SSL_free(smtp->tls);
+ SSL_CTX_free(smtp->tls_ctx);
+ }
+#endif /* SMTP_OPENSSL */
+
+#ifdef SMTP_IS_WINDOWS
+ closesocket(smtp->sock);
+ WSACleanup();
+#else /* POSIX */
+ if(close(smtp->sock) < 0){
+ if(smtp->status_code == SMTP_STATUS_OK){
+ smtp_status_code_set(smtp, SMTP_STATUS_CLOSE);
+ }
+ }
+#endif /* SMTP_IS_WINDOWS */
+ }
+
+ smtp_str_getdelimfd_free(&smtp->gdfd);
+ smtp_header_clear_all(smtp);
+ smtp_address_clear_all(smtp);
+ smtp_attachment_clear_all(smtp);
+ if(status_code == SMTP_STATUS_OK){
+ status_code = smtp->status_code;
+ }
+ free(smtp);
+
+ return status_code;
+}
+
+enum smtp_status_code
+smtp_status_code_get(const struct smtp *const smtp){
+ return smtp->status_code;
+}
+
+enum smtp_status_code
+smtp_status_code_clear(struct smtp *const smtp){
+ enum smtp_status_code old_status;
+
+ old_status = smtp_status_code_get(smtp);
+ smtp_status_code_set(smtp, SMTP_STATUS_OK);
+ return old_status;
+}
+
+enum smtp_status_code
+smtp_status_code_set(struct smtp *const smtp,
+ enum smtp_status_code status_code){
+ if((unsigned)status_code >= SMTP_STATUS__LAST){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+ smtp->status_code = status_code;
+ return status_code;
+}
+
+const char *
+smtp_status_code_errstr(enum smtp_status_code status_code){
+ const char *const status_code_err_str[] = {
+ /* SMTP_STATUS_OK */
+ "Success",
+ /* SMTP_STATUS_NOMEM */
+ "Memory allocation failed",
+ /* SMTP_STATUS_CONNECT */
+ "Failed to connect to the mail server",
+ /* SMTP_STATUS_HANDSHAKE */
+ "Failed to handshake or negotiate a TLS connection with the server",
+ /* SMTP_STATUS_AUTH */
+ "Failed to authenticate with the given credentials",
+ /* SMTP_STATUS_SEND */
+ "Failed to send bytes to the server",
+ /* SMTP_STATUS_RECV */
+ "Failed to receive bytes from the server",
+ /* SMTP_STATUS_CLOSE */
+ "Failed to properly close a connection",
+ /* SMTP_STATUS_SERVER_RESPONSE */
+ "SMTP server sent back an unexpected status code",
+ /* SMTP_STATUS_PARAM */
+ "Invalid parameter",
+ /* SMTP_STATUS_FILE */
+ "Failed to read or open a local file",
+ /* SMTP_STATUS_DATE */
+ "Failed to get the local date and time",
+ /* SMTP_STATUS__LAST */
+ "Unknown error"
+ };
+
+ if((unsigned)status_code > SMTP_STATUS__LAST){
+ status_code = SMTP_STATUS__LAST;
+ }
+ return status_code_err_str[status_code];
+}
+
+enum smtp_status_code
+smtp_header_add(struct smtp *const smtp,
+ const char *const key,
+ const char *const value){
+ struct smtp_header *new_header_list;
+ struct smtp_header *new_header;
+ size_t num_headers_inc;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ if(smtp_header_key_validate(key) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(value && smtp_header_value_validate(value) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(smtp_si_add_size_t(smtp->num_headers, 1, &num_headers_inc) ||
+ (new_header_list = smtp_reallocarray(
+ smtp->header_list,
+ num_headers_inc,
+ sizeof(*smtp->header_list))) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ smtp->header_list = new_header_list;
+ new_header = &smtp->header_list[smtp->num_headers];
+
+ new_header->key = smtp_strdup(key);
+ if(value){
+ new_header->value = smtp_strdup(value);
+ }
+ else{
+ new_header->value = NULL;
+ }
+ if(new_header->key == NULL ||
+ (new_header->value == NULL && value)){
+ free(new_header->key);
+ free(new_header->value);
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ smtp->num_headers = num_headers_inc;
+
+ qsort(smtp->header_list,
+ smtp->num_headers,
+ sizeof(*smtp->header_list),
+ smtp_header_cmp);
+
+ return smtp->status_code;
+}
+
+void
+smtp_header_clear_all(struct smtp *const smtp){
+ size_t i;
+ struct smtp_header *header;
+
+ for(i = 0; i < smtp->num_headers; i++){
+ header = &smtp->header_list[i];
+ free(header->key);
+ free(header->value);
+ }
+ free(smtp->header_list);
+ smtp->header_list = NULL;
+ smtp->num_headers = 0;
+}
+
+enum smtp_status_code
+smtp_address_add(struct smtp *const smtp,
+ enum smtp_address_type type,
+ const char *const email,
+ const char *const name){
+ struct smtp_address *new_address_list;
+ struct smtp_address *new_address;
+ size_t num_address_inc;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ if(smtp_address_validate_email(email) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(name && smtp_address_validate_name(name) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(smtp_si_add_size_t(smtp->num_address, 1, &num_address_inc)){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ new_address_list = smtp_reallocarray(smtp->address_list,
+ num_address_inc,
+ sizeof(*new_address_list));
+ if(new_address_list == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ new_address = &new_address_list[smtp->num_address];
+
+ smtp->address_list = new_address_list;
+
+ new_address->type = type;
+ new_address->email = smtp_strdup(email);
+ if(name){
+ new_address->name = smtp_strdup(name);
+ }
+ else{
+ new_address->name = NULL;
+ }
+ if(new_address->email == NULL ||
+ (new_address->name == NULL && name)){
+ free(new_address->email);
+ free(new_address->name);
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ smtp->num_address = num_address_inc;
+
+ return smtp->status_code;
+}
+
+void
+smtp_address_clear_all(struct smtp *const smtp){
+ size_t i;
+ struct smtp_address *address;
+
+ for(i = 0; i < smtp->num_address; i++){
+ address = &smtp->address_list[i];
+ free(address->email);
+ free(address->name);
+ }
+ free(smtp->address_list);
+ smtp->address_list = NULL;
+ smtp->num_address = 0;
+}
+
+enum smtp_status_code
+smtp_attachment_add_path(struct smtp *const smtp,
+ const char *const name,
+ const char *const path){
+ char *data;
+ size_t bytes_read;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ errno = 0;
+ if((data = smtp_file_get_contents(path, &bytes_read)) == NULL){
+ if(errno == ENOMEM){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ return smtp_status_code_set(smtp, SMTP_STATUS_FILE);
+ }
+ smtp_attachment_add_mem(smtp, name, data, bytes_read);
+ free(data);
+ return smtp->status_code;
+}
+
+enum smtp_status_code
+smtp_attachment_add_fp(struct smtp *const smtp,
+ const char *const name,
+ FILE *fp){
+ char *data;
+ size_t bytes_read;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ errno = 0;
+ if((data = smtp_ffile_get_contents(fp, &bytes_read)) == NULL){
+ if(errno == ENOMEM){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ return smtp_status_code_set(smtp, SMTP_STATUS_FILE);
+ }
+ smtp_attachment_add_mem(smtp, name, data, bytes_read);
+ free(data);
+ return smtp->status_code;
+}
+
+enum smtp_status_code
+smtp_attachment_add_mem(struct smtp *const smtp,
+ const char *const name,
+ const void *const data,
+ size_t datasz){
+ size_t num_attachment_inc;
+ struct smtp_attachment *new_attachment_list;
+ struct smtp_attachment *new_attachment;
+ char *b64_encode;
+
+ if(smtp->status_code != SMTP_STATUS_OK){
+ return smtp->status_code;
+ }
+
+ if(smtp_attachment_validate_name(name) < 0){
+ return smtp_status_code_set(smtp, SMTP_STATUS_PARAM);
+ }
+
+ if(datasz == SIZE_MAX){
+ datasz = strlen(data);
+ }
+
+ if(smtp_si_add_size_t(smtp->num_attachment, 1, &num_attachment_inc) ||
+ (new_attachment_list = smtp_reallocarray(
+ smtp->attachment_list,
+ num_attachment_inc,
+ sizeof(*new_attachment_list))) == NULL){
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+ smtp->attachment_list = new_attachment_list;
+ new_attachment = &new_attachment_list[smtp->num_attachment];
+
+ new_attachment->name = smtp_strdup(name);
+ b64_encode = smtp_base64_encode(data, datasz);
+ if(new_attachment->name == NULL || b64_encode == NULL){
+ free(new_attachment->name);
+ free(b64_encode);
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ new_attachment->b64_data = smtp_chunk_split(b64_encode,
+ SMTP_LINE_MAX,
+ "\r\n");
+ free(b64_encode);
+ if(new_attachment->b64_data == NULL){
+ free(new_attachment->name);
+ return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
+ }
+
+ smtp->num_attachment = num_attachment_inc;
+ return smtp->status_code;
+}
+
+void
+smtp_attachment_clear_all(struct smtp *const smtp){
+ size_t i;
+ struct smtp_attachment *attachment;
+
+ for(i = 0; i < smtp->num_attachment; i++){
+ attachment = &smtp->attachment_list[i];
+ free(attachment->name);
+ free(attachment->b64_data);
+ }
+ free(smtp->attachment_list);
+ smtp->attachment_list = NULL;
+ smtp->num_attachment = 0;
+}
+
diff --git a/smtp.h b/smtp.h
new file mode 100644
index 00000000..f035cc5b
--- /dev/null
+++ b/smtp.h
@@ -0,0 +1,635 @@
+/**
+ * @file
+ * @brief SMTP client library.
+ * @author James Humphrey (mail@somnisoft.com)
+ * @version 1.00
+ *
+ * This SMTP client library allows the user to send emails to an SMTP server.
+ * The user can include custom headers and MIME attachments.
+ *
+ * This software has been placed into the public domain using CC0.
+ */
+#ifndef SMTP_H
+#define SMTP_H
+
+#include
+#include
+#include
+
+#ifndef SIZE_MAX
+/**
+ * Maximum value of size_t type.
+ */
+# define SIZE_MAX ((size_t)(-1))
+#endif /* SIZE_MAX */
+
+/**
+ * Status codes indicating success or failure from calling any of the
+ * SMTP library functions.
+ *
+ * This code gets returned by all functions in this header.
+ */
+enum smtp_status_code{
+ /**
+ * Successful operation completed.
+ */
+ SMTP_STATUS_OK = 0,
+
+ /**
+ * Memory allocation failed.
+ */
+ SMTP_STATUS_NOMEM = 1,
+
+ /**
+ * Failed to connect to the mail server.
+ */
+ SMTP_STATUS_CONNECT = 2,
+
+ /**
+ * Failed to handshake or negotiate a TLS connection with the server.
+ */
+ SMTP_STATUS_HANDSHAKE = 3,
+
+ /**
+ * Failed to authenticate with the given credentials.
+ */
+ SMTP_STATUS_AUTH = 4,
+
+ /**
+ * Failed to send bytes to the server.
+ */
+ SMTP_STATUS_SEND = 5,
+
+ /**
+ * Failed to receive bytes from the server.
+ */
+ SMTP_STATUS_RECV = 6,
+
+ /**
+ * Failed to properly close a connection.
+ */
+ SMTP_STATUS_CLOSE = 7,
+
+ /**
+ * SMTP server sent back an unexpected status code.
+ */
+ SMTP_STATUS_SERVER_RESPONSE = 8,
+
+ /**
+ * Invalid parameter.
+ */
+ SMTP_STATUS_PARAM = 9,
+
+ /**
+ * Failed to open or read a local file.
+ */
+ SMTP_STATUS_FILE = 10,
+
+ /**
+ * Failed to get the local date and time.
+ */
+ SMTP_STATUS_DATE = 11,
+
+ /**
+ * Indicates the last status code in the enumeration, useful for
+ * bounds checking.
+ *
+ * Not a valid status code.
+ */
+ SMTP_STATUS__LAST
+};
+
+/**
+ * Address source and destination types.
+ */
+enum smtp_address_type{
+ /**
+ * From address.
+ */
+ SMTP_ADDRESS_FROM = 0,
+
+ /**
+ * To address.
+ */
+ SMTP_ADDRESS_TO = 1,
+
+ /**
+ * Copy address.
+ */
+ SMTP_ADDRESS_CC = 2,
+
+ /**
+ * Blind copy address.
+ *
+ * Recipients should not see any of the BCC addresses when they receive
+ * their email. However, some SMTP server implementations may copy this
+ * information into the mail header, so do not assume that this will
+ * always get hidden. If the BCC addresses must not get shown to the
+ * receivers, then send one separate email to each BCC party and add
+ * the TO and CC addresses manually as a header property using
+ * @ref smtp_header_add instead of as an address using
+ * @ref smtp_address_add.
+ */
+ SMTP_ADDRESS_BCC = 3
+};
+
+/**
+ * Connect to the SMTP server using either an unencrypted socket or
+ * TLS encryption.
+ */
+enum smtp_connection_security{
+#ifdef SMTP_OPENSSL
+ /**
+ * First connect without encryption, then negotiate an encrypted connection
+ * by issuing a STARTTLS command.
+ *
+ * Typically used when connecting to a mail server on port 25 or 587.
+ */
+ SMTP_SECURITY_STARTTLS = 0,
+
+ /**
+ * Use TLS when initially connecting to server.
+ *
+ * Typically used when connecting to a mail server on port 465.
+ */
+ SMTP_SECURITY_TLS = 1,
+#endif /* SMTP_OPENSSL */
+
+ /**
+ * Do not use TLS encryption.
+ *
+ * Not recommended unless connecting to the SMTP server locally.
+ */
+ SMTP_SECURITY_NONE = 2
+};
+
+/**
+ * List of supported methods for authenticating a mail user account on
+ * the server.
+ */
+enum smtp_authentication_method{
+#ifdef SMTP_OPENSSL
+ /**
+ * Use HMAC-MD5.
+ */
+ SMTP_AUTH_CRAM_MD5 = 0,
+#endif /* SMTP_OPENSSL */
+ /**
+ * No authentication required.
+ *
+ * Some servers support this option if connecting locally.
+ */
+ SMTP_AUTH_NONE = 1,
+
+ /**
+ * Authenticate using base64 user and password.
+ */
+ SMTP_AUTH_PLAIN = 2,
+
+ /**
+ * Another base64 authentication method, similar to SMTP_AUTH_PLAIN.
+ */
+ SMTP_AUTH_LOGIN = 3
+};
+
+/**
+ * Special flags defining certain behaviors for the SMTP client context.
+ */
+enum smtp_flag{
+ /**
+ * Print client and server communication on stderr.
+ */
+ SMTP_DEBUG = 1 << 0,
+
+ /**
+ * Do not verify TLS certificate.
+ *
+ * By default, the TLS handshake function will check if a certificate
+ * has expired or if using a self-signed certificate. Either of those
+ * conditions will cause the connection to fail. This option allows the
+ * connection to proceed even if those checks fail.
+ */
+ SMTP_NO_CERT_VERIFY = 1 << 1
+};
+
+struct smtp;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Open a connection to an SMTP server and return the context.
+ *
+ * After successfully connecting and performing a handshake with the
+ * SMTP server, this will return a valid SMTP client context that
+ * the application can use in the other library functions.
+ *
+ * This always returns a valid SMTP client context even if
+ * the server connection or memory allocation fails. In this scenario, the
+ * error status will continue to propagate to future library calls for
+ * the SMTP context while in this failure mode.
+ *
+ * This function will ignore the SIGPIPE signal. Applications that require a
+ * handler for that signal should set it up after calling this function.
+ *
+ * @param[in] server Server name or IP address.
+ * @param[in] port Server port number.
+ * @param[in] connection_security See @ref smtp_connection_security.
+ * @param[in] flags See @ref smtp_flag.
+ * @param[in] cafile Path to certificate file, or NULL to use
+ * certificates in the default path.
+ * @param[out] smtp Pointer to a new SMTP context. When
+ * finished, the caller must free this
+ * context using @ref smtp_close.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_open(const char *const server,
+ const char *const port,
+ enum smtp_connection_security connection_security,
+ enum smtp_flag flags,
+ const char *const cafile,
+ struct smtp **smtp);
+
+/**
+ * Authenticate the user using one of the methods listed in
+ * @ref smtp_authentication_method.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] auth_method See @ref smtp_authentication_method.
+ * @param[in] user SMTP user name.
+ * @param[in] pass SMTP password.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_auth(struct smtp *const smtp,
+ enum smtp_authentication_method auth_method,
+ const char *const user,
+ const char *const pass);
+
+/**
+ * Sends an email using the addresses, attachments, and headers defined
+ * in the current SMTP context.
+ *
+ * The caller must call the @ref smtp_open function prior to this.
+ *
+ * The 'Date' header will automatically get generated here if it hasn't
+ * already been set using @ref smtp_header_add.
+ *
+ * If the application overrides the default 'Content-Type' header, then
+ * this function will output the @p body as raw data just below the email
+ * headers, and it will not output the attachments added using the
+ * smtp_attachment_add_* functions. In other words, the application must
+ * create its own MIME sections (if needed) when overriding the
+ * 'Content-Type' header.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] body Null-terminated string to send in the email body.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_mail(struct smtp *const smtp,
+ const char *const body);
+
+/**
+ * Close the SMTP connection and frees all resources held by the
+ * SMTP context.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_close(struct smtp *smtp);
+
+/**
+ * Get the current status/error code.
+ *
+ * @param[in] smtp SMTP client context.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_status_code_get(const struct smtp *const smtp);
+
+/**
+ * Clear the current error code set in the SMTP client context.
+ *
+ * @param[in,out] smtp SMTP client context.
+ * @return Previous error code before clearing.
+ */
+enum smtp_status_code
+smtp_status_code_clear(struct smtp *const smtp);
+
+/**
+ * Set the error status of the SMTP client context and return the same code.
+ *
+ * This allows the caller to clear an error status to SMTP_STATUS_OK
+ * so that previous errors will stop propagating. However, this will only
+ * work correctly for clearing the SMTP_STATUS_PARAM and SMTP_STATUS_FILE
+ * errors. Do not use this to clear any other error codes.
+ *
+ * @deprecated Use @ref smtp_status_code_clear instead.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] new_status_code See @ref smtp_status_code.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_status_code_set(struct smtp *const smtp,
+ enum smtp_status_code new_status_code);
+
+/**
+ * Convert a standard SMTP client status code to a descriptive string.
+ *
+ * @param[in] status_code Status code returned from one of the other
+ * library functions.
+ * @return String containing a description of the @p status_code. The caller
+ * must not free or modify this string.
+ */
+const char *
+smtp_status_code_errstr(enum smtp_status_code status_code);
+
+/**
+ * Add a key/value header to the header list in the SMTP context.
+ *
+ * If adding a header with an existing key, this will insert instead of
+ * replacing the existing header. See @ref smtp_header_clear_all.
+ *
+ * See @ref smtp_mail when overriding the default 'Content-Type' header.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] key Key name for new header. It must consist only of
+ * printable US-ASCII characters except colon.
+ * @param[in] value Value for new header. It must consist only of printable
+ * US-ASCII, space, or horizontal tab. If set to NULL,
+ * this will prevent the header from printing out.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_header_add(struct smtp *const smtp,
+ const char *const key,
+ const char *const value);
+
+/**
+ * Free all memory related to email headers.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_header_clear_all(struct smtp *const smtp);
+
+/**
+ * Add a FROM, TO, CC, or BCC address destination to this SMTP context.
+ *
+ * @note Some SMTP servers may reject over 100 recipients.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] type See @ref smtp_address_type.
+ * @param[in] email The email address of the party. Must consist only of
+ * printable characters excluding the angle brackets
+ * (<) and (>).
+ * @param[in] name Name or description of the party. Must consist only of
+ * printable characters, excluding the quote characters. If
+ * set to NULL or empty string, no name will get associated
+ * with this email.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_address_add(struct smtp *const smtp,
+ enum smtp_address_type type,
+ const char *const email,
+ const char *const name);
+
+/**
+ * Free all memory related to the address list.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_address_clear_all(struct smtp *const smtp);
+
+/**
+ * Add a file attachment from a path.
+ *
+ * See @ref smtp_attachment_add_mem for more details.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] path Path to file.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_attachment_add_path(struct smtp *const smtp,
+ const char *const name,
+ const char *const path);
+
+/**
+ * Add an attachment using a file pointer.
+ *
+ * See @ref smtp_attachment_add_mem for more details.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] fp File pointer already opened by the caller.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_attachment_add_fp(struct smtp *const smtp,
+ const char *const name,
+ FILE *fp);
+
+/**
+ * Add a MIME attachment to this SMTP context with the data retrieved
+ * from memory.
+ *
+ * The attachment data will get base64 encoded before sending to the server.
+ *
+ * @param[in] smtp SMTP client context.
+ * @param[in] name Filename of the attachment shown to recipients. Must
+ * consist only of printable ASCII characters, excluding
+ * the quote characters (') and (").
+ * @param[in] data Raw attachment data stored in memory.
+ * @param[in] datasz Number of bytes in @p data, or -1 if data
+ * null-terminated.
+ * @return See @ref smtp_status_code.
+ */
+enum smtp_status_code
+smtp_attachment_add_mem(struct smtp *const smtp,
+ const char *const name,
+ const void *const data,
+ size_t datasz);
+
+/**
+ * Remove all attachments from the SMTP client context.
+ *
+ * @param[in] smtp SMTP client context.
+ */
+void
+smtp_attachment_clear_all(struct smtp *const smtp);
+
+
+/*
+ * The SMTP_INTERNAL DEFINE section contains definitions that get used
+ * internally by the SMTP client library.
+ */
+#ifdef SMTP_INTERNAL_DEFINE
+/**
+ * SMTP codes returned by the server and parsed by the client.
+ */
+enum smtp_result_code{
+ /**
+ * Client error code which does not get set by the server.
+ */
+ SMTP_INTERNAL_ERROR = -1,
+
+ /**
+ * Returned when ready to begin processing next step.
+ */
+ SMTP_READY = 220,
+
+ /**
+ * Returned in response to QUIT.
+ */
+ SMTP_CLOSE = 221,
+
+ /**
+ * Returned if client successfully authenticates.
+ */
+ SMTP_AUTH_SUCCESS = 235,
+
+ /**
+ * Returned when some commands successfully complete.
+ */
+ SMTP_DONE = 250,
+
+ /**
+ * Returned for some multi-line authentication mechanisms where this code
+ * indicates the next stage in the authentication step.
+ */
+ SMTP_AUTH_CONTINUE = 334,
+
+ /**
+ * Returned in response to DATA command.
+ */
+ SMTP_BEGIN_MAIL = 354
+};
+
+/**
+ * Used for parsing out the responses from the SMTP server.
+ *
+ * For example, if the server sends back '250-STARTTLS', then code would
+ * get set to 250, more would get set to 1, and text would get set to STARTTLS.
+ */
+struct smtp_command{
+ /**
+ * Result code converted to an integer.
+ */
+ enum smtp_result_code code;
+
+ /**
+ * Indicates if more server commands follow.
+ *
+ * This will get set to 1 if the fourth character in the response line
+ * contains a '-', otherwise this will get set to 0.
+ */
+ int more;
+
+ /**
+ * The text shown after the status code.
+ */
+ const char *text;
+};
+
+/**
+ * Return codes for the getdelim interface which allows the caller to check
+ * if more delimited lines can get processed.
+ */
+enum str_getdelim_retcode{
+ /**
+ * An error occurred during the getdelim processing.
+ */
+ STRING_GETDELIMFD_ERROR = -1,
+
+ /**
+ * Found a new line and can process more lines in the next call.
+ */
+ STRING_GETDELIMFD_NEXT = 0,
+
+ /**
+ * Found a new line and unable to read any more lines at this time.
+ */
+ STRING_GETDELIMFD_DONE = 1
+};
+
+/**
+ * Data structure for read buffer and line parsing.
+ *
+ * This assists with getting and parsing the server response lines.
+ */
+struct str_getdelimfd{
+ /**
+ * Read buffer which may include bytes past the delimiter.
+ */
+ char *_buf;
+
+ /**
+ * Number of allocated bytes in the read buffer.
+ */
+ size_t _bufsz;
+
+ /**
+ * Number of actual stored bytes in the read buffer.
+ */
+ size_t _buf_len;
+
+ /**
+ * Current line containing the text up to the delimiter.
+ */
+ char *line;
+
+ /**
+ * Number of stored bytes in the line buffer.
+ */
+ size_t line_len;
+
+ /**
+ * Function pointer to a custom read function for the
+ * @ref smtp_str_getdelimfd interface.
+ *
+ * This function prototype has similar semantics to the read function.
+ * The @p gdfd parameter allows the custom function to pull the user_data
+ * info from the @ref str_getdelimfd struct which can contain file pointer,
+ * socket connection, etc.
+ */
+ long (*getdelimfd_read)(struct str_getdelimfd *const gdfd,
+ void *buf,
+ size_t count);
+
+ /**
+ * User data which gets sent to the read handler function.
+ */
+ void *user_data;
+
+ /**
+ * Character delimiter used for determining line separation.
+ */
+ int delim;
+
+ /**
+ * Padding structure to align.
+ */
+ char pad[4];
+};
+
+#endif /* SMTP_INTERNAL_DEFINE */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SMTP_H */
+
From 63a8ce36fabbbd9f0a355c8d47beb4891036b1e3 Mon Sep 17 00:00:00 2001
From: "Nathan P."
Date: Fri, 28 Jun 2024 12:24:22 -0400
Subject: [PATCH 07/61] Fixes for Arduino and RPI email notifications.
---
EMailSender.cpp | 1114 ++++++++++++++++++++++++++++++++++++++++++++++
EMailSender.h | 525 ++++++++++++++++++++++
EMailSenderKey.h | 157 +++++++
main.cpp | 11 +-
platformio.ini | 1 -
smtp.c | 76 ++--
smtp.h | 3 +
7 files changed, 1845 insertions(+), 42 deletions(-)
create mode 100644 EMailSender.cpp
create mode 100644 EMailSender.h
create mode 100644 EMailSenderKey.h
diff --git a/EMailSender.cpp b/EMailSender.cpp
new file mode 100644
index 00000000..dfdc9d5b
--- /dev/null
+++ b/EMailSender.cpp
@@ -0,0 +1,1114 @@
+/*
+ * EMail Sender Arduino, esp8266, stm32 and esp32 library to send email
+ *
+ * AUTHOR: Renzo Mischianti
+ * VERSION: 3.0.14
+ *
+ * https://www.mischianti.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Renzo Mischianti www.mischianti.org All right reserved.
+ *
+ * You may copy, alter and reuse this code in any way you like, but please leave
+ * reference to www.mischianti.org in your comments if you redistribute this code.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "EMailSender.h"
+#include
+#if defined(ESP32)
+#include
+#endif
+
+//#include
+//#include
+
+//#define SD SPIFFS
+
+// BASE64 -----------------------------------------------------------
+const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+#define encode64(arr) encode64_f(arr,strlen(arr))
+
+inline void a3_to_a4(unsigned char * a4, unsigned char * a3) {
+ a4[0] = (a3[0] & 0xfc) >> 2;
+ a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
+ a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
+ a4[3] = (a3[2] & 0x3f);
+}
+
+int base64_encode(char *output, char *input, int inputLen) {
+ int i = 0, j = 0;
+ int encLen = 0;
+ unsigned char a3[3];
+ unsigned char a4[4];
+
+ while (inputLen--) {
+ a3[i++] = *(input++);
+ if (i == 3) {
+ a3_to_a4(a4, a3);
+
+ for (i = 0; i < 4; i++) {
+ output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]);
+ }
+
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 3; j++) {
+ a3[j] = '\0';
+ }
+
+ a3_to_a4(a4, a3);
+
+ for (j = 0; j < i + 1; j++) {
+ output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]);
+ }
+
+ while ((i++ < 3)) {
+ output[encLen++] = '=';
+ }
+ }
+ output[encLen] = '\0';
+ return encLen;
+}
+
+int base64_enc_length(int plainLen) {
+ int n = plainLen;
+ return (n + 2 - ((n + 2) % 3)) / 3 * 4;
+}
+
+const char* encode64_f(char* input, uint8_t len) {
+ // encoding
+
+ DEBUG_PRINTLN(F("Encoding"));
+
+ DEBUG_PRINTLN(input);
+ DEBUG_PRINTLN(len);
+
+ //int encodedLen =
+ base64_enc_length(len);
+ static char encoded[256];
+ // note input is consumed in this step: it will be empty afterwards
+ base64_encode(encoded, input, len);
+ return encoded;
+}
+
+// END BASE64 ---------------------------------------------------------
+
+EMailSender::EMailSender(const char* email_login, const char* email_password, const char* email_from, const char* name_from ,
+ const char* smtp_server, uint16_t smtp_port) {
+ this->setEMailLogin(email_login);
+ this->setEMailFrom(email_from);
+ this->setEMailPassword(email_password);
+ this->setSMTPServer(smtp_server);
+ this->setSMTPPort(smtp_port);
+ this->setNameFrom(name_from);
+// this->isSecure = isSecure;
+}
+EMailSender::EMailSender(const char* email_login, const char* email_password, const char* email_from,
+ const char* smtp_server, uint16_t smtp_port) {
+ this->setEMailLogin(email_login);
+ this->setEMailFrom(email_from);
+ this->setEMailPassword(email_password);
+ this->setSMTPServer(smtp_server);
+ this->setSMTPPort(smtp_port);
+
+// this->isSecure = isSecure;
+}
+
+EMailSender::EMailSender(const char* email_login, const char* email_password, const char* email_from, const char* name_from ) {
+ this->setEMailLogin(email_login);
+ this->setEMailFrom(email_from);
+ this->setEMailPassword(email_password);
+ this->setNameFrom(name_from);
+ this->setNameFrom(name_from);
+
+// this->isSecure = isSecure;
+}
+EMailSender::EMailSender(const char* email_login, const char* email_password, const char* email_from) {
+ this->setEMailLogin(email_login);
+ this->setEMailFrom(email_from);
+ this->setEMailPassword(email_password);
+
+// this->isSecure = isSecure;
+}
+
+EMailSender::EMailSender(const char* email_login, const char* email_password){
+ this->setEMailLogin(email_login);
+ this->setEMailFrom(email_login);
+ this->setEMailPassword(email_password);
+
+// this->isSecure = isSecure;
+}
+
+void EMailSender::setSMTPPort(uint16_t smtp_port){
+ this->smtp_port = smtp_port;
+};
+void EMailSender::setSMTPServer(const char* smtp_server){
+ delete [] this->smtp_server;
+ this->smtp_server = new char[strlen(smtp_server)+1];
+ strcpy(this->smtp_server, smtp_server);
+};
+
+void EMailSender::setEMailLogin(const char* email_login){
+ delete [] this->email_login;
+ this->email_login = new char[strlen(email_login)+1];
+ strcpy(this->email_login, email_login);
+};
+void EMailSender::setEMailFrom(const char* email_from){
+ delete [] this->email_from;
+ this->email_from = new char[strlen(email_from)+1];
+ strcpy(this->email_from, email_from);
+};
+void EMailSender::setNameFrom(const char* name_from){
+ delete [] this->name_from;
+ this->name_from = new char[strlen(name_from)+1];
+ strcpy(this->name_from, name_from);
+};
+void EMailSender::setEMailPassword(const char* email_password){
+ delete [] this->email_password;
+ this->email_password = new char[strlen(email_password)+1];
+ strcpy(this->email_password, email_password);
+};
+
+void EMailSender::setIsSecure(bool isSecure) {
+ this->isSecure = isSecure;
+}
+
+#ifdef SSLCLIENT_WRAPPER
+EMailSender::Response EMailSender::awaitSMTPResponse(SSLClient &client,
+ const char* resp, const char* respDesc, uint16_t timeOut) {
+ EMailSender::Response response;
+ uint32_t ts = millis();
+ while (!client.available()) {
+ if (millis() > (ts + timeOut)) {
+ response.code = F("1");
+ response.desc = String(respDesc) + "! " + F("SMTP Response TIMEOUT!");
+ response.status = false;
+
+ return response;
+ }
+ }
+ _serverResponce = client.readStringUntil('\n');
+
+ DEBUG_PRINTLN(_serverResponce);
+ if (resp && _serverResponce.indexOf(resp) == -1){
+ response.code = resp;
+ response.desc = respDesc +String(" (") + _serverResponce + String(")");
+ response.status = false;
+
+ return response;
+ }
+
+ response.status = true;
+ return response;
+}
+#else
+EMailSender::Response EMailSender::awaitSMTPResponse(EMAIL_NETWORK_CLASS &client,
+ const char* resp, const char* respDesc, uint16_t timeOut) {
+ EMailSender::Response response;
+ uint32_t ts = millis();
+ while (!client.available()) {
+ if (millis() > (ts + timeOut)) {
+ response.code = F("1");
+ response.desc = String(respDesc) + "! " + F("SMTP Response TIMEOUT!");
+ response.status = false;
+ return response;
+ }
+ }
+ _serverResponce = client.readStringUntil('\n');
+
+ DEBUG_PRINTLN(_serverResponce);
+ if (resp && _serverResponce.indexOf(resp) == -1){
+ response.code = resp;
+ response.desc = respDesc +String(" (") + _serverResponce + String(")");
+ response.status = false;
+ return response;
+ }
+
+ response.status = true;
+ return response;
+}
+#endif
+
+static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
+ out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
+ out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
+ out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
+}
+
+#ifdef ENABLE_ATTACHMENTS
+ #ifdef STORAGE_EXTERNAL_ENABLED
+ #if (defined(DIFFERENT_FILE_MANAGE) && defined(EMAIL_FILE_EX)) || !defined(STORAGE_INTERNAL_ENABLED)
+ #ifdef SSLCLIENT_WRAPPER
+ void encode(EMAIL_FILE_EX *file, SSLClient *client) {
+ unsigned char in[3],out[4];
+ int i,len,blocksout=0;
+
+ while (file->available()!=0) {
+ len=0;
+ for (i=0;i<3;i++){
+ in[i]=(unsigned char) file->read();
+ if (file->available()!=0) len++;
+ else in[i]=0;
+ }
+ if (len){
+ encodeblock(in,out,len);
+ // for(i=0;i<4;i++) client->write(out[i]);
+ client->write(out, 4);
+ blocksout++; }
+ if (blocksout>=19||file->available()==0){
+ if (blocksout) {
+ client->print("\r\n");
+ }
+ blocksout=0;
+ }
+ }
+ }
+
+ #else
+ void encode(EMAIL_FILE_EX *file, EMAIL_NETWORK_CLASS *client) {
+ unsigned char in[3],out[4];
+ int i,len,blocksout=0;
+
+ while (file->available()!=0) {
+ len=0;
+ for (i=0;i<3;i++){
+ in[i]=(unsigned char) file->read();
+ if (file->available()!=0) len++;
+ else in[i]=0;
+ }
+ if (len){
+ encodeblock(in,out,len);
+ // for(i=0;i<4;i++) client->write(out[i]);
+ client->write(out, 4);
+ blocksout++; }
+ if (blocksout>=19||file->available()==0){
+ if (blocksout) {
+ client->print("\r\n");
+ }
+ blocksout=0;
+ }
+ }
+ }
+ #endif
+
+ #endif
+ #endif
+ #ifdef STORAGE_INTERNAL_ENABLED
+ #if defined(DIFFERENT_FILE_MANAGE) || (!defined(DIFFERENT_FILE_MANAGE) && defined(EMAIL_FILE)) || !defined(STORAGE_EXTERNAL_ENABLED)
+
+ #ifdef SSLCLIENT_WRAPPER
+ void encode(EMAIL_FILE *file, SSLClient *client) {
+ unsigned char in[3],out[4];
+ int i,len,blocksout=0;
+
+ while (file->available()!=0) {
+ len=0;
+ for (i=0;i<3;i++){
+ in[i]=(unsigned char) file->read();
+ if (file->available()!=0) len++;
+ else in[i]=0;
+ }
+ if (len){
+ encodeblock(in,out,len);
+ // for(i=0;i<4;i++) client->write(out[i]);
+ client->write(out, 4);
+ blocksout++; }
+ if (blocksout>=19||file->available()==0){
+ if (blocksout) {
+ client->print("\r\n");
+ }
+ blocksout=0;
+ }
+ }
+ }
+
+ #else
+ void encode(EMAIL_FILE *file, EMAIL_NETWORK_CLASS *client) {
+ unsigned char in[3],out[4];
+ int i,len,blocksout=0;
+
+ while (file->available()!=0) {
+ len=0;
+ for (i=0;i<3;i++){
+ in[i]=(unsigned char) file->read();
+ if (file->available()!=0) len++;
+ else in[i]=0;
+ }
+ if (len){
+ encodeblock(in,out,len);
+ // for(i=0;i<4;i++) client->write(out[i]);
+ client->write(out, 4);
+ blocksout++; }
+ if (blocksout>=19||file->available()==0){
+ if (blocksout) {
+ client->print("\r\n");
+ }
+ blocksout=0;
+ }
+ }
+ }
+ #endif
+ #endif
+ #endif
+#endif
+
+const char** toCharArray(String arr[], int num) {
+ // If we ever alloc with new with have to delete
+ const char** buffer = new const char*[num];
+
+ for(int i = 0; i < num; i++) {
+ buffer[i] = arr[i].c_str();
+ }
+
+ return buffer;
+}
+const char** toCharArray(char* arr[], int num) {
+ // If we ever alloc with new with have to delete
+ const char** buffer = new const char*[num];
+
+ for(int i = 0; i < num; i++) {
+ buffer[i] = arr[i];
+ }
+
+ return buffer;
+}
+
+EMailSender::Response EMailSender::send(char* tos[], byte sizeOfTo, EMailMessage &email, Attachments attachments) {
+ return send(toCharArray(tos, sizeOfTo), sizeOfTo, 0, 0, email, attachments);
+}
+EMailSender::Response EMailSender::send(char* tos[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments attachments) {
+ return send(toCharArray(tos, sizeOfTo+sizeOfCc), sizeOfTo, sizeOfCc, 0, email, attachments);
+}
+EMailSender::Response EMailSender::send(char* tos[], byte sizeOfTo, byte sizeOfCc,byte sizeOfCCn, EMailMessage &email, Attachments attachments){
+ return send(toCharArray(tos, sizeOfTo+sizeOfCc+sizeOfCCn), sizeOfTo, sizeOfCc, sizeOfCCn, email, attachments);
+}
+
+
+EMailSender::Response EMailSender::send(String to, EMailMessage &email, Attachments attachments){
+ DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
+
+ const char* arrEmail[] = {to.c_str()};
+ return send(arrEmail, 1, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(String tos[], byte sizeOfTo, EMailMessage &email, Attachments attachments) {
+ return send(toCharArray(tos, sizeOfTo), sizeOfTo, 0, 0, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(String tos[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments attachments) {
+ return send(toCharArray(tos, sizeOfTo+sizeOfCc), sizeOfTo, sizeOfCc, 0, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(String tos[], byte sizeOfTo, byte sizeOfCc,byte sizeOfCCn, EMailMessage &email, Attachments attachments){
+ return send(toCharArray(tos, sizeOfTo+sizeOfCc+sizeOfCCn), sizeOfTo, sizeOfCc, sizeOfCCn, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(const char* to, EMailMessage &email, Attachments attachments){
+ DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
+
+ const char* arrEmail[] = {to};
+ return send(arrEmail, 1, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, EMailMessage &email, Attachments attachments) {
+ DEBUG_PRINTLN(F("miltiple destination and attachments"));
+ return send(to, sizeOfTo, 0, email, attachments);
+}
+
+EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments attachments)
+{
+ return send(to, sizeOfTo, sizeOfCc, 0, email, attachments);
+}
+
+#ifdef SSLCLIENT_WRAPPER
+#ifdef PUT_OUTSIDE_SCOPE_CLIENT_DECLARATION
+ // Initialize the SSL client library
+ // We input an EthernetClient, our trust anchors, and the analog pin
+ EMAIL_NETWORK_CLASS base_client;
+ SSLClient client(base_client, TAs, (size_t)TAs_NUM, ANALOG_PIN, 2);
+#else
+ #error "You must put outside scope the client declaration if you want use SSLClient!"
+#endif
+#else
+ #ifdef PUT_OUTSIDE_SCOPE_CLIENT_DECLARATION
+ EMAIL_NETWORK_CLASS client;
+ #endif
+#endif
+
+EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte sizeOfCc,byte sizeOfCCn, EMailMessage &email, Attachments attachments)
+{
+#ifdef SSLCLIENT_WRAPPER
+ DEBUG_PRINTLN(F("SSLClient active!"));
+#else
+ #ifndef PUT_OUTSIDE_SCOPE_CLIENT_DECLARATION
+ EMAIL_NETWORK_CLASS client;
+ #endif
+
+ DEBUG_PRINT(F("Insecure client:"));
+ DEBUG_PRINTLN(this->isSecure);
+ #ifndef FORCE_DISABLE_SSL
+ #if (EMAIL_NETWORK_TYPE == NETWORK_ESP8266 || EMAIL_NETWORK_TYPE == NETWORK_ESP8266_242)
+ #ifndef ARDUINO_ESP8266_RELEASE_2_4_2
+ if (this->isSecure == false){
+ client.setInsecure();
+ bool mfln = client.probeMaxFragmentLength(this->smtp_server, this->smtp_port, 512);
+
+ DEBUG_PRINT("MFLN supported: ");
+ DEBUG_PRINTLN(mfln?"yes":"no");
+
+ if (mfln) {
+ client.setBufferSizes(512, 512);
+ } else {
+ client.setBufferSizes(2048, 2048);
+ }
+ }
+ #endif
+ #elif (EMAIL_NETWORK_TYPE == NETWORK_ESP32)
+ // String coreVersion = String(ESP.getSdkVersion());
+ // uint8_t firstdot = coreVersion.indexOf('.');
+ //
+ // DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat());
+ // DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat() >= 3.3f);
+ // if (coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat() >= 3.3f) {
+ // client.setInsecure();
+ // }
+ #ifndef ARDUINO_ARCH_RP2040
+ #include
+ #if ((!defined(ARDUINO_ESP32_RELEASE_1_0_4)) && (!defined(ARDUINO_ESP32_RELEASE_1_0_3)) && (!defined(ARDUINO_ESP32_RELEASE_1_0_2)))
+ client.setInsecure();
+ #endif
+ #else
+ client.setInsecure();
+ #endif
+ #endif
+ #endif
+#endif
+ EMailSender::Response response;
+
+ DEBUG_PRINTLN(this->smtp_server);
+ DEBUG_PRINTLN(this->smtp_port);
+
+ if(!client.connect(this->smtp_server, this->smtp_port)) {
+ response.desc = F("Could not connect to mail server");
+ response.code = F("2");
+ response.status = false;
+
+ client.flush();
+ client.stop();
+
+ return response;
+ }
+
+ response = awaitSMTPResponse(client, "220", "Connection Error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+
+ if (this->additionalResponseLineOnConnection == 0) {
+ if (DEFAULT_CONNECTION_RESPONSE_COUNT == 'a'){
+ this->additionalResponseLineOnConnection = 255;
+ } else {
+ this->additionalResponseLineOnConnection = DEFAULT_CONNECTION_RESPONSE_COUNT;
+ }
+ }
+
+ if (this->additionalResponseLineOnConnection > 0){
+ for (int i = 0; i<=this->additionalResponseLineOnConnection; i++) {
+ response = awaitSMTPResponse(client, "220", "Connection response error ", 2500);
+ //if additionalResponseLineOnConnection is set to 255: wait out all code 250 responses, then continue
+ if (this->additionalResponseLineOnConnection == 255) break;
+ else {
+ if (!response.status && response.code == F("1")) {
+ response.desc = F("Connection error! Reduce the HELO response line!");
+ client.flush();
+ client.stop();
+ return response;
+ }
+ }
+ }
+ }
+
+ String commandHELO = "HELO";
+ if (this->useEHLO == true) {
+ commandHELO = "EHLO";
+ }
+ String helo = commandHELO + " "+String(publicIPDescriptor)+" ";
+ DEBUG_PRINTLN(helo);
+ client.println(helo);
+
+ response = awaitSMTPResponse(client, "250", "Identification error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+
+ if (this->useEHLO == true && this->additionalResponseLineOnHELO == 0) {
+ if (DEFAULT_EHLO_RESPONSE_COUNT == 'a'){
+ this->additionalResponseLineOnHELO = 255;
+ } else {
+ this->additionalResponseLineOnHELO = DEFAULT_EHLO_RESPONSE_COUNT;
+ }
+ }
+
+ if (this->additionalResponseLineOnHELO > 0){
+ for (int i = 0; i<=this->additionalResponseLineOnHELO; i++) {
+ response = awaitSMTPResponse(client, "250", "EHLO error", 2500);
+ if (!response.status && response.code == F("1")) {
+ //if additionalResponseLineOnHELO is set to 255: wait out all code 250 responses, then continue
+ if (this->additionalResponseLineOnHELO == 255) break;
+ else {
+ response.desc = F("Timeout! Reduce additional HELO response line count or set it to 255!");
+ client.flush();
+ client.stop();
+ return response;
+ }
+ }
+ }
+ }
+
+ if (useAuth){
+ if (this->isSASLLogin == true){
+
+ int size = 1 + strlen(this->email_login)+ strlen(this->email_password)+2;
+ char * logPass = (char *) malloc(size);
+
+// strcpy(logPass, " ");
+// strcat(logPass, this->email_login);
+// strcat(logPass, " ");
+// strcat(logPass, this->email_password);
+
+// String logPass;
+ int maincont = 0;
+
+ logPass[maincont++] = ' ';
+ logPass[maincont++] = (char) 0;
+
+ for (unsigned int i = 0;iemail_login);i++){
+ logPass[maincont++] = this->email_login[i];
+ }
+ logPass[maincont++] = (char) 0;
+ for (unsigned int i = 0;iemail_password);i++){
+ logPass[maincont++] = this->email_password[i];
+ }
+
+
+// strcpy(logPass, "\0");
+// strcat(logPass, this->email_login);
+// strcat(logPass, "\0");
+// strcat(logPass, this->email_password);
+
+ String auth = "AUTH PLAIN "+String(encode64_f(logPass, size));
+// String auth = "AUTH PLAIN "+String(encode64(logPass));
+ DEBUG_PRINTLN(auth);
+ client.println(auth);
+ }
+#if defined(ESP32)
+ else if (this->isCramMD5Login == true) {
+ DEBUG_PRINTLN(F("AUTH CRAM-MD5"));
+ client.println(F("AUTH CRAM-MD5"));
+
+ // read back the base64 encoded digest.
+ //
+ response = awaitSMTPResponse(client,"334","No digest error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ };
+ _serverResponce = _serverResponce.substring(4); // '334'
+
+ size_t b64l = _serverResponce.length()-1; // C vs C++ counting of \0
+ const unsigned char * b64 = (const unsigned char *)_serverResponce.c_str();
+ DEBUG_PRINTLN("B64digest="+String((char *)b64) + " Len=" + String((int)b64l));
+
+ unsigned char digest[256];
+ size_t len;
+
+ int e = mbedtls_base64_decode(digest, sizeof(digest), &len, b64, b64l);
+ if (e || len < 3 || len >= 256) {
+ response.code = F("999");
+ response.desc = F("Invalid digest");
+ response.status = false;
+ client.flush();
+ client.stop();
+ return response;
+ };
+
+ // calculate HMAC with the password as the key of this digest.
+ //
+ mbedtls_md_context_t ctx;
+ mbedtls_md_type_t md_type = MBEDTLS_MD_MD5;
+ unsigned char md5[16];
+
+ mbedtls_md_init(&ctx);
+ mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
+ mbedtls_md_hmac_starts(&ctx,
+ (const unsigned char *)this->email_password, strlen(this->email_password));
+ mbedtls_md_hmac_update(&ctx, digest, len);
+ mbedtls_md_hmac_finish(&ctx, md5);
+ mbedtls_md_free(&ctx);
+
+ // build an output string of the username followed by the __lowercase__ hex of the md5
+ //
+ String rsp = String(this->email_login) + " ";
+ for(int i = 0; i < sizeof(md5); i++) {
+ unsigned char c = md5[i];
+ char h[16+1] = "0123456789abcdef";
+ rsp += String(h[ (c >> 4) &0xF]) + String(h[ (c >> 0) &0xF]);
+ };
+
+ // And submit this to the server as a login string.
+ DEBUG_PRINTLN(encode64((char*)rsp.c_str()));
+ client.println(encode64((char*)rsp.c_str()));
+
+ // now exepct the normal login confirmation to continue.
+ }
+#endif
+ else{
+ DEBUG_PRINTLN(F("AUTH LOGIN:"));
+ client.println(F("AUTH LOGIN"));
+ awaitSMTPResponse(client);
+
+ DEBUG_PRINTLN(encode64(this->email_login));
+ client.println(encode64(this->email_login));
+ awaitSMTPResponse(client);
+
+ DEBUG_PRINTLN(encode64(this->email_password));
+ client.println(encode64(this->email_password));
+ }
+ response = awaitSMTPResponse(client, "235", "SMTP AUTH error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+ }
+ DEBUG_PRINT(F("MAIL FROM: <"));
+ DEBUG_PRINT(this->email_from);
+ DEBUG_PRINTLN(F(">"));
+
+ client.print(F("MAIL FROM: <"));
+ client.print(this->email_from);
+ client.println(F(">"));
+ awaitSMTPResponse(client);
+
+// String rcpt = "RCPT TO: <" + String(to) + '>';
+//
+// DEBUG_PRINTLN(rcpt);
+// client.println(rcpt);
+
+ int cont;
+ for (cont=0;cont<(sizeOfTo+sizeOfCc+sizeOfCCn);cont++){
+ DEBUG_PRINT(F("RCPT TO: <"));
+ DEBUG_PRINT(to[cont]);
+ DEBUG_PRINTLN(F(">"));
+
+ client.print(F("RCPT TO: <"));
+ client.print(to[cont]);
+ client.println(F(">"));
+ awaitSMTPResponse(client);
+ }
+
+ DEBUG_PRINTLN(F("DATA:"));
+ client.println(F("DATA"));
+
+ response = awaitSMTPResponse(client, "354", "SMTP DATA error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+
+// client.println("From: <" + String(this->email_from) + '>');
+
+ client.print(F("From: "));
+ if (this->name_from){
+ client.print(this->name_from);
+ }
+ client.print(F(" <"));
+ client.print(this->email_from);
+ client.println(F(">"));
+
+// client.println("To: <" + String(to) + '>');
+
+ client.print(F("To: "));
+ for (cont=0;cont");
+ if (cont!=sizeOfTo-1){
+ client.print(",");
+ }
+ }
+ client.println();
+
+ if (sizeOfCc>0){
+ client.print(F("Cc: "));
+ for (;cont");
+ if (cont!=sizeOfCc-1){
+ client.print(",");
+ }
+ }
+ client.println();
+ }
+
+ if (sizeOfCCn>0){
+ client.print(F("CCn: "));
+ for (;cont");
+ if (cont!=sizeOfCCn-1){
+ client.print(",");
+ }
+ }
+ client.println();
+ }
+
+ client.print(F("Subject: "));
+ client.println(email.subject);
+
+// client.println(F("Mime-Version: 1.0"));
+
+ client.println(F("MIME-Version: 1.0"));
+ client.println(F("Content-Type: Multipart/mixed; boundary=frontier"));
+
+ client.println(F("--frontier"));
+
+ client.print(F("Content-Type: "));
+ client.print(email.mime);
+ client.println(F("; charset=\"UTF-8\""));
+// client.println(F("Content-Type: text/html; charset=\"UTF-8\""));
+ client.println(F("Content-Transfer-Encoding: 7bit"));
+ client.println();
+ if (email.mime==F("text/html")){
+// String body = "" + String(email.message) + "";
+
+ client.print(F(""));
+ client.print(email.message);
+ client.println(F(""));
+
+// client.println(body);
+ }else{
+ client.println(email.message);
+ }
+ client.println();
+
+#ifdef STORAGE_INTERNAL_ENABLED
+ bool spiffsActive = false;
+#endif
+#ifdef STORAGE_EXTERNAL_ENABLED
+ bool sdActive = false;
+#endif
+
+#if defined(ENABLE_ATTACHMENTS) && (defined(STORAGE_EXTERNAL_ENABLED) || defined(STORAGE_INTERNAL_ENABLED))
+// if ((sizeof(attachs) / sizeof(attachs[0]))>0){
+ if (sizeof(attachments)>0 && attachments.number>0){
+
+ DEBUG_PRINT(F("Array: "));
+// for (int i = 0; i<(sizeof(attachs) / sizeof(attachs[0])); i++){
+ for (int i = 0; i "));
+ DEBUG_PRINTLN(myFile.name());
+ if (attachments.fileDescriptor[i].encode64){
+ encode(&myFile, &client);
+ }else{
+ while(myFile.available()) {
+ clientCount = myFile.read(tBuf,64);
+ DEBUG_PRINTLN(clientCount);
+ client.write((byte*)tBuf,clientCount);
+ }
+ }
+ myFile.close();
+
+ client.println();
+ } // Else myfile
+ else {
+ EMailSender::Response response;
+ response.code = F("404");
+ response.desc = "Error opening attachments file "+attachments.fileDescriptor[i].url;
+ response.status = false;
+ client.flush();
+ client.stop();
+
+ return response;
+ } // Close myfile
+ } // Close storageType
+
+#endif
+#ifdef STORAGE_EXTERNAL_ENABLED
+ if (attachments.fileDescriptor[i].storageType==EMAIL_STORAGE_TYPE_SD){
+#ifdef OPEN_CLOSE_SD
+ DEBUG_PRINTLN(F("SD Check"));
+ if (!EXTERNAL_STORAGE_CLASS.exists(attachments.fileDescriptor[i].url.c_str())){
+#if EXTERNAL_STORAGE == STORAGE_SD || EXTERNAL_STORAGE == STORAGE_SDFAT2 || EXTERNAL_STORAGE == STORAGE_SDFAT_RP2040_ESP8266
+ if(!EXTERNAL_STORAGE_CLASS.begin(SD_CS_PIN)){
+ response.code = F("500");
+ response.desc = F("Error on startup SD filesystem!");
+ response.status = false;
+ client.flush();
+ client.stop();
+
+ return response;
+ } // Close EXTERNAL_STORAGE_CLASS.begin
+#elif EXTERNAL_STORAGE == STORAGE_SPIFM
+ Adafruit_FlashTransport_SPI flashTransport(SS, SPI); // Set CS and SPI interface
+ Adafruit_SPIFlash flash(&flashTransport);
+
+ if (!EXTERNAL_STORAGE_CLASS.begin(&flash)) {
+ response.code = F("500");
+ response.desc = F("Error on startup SDFAT2 filesystem!");
+ response.status = false;
+ client.flush();
+ client.stop();
+
+ return response;
+ }
+#endif
+ sdActive = true;
+ } // Close EXTERNAL_STORAGE_CLASS.exists
+#endif
+
+ DEBUG_PRINTLN(F("Open file: "));
+ EMAIL_FILE_EX myFile = EXTERNAL_STORAGE_CLASS.open(attachments.fileDescriptor[i].url.c_str());
+
+ if(myFile) {
+ myFile.seek(0);
+ DEBUG_PRINTLN(F("OK"));
+ if (attachments.fileDescriptor[i].encode64){
+ DEBUG_PRINTLN(F("BASE 64"));
+ encode(&myFile, &client);
+ }else{
+ DEBUG_PRINTLN(F("NORMAL"));
+ while(myFile.available()) {
+ clientCount = myFile.read(tBuf,64);
+ client.write((byte*)tBuf,clientCount);
+ }
+ }
+ myFile.close();
+
+ client.println();
+ } // Else myfile
+ else {
+ response.code = F("404");
+ response.desc = "Error opening attachments file "+attachments.fileDescriptor[i].url;
+ response.status = false;
+ client.flush();
+ client.stop();
+
+ return response;
+ } // Close myFile
+
+ } // Close storageType==EMAIL_STORAGE_TYPE_SD
+#else
+ if (attachments.fileDescriptor[i].storageType==EMAIL_STORAGE_TYPE_SD){
+ response.code = F("500");
+ response.desc = F("EMAIL_STORAGE_TYPE_SD not enabled on EMailSenderKey.h");
+ response.status = false;
+ client.flush();
+ client.stop();
+
+ return response;
+ }
+#endif
+
+ } // Close attachment cycle
+ client.println();
+ client.println(F("--frontier--"));
+#ifdef STORAGE_EXTERNAL_ENABLED
+ #ifdef OPEN_CLOSE_SD
+ if (sdActive){
+ DEBUG_PRINTLN(F("SD end"));
+ #ifndef ARDUINO_ESP8266_RELEASE_2_4_2
+ #if EXTERNAL_STORAGE != STORAGE_SDFAT_RP2040_ESP8266
+ EXTERNAL_STORAGE_CLASS.end();
+ #endif
+ #endif
+ DEBUG_PRINTLN(F("SD end 2"));
+ }
+ #endif
+#endif
+
+#ifdef STORAGE_INTERNAL_ENABLED
+ #ifdef OPEN_CLOSE_INTERNAL
+ #if INTERNAL_STORAGE != STORAGE_SPIFM
+ if (spiffsActive){
+ INTERNAL_STORAGE_CLASS.end();
+ DEBUG_PRINTLN(F("SPIFFS END"));
+ }
+ #endif
+#endif
+
+
+#endif
+ } // Close attachement enable
+
+#endif
+ DEBUG_PRINTLN(F("Message end"));
+ client.println(F("."));
+
+ response = awaitSMTPResponse(client, "250", "Sending message error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+
+ client.println(F("QUIT"));
+
+ response = awaitSMTPResponse(client, "221", "SMTP QUIT error");
+ if (!response.status) {
+ client.flush();
+ client.stop();
+ return response;
+ }
+
+ response.status = true;
+ response.code = F("0");
+ response.desc = F("Message sent!");
+
+ client.flush();
+ client.stop();
+
+ return response;
+}
+
diff --git a/EMailSender.h b/EMailSender.h
new file mode 100644
index 00000000..4249da08
--- /dev/null
+++ b/EMailSender.h
@@ -0,0 +1,525 @@
+/*
+ * EMail Sender Arduino, esp8266, stm32 and esp32 library to send email
+ *
+ * AUTHOR: Renzo Mischianti
+ * VERSION: 3.0.14
+ *
+ * https://www.mischianti.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Renzo Mischianti www.mischianti.org All right reserved.
+ *
+ * You may copy, alter and reuse this code in any way you like, but please leave
+ * reference to www.mischianti.org in your comments if you redistribute this code.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef EMailSender_h
+#define EMailSender_h
+
+#include "EMailSenderKey.h"
+
+#if ARDUINO >= 100
+#include "Arduino.h"
+#else
+#include "WProgram.h"
+#endif
+
+#if(NETWORK_ESP8266_242 == DEFAULT_EMAIL_NETWORK_TYPE_ESP8266)
+ #define ARDUINO_ESP8266_RELEASE_2_4_2
+ #define DEFAULT_EMAIL_NETWORK_TYPE_ESP8266 NETWORK_ESP8266
+#endif
+//
+//#if(NETWORK_ESP8266_SD == DEFAULT_EMAIL_NETWORK_TYPE_ESP8266)
+// #define ESP8266_GT_2_4_2_SD_STORAGE_SELECTED
+// #define DEFAULT_EMAIL_NETWORK_TYPE_ESP8266 NETWORK_ESP8266
+//#endif
+#if !defined(EMAIL_NETWORK_TYPE)
+// select Network type based
+ #if defined(ESP8266) || defined(ESP31B)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_ESP8266
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ESP8266_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ESP8266_STORAGE
+ #elif defined(ARDUINO_ARCH_STM32) || defined(__STM32F1__) || defined(__STM32F4__)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_STM32
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_STM32_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_STM32_STORAGE
+ #elif defined(ESP32)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_ESP32
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ESP32_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ESP32_STORAGE
+ #elif defined(ARDUINO_ARCH_RP2040)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_RP2040
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ARDUINO_RP2040_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ARDUINO_RP2040_STORAGE
+ #elif defined(ARDUINO_ARCH_SAMD)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_SAMD
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ARDUINO_SAMD_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ARDUINO_SAMD_STORAGE
+ #elif defined(ARDUINO_ARCH_MBED)
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_MBED
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ARDUINO_MBED_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ARDUINO_MBED_STORAGE
+ #else
+ #define EMAIL_NETWORK_TYPE DEFAULT_EMAIL_NETWORK_TYPE_ARDUINO
+ #define INTERNAL_STORAGE DEFAULT_INTERNAL_ARDUINO_STORAGE
+ #define EXTERNAL_STORAGE DEFAULT_EXTERNAL_ARDUINO_STORAGE
+ #endif
+#endif
+
+//#if defined(ESP8266) || defined(ESP31B)
+// #ifndef STORAGE_EXTERNAL_FORCE_DISABLE
+// #define STORAGE_EXTERNAL_ENABLED
+// #endif
+// #ifndef STORAGE_INTERNAL_FORCE_DISABLE
+// #define STORAGE_INTERNAL_ENABLED
+// #endif
+//#elif defined(ESP32)
+// #ifndef STORAGE_EXTERNAL_FORCE_DISABLE
+// #define STORAGE_EXTERNAL_ENABLED
+// #endif
+// #ifndef STORAGE_INTERNAL_FORCE_DISABLE
+// #define STORAGE_INTERNAL_ENABLED
+// #endif
+//#elif defined(ARDUINO_ARCH_STM32)
+// #ifndef STORAGE_EXTERNAL_FORCE_DISABLE
+// #define STORAGE_EXTERNAL_ENABLED
+// #endif
+//#else
+// #ifndef STORAGE_EXTERNAL_FORCE_DISABLE
+// #define STORAGE_EXTERNAL_ENABLED
+// #endif
+//#endif
+
+#ifdef ENABLE_ATTACHMENTS
+ #if !defined(STORAGE_EXTERNAL_FORCE_DISABLE) && (EXTERNAL_STORAGE != STORAGE_NONE)
+ #define STORAGE_EXTERNAL_ENABLED
+ #endif
+ #if !defined(STORAGE_INTERNAL_FORCE_DISABLE) && (INTERNAL_STORAGE != STORAGE_NONE)
+ #define STORAGE_INTERNAL_ENABLED
+ #endif
+#endif
+
+// Includes and defined based on Network Type
+#if(EMAIL_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+
+// Note:
+// No SSL/WSS support for client in Async mode
+// TLS lib need a sync interface!
+
+#if defined(ESP8266)
+#include
+#include
+#elif defined(ESP32)
+#include
+#include
+
+#ifndef FORCE_DISABLE_SSL
+ #define EMAIL_NETWORK_CLASS WiFiClientSecure
+#else
+ #define EMAIL_NETWORK_CLASS WiFiClient
+#endif
+//#define EMAIL_NETWORK_SSL_CLASS WiFiClientSecure
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+
+#elif defined(ESP31B)
+#include
+#else
+#error "network type ESP8266 ASYNC only possible on the ESP mcu!"
+#endif
+//
+//#include
+//#include
+//#define EMAIL_NETWORK_CLASS AsyncTCPbuffer
+//#define EMAIL_NETWORK_SERVER_CLASS AsyncServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ESP8266 || EMAIL_NETWORK_TYPE == NETWORK_ESP8266_242)
+
+#if !defined(ESP8266) && !defined(ESP31B)
+#error "network type ESP8266 only possible on the ESP mcu!"
+#endif
+
+#ifdef ESP8266
+#include
+#else
+#include
+#endif
+#ifndef FORCE_DISABLE_SSL
+ #define EMAIL_NETWORK_CLASS WiFiClientSecure
+#else
+ #define EMAIL_NETWORK_CLASS WiFiClient
+#endif
+//#define EMAIL_NETWORK_SSL_CLASS WiFiClientSecure
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_W5100 || EMAIL_NETWORK_TYPE == NETWORK_ETHERNET_ENC)
+
+#include
+#include
+#define EMAIL_NETWORK_CLASS EthernetClient
+#define EMAIL_NETWORK_SERVER_CLASS EthernetServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ETHERNET_GENERIC)
+
+#include
+#include
+#define EMAIL_NETWORK_CLASS EthernetClient
+#define EMAIL_NETWORK_SERVER_CLASS EthernetServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ENC28J60 || EMAIL_NETWORK_TYPE == NETWORK_UIPETHERNET)
+
+#include
+
+#define EMAIL_NETWORK_CLASS UIPClient
+#define EMAIL_NETWORK_SERVER_CLASS UIPServer
+
+//#include
+//UIPClient base_client;
+//SSLClient client(base_client, TAs, (size_t)TAs_NUM, A5);
+//
+//#define EMAIL_NETWORK_CLASS SSLClient
+//#define EMAIL_NETWORK_SERVER_CLASS UIPServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ESP32)
+
+#include
+#include
+#ifndef FORCE_DISABLE_SSL
+ #define EMAIL_NETWORK_CLASS WiFiClientSecure
+#else
+ #define EMAIL_NETWORK_CLASS WiFiClient
+#endif
+//#define EMAIL_NETWORK_SSL_CLASS WiFiClientSecure
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ESP32_ETH)
+
+#include
+#define EMAIL_NETWORK_CLASS WiFiClient
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ETHERNET_LARGE)
+
+#include
+#include
+#define EMAIL_NETWORK_CLASS EthernetClient
+#define EMAIL_NETWORK_SERVER_CLASS EthernetServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ETHERNET_2)
+
+#include
+#include
+#define EMAIL_NETWORK_CLASS EthernetClient
+#define EMAIL_NETWORK_SERVER_CLASS EthernetServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_ETHERNET_STM)
+
+#include
+#include
+#define EMAIL_NETWORK_CLASS EthernetClient
+#define EMAIL_NETWORK_SERVER_CLASS EthernetServer
+
+#elif(EMAIL_NETWORK_TYPE == NETWORK_WiFiNINA)
+
+#include
+#ifndef FORCE_DISABLE_SSL
+ #define EMAIL_NETWORK_CLASS WiFiSSLClient
+#else
+ #define EMAIL_NETWORK_CLASS WiFiClient
+#endif
+//#define EMAIL_NETWORK_SSL_CLASS WiFiSSLClient
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+#elif(EMAIL_NETWORK_TYPE == NETWORK_MBED_WIFI)
+
+#include
+#include
+
+#ifndef FORCE_DISABLE_SSL
+ #define EMAIL_NETWORK_CLASS WiFiSSLClient
+#else
+ #define EMAIL_NETWORK_CLASS WiFiClient
+#endif
+//#define EMAIL_NETWORK_SSL_CLASS WiFiSSLClient
+#define EMAIL_NETWORK_SERVER_CLASS WiFiServer
+
+#else
+#error "no network type selected!"
+#endif
+
+#ifdef STORAGE_INTERNAL_ENABLED
+// #define FS_NO_GLOBALS
+ #if (INTERNAL_STORAGE == STORAGE_SPIFFS)
+ #if defined(ESP32)
+ #include
+ #define INTERNAL_STORAGE_CLASS SPIFFS
+
+ #define EMAIL_FILE_READ "r"
+ #elif defined(ESP8266)
+ #ifdef ARDUINO_ESP8266_RELEASE_2_4_2
+ #define DIFFERENT_FILE_MANAGE
+ #endif
+ #include "FS.h"
+
+ #define INTERNAL_STORAGE_CLASS SPIFFS
+ #define EMAIL_FILE_READ "r"
+ #endif
+ #define EMAIL_FILE fs::File
+ #elif (INTERNAL_STORAGE == STORAGE_LITTLEFS)
+ #if defined(ESP32)
+ #if ESP_ARDUINO_VERSION_MAJOR >= 2
+ #include "FS.h"
+ #include "LittleFS.h"
+ #define INTERNAL_STORAGE_CLASS LittleFS
+ #else
+ #include "LITTLEFS.h"
+ #define INTERNAL_STORAGE_CLASS LITTLEFS
+ #endif
+ #else
+ #include "LittleFS.h"
+ #define INTERNAL_STORAGE_CLASS LittleFS
+ #endif
+ #define EMAIL_FILE_READ "r"
+ #define EMAIL_FILE fs::File
+ #elif (INTERNAL_STORAGE == STORAGE_FFAT)
+ #include "FFat.h"
+ #define INTERNAL_STORAGE_CLASS FFat
+ #define EMAIL_FILE_READ 'r'
+ #define EMAIL_FILE fs::File
+ #elif (INTERNAL_STORAGE == STORAGE_SPIFM)
+ #include
+
+ #include "SdFat.h"
+ #include "Adafruit_SPIFlash.h"
+
+ #define INTERNAL_STORAGE_CLASS fatfs
+ extern FatFileSystem INTERNAL_STORAGE_CLASS;
+
+ #define EMAIL_FILE_READ O_READ
+ #define EMAIL_FILE File
+ #endif
+#endif
+
+#ifdef STORAGE_EXTERNAL_ENABLED
+ #include
+ #if (EXTERNAL_STORAGE == STORAGE_SDFAT_RP2040_ESP8266)
+ #include
+ #include
+
+ #define EXTERNAL_STORAGE_CLASS sd
+ extern SdFat EXTERNAL_STORAGE_CLASS;
+
+ #define EMAIL_FILE_READ_EX FILE_READ
+ #define EMAIL_FILE_EX File32
+
+ #define DIFFERENT_FILE_MANAGE
+ #elif (EXTERNAL_STORAGE == STORAGE_SDFAT2)
+ #include
+ #include
+
+ #define EXTERNAL_STORAGE_CLASS sd
+ extern SdFat EXTERNAL_STORAGE_CLASS;
+
+ #define EMAIL_FILE_READ_EX FILE_READ
+ #define EMAIL_FILE_EX FsFile
+
+ #define DIFFERENT_FILE_MANAGE
+ #elif (EXTERNAL_STORAGE == STORAGE_SD)
+ #include
+
+ #define EXTERNAL_STORAGE_CLASS SD
+ #define EMAIL_FILE_READ_EX 'r'
+
+ #define EMAIL_FILE_EX File
+ #endif
+#endif
+
+//#ifdef EMAIL_NETWORK_SSL_CLASS
+// #ifndef FORCE_DISABLE_SSL
+// #define EMAIL_NETWORK_CLASS WiFiClientSecure
+// #else
+// #define EMAIL_NETWORK_CLASS WiFiClient
+// #endif
+//#endif
+
+#define OPEN_CLOSE_INTERNAL
+#define OPEN_CLOSE_SD
+
+// Setup debug printing macros.
+#ifdef EMAIL_SENDER_DEBUG
+ #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
+ #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
+#else
+ #define DEBUG_PRINT(...) {}
+ #define DEBUG_PRINTLN(...) {}
+#endif
+
+// Debug level for SSLClient
+#ifndef EMAIL_SENDER_SSL_CLIENT_DEBUG
+ #define EMAIL_SENDER_SSL_CLIENT_DEBUG 2
+#endif
+
+class EMailSender {
+public:
+ EMailSender(const char* email_login, const char* email_password, const char* email_from, const char* name_from, const char* smtp_server, uint16_t smtp_port );
+ EMailSender(const char* email_login, const char* email_password, const char* email_from, const char* smtp_server, uint16_t smtp_port);
+ EMailSender(const char* email_login, const char* email_password, const char* email_from, const char* name_from );
+ EMailSender(const char* email_login, const char* email_password, const char* email_from);
+ EMailSender(const char* email_login, const char* email_password);
+
+#define STORAGE_SPIFFS (1)
+#define STORAGE_LITTLEFS (2)
+#define STORAGE_FFAT (3)
+#define STORAGE_SPIFM (5) // Libraries Adafruit_SPIFlash and SdFat-Adafruit-Fork
+// EXTERNAL STORAGE
+#define STORAGE_SD (4)
+#define STORAGE_SDFAT2 (6) // Library SdFat version >= 2.0.2
+#define STORAGE_SDFAT_RP2040_ESP8266 (7) // Library ESP8266SdFat on Raspberry Pi Pico
+
+
+ enum StorageType {
+ EMAIL_STORAGE_TYPE_SPIFFS,
+ EMAIL_STORAGE_TYPE_LITTLE_FS,
+ EMAIL_STORAGE_TYPE_FFAT,
+ EMAIL_STORAGE_TYPE_SPIFM,
+ EMAIL_STORAGE_TYPE_SD
+ };
+
+#define MIME_TEXT_HTML F("text/html")
+#define MIME_TEXT_PLAIN F("text/plain")
+#define MIME_IMAGE_JPG F("image/jpg")
+#define MIME_IMAGE_PNG F("image/png")
+
+ typedef struct {
+ String mime = "text/html";
+ String subject;
+ String message;
+ } EMailMessage;
+
+#ifdef ENABLE_ATTACHMENTS
+ typedef struct {
+ StorageType storageType = EMAIL_STORAGE_TYPE_SD;
+ String mime;
+ bool encode64 = false;
+ String filename;
+ String url;
+ } FileDescriptior;
+
+ typedef struct {
+ byte number;
+ FileDescriptior *fileDescriptor;
+ } Attachments;
+#else
+ typedef struct {
+ char* noop1;
+ char* noop2;
+ } Attachments;
+#endif
+
+ typedef struct {
+ String code;
+ String desc;
+ bool status = false;
+ } Response;
+
+ void setSMTPPort(uint16_t smtp_port);
+ void setSMTPServer(const char* smtp_server);
+ void setEMailLogin(const char* email_login);
+ void setEMailFrom(const char* email_from);
+ void setNameFrom(const char* name_from);
+ void setEMailPassword(const char* email_password);
+
+ EMailSender::Response send(char* to[], byte sizeOfTo, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(char* to[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(char* to[], byte sizeOfTo, byte sizeOfCc, byte sizeOfCCn, EMailMessage &email, Attachments att = {0, 0});
+
+ EMailSender::Response send(const char* to, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(const char* to[], byte sizeOfTo, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(const char* to[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(const char* to[], byte sizeOfTo, byte sizeOfCc, byte sizeOfCCn, EMailMessage &email, Attachments att = {0, 0});
+
+ EMailSender::Response send(String to, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(String to[], byte sizeOfTo, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(String to[], byte sizeOfTo, byte sizeOfCc, EMailMessage &email, Attachments att = {0, 0});
+ EMailSender::Response send(String to[], byte sizeOfTo, byte sizeOfCc, byte sizeOfCCn, EMailMessage &email, Attachments att = {0, 0});
+
+ void setIsSecure(bool isSecure = false);
+
+ void setUseAuth(bool useAuth = true) {
+ this->useAuth = useAuth;
+ }
+
+ void setPublicIpDescriptor(const char *publicIpDescriptor = "mischianti") {
+ publicIPDescriptor = publicIpDescriptor;
+ }
+
+ void setEHLOCommand(bool useEHLO = false) {
+ this->useEHLO = useEHLO;
+ }
+
+ void setSASLLogin(bool isSASLLogin = false) {
+ this->isSASLLogin = isSASLLogin;
+ }
+
+#if defined(ESP32)
+ // Conditional - as it relies on considerable crypto infra.
+ void setCramMD5Login(bool onoff= false) {
+ this->isCramMD5Login = onoff;
+ }
+#endif
+
+ void setAdditionalResponseLineOnConnection(uint8_t numLines = 0) {
+ this->additionalResponseLineOnConnection = numLines;
+ }
+ void setAdditionalResponseLineOnHELO(uint8_t numLines = 0) {
+ this->additionalResponseLineOnHELO = numLines;
+ }
+
+private:
+ uint16_t smtp_port = 465;
+ char* smtp_server = strdup("smtp.gmail.com");
+ char* email_login = 0;
+ char* email_from = 0;
+ char* name_from = 0;
+ char* email_password = 0;
+
+ const char* publicIPDescriptor = "mischianti";
+
+ bool isSecure = false;
+
+ bool useEHLO = false;
+ bool isSASLLogin = false;
+
+ bool useAuth = true;
+ bool isCramMD5Login = false;
+
+ String _serverResponce;
+
+ uint8_t additionalResponseLineOnConnection = 0;
+ uint8_t additionalResponseLineOnHELO = 0;
+
+#ifdef SSLCLIENT_WRAPPER
+ Response awaitSMTPResponse(SSLClient &client, const char* resp = "", const char* respDesc = "", uint16_t timeOut = 10000);
+#else
+ Response awaitSMTPResponse(EMAIL_NETWORK_CLASS &client, const char* resp = "", const char* respDesc = "", uint16_t timeOut = 10000);
+#endif
+};
+
+#endif
diff --git a/EMailSenderKey.h b/EMailSenderKey.h
new file mode 100644
index 00000000..75694b50
--- /dev/null
+++ b/EMailSenderKey.h
@@ -0,0 +1,157 @@
+/*
+ * EMail Sender Arduino, esp8266, stm32 and esp32 library to send email
+ *
+ * AUTHOR: Renzo Mischianti
+ * VERSION: 3.0.14
+ *
+ * https://www.mischianti.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Renzo Mischianti www.mischianti.org All right reserved.
+ *
+ * You may copy, alter and reuse this code in any way you like, but please leave
+ * reference to www.mischianti.org in your comments if you redistribute this code.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef EMailSenderKey_h
+#define EMailSenderKey_h
+
+// Uncomment if you use esp8266 core <= 2.4.2
+//#define ARDUINO_ESP8266_RELEASE_2_4_2
+
+// If you want disable attachments and save memory comment this define
+//#define ENABLE_ATTACHMENTS
+
+// Uncomment to enable printing out nice debug messages.
+//#define EMAIL_SENDER_DEBUG
+
+// Define where debug output will be printed.
+#define DEBUG_PRINTER Serial
+
+#define STORAGE_NONE (0)
+// INTERNAL STORAGE
+#define STORAGE_SPIFFS (1)
+#define STORAGE_LITTLEFS (2)
+#define STORAGE_FFAT (3)
+#define STORAGE_SPIFM (5) // Libraries Adafruit_SPIFlash and SdFat-Adafruit-Fork
+// EXTERNAL STORAGE
+#define STORAGE_SD (4)
+#define STORAGE_SDFAT2 (6) // Library SdFat version >= 2.0.2
+#define STORAGE_SDFAT_RP2040_ESP8266 (7) // Library ESP8266SdFat on Raspberry Pi Pico
+
+#define NETWORK_ESP8266_ASYNC (0)
+#define NETWORK_ESP8266 (1)
+#define NETWORK_ESP8266_242 (6)
+#define NETWORK_W5100 (2)
+#define NETWORK_ETHERNET (2) // Standard Arduino Ethernet library
+#define NETWORK_ENC28J60 (3)
+#define NETWORK_ESP32 (4)
+#define NETWORK_RP2040_WIFI (4)
+#define NETWORK_ESP32_ETH (5)
+#define NETWORK_WiFiNINA (7)
+#define NETWORK_ETHERNET_LARGE (8)
+#define NETWORK_ETHERNET_ENC (9)
+#define NETWORK_ETHERNET_STM (10)
+#define NETWORK_UIPETHERNET (11)
+#define NETWORK_ETHERNET_2 (12)
+#define NETWORK_ETHERNET_GENERIC (13) // Ethernet generic
+#define NETWORK_MBED_WIFI (14) // Arduino GIGA R1 WiFi
+
+// if you want force disable SSL if present uncomment this define
+// #define FORCE_DISABLE_SSL
+
+// If you want add a wrapper to emulate SSL over Client like EthernetClient
+// #define SSLCLIENT_WRAPPER
+
+// esp8266 microcontrollers configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_ESP8266
+ #define DEFAULT_EMAIL_NETWORK_TYPE_ESP8266 NETWORK_ESP8266
+ #define DEFAULT_INTERNAL_ESP8266_STORAGE STORAGE_LITTLEFS
+ #define DEFAULT_EXTERNAL_ESP8266_STORAGE STORAGE_NONE
+#endif
+// esp32 microcontrollers configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_ESP32
+ #define DEFAULT_EMAIL_NETWORK_TYPE_ESP32 NETWORK_ESP32
+ #define DEFAULT_INTERNAL_ESP32_STORAGE STORAGE_SPIFFS
+ #define DEFAULT_EXTERNAL_ESP32_STORAGE STORAGE_SD
+#endif
+// stm32 microcontrollers configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_STM32
+ #define DEFAULT_EMAIL_NETWORK_TYPE_STM32 NETWORK_W5100
+ #define DEFAULT_INTERNAL_STM32_STORAGE STORAGE_NONE
+ #define DEFAULT_EXTERNAL_STM32_STORAGE STORAGE_SDFAT2
+#endif
+// Arduino microcontrollers configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_ARDUINO
+ #define DEFAULT_EMAIL_NETWORK_TYPE_ARDUINO NETWORK_W5100
+ #define DEFAULT_INTERNAL_ARDUINO_STORAGE STORAGE_NONE
+ #define DEFAULT_EXTERNAL_ARDUINO_STORAGE STORAGE_SD
+#endif
+// Arduino SAMD microcontrollers configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_ARDUINO_SAMD
+ #define DEFAULT_EMAIL_NETWORK_TYPE_SAMD NETWORK_WiFiNINA
+ #define DEFAULT_INTERNAL_ARDUINO_SAMD_STORAGE STORAGE_NONE
+ #define DEFAULT_EXTERNAL_ARDUINO_SAMD_STORAGE STORAGE_SD
+#endif
+// Raspberry Pi Pico (rp2040) configuration
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_RP2040
+ #define DEFAULT_EMAIL_NETWORK_TYPE_RP2040 NETWORK_RP2040_WIFI
+ #define DEFAULT_INTERNAL_ARDUINO_RP2040_STORAGE STORAGE_LITTLEFS
+ #define DEFAULT_EXTERNAL_ARDUINO_RP2040_STORAGE STORAGE_NONE
+#endif
+// Arduino MBED microcontrollers configuration LIKE Arduino GIGA
+#ifndef DEFAULT_EMAIL_NETWORK_TYPE_ARDUINO_MBED
+ #define DEFAULT_EMAIL_NETWORK_TYPE_MBED NETWORK_MBED_WIFI
+ #define DEFAULT_INTERNAL_ARDUINO_MBED_STORAGE STORAGE_NONE
+ #define DEFAULT_EXTERNAL_ARDUINO_MBED_STORAGE STORAGE_SD
+#endif
+
+#ifdef SSLCLIENT_WRAPPER
+ // Generate the trust_anchors.h with this online generator
+ // https://openslab-osu.github.io/bearssl-certificate-utility/
+ /**
+ * For Ethernet w5x00 card you must follow the step given
+ * from this link https://github.com/OPEnSLab-OSU/SSLClient#sslclient-with-ethernet
+ * you can modify Ethernet library or use directly this modified one
+ * https://github.com/OPEnSLab-OSU/EthernetLarge
+ *
+ * For enc28j60 use EthernetENC available from library manager or
+ * https://github.com/jandrassy/EthernetENC
+ */
+ #define ANALOG_PIN A7
+ #include
+ #include "trust_anchors.h"
+
+ #define PUT_OUTSIDE_SCOPE_CLIENT_DECLARATION
+#endif
+
+// This two value can take a number 1..20 or you can specify 'a'
+// but with auto you can lost specified response error
+#define DEFAULT_EHLO_RESPONSE_COUNT 6
+#define DEFAULT_CONNECTION_RESPONSE_COUNT 0
+
+#define SD_CS_PIN SS
+#define SPIFM_CS_PIN SS
+
+//#define STORAGE_INTERNAL_FORCE_DISABLE
+//#define STORAGE_EXTERNAL_FORCE_DISABLE
+
+#endif
diff --git a/main.cpp b/main.cpp
index 57ccd7fb..d8c2dba1 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,12 +30,13 @@
#include "opensprinkler_server.h"
#include "mqtt.h"
#include "main.h"
-#if defined(ARUDINO)
- #include "EmailSender.h"
+#include "EmailSender.h"
+#include "smtp.h"
+
+#if defined(ARDUINO)
#else
- #include "smtp.h"
#define MAIL_CONNECTION_SECURITY SMTP_SECURITY_TLS
- #define MAIL_FLAGS (SMTP_DEBUG | SMTP_NO_CERT_VERIFY)
+ #define MAIL_FLAGS (smtp_flag)(SMTP_NO_CERT_VERIFY)
#define MAIL_AUTH SMTP_AUTH_PLAIN
#endif
@@ -1611,7 +1612,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
break;
case NOTIFY_REBOOT:
- #if defined(ARUDINO)
+ #if defined(ARDUINO)
if(!delayed){
delayed = true;
return;
diff --git a/platformio.ini b/platformio.ini
index 8e68b6be..8af89200 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -22,7 +22,6 @@ lib_deps =
https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip
knolleary/PubSubClient @ ^2.8
https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.1.0
- xreef/EMailSender@^3.0.14
build_src_filter = +<*> -
upload_speed = 460800
monitor_speed = 115200
diff --git a/smtp.c b/smtp.c
index 40e77b49..c256a4f6 100644
--- a/smtp.c
+++ b/smtp.c
@@ -22,6 +22,9 @@
* .
*/
+#if defined(ARDUINO)
+#else
+
#if defined(_WIN32) || defined(WIN32)
# define SMTP_IS_WINDOWS
#endif /* SMTP_IS_WINDOWS */
@@ -385,7 +388,7 @@ smtp_str_getdelimfd_read(struct str_getdelimfd *const gdfd,
struct smtp *smtp;
long bytes_read;
- smtp = gdfd->user_data;
+ smtp = (struct smtp*)(gdfd->user_data);
if(smtp_str_getdelimfd_read_timeout(smtp) != SMTP_STATUS_OK){
return -1;
@@ -468,7 +471,7 @@ smtp_str_getdelimfd_set_line_and_buf(struct str_getdelimfd *const gdfd,
if(smtp_si_add_size_t(copy_len, 1, ©_len_inc) ||
smtp_si_add_size_t((size_t)gdfd->_buf, copy_len_inc, NULL) ||
smtp_si_sub_size_t(gdfd->_buf_len, copy_len, &nbytes_to_shift) ||
- (gdfd->line = calloc(1, copy_len_inc)) == NULL){
+ (gdfd->line = (char*)(calloc(1, copy_len_inc))) == NULL){
return -1;
}
memcpy(gdfd->line, gdfd->_buf, copy_len);
@@ -560,7 +563,7 @@ smtp_str_getdelimfd(struct str_getdelimfd *const gdfd){
&buf_sz_new)){
return smtp_str_getdelimfd_throw_error(gdfd);
}
- buf_new = realloc(gdfd->_buf, buf_sz_new);
+ buf_new = (char*)(realloc(gdfd->_buf, buf_sz_new));
if(buf_new == NULL){
return smtp_str_getdelimfd_throw_error(gdfd);
}
@@ -657,7 +660,7 @@ smtp_strdup(const char *s){
dup = NULL;
errno = ENOMEM;
}
- else if((dup = malloc(dup_len)) != NULL){
+ else if((dup = (char*)(malloc(dup_len))) != NULL){
memcpy(dup, s, dup_len);
}
return dup;
@@ -731,7 +734,7 @@ smtp_str_replace(const char *const search,
/* snew_sz += snew_sz + slen + replace_len + 1 */
if(smtp_si_add_size_t(snew_sz_dup, slen_inc, &snew_sz) ||
smtp_si_add_size_t(snew_sz, replace_len, &snew_sz) ||
- (stmp = realloc(snew, snew_sz)) == NULL){
+ (stmp = (char*)(realloc(snew, snew_sz))) == NULL){
free(snew);
return NULL;
}
@@ -746,7 +749,7 @@ smtp_str_replace(const char *const search,
/* snew_sz += snew_sz + slen + snew_len + 1 */
if(smtp_si_add_size_t(snew_sz_dup, slen, &snew_sz) ||
smtp_si_add_size_t(snew_sz, snew_len_inc, &snew_sz) ||
- (stmp = realloc(snew, snew_sz)) == NULL){
+ (stmp = (char*)(realloc(snew, snew_sz))) == NULL){
free(snew);
return NULL;
}
@@ -845,7 +848,7 @@ smtp_base64_encode(const char *const buf,
return NULL;
}
b64_sz = (4 * buflen / 3) + 1 + 2 + 1;
- if((b64 = calloc(1, b64_sz)) == NULL){
+ if((b64 = (char*)(calloc(1, b64_sz))) == NULL){
return NULL;
}
@@ -998,7 +1001,7 @@ smtp_base64_decode(const char *const buf,
}
if(smtp_si_add_size_t(buf_len, 1, &buf_len_inc) ||
- (b64_decode = calloc(1, buf_len_inc)) == NULL){
+ (b64_decode = (unsigned char*)(calloc(1, buf_len_inc))) == NULL){
return SIZE_MAX;
}
@@ -1042,7 +1045,7 @@ smtp_bin2hex(const unsigned char *const s,
smtp_si_add_size_t(alloc_sz, 1, &alloc_sz)){
return NULL;
}
- if((snew = malloc(alloc_sz)) == NULL){
+ if((snew = (char*)(malloc(alloc_sz))) == NULL){
return NULL;
}
@@ -1276,7 +1279,7 @@ smtp_fold_whitespace(const char *const s,
if(smtp_si_add_size_t(bufsz, ws_offset, &bufsz) ||
smtp_si_add_size_t(bufsz, end_slen, &bufsz) ||
smtp_si_add_size_t(bufsz, 1, &bufsz) ||
- (buf_new = realloc(buf, bufsz)) == NULL){
+ (buf_new = (char*)(realloc(buf, bufsz))) == NULL){
free(buf);
return NULL;
}
@@ -1343,7 +1346,7 @@ smtp_chunk_split(const char *const s,
smtp_si_add_size_t(bodylen, 1, &bodylen_inc) ||
smtp_si_mul_size_t(endlen_inc, bodylen / chunklen + 1, &snewlen) ||
smtp_si_add_size_t(snewlen, bodylen_inc, &snewlen) ||
- (snew = calloc(1, snewlen)) == NULL){
+ (snew = (char*)(calloc(1, snewlen))) == NULL){
return NULL;
}
@@ -1401,7 +1404,7 @@ smtp_ffile_get_contents(FILE *stream,
do{
if(smtp_si_add_size_t(bufsz, BUFSZ_INCREMENT, &bufsz_inc) ||
- (new_buf = realloc(read_buf, bufsz_inc)) == NULL){
+ (new_buf = (char*)(realloc(read_buf, bufsz_inc))) == NULL){
free(read_buf);
return NULL;
}
@@ -1676,7 +1679,7 @@ smtp_puts_terminate(struct smtp *const smtp,
slen = strlen(s);
if(smtp_si_add_size_t(slen, 3, &allocsz) ||
- (line = malloc(allocsz)) == NULL){
+ (line = (char*)(malloc(allocsz))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
concat = smtp_stpcpy(line, s);
@@ -1908,7 +1911,7 @@ smtp_auth_plain(struct smtp *const smtp,
/* login_len = 1 + user_len + 1 + pass_len */
if(smtp_si_add_size_t(user_len, pass_len, &login_len) ||
smtp_si_add_size_t(login_len, 2, &login_len) ||
- (login_str = malloc(login_len)) == NULL){
+ (login_str = (char*)(malloc(login_len))) == NULL){
return -1;
}
login_str[0] = '\0';
@@ -1926,7 +1929,7 @@ smtp_auth_plain(struct smtp *const smtp,
/* (3) */
login_b64_len = strlen(login_b64);
if(smtp_si_add_size_t(login_b64_len, 14, &login_b64_len) ||
- (login_send = malloc(login_b64_len)) == NULL){
+ (login_send = (char*)(malloc(login_b64_len))) == NULL){
free(login_b64);
return -1;
}
@@ -1980,7 +1983,7 @@ smtp_auth_login(struct smtp *const smtp,
/* (2) */
b64_user_len = strlen(b64_user);
if(smtp_si_add_size_t(b64_user_len, 14, &b64_user_len) ||
- (login_str = malloc(b64_user_len)) == NULL){
+ (login_str = (char*)(malloc(b64_user_len))) == NULL){
free(b64_user);
return -1;
}
@@ -2098,7 +2101,7 @@ smtp_auth_cram_md5(struct smtp *const smtp,
/* auth_concat_len = user_len + 1 + challenge_hex_len + 1 */
if(smtp_si_add_size_t(user_len, challenge_hex_len, &auth_concat_len) ||
smtp_si_add_size_t(auth_concat_len, 2, &auth_concat_len) ||
- (auth_concat = malloc(auth_concat_len)) == NULL){
+ (auth_concat = (char*)(malloc(auth_concat_len))) == NULL){
free(challenge_hex);
return -1;
}
@@ -2335,8 +2338,8 @@ smtp_header_cmp_key(const void *const v1,
const char *key;
const struct smtp_header *header2;
- key = v1;
- header2 = v2;
+ key = (char*)(v1);
+ header2 = (smtp_header*)(v2);
return strcmp(key, header2->key);
}
@@ -2421,7 +2424,7 @@ smtp_print_mime_header_and_body(struct smtp *const smtp,
if(smtp_si_add_size_t(data_double_dot_len,
MIME_TEXT_BUFSZ,
&data_double_dot_len) ||
- (data_header_and_body = malloc(data_double_dot_len)) == NULL){
+ (data_header_and_body = (char*)(malloc(data_double_dot_len))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
@@ -2481,7 +2484,7 @@ smtp_print_mime_attachment(struct smtp *const smtp,
smtp_si_add_size_t(bufsz,
SMTP_MIME_BOUNDARY_LEN + MIME_TEXT_BUFSZ,
&bufsz) ||
- (mime_attach_text = malloc(bufsz)) == NULL){
+ (mime_attach_text = (char*)(malloc(bufsz))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
@@ -2640,7 +2643,7 @@ smtp_print_header(struct smtp *const smtp,
/* concat_len = key_len + 2 + value_len + 1 */
if(smtp_si_add_size_t(key_len, value_len, &concat_len) ||
smtp_si_add_size_t(concat_len, 3, &concat_len) ||
- (header_concat = malloc(concat_len)) == NULL){
+ (header_concat = (char*)(malloc(concat_len))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
concat = smtp_stpcpy(header_concat, header->key);
@@ -2705,8 +2708,8 @@ smtp_append_address_to_header(struct smtp *const smtp,
if(smtp_si_add_size_t(header_value_sz, name_slen, &header_value_sz) ||
smtp_si_add_size_t(header_value_sz, email_slen, &header_value_sz) ||
smtp_si_add_size_t(header_value_sz, 3 + 3 + 1 + 1, &header_value_sz)||
- (header_value_new = realloc(header_value,
- header_value_sz)) == NULL){
+ (header_value_new = (char*)(realloc(header_value,
+ header_value_sz))) == NULL){
free(header_value);
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
@@ -2764,7 +2767,7 @@ smtp_mail_envelope_header(struct smtp *const smtp,
/* bufsz = 14 + email_len + SMTPUTF8_LEN + 1 */
if(smtp_si_add_size_t(email_len, SMTPUTF8_LEN + 14 + 1, &bufsz) ||
- (envelope_address = malloc(bufsz)) == NULL){
+ (envelope_address = (char*)(malloc(bufsz))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
@@ -2804,8 +2807,8 @@ smtp_header_cmp(const void *v1,
const struct smtp_header *header1;
const struct smtp_header *header2;
- header1 = v1;
- header2 = v2;
+ header1 = (smtp_header*)(v1);
+ header2 = (smtp_header*)(v2);
return strcmp(header1->key, header2->key);
}
@@ -2992,7 +2995,7 @@ smtp_open(const char *const server,
struct smtp **smtp){
struct smtp *snew;
- if((snew = calloc(1, sizeof(**smtp))) == NULL){
+ if((snew = (struct smtp*)(calloc(1, sizeof(**smtp)))) == NULL){
*smtp = &g_smtp_error;
return smtp_status_code_get(*smtp);
}
@@ -3295,10 +3298,10 @@ smtp_header_add(struct smtp *const smtp,
}
if(smtp_si_add_size_t(smtp->num_headers, 1, &num_headers_inc) ||
- (new_header_list = smtp_reallocarray(
+ (new_header_list = (smtp_header*)(smtp_reallocarray(
smtp->header_list,
num_headers_inc,
- sizeof(*smtp->header_list))) == NULL){
+ sizeof(*smtp->header_list)))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
smtp->header_list = new_header_list;
@@ -3368,9 +3371,9 @@ smtp_address_add(struct smtp *const smtp,
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
- new_address_list = smtp_reallocarray(smtp->address_list,
+ new_address_list = (smtp_address*)(smtp_reallocarray(smtp->address_list,
num_address_inc,
- sizeof(*new_address_list));
+ sizeof(*new_address_list)));
if(new_address_list == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
@@ -3477,21 +3480,21 @@ smtp_attachment_add_mem(struct smtp *const smtp,
}
if(datasz == SIZE_MAX){
- datasz = strlen(data);
+ datasz = strlen((char*)(data));
}
if(smtp_si_add_size_t(smtp->num_attachment, 1, &num_attachment_inc) ||
- (new_attachment_list = smtp_reallocarray(
+ (new_attachment_list = (smtp_attachment*)(smtp_reallocarray(
smtp->attachment_list,
num_attachment_inc,
- sizeof(*new_attachment_list))) == NULL){
+ sizeof(*new_attachment_list)))) == NULL){
return smtp_status_code_set(smtp, SMTP_STATUS_NOMEM);
}
smtp->attachment_list = new_attachment_list;
new_attachment = &new_attachment_list[smtp->num_attachment];
new_attachment->name = smtp_strdup(name);
- b64_encode = smtp_base64_encode(data, datasz);
+ b64_encode = smtp_base64_encode((char*)(data), datasz);
if(new_attachment->name == NULL || b64_encode == NULL){
free(new_attachment->name);
free(b64_encode);
@@ -3526,3 +3529,4 @@ smtp_attachment_clear_all(struct smtp *const smtp){
smtp->num_attachment = 0;
}
+#endif
\ No newline at end of file
diff --git a/smtp.h b/smtp.h
index f035cc5b..5316801d 100644
--- a/smtp.h
+++ b/smtp.h
@@ -1,3 +1,5 @@
+#if defined(ARDUINO)
+#else
/**
* @file
* @brief SMTP client library.
@@ -633,3 +635,4 @@ struct str_getdelimfd{
#endif /* SMTP_H */
+#endif
\ No newline at end of file
From 47993ec8c7fdcca1715cb54b4a8ec54d037fd85a Mon Sep 17 00:00:00 2001
From: Ray
Date: Fri, 28 Jun 2024 14:06:35 -0400
Subject: [PATCH 08/61] relocate some macros, change option name nfe back to
ife for backward compatibility
---
OpenSprinkler.cpp | 2 +-
main.cpp | 9 +--------
opensprinkler_server.cpp | 7 +++++--
smtp.c | 1 +
smtp.h | 4 ++++
5 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 953139ad..74b27da1 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -154,7 +154,7 @@ const char iopt_json_names[] PROGMEM =
"dns3\0"
"dns4\0"
"sar\0\0"
- "nfe\0\0"
+ "ife\0\0"
"sn1t\0"
"sn1o\0"
"sn2t\0"
diff --git a/main.cpp b/main.cpp
index d8c2dba1..bbdeaea1 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,16 +30,9 @@
#include "opensprinkler_server.h"
#include "mqtt.h"
#include "main.h"
-#include "EmailSender.h"
+#include "EMailSender.h"
#include "smtp.h"
-#if defined(ARDUINO)
-#else
- #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_TLS
- #define MAIL_FLAGS (smtp_flag)(SMTP_NO_CERT_VERIFY)
- #define MAIL_AUTH SMTP_AUTH_PLAIN
-#endif
-
#define str(s) #s
#define xstr(s) str(s)
diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp
index 8e018036..d7321db2 100644
--- a/opensprinkler_server.cpp
+++ b/opensprinkler_server.cpp
@@ -1137,18 +1137,21 @@ void server_json_controller_main(OTF_PARAMS_DEF) {
bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"email\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","),
+ bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","),
SOPT_LOCATION,
SOPT_JAVASCRIPTURL,
SOPT_WEATHERURL,
SOPT_WEATHER_OPTS,
SOPT_IFTTT_KEY,
- SOPT_MQTT_OPTS,
SOPT_EMAIL_OPTS,
strlen(wt_rawData)==0?"{}":wt_rawData,
wt_errCode,
SOPT_DEVICE_NAME);
+#if !(defined(__AVR_ATmega1284__)||defined(__AVR_ATmega1284P__))
+ bfill.emit_p(PSTR("\"email\":{$O},"), SOPT_MQTT_OPTS);
+#endif
+
#if defined(ARDUINO)
if(os.status.has_curr_sense) {
uint16_t current = os.read_current();
diff --git a/smtp.c b/smtp.c
index c256a4f6..9b9db72c 100644
--- a/smtp.c
+++ b/smtp.c
@@ -23,6 +23,7 @@
*/
#if defined(ARDUINO)
+
#else
#if defined(_WIN32) || defined(WIN32)
diff --git a/smtp.h b/smtp.h
index 5316801d..d587018c 100644
--- a/smtp.h
+++ b/smtp.h
@@ -14,6 +14,10 @@
#ifndef SMTP_H
#define SMTP_H
+#define MAIL_CONNECTION_SECURITY SMTP_SECURITY_TLS
+#define MAIL_FLAGS (smtp_flag)(SMTP_NO_CERT_VERIFY)
+#define MAIL_AUTH SMTP_AUTH_PLAIN
+
#include
#include
#include
From ac1dc101151a43d48ef199719239bdb9d8244ba0 Mon Sep 17 00:00:00 2001
From: Ray
Date: Fri, 28 Jun 2024 15:11:25 -0400
Subject: [PATCH 09/61] fix compilation errors for all three platforms
---
OpenSprinkler.h | 2 +
build.sh | 6 +-
main.cpp | 166 +++++++++++++++++++++---------------------------
platformio.ini | 44 +++++++++----
smtp.c | 2 +-
smtp.h | 6 +-
6 files changed, 111 insertions(+), 115 deletions(-)
diff --git a/OpenSprinkler.h b/OpenSprinkler.h
index 76a35a57..69a3872b 100644
--- a/OpenSprinkler.h
+++ b/OpenSprinkler.h
@@ -49,6 +49,7 @@
#include
#include "SSD1306Display.h"
#include "espconnect.h"
+ #include "EMailSender.h"
#else // for AVR
#include
#include
@@ -62,6 +63,7 @@
#include
#include
#include "etherport.h"
+ #include "smtp.h"
#endif // end of headers
#if defined(ARDUINO)
diff --git a/build.sh b/build.sh
index 3f009214..e2553dde 100755
--- a/build.sh
+++ b/build.sh
@@ -15,13 +15,13 @@ if [ "$1" == "demo" ]; then
apt-get install -y libmosquitto-dev
apt-get install -y libssl-dev
echo "Compiling demo firmware..."
- g++ -o OpenSprinkler -DDEMO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto -l crypto -lssl
+ g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp smtp.c -lpthread -lmosquitto -l crypto -lssl
elif [ "$1" == "osbo" ]; then
echo "Installing required libraries..."
apt-get install -y libmosquitto-dev
apt-get install -y libssl-dev
echo "Compiling osbo firmware..."
- g++ -o OpenSprinkler -DOSBO -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto -l crypto -lssl
+ g++ -o OpenSprinkler -DOSBO -DSMTP_OPENSSL -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp smtp.c -lpthread -lmosquitto -l crypto -lssl
else
echo "Installing required libraries..."
apt-get update
@@ -47,7 +47,7 @@ else
fi
echo "Compiling ospi firmware..."
- g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL -fpermissive -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp smtp.c -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB
+ g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSLL -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp smtp.c -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB
fi
if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then
diff --git a/main.cpp b/main.cpp
index bbdeaea1..2ff16aad 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,8 +30,6 @@
#include "opensprinkler_server.h"
#include "mqtt.h"
#include "main.h"
-#include "EMailSender.h"
-#include "smtp.h"
#define str(s) #s
#define xstr(s) str(s)
@@ -94,9 +92,6 @@ float flow_last_gpm=0;
uint32_t reboot_timer = 0;
-//boolean to delay reboot notification until wifi is reconnected
-bool delayed = false;
-
void flow_poll() {
#if defined(ESP8266)
if(os.hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2
@@ -449,16 +444,6 @@ void do_loop()
os.status.mas2= os.iopts[IOPT_MASTER_STATION_2];
time_os_t curr_time = os.now_tz();
- //handle delayed reboot notification until wifi connection
- #if defined(ARDUINO)
- if(delayed){
- if(WiFi.status() == WL_CONNECTED){
- push_message(NOTIFY_REBOOT);
- delayed = false;
- }
- }
- #endif
-
// ====== Process Ethernet packets ======
#if defined(ARDUINO) // Process Ethernet packets for Arduino
#if defined(ESP8266)
@@ -997,8 +982,13 @@ void do_loop()
}
static byte reboot_notification = 1;
if(reboot_notification) {
+ #if defined(ESP266)
+ if(useEth || WiFi.status()==WL_CONNECTED)
+ #endif
+ {
reboot_notification = 0;
push_message(NOTIFY_REBOOT);
+ }
}
}
@@ -1389,47 +1379,48 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
char* postval = tmp_buffer;
uint32_t volume;
+ //check if ifttt key exists and also if the enable bit is set
+ bool ifttt_enabled;
+ if(strlen(os.sopts[SOPT_IFTTT_KEY]) != 0){
+ ifttt_enabled = false;
+ }else{
+ ifttt_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
+ }
+
//define email variables
- char host[50 + 1] = {0};
- char username[32 + 1] = {0};
- char password[32 + 1] = {0};
- char recipient[32 + 1] = {0};
- int port = 465;
- int enabled = 0;
+ char email_host[32 + 1] = {0};
+ char email_username[32 + 1] = {0};
+ char email_password[32 + 1] = {0};
+ char email_recipient[32 + 1] = {0};
+ int email_port = 465;
+ int email_en = 0;
//pull email variables
- os.sopt_load(SOPT_EMAIL_OPTS, postval);
- if (*postval != 0) {
- sscanf(
- postval,
- "\"en\":%d,\"host\":\"%" xstr(32) "[^\"]\",\"port\":%d,\"user\":\"%" xstr(50) "[^\"]\",\"pass\":\"%" xstr(32) "[^\"]\",\"recipient\":\"%" xstr(32) "[^\"]\"",
- &enabled, host, &port, username, password, recipient
- );
- }
+ #if defined(__AVR_ATmega1284__) | defined(__AVR_ATmega1284P__)
+ // AVR doesn't support email, so no need to pull
+ #else
+ os.sopt_load(SOPT_EMAIL_OPTS, postval);
+ if (*postval != 0) {
+ sscanf(
+ postval,
+ "\"en\":%d,\"host\":\"%" xstr(32) "[^\"]\",\"port\":%d,\"user\":\"%" xstr(32) "[^\"]\",\"pass\":\"%" xstr(32) "[^\"]\",\"recipient\":\"%" xstr(32) "[^\"]\"",
+ &email_en, email_host, &email_port, email_username, email_password, email_recipient
+ );
+ }
+ #endif
- //assign email variables necessary
- #if defined(ARDUINO)
- EMailSender emailSend(username, password);
- EMailSender::EMailMessage message;
+ #if defined(ESP8266)
+ EMailSender::EMailMessage email_message;
#else
- struct smtp *smtp;
int rc;
struct {
String subject;
String message;
- }message;
+ } email_message;
#endif
- //check if ifttt key exists
- bool ifttt_enabled;
- if(SOPT_IFTTT_KEY != 0){
- ifttt_enabled = false;
- }else{
- ifttt_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
- }
-
bool email_enabled;
- if(!enabled){
+ if(!email_en){
email_enabled = false;
}else{
email_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
@@ -1439,7 +1430,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
if (!ifttt_enabled && !os.mqtt.enabled() && !email_enabled)
return;
- if (ifttt_enabled) {
+ if (ifttt_enabled || email_enabled) {
+ // todo: modify the format for email
strcpy_P(postval, PSTR("{\"value1\":\"On site ["));
os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval));
strcat_P(postval, PSTR("], "));
@@ -1450,16 +1442,6 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
payload[0] = 0;
}
- if (email_enabled) {
- strcpy_P(postval, PSTR("On site ["));
- os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval));
- strcat_P(postval, PSTR("], "));
- #if defined(ARDUINO)
- emailSend.setSMTPServer(strdup(host));
- emailSend.setSMTPPort(port);
- #endif
- }
-
switch(type) {
case NOTIFY_STATION_ON:
@@ -1493,8 +1475,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
}
}
if (email_enabled) {
- message.subject = "Station Off";
- message.message = postval;
+ email_message.subject = "Station Off";
+ email_message.message = postval;
}
break;
@@ -1512,8 +1494,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(postval+strlen(postval), PSTR(" with %d%% water level."), (int)fval);
}
if (email_enabled) {
- message.subject = "Program Scheduled";
- message.message = postval;
+ email_message.subject = "Program Scheduled";
+ email_message.message = postval;
}
break;
@@ -1528,8 +1510,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
if (email_enabled) {
- message.subject = "Sensor 1 Notification";
- message.message = postval;
+ email_message.subject = "Sensor 1 Notification";
+ email_message.message = postval;
}
break;
@@ -1544,8 +1526,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
if (email_enabled) {
- message.subject = "Sensor 2 Notification";
- message.message = postval;
+ email_message.subject = "Sensor 2 Notification";
+ email_message.message = postval;
}
break;
@@ -1560,8 +1542,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated."));
}
if (email_enabled) {
- message.subject = "Rain Delay";
- message.message = postval;
+ email_message.subject = "Rain Delay";
+ email_message.message = postval;
}
break;
@@ -1578,8 +1560,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
sprintf_P(postval+strlen(postval), PSTR("Flow count: %u, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100);
}
if (email_enabled) {
- message.subject = "Flow Sensor Notification";
- message.message = postval;
+ email_message.subject = "Flow Sensor Notification";
+ email_message.message = postval;
}
break;
@@ -1599,19 +1581,12 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
}
}
if (email_enabled) {
- message.subject = "Weather Update";
- message.message = postval;
+ email_message.subject = "Weather Update";
+ email_message.message = postval;
}
break;
case NOTIFY_REBOOT:
- #if defined(ARDUINO)
- if(!delayed){
- delayed = true;
- return;
- }
- #endif
-
if (os.mqtt.enabled()) {
strcpy_P(topic, PSTR("opensprinkler/system"));
strcpy_P(payload, PSTR("{\"state\":\"started\"}"));
@@ -1641,8 +1616,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
#endif
}
if (email_enabled) {
- message.subject = "Reboot Notification";
- message.message = postval;
+ email_message.subject = "Reboot Notification";
+ email_message.message = postval;
}
break;
}
@@ -1667,23 +1642,30 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
if(email_enabled){
#if defined(ARDUINO)
- EMailSender::Response resp = emailSend.send(recipient, message);
- // DEBUG_PRINTLN(F("Sending Status:"));
- // DEBUG_PRINTLN(resp.status);
- // DEBUG_PRINTLN(resp.code);
- // DEBUG_PRINTLN(resp.desc);
+ #if defined(ESP8266)
+ EMailSender emailSend(email_username, email_password);
+ emailSend.setSMTPServer(strdup(email_host));
+ emailSend.setSMTPPort(email_port);
+ EMailSender::Response resp = emailSend.send(email_recipient, email_message);
+ // DEBUG_PRINTLN(F("Sending Status:"));
+ // DEBUG_PRINTLN(resp.status);
+ // DEBUG_PRINTLN(resp.code);
+ // DEBUG_PRINTLN(resp.desc);
+ #endif
#else
- char piSubject[message.subject.length() + 1];
- char piMessage[message.message.length() + 1];
- String sPort = to_string(port);
+ struct smtp *smtp = NULL;
+ char piSubject[email_message.subject.length() + 1];
+ char piMessage[email_message.message.length() + 1];
+ String sPort = to_string(email_port);
char piPort[sPort.length() + 1];
- strcpy(piSubject, message.subject.c_str());
- strcpy(piMessage, message.message.c_str());
+ strcpy(piSubject, email_message.subject.c_str());
+ strcpy(piMessage, email_message.message.c_str());
strcpy(piPort, sPort.c_str());
- rc = smtp_open(host, piPort, MAIL_CONNECTION_SECURITY, MAIL_FLAGS, NULL, &smtp);
- rc = smtp_auth(smtp, MAIL_AUTH, username, password);
- rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, username, "OpenSprinkler");
- rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, recipient, "User");
+ // todo: check error?
+ rc = smtp_open(email_host, piPort, SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp);
+ rc = smtp_auth(smtp, SMTP_AUTH_PLAIN, email_username, email_password);
+ rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, email_username, "OpenSprinkler");
+ rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, email_recipient, "User");
rc = smtp_header_add(smtp, "Subject", piSubject);
rc = smtp_mail(smtp, piMessage);
rc = smtp_close(smtp);
diff --git a/platformio.ini b/platformio.ini
index 8af89200..0ed3b310 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -12,20 +12,36 @@
src_dir = .
include_dir = .
-[env:d1_mini]
-platform = espressif8266@4.2.1
-board = d1_mini
+; [env:d1_mini]
+; platform = espressif8266@4.2.1
+; board = d1_mini
+; framework = arduino
+; lib_ldf_mode = deep
+; lib_deps =
+; sui77/rc-switch @ ^2.6.3
+; https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip
+; knolleary/PubSubClient @ ^2.8
+; https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.1.0
+; build_src_filter = +<*> -
+; upload_speed = 460800
+; monitor_speed = 115200
+; board_build.flash_mode = dio
+; board_build.ldscript = eagle.flash.4m2m.ld
+; board_build.f_cpu = 160000000L
+; board_build.f_flash = 80000000L
+
+
+[env:sanguino_atmega1284p]
+platform = atmelavr
+board = ATmega1284P
+board_build.f_cpu = 16000000L
+board_build.variant = sanguino
framework = arduino
lib_ldf_mode = deep
-lib_deps =
- sui77/rc-switch @ ^2.6.3
- https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip
- knolleary/PubSubClient @ ^2.8
- https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.1.0
+lib_deps =
+ https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip
+ knolleary/PubSubClient @ ^2.8
+ greiman/SdFat @ 1.0.7
+ Wire
build_src_filter = +<*> -
-upload_speed = 460800
-monitor_speed = 115200
-board_build.flash_mode = dio
-board_build.ldscript = eagle.flash.4m2m.ld
-board_build.f_cpu = 160000000L
-board_build.f_flash = 80000000L
+monitor_speed=115200
\ No newline at end of file
diff --git a/smtp.c b/smtp.c
index 9b9db72c..bcd91a35 100644
--- a/smtp.c
+++ b/smtp.c
@@ -3530,4 +3530,4 @@ smtp_attachment_clear_all(struct smtp *const smtp){
smtp->num_attachment = 0;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/smtp.h b/smtp.h
index d587018c..c1dac854 100644
--- a/smtp.h
+++ b/smtp.h
@@ -14,10 +14,6 @@
#ifndef SMTP_H
#define SMTP_H
-#define MAIL_CONNECTION_SECURITY SMTP_SECURITY_TLS
-#define MAIL_FLAGS (smtp_flag)(SMTP_NO_CERT_VERIFY)
-#define MAIL_AUTH SMTP_AUTH_PLAIN
-
#include
#include
#include
@@ -639,4 +635,4 @@ struct str_getdelimfd{
#endif /* SMTP_H */
-#endif
\ No newline at end of file
+#endif
From 338672cc3ba765345329025030514e171679861e Mon Sep 17 00:00:00 2001
From: Ray
Date: Fri, 28 Jun 2024 21:11:33 -0400
Subject: [PATCH 10/61] streamline email notification message; fix a couple of
bugs
---
main.cpp | 80 ++++++++++++++--------------------------
opensprinkler_server.cpp | 4 +-
2 files changed, 29 insertions(+), 55 deletions(-)
diff --git a/main.cpp b/main.cpp
index 2ff16aad..5a20fcf4 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1379,15 +1379,15 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
char* postval = tmp_buffer;
uint32_t volume;
- //check if ifttt key exists and also if the enable bit is set
+ // check if ifttt key exists and also if the enable bit is set
bool ifttt_enabled;
- if(strlen(os.sopts[SOPT_IFTTT_KEY]) != 0){
+ if(strlen(os.sopts[SOPT_IFTTT_KEY]) == 0) {
ifttt_enabled = false;
- }else{
+ } else {
ifttt_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
}
- //define email variables
+ // define email variables
char email_host[32 + 1] = {0};
char email_username[32 + 1] = {0};
char email_password[32 + 1] = {0};
@@ -1395,9 +1395,9 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
int email_port = 465;
int email_en = 0;
- //pull email variables
+ // parse email variables
#if defined(__AVR_ATmega1284__) | defined(__AVR_ATmega1284P__)
- // AVR doesn't support email, so no need to pull
+ // AVR doesn't support email, so no need to parse
#else
os.sopt_load(SOPT_EMAIL_OPTS, postval);
if (*postval != 0) {
@@ -1426,15 +1426,19 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
email_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type;
}
- // check if this type of event is enabled for push notification
+ // if none if enabled, return here
if (!ifttt_enabled && !os.mqtt.enabled() && !email_enabled)
return;
if (ifttt_enabled || email_enabled) {
- // todo: modify the format for email
strcpy_P(postval, PSTR("{\"value1\":\"On site ["));
- os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval));
+ os.sopt_load(SOPT_DEVICE_NAME, topic);
+ strcat(postval+strlen(postval), topic);
strcat_P(postval, PSTR("], "));
+ if(email_enabled) {
+ strcat(topic, " ");
+ email_message.subject = topic; // prefix the email subject with device name
+ }
}
if (os.mqtt.enabled()) {
@@ -1473,10 +1477,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) {
sprintf_P(postval+strlen(postval), PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100);
}
- }
- if (email_enabled) {
- email_message.subject = "Station Off";
- email_message.message = postval;
+ if(email_enabled) { email_message.subject += PSTR("station event"); }
}
break;
@@ -1492,10 +1493,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
if(lval=0) {
sprintf_P(postval+strlen(postval), PSTR("water level updated: %d%%."), (int)fval);
}
- }
- if (email_enabled) {
- email_message.subject = "Weather Update";
- email_message.message = postval;
+ if(email_enabled) { email_message.subject += PSTR("weather update event"); }
}
break;
@@ -1612,12 +1595,9 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
//strcat(postval, ":");
//itoa(_port, postval+strlen(postval), 10);
#else
- strcat_P(postval, PSTR("process restarted."));
+ strcat_P(postval, PSTR("controller process restarted."));
#endif
- }
- if (email_enabled) {
- email_message.subject = "Reboot Notification";
- email_message.message = postval;
+ if(email_enabled) { email_message.subject += PSTR("reboot event"); }
}
break;
}
@@ -1628,7 +1608,6 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
if (ifttt_enabled) {
strcat_P(postval, PSTR("\"}"));
- //char postBuffer[1500];
BufferFiller bf = ether_buffer;
bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n"
"Host: $S\r\n"
@@ -1641,6 +1620,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
}
if(email_enabled){
+ email_message.message = strchr(postval, 'O'); // ad-hoc: remove the value1 part from the ifttt message
#if defined(ARDUINO)
#if defined(ESP8266)
EMailSender emailSend(email_username, email_password);
@@ -1654,20 +1634,14 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) {
#endif
#else
struct smtp *smtp = NULL;
- char piSubject[email_message.subject.length() + 1];
- char piMessage[email_message.message.length() + 1];
- String sPort = to_string(email_port);
- char piPort[sPort.length() + 1];
- strcpy(piSubject, email_message.subject.c_str());
- strcpy(piMessage, email_message.message.c_str());
- strcpy(piPort, sPort.c_str());
+ String email_port_str = to_string(email_port);
// todo: check error?
- rc = smtp_open(email_host, piPort, SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp);
+ rc = smtp_open(email_host, email_port_str.c_str(), SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp);
rc = smtp_auth(smtp, SMTP_AUTH_PLAIN, email_username, email_password);
rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, email_username, "OpenSprinkler");
rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, email_recipient, "User");
- rc = smtp_header_add(smtp, "Subject", piSubject);
- rc = smtp_mail(smtp, piMessage);
+ rc = smtp_header_add(smtp, "Subject", email_message.subject.c_str());
+ rc = smtp_mail(smtp, email_message.message.c_str());
rc = smtp_close(smtp);
#endif
}
diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp
index d7321db2..0312ffdc 100644
--- a/opensprinkler_server.cpp
+++ b/opensprinkler_server.cpp
@@ -1143,13 +1143,13 @@ void server_json_controller_main(OTF_PARAMS_DEF) {
SOPT_WEATHERURL,
SOPT_WEATHER_OPTS,
SOPT_IFTTT_KEY,
- SOPT_EMAIL_OPTS,
+ SOPT_MQTT_OPTS,
strlen(wt_rawData)==0?"{}":wt_rawData,
wt_errCode,
SOPT_DEVICE_NAME);
#if !(defined(__AVR_ATmega1284__)||defined(__AVR_ATmega1284P__))
- bfill.emit_p(PSTR("\"email\":{$O},"), SOPT_MQTT_OPTS);
+ bfill.emit_p(PSTR("\"email\":{$O},"), SOPT_EMAIL_OPTS);
#endif
#if defined(ARDUINO)
From 2aa61afd43b42fd0ca9bb306ac7d73e5f0738e93 Mon Sep 17 00:00:00 2001
From: Ray
Date: Fri, 28 Jun 2024 22:46:34 -0400
Subject: [PATCH 11/61] modify email library's DEBUG_PRINT macro to avoid
conflicting with main DEBUG_PRINT
---
EMailSender.cpp | 119 ++++++++++++++++++++++++------------------------
EMailSender.h | 8 ++--
2 files changed, 63 insertions(+), 64 deletions(-)
diff --git a/EMailSender.cpp b/EMailSender.cpp
index dfdc9d5b..22b87894 100644
--- a/EMailSender.cpp
+++ b/EMailSender.cpp
@@ -103,10 +103,9 @@ int base64_enc_length(int plainLen) {
const char* encode64_f(char* input, uint8_t len) {
// encoding
- DEBUG_PRINTLN(F("Encoding"));
-
- DEBUG_PRINTLN(input);
- DEBUG_PRINTLN(len);
+ EMAIL_DEBUG_PRINTLN(F("Encoding"));
+ EMAIL_DEBUG_PRINTLN(input);
+ EMAIL_DEBUG_PRINTLN(len);
//int encodedLen =
base64_enc_length(len);
@@ -214,7 +213,7 @@ EMailSender::Response EMailSender::awaitSMTPResponse(SSLClient &client,
}
_serverResponce = client.readStringUntil('\n');
- DEBUG_PRINTLN(_serverResponce);
+ EMAIL_DEBUG_PRINTLN(_serverResponce);
if (resp && _serverResponce.indexOf(resp) == -1){
response.code = resp;
response.desc = respDesc +String(" (") + _serverResponce + String(")");
@@ -241,7 +240,7 @@ EMailSender::Response EMailSender::awaitSMTPResponse(EMAIL_NETWORK_CLASS &client
}
_serverResponce = client.readStringUntil('\n');
- DEBUG_PRINTLN(_serverResponce);
+ EMAIL_DEBUG_PRINTLN(_serverResponce);
if (resp && _serverResponce.indexOf(resp) == -1){
response.code = resp;
response.desc = respDesc +String(" (") + _serverResponce + String(")");
@@ -411,7 +410,7 @@ EMailSender::Response EMailSender::send(char* tos[], byte sizeOfTo, byte sizeOf
EMailSender::Response EMailSender::send(String to, EMailMessage &email, Attachments attachments){
- DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
+ EMAIL_DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
const char* arrEmail[] = {to.c_str()};
return send(arrEmail, 1, email, attachments);
@@ -430,14 +429,14 @@ EMailSender::Response EMailSender::send(String tos[], byte sizeOfTo, byte sizeO
}
EMailSender::Response EMailSender::send(const char* to, EMailMessage &email, Attachments attachments){
- DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
+ EMAIL_DEBUG_PRINT(F("ONLY ONE RECIPIENT"));
const char* arrEmail[] = {to};
return send(arrEmail, 1, email, attachments);
}
EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, EMailMessage &email, Attachments attachments) {
- DEBUG_PRINTLN(F("miltiple destination and attachments"));
+ EMAIL_DEBUG_PRINTLN(F("miltiple destination and attachments"));
return send(to, sizeOfTo, 0, email, attachments);
}
@@ -464,14 +463,14 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte sizeOfCc,byte sizeOfCCn, EMailMessage &email, Attachments attachments)
{
#ifdef SSLCLIENT_WRAPPER
- DEBUG_PRINTLN(F("SSLClient active!"));
+ EMAIL_DEBUG_PRINTLN(F("SSLClient active!"));
#else
#ifndef PUT_OUTSIDE_SCOPE_CLIENT_DECLARATION
EMAIL_NETWORK_CLASS client;
#endif
- DEBUG_PRINT(F("Insecure client:"));
- DEBUG_PRINTLN(this->isSecure);
+ EMAIL_DEBUG_PRINT(F("Insecure client:"));
+ EMAIL_DEBUG_PRINTLN(this->isSecure);
#ifndef FORCE_DISABLE_SSL
#if (EMAIL_NETWORK_TYPE == NETWORK_ESP8266 || EMAIL_NETWORK_TYPE == NETWORK_ESP8266_242)
#ifndef ARDUINO_ESP8266_RELEASE_2_4_2
@@ -479,8 +478,8 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
client.setInsecure();
bool mfln = client.probeMaxFragmentLength(this->smtp_server, this->smtp_port, 512);
- DEBUG_PRINT("MFLN supported: ");
- DEBUG_PRINTLN(mfln?"yes":"no");
+ EMAIL_DEBUG_PRINT("MFLN supported: ");
+ EMAIL_DEBUG_PRINTLN(mfln?"yes":"no");
if (mfln) {
client.setBufferSizes(512, 512);
@@ -493,8 +492,8 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
// String coreVersion = String(ESP.getSdkVersion());
// uint8_t firstdot = coreVersion.indexOf('.');
//
- // DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat());
- // DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat() >= 3.3f);
+ // EMAIL_DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat());
+ // EMAIL_DEBUG_PRINTLN(coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat() >= 3.3f);
// if (coreVersion.substring(1, coreVersion.indexOf('.', firstdot+1)).toFloat() >= 3.3f) {
// client.setInsecure();
// }
@@ -511,8 +510,8 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
#endif
EMailSender::Response response;
- DEBUG_PRINTLN(this->smtp_server);
- DEBUG_PRINTLN(this->smtp_port);
+ EMAIL_DEBUG_PRINTLN(this->smtp_server);
+ EMAIL_DEBUG_PRINTLN(this->smtp_port);
if(!client.connect(this->smtp_server, this->smtp_port)) {
response.desc = F("Could not connect to mail server");
@@ -561,7 +560,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
commandHELO = "EHLO";
}
String helo = commandHELO + " "+String(publicIPDescriptor)+" ";
- DEBUG_PRINTLN(helo);
+ EMAIL_DEBUG_PRINTLN(helo);
client.println(helo);
response = awaitSMTPResponse(client, "250", "Identification error");
@@ -628,12 +627,12 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
String auth = "AUTH PLAIN "+String(encode64_f(logPass, size));
// String auth = "AUTH PLAIN "+String(encode64(logPass));
- DEBUG_PRINTLN(auth);
+ EMAIL_DEBUG_PRINTLN(auth);
client.println(auth);
}
#if defined(ESP32)
else if (this->isCramMD5Login == true) {
- DEBUG_PRINTLN(F("AUTH CRAM-MD5"));
+ EMAIL_DEBUG_PRINTLN(F("AUTH CRAM-MD5"));
client.println(F("AUTH CRAM-MD5"));
// read back the base64 encoded digest.
@@ -648,7 +647,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
size_t b64l = _serverResponce.length()-1; // C vs C++ counting of \0
const unsigned char * b64 = (const unsigned char *)_serverResponce.c_str();
- DEBUG_PRINTLN("B64digest="+String((char *)b64) + " Len=" + String((int)b64l));
+ EMAIL_DEBUG_PRINTLN("B64digest="+String((char *)b64) + " Len=" + String((int)b64l));
unsigned char digest[256];
size_t len;
@@ -687,22 +686,22 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
};
// And submit this to the server as a login string.
- DEBUG_PRINTLN(encode64((char*)rsp.c_str()));
+ EMAIL_DEBUG_PRINTLN(encode64((char*)rsp.c_str()));
client.println(encode64((char*)rsp.c_str()));
// now exepct the normal login confirmation to continue.
}
#endif
else{
- DEBUG_PRINTLN(F("AUTH LOGIN:"));
+ EMAIL_DEBUG_PRINTLN(F("AUTH LOGIN:"));
client.println(F("AUTH LOGIN"));
awaitSMTPResponse(client);
- DEBUG_PRINTLN(encode64(this->email_login));
+ EMAIL_DEBUG_PRINTLN(encode64(this->email_login));
client.println(encode64(this->email_login));
awaitSMTPResponse(client);
- DEBUG_PRINTLN(encode64(this->email_password));
+ EMAIL_DEBUG_PRINTLN(encode64(this->email_password));
client.println(encode64(this->email_password));
}
response = awaitSMTPResponse(client, "235", "SMTP AUTH error");
@@ -712,9 +711,9 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
return response;
}
}
- DEBUG_PRINT(F("MAIL FROM: <"));
- DEBUG_PRINT(this->email_from);
- DEBUG_PRINTLN(F(">"));
+ EMAIL_DEBUG_PRINT(F("MAIL FROM: <"));
+ EMAIL_DEBUG_PRINT(this->email_from);
+ EMAIL_DEBUG_PRINTLN(F(">"));
client.print(F("MAIL FROM: <"));
client.print(this->email_from);
@@ -723,14 +722,14 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
// String rcpt = "RCPT TO: <" + String(to) + '>';
//
-// DEBUG_PRINTLN(rcpt);
+// EMAIL_DEBUG_PRINTLN(rcpt);
// client.println(rcpt);
int cont;
for (cont=0;cont<(sizeOfTo+sizeOfCc+sizeOfCCn);cont++){
- DEBUG_PRINT(F("RCPT TO: <"));
- DEBUG_PRINT(to[cont]);
- DEBUG_PRINTLN(F(">"));
+ EMAIL_DEBUG_PRINT(F("RCPT TO: <"));
+ EMAIL_DEBUG_PRINT(to[cont]);
+ EMAIL_DEBUG_PRINTLN(F(">"));
client.print(F("RCPT TO: <"));
client.print(to[cont]);
@@ -738,7 +737,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
awaitSMTPResponse(client);
}
- DEBUG_PRINTLN(F("DATA:"));
+ EMAIL_DEBUG_PRINTLN(F("DATA:"));
client.println(F("DATA"));
response = awaitSMTPResponse(client, "354", "SMTP DATA error");
@@ -837,7 +836,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
// if ((sizeof(attachs) / sizeof(attachs[0]))>0){
if (sizeof(attachments)>0 && attachments.number>0){
- DEBUG_PRINT(F("Array: "));
+ EMAIL_DEBUG_PRINT(F("Array: "));
// for (int i = 0; i<(sizeof(attachs) / sizeof(attachs[0])); i++){
for (int i = 0; i "));
- DEBUG_PRINTLN(myFile.name());
+ EMAIL_DEBUG_PRINT(F("Filename -> "));
+ EMAIL_DEBUG_PRINTLN(myFile.name());
if (attachments.fileDescriptor[i].encode64){
encode(&myFile, &client);
}else{
while(myFile.available()) {
clientCount = myFile.read(tBuf,64);
- DEBUG_PRINTLN(clientCount);
+ EMAIL_DEBUG_PRINTLN(clientCount);
client.write((byte*)tBuf,clientCount);
}
}
@@ -978,7 +977,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
#ifdef STORAGE_EXTERNAL_ENABLED
if (attachments.fileDescriptor[i].storageType==EMAIL_STORAGE_TYPE_SD){
#ifdef OPEN_CLOSE_SD
- DEBUG_PRINTLN(F("SD Check"));
+ EMAIL_DEBUG_PRINTLN(F("SD Check"));
if (!EXTERNAL_STORAGE_CLASS.exists(attachments.fileDescriptor[i].url.c_str())){
#if EXTERNAL_STORAGE == STORAGE_SD || EXTERNAL_STORAGE == STORAGE_SDFAT2 || EXTERNAL_STORAGE == STORAGE_SDFAT_RP2040_ESP8266
if(!EXTERNAL_STORAGE_CLASS.begin(SD_CS_PIN)){
@@ -1008,17 +1007,17 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
} // Close EXTERNAL_STORAGE_CLASS.exists
#endif
- DEBUG_PRINTLN(F("Open file: "));
+ EMAIL_DEBUG_PRINTLN(F("Open file: "));
EMAIL_FILE_EX myFile = EXTERNAL_STORAGE_CLASS.open(attachments.fileDescriptor[i].url.c_str());
if(myFile) {
myFile.seek(0);
- DEBUG_PRINTLN(F("OK"));
+ EMAIL_DEBUG_PRINTLN(F("OK"));
if (attachments.fileDescriptor[i].encode64){
- DEBUG_PRINTLN(F("BASE 64"));
+ EMAIL_DEBUG_PRINTLN(F("BASE 64"));
encode(&myFile, &client);
}else{
- DEBUG_PRINTLN(F("NORMAL"));
+ EMAIL_DEBUG_PRINTLN(F("NORMAL"));
while(myFile.available()) {
clientCount = myFile.read(tBuf,64);
client.write((byte*)tBuf,clientCount);
@@ -1057,13 +1056,13 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
#ifdef STORAGE_EXTERNAL_ENABLED
#ifdef OPEN_CLOSE_SD
if (sdActive){
- DEBUG_PRINTLN(F("SD end"));
+ EMAIL_DEBUG_PRINTLN(F("SD end"));
#ifndef ARDUINO_ESP8266_RELEASE_2_4_2
#if EXTERNAL_STORAGE != STORAGE_SDFAT_RP2040_ESP8266
EXTERNAL_STORAGE_CLASS.end();
#endif
#endif
- DEBUG_PRINTLN(F("SD end 2"));
+ EMAIL_DEBUG_PRINTLN(F("SD end 2"));
}
#endif
#endif
@@ -1073,7 +1072,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
#if INTERNAL_STORAGE != STORAGE_SPIFM
if (spiffsActive){
INTERNAL_STORAGE_CLASS.end();
- DEBUG_PRINTLN(F("SPIFFS END"));
+ EMAIL_DEBUG_PRINTLN(F("SPIFFS END"));
}
#endif
#endif
@@ -1083,7 +1082,7 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s
} // Close attachement enable
#endif
- DEBUG_PRINTLN(F("Message end"));
+ EMAIL_DEBUG_PRINTLN(F("Message end"));
client.println(F("."));
response = awaitSMTPResponse(client, "250", "Sending message error");
diff --git a/EMailSender.h b/EMailSender.h
index 4249da08..36c42f58 100644
--- a/EMailSender.h
+++ b/EMailSender.h
@@ -364,11 +364,11 @@
// Setup debug printing macros.
#ifdef EMAIL_SENDER_DEBUG
- #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
- #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
+ #define EMAIL_DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
+ #define EMAIL_DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
#else
- #define DEBUG_PRINT(...) {}
- #define DEBUG_PRINTLN(...) {}
+ #define EMAIL_DEBUG_PRINT(...) {}
+ #define EMAIL_DEBUG_PRINTLN(...) {}
#endif
// Debug level for SSLClient
From c91801622916112b2da44f4b8032eed0efc9f7b9 Mon Sep 17 00:00:00 2001
From: "Nathan P."
Date: Tue, 2 Jul 2024 11:24:44 -0400
Subject: [PATCH 12/61] Initial support for MQTT subscribe on Arduino.
---
main.cpp | 1 +
mqtt.cpp | 366 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
mqtt.h | 3 +
3 files changed, 370 insertions(+)
diff --git a/main.cpp b/main.cpp
index 81113530..a9d4e121 100644
--- a/main.cpp
+++ b/main.cpp
@@ -604,6 +604,7 @@ void do_loop()
DEBUG_PRINTLN(F("req_mqtt_restart"));
os.mqtt.begin();
os.status.req_mqtt_restart = false;
+ os.mqtt.subscribe();
}
os.mqtt.loop();
diff --git a/mqtt.cpp b/mqtt.cpp
index d2f97136..ed6ba959 100644
--- a/mqtt.cpp
+++ b/mqtt.cpp
@@ -41,6 +41,9 @@
#endif
#include "OpenSprinkler.h"
+#include "opensprinkler_server.h"
+#include "main.h"
+#include "program.h"
#include "types.h"
#include "mqtt.h"
@@ -67,6 +70,7 @@
#define xstr(s) str(s)
extern OpenSprinkler os;
+extern ProgramData pd;
extern char tmp_buffer[];
#define OS_MQTT_KEEPALIVE 60
@@ -75,6 +79,7 @@ extern char tmp_buffer[];
#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username
#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password
#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit
+#define MQTT_MAX_TOPIC_LEN 20 // Note: App is set to max 20 chars for topic name
#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts
#define MQTT_ROOT_TOPIC "opensprinkler"
@@ -91,6 +96,269 @@ char OSMqtt::_username[MQTT_MAX_USERNAME_LEN + 1] = {0}; // username to connect
char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect to the broker
int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883)
bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled
+char OSMqtt::_topic[MQTT_MAX_TOPIC_LEN + 1] = {0}; // topic to subscribe to for commands
+
+//******************************** HELPER FUNCTIONS ********************************//
+
+//parsing a comma seperated value list
+uint16_t parseListData(char **p) {
+ char* pv;
+ int i=0;
+ tmp_buffer[i]=0;
+ // copy to tmp_buffer until a non-number is encountered
+ for(pv=(*p);pv<(*p)+10;pv++) {
+ if ((*pv)=='-' || (*pv)=='+' || ((*pv)>='0'&&(*pv)<='9'))
+ tmp_buffer[i++] = (*pv);
+ else
+ break;
+ }
+ tmp_buffer[i]=0;
+ *p = pv+1;
+ return (uint16_t)atol(tmp_buffer);
+}
+
+//finding a key value pair
+bool findKeyVal(char *str, char *strbuf, uint16_t maxlen, const char *key, const char stop){
+ DEBUG_LOGF("Finding Key Val\r\n");
+ bool found = false;
+ uint16_t i = 0;
+ if(str == NULL || strbuf == NULL || key == NULL){return false;}
+ const char *kp = key;
+ while(*str && *str!= ' ' && *str!= '\n' && !found){
+ if (*str == *kp){
+ kp++;
+ if(*kp == '\0'){
+ str++;
+ kp = key;
+ if(*str == stop){
+ found=true;
+ }
+ }
+ } else {
+ kp = key;
+ }
+ str++;
+ }
+ if(found){
+ while(*str && *str!=' ' && *str!='\n' && *str!='&' && i0){
+ os.nvdata.rd_stop_time = os.now_tz() + (unsigned long) rd * 3600;
+ os.raindelay_start();
+ }else if (rd==0){
+ os.raindelay_stop();
+ }
+ }
+}
+
+//handles /cm command
+void manualRun(char *message){
+ int sid = -1;
+ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "sid", '=')){
+ sid = atoi(tmp_buffer);
+ if(sid < 0 || sid >= os.nstations){
+ DEBUG_LOGF("Invalid station ID.\r\n");
+ return;
+ }
+ }else{
+ DEBUG_LOGF("No station ID found.\r\n");
+ return;
+ }
+
+ byte en = 0;
+ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "en", '=')){
+ en = atoi(tmp_buffer);
+ }else{
+ DEBUG_LOGF("No enable bit found.\r\n");
+ return;
+ }
+
+ uint16_t timer = 0;
+ unsigned long curr_time = os.now_tz();
+ if(en){
+ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "t", '=')){
+ timer = (uint16_t)atol(tmp_buffer);
+ if(timer==0 || timer>64800){
+ DEBUG_LOGF("Time out of bounds.\r\n");
+ return;
+ }
+ if((os.status.mas==sid+1) || (os.status.mas2==sid+1)){
+ DEBUG_LOGF("Cannot independently schedule master.\r\n");
+ return;
+ }
+ RuntimeQueueStruct *q = NULL;
+ byte sqi = pd.station_qid[sid];
+ //check if station has schedule
+ if(sqi!=0xFF){
+ q = pd.queue+sqi;
+ }else{
+ q = pd.enqueue();
+ }
+
+ if(q){
+ q->st = 0;
+ q->dur = timer;
+ q->sid = sid;
+ q->pid = 99;
+ schedule_all_stations(curr_time);
+ }else{
+ DEBUG_LOGF("Queue is full.\r\n");
+ return;
+ }
+ }else{
+ DEBUG_LOGF("No time value found.\r\n")
+ return;
+ }
+ }else{
+ byte ssta = 0;
+ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "ssta",'=')){
+ ssta = atoi(tmp_buffer);
+ }
+ RuntimeQueueStruct *q = pd.queue + pd.station_qid[sid];
+ q->deque_time = curr_time;
+ turn_off_station(sid, curr_time, ssta);
+ }
+ return;
+}
+
+//handles /mp command
+void manual_start_program(byte, byte);
+void programStart(char *message){
+ if(!findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "pid",'=')){
+ DEBUG_LOGF("Program ID missing.\r\n")
+ return;
+ }
+ int pid = atoi(tmp_buffer);
+ if(pid < 0 || pid >= pd.nprograms){
+ DEBUG_LOGF("Program ID out of bounds.\r\n");
+ }
+
+ byte uwt = 0;
+ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, "uwt",'=')){
+ if(tmp_buffer[0]=='1') uwt = 1;
+ }
+
+ reset_all_stations_immediate();
+
+ manual_start_program(pid+1, uwt);
+ return;
+}
+
+//handles /cr command
+void runOnceProgram(char *message){
+ char *pv;
+ bool found = false;
+ for(pv = message; (*pv) != 0 && pv> 3;
+ s = sid&0x07;
+
+ if(dur > 0 && !(os.attrib_dis[bid]&(1<st = 0;
+ q->dur = water_time_resolve(dur);
+ q->pid = 254;
+ q->sid = sid;
+ match_found = true;
+ }
+ }
+ }
+ if(match_found){
+ schedule_all_stations(os.now_tz());
+ return;
+ }
+ return;
+}
+
+//****************************** MQTT FUNCTIONS ******************************//
// Initialise the client libraries and event handlers.
void OSMqtt::init(void) {
@@ -139,6 +407,14 @@ void OSMqtt::begin(void) {
);
}
+ if(findKeyVal(config, tmp_buffer, TMP_BUFFER_SIZE, "\"topic\"", ':')){
+ for(int i = 0; tmp_buffer[i+2] != 0; i++){
+ _topic[i] = tmp_buffer[i+1];
+ }
+ }else{
+ DEBUG_LOGF("No topic found\r\n");
+ }
+
begin(host, port, username, password, (bool)enabled);
}
@@ -180,6 +456,19 @@ void OSMqtt::publish(const char *topic, const char *payload) {
_publish(topic, payload);
}
+//Subscribe to a specific topic
+void OSMqtt::subscribe(void){
+ DEBUG_LOGF("MQTT Subscribe: %s\r\n", _topic);
+
+ if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return;
+
+ if (!_connected()) {
+ DEBUG_LOGF("MQTT Subscribe: Not connected\r\n");
+ return;
+ }
+
+ _subscribe();
+}
// Regularly call the loop function to ensure "keep alive" messages are sent to the broker and to reconnect if needed.
void OSMqtt::loop(void) {
static unsigned long last_reconnect_attempt = 0;
@@ -286,6 +575,43 @@ int OSMqtt::_publish(const char *topic, const char *payload) {
return MQTT_SUCCESS;
}
+void callback(const char *topic, byte *payload, unsigned int length) {
+ DEBUG_LOGF("Callback\r\n");
+ char message[length + 1];
+ for (unsigned int i=0;isetCallback(callback);
+ if (!mqtt_client->subscribe(_topic)) {
+ DEBUG_LOGF("MQTT Subscribe: Failed (%d)\r\n", mqtt_client->state());
+ return MQTT_ERROR;
+ }
+ return MQTT_SUCCESS;
+}
+
int OSMqtt::_loop(void) {
mqtt_client->loop();
return mqtt_client->state();
@@ -397,6 +723,46 @@ int OSMqtt::_publish(const char *topic, const char *payload) {
return MQTT_SUCCESS;
}
+void piCallback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message){
+ DEBUG_LOGF("Callback\r\n");
+ char *topic = message->topic;
+ void *payload = message->payload;
+ char msg[message->payloadlen + 1];
+ for (unsigned int i=0;ipayloadlen;i++) {
+ msg[i] = (char)payload[i];
+ }
+
+ if(!checkPassword(msg)){
+ return;
+ }
+ DEBUG_LOGF("Password Passed\r\n");
+
+ if(msg[0]=='c'){
+ if(msg[1]=='v'){
+ changeValues(msg);
+ }else if(msg[1]=='m'){
+ manualRun(msg);
+ }else if(msg[1]=='r'){
+ runOnceProgram(msg);
+ }
+ }else if(msg[0]=='m' && msg[1]=='p'){
+ programStart(msg);
+ }else{
+ DEBUG_LOGF("Invalid request\r\n");
+ return;
+ }
+}
+
+int OSMQtt::_subscribe(void) {
+ mosquitto_message_callback_set(mqtt_client, piCallback)
+ int rc = mosquitto_subscribe(mqtt_client, NULL, _topic, 0);
+ if (rc != MOSQ_ERR_SUCCESS) {
+ DEBUG_LOGF("MQTT Subscribe: Failed (%s)\r\n", mosquitto_strerror(rc));
+ return MQTT_ERROR;
+ }
+ return MQTT_SUCCESS;
+}
+
int OSMqtt::_loop(void) {
return mosquitto_loop(mqtt_client, 0 , 1);
}
diff --git a/mqtt.h b/mqtt.h
index 3926d31b..18076493 100644
--- a/mqtt.h
+++ b/mqtt.h
@@ -32,6 +32,7 @@ class OSMqtt {
static char _username[];
static char _password[];
static bool _enabled;
+ static char _topic[];
// Following routines are platform specific versions of the public interface
static int _init(void);
@@ -39,6 +40,7 @@ class OSMqtt {
static int _disconnect(void);
static bool _connected(void);
static int _publish(const char *topic, const char *payload);
+ static int _subscribe(void);
static int _loop(void);
static const char * _state_string(int state);
public:
@@ -48,6 +50,7 @@ class OSMqtt {
static void begin(const char * host, int port, const char * username, const char * password, bool enable);
static bool enabled(void) { return _enabled; };
static void publish(const char *topic, const char *payload);
+ static void subscribe();
static void loop(void);
};
From 03b109058545746446085219eebd52877e1dd101 Mon Sep 17 00:00:00 2001
From: "Nathan P."
Date: Tue, 2 Jul 2024 12:34:47 -0400
Subject: [PATCH 13/61] Initial support for MQTT subsribe on OSPi.
---
mqtt.cpp | 11 +++++++++--
mqtt.h | 1 +
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/mqtt.cpp b/mqtt.cpp
index ed6ba959..73400e3c 100644
--- a/mqtt.cpp
+++ b/mqtt.cpp
@@ -97,6 +97,7 @@ char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect
int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883)
bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled
char OSMqtt::_topic[MQTT_MAX_TOPIC_LEN + 1] = {0}; // topic to subscribe to for commands
+bool OSMqtt::_subscribed = false; //Flag indicating if command topic has been subscribed to
//******************************** HELPER FUNCTIONS ********************************//
@@ -395,6 +396,7 @@ void OSMqtt::begin(void) {
char password[MQTT_MAX_PASSWORD_LEN + 1] = {0};
int port = MQTT_DEFAULT_PORT;
int enabled = 0;
+ _subscribed = false;
// JSON configuration settings in the form of {"en":0|1,"host":"server_name|IP address","port":1883,user:"",pass:""}
char *config = tmp_buffer;
@@ -467,6 +469,7 @@ void OSMqtt::subscribe(void){
return;
}
+ _subscribed = true;
_subscribe();
}
// Regularly call the loop function to ensure "keep alive" messages are sent to the broker and to reconnect if needed.
@@ -482,6 +485,10 @@ void OSMqtt::loop(void) {
last_reconnect_attempt = millis();
}
+ if(!_subscribed){
+ subscribe();
+ }
+
#if defined(ENABLE_DEBUG)
int state = _loop();
#else
@@ -726,7 +733,7 @@ int OSMqtt::_publish(const char *topic, const char *payload) {
void piCallback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message){
DEBUG_LOGF("Callback\r\n");
char *topic = message->topic;
- void *payload = message->payload;
+ char *payload = (char*)(message->payload);
char msg[message->payloadlen + 1];
for (unsigned int i=0;ipayloadlen;i++) {
msg[i] = (char)payload[i];
@@ -754,7 +761,7 @@ void piCallback(struct mosquitto *mosq, void *obj, const struct mosquitto_messag
}
int OSMQtt::_subscribe(void) {
- mosquitto_message_callback_set(mqtt_client, piCallback)
+ mosquitto_message_callback_set(mqtt_client, piCallback);
int rc = mosquitto_subscribe(mqtt_client, NULL, _topic, 0);
if (rc != MOSQ_ERR_SUCCESS) {
DEBUG_LOGF("MQTT Subscribe: Failed (%s)\r\n", mosquitto_strerror(rc));
diff --git a/mqtt.h b/mqtt.h
index 18076493..9f4d7d2a 100644
--- a/mqtt.h
+++ b/mqtt.h
@@ -33,6 +33,7 @@ class OSMqtt {
static char _password[];
static bool _enabled;
static char _topic[];
+ static bool _subscribed;
// Following routines are platform specific versions of the public interface
static int _init(void);
From ced4eab216e0be5bbb6baf6b2435aa9e331cc1d9 Mon Sep 17 00:00:00 2001
From: Ray
Date: Wed, 10 Jul 2024 06:43:53 -0400
Subject: [PATCH 14/61] add a few more options, including reserved options for
the future; increase tmp buffer and string option max length
---
OpenSprinkler.cpp | 40 ++++++++++++++++++++++++++++++++++++++++
defines.h | 18 ++++++++++++++----
mqtt.cpp | 8 ++++----
3 files changed, 58 insertions(+), 8 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index 74b27da1..b41041d8 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -167,6 +167,16 @@ const char iopt_json_names[] PROGMEM =
"subn2"
"subn3"
"subn4"
+ "sstag"
+ "fwire"
+ "resv1"
+ "resv2"
+ "resv3"
+ "resv4"
+ "resv5"
+ "resv6"
+ "resv7"
+ "resv8"
"wimod"
"reset"
;
@@ -237,6 +247,16 @@ const char iopt_prompts[] PROGMEM =
"Subnet mask2: "
"Subnet mask3: "
"Subnet mask4: "
+ "Station stagger?"
+ "Force wired? "
+ "Reserved 1 "
+ "Reserved 2 "
+ "Reserved 3 "
+ "Reserved 4 "
+ "Reserved 5 "
+ "Reserved 6 "
+ "Reserved 7 "
+ "Reserved 8 "
"WiFi mode? "
"Factory reset? ";
@@ -306,6 +326,16 @@ const byte iopt_max[] PROGMEM = {
255,
255,
255,
+ 1,
+ 1,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
255,
1
};
@@ -381,6 +411,16 @@ byte OpenSprinkler::iopts[] = {
255,// subnet mask 2
255,// subnet mask 3
0,
+ 1, // station stagger
+ 1, // force wired connection
+ 0, // reserved 1
+ 0, // reserved 2
+ 0, // reserved 3
+ 0, // reserved 4
+ 0, // reserved 5
+ 0, // reserved 6
+ 0, // reserved 7
+ 0, // reserved 8
WIFI_MODE_AP, // wifi mode
0 // reset
};
diff --git a/defines.h b/defines.h
index bf088f29..04df087f 100755
--- a/defines.h
+++ b/defines.h
@@ -29,14 +29,14 @@
typedef unsigned char byte;
typedef unsigned long ulong;
-#define TMP_BUFFER_SIZE 255 // scratch buffer size
+#define TMP_BUFFER_SIZE 320 // scratch buffer size
/** Firmware version, hardware version, and maximal values */
-#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0
+#define OS_FW_VERSION 221 // Firmware version: 220 means 2.2.0
// if this number is different from the one stored in non-volatile memory
// a device reset will be automatically triggered
-#define OS_FW_MINOR 6 // Firmware minor version
+#define OS_FW_MINOR 0 // Firmware minor version
/** Hardware version base numbers */
#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler
@@ -135,7 +135,7 @@ typedef unsigned long ulong;
#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders
#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations
#define STATION_NAME_SIZE 32 // maximum number of characters in each station name
-#define MAX_SOPTS_SIZE 160 // maximum string option size
+#define MAX_SOPTS_SIZE 320 // maximum string option size
#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12)
@@ -247,6 +247,16 @@ enum {
IOPT_SUBNET_MASK2,
IOPT_SUBNET_MASK3,
IOPT_SUBNET_MASK4,
+ IOPT_STATION_STAGGER,
+ IOPT_FORCE_WIRED,
+ IOPT_RESERVE_1,
+ IOPT_RESERVE_2,
+ IOPT_RESERVE_3,
+ IOPT_RESERVE_4,
+ IOPT_RESERVE_5,
+ IOPT_RESERVE_6,
+ IOPT_RESERVE_7,
+ IOPT_RESERVE_8,
IOPT_WIFI_MODE, //ro
IOPT_RESET, //ro
NUM_IOPTS // total number of integer options
diff --git a/mqtt.cpp b/mqtt.cpp
index d2f97136..529ddbcf 100644
--- a/mqtt.cpp
+++ b/mqtt.cpp
@@ -71,11 +71,11 @@ extern char tmp_buffer[];
#define OS_MQTT_KEEPALIVE 60
#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config
-#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name
-#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username
-#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password
+#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 100 chars for broker name
+#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 50 chars for username
+#define MQTT_MAX_PASSWORD_LEN 120 // Note: App is set to max 120 chars for password
#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit
-#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts
+#define MQTT_RECONNECT_DELAY 100 // Minumum of 60 seconds between reconnect attempts
#define MQTT_ROOT_TOPIC "opensprinkler"
#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability"
From df603d129e2dc194ad4fae4321f2837644e74a30 Mon Sep 17 00:00:00 2001
From: Ray
Date: Wed, 10 Jul 2024 08:06:56 -0400
Subject: [PATCH 15/61] add firmware upgrade message on the /update pages
---
OpenSprinkler.cpp | 2 +-
html/ap_home.html | 5 ++---
html/ap_update.html | 3 ++-
html/sta_update.html | 3 ++-
htmls.h | 5 +++--
5 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp
index b41041d8..774f2a3f 100644
--- a/OpenSprinkler.cpp
+++ b/OpenSprinkler.cpp
@@ -2261,7 +2261,7 @@ void OpenSprinkler::parse_otc_config() {
void OpenSprinkler::options_setup() {
// Check reset conditions:
- if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219)
+ if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)0) {
+ if(id('bssid').value.length>0) {
if(id('channel').value.length>0) comm+='&extra='+encodeURIComponent(id('bssid').value+'@'+id('channel').value);
else comm+='&extra='+encodeURIComponent(id('bssid').value+'@0');
}
-
xhr.open('POST', comm, true); xhr.send();
id('butt').disabled=true;id('ssid').disabled=true;id('pass').disabled=true;
id('bssid').disabled=true;id('channel').disabled=true;
@@ -84,4 +83,4 @@
}
setTimeout(loadSSIDs, 1000);
-
\ No newline at end of file
+
diff --git a/html/ap_update.html b/html/ap_update.html
index c6434c13..bfb9841c 100644
--- a/html/ap_update.html
+++ b/html/ap_update.html
@@ -11,6 +11,7 @@
Device password:
+ IMPORTANT: firmware update across major revisions (e.g. from 2.2.0 to 2.2.1) will trigger a factory reset. Save a copy of your current configurations before proceeding, so you can import and recover them after the upgrade.
Submit
@@ -53,4 +54,4 @@
xhr.send(fd);
});
-
\ No newline at end of file
+
diff --git a/html/sta_update.html b/html/sta_update.html
index 2671a39b..1427ce0e 100644
--- a/html/sta_update.html
+++ b/html/sta_update.html
@@ -14,6 +14,7 @@
Device password:
+ IMPORTANT: firmware update across major revisions (e.g. from 2.2.0 to 2.2.1) will trigger a factory reset. Save a copy of your current configurations before proceeding, so you can import and recover them after the upgrade.
Submit
@@ -59,4 +60,4 @@
});
-
\ No newline at end of file
+
diff --git a/htmls.h b/htmls.h
index a4eeb2bc..a331b631 100644
--- a/htmls.h
+++ b/htmls.h
@@ -53,11 +53,10 @@ id('bssid').disabled=false;id('channel').disabled=false;
}
};
var comm='ccap?ssid='+encodeURIComponent(id('ssid').value)+'&pass='+encodeURIComponent(id('pass').value);
-if(id('bssid').value.length>0) {
+if(id('bssid').value.length>0) {
if(id('channel').value.length>0) comm+='&extra='+encodeURIComponent(id('bssid').value+'@'+id('channel').value);
else comm+='&extra='+encodeURIComponent(id('bssid').value+'@0');
}
-
xhr.open('POST', comm, true); xhr.send();
id('butt').disabled=true;id('ssid').disabled=true;id('pass').disabled=true;
id('bssid').disabled=true;id('channel').disabled=true;
@@ -99,6 +98,7 @@ const char ap_update_html[] PROGMEM = R"(