diff --git a/Sming/SmingCore/Network/SmtpClient.cpp b/Sming/SmingCore/Network/SmtpClient.cpp new file mode 100644 index 0000000000..5c3284197d --- /dev/null +++ b/Sming/SmingCore/Network/SmtpClient.cpp @@ -0,0 +1,534 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * SmtpClient + * + * Author: 2018 - Slavey Karadzhov + * + ****/ + +/** @defgroup smtpclient SMTP client + * @brief Provides SMTP/S client + * @ingroup tcpclient + * @{ + */ + +#include "SmtpClient.h" +#include "../../Services/WebHelpers/base64.h" +#include "../Data/Stream/QuotedPrintableOutputStream.h" +#include "../Data/Stream/Base64OutputStream.h" + +#if !defined(ENABLE_SSL) || ENABLE_SSL == 0 +// if our SSL is not used then we try to use the one coming from the SDK +#define MD5_SIZE 16 +extern "C" { +void ssl_hmac_md5(const uint8_t *msg, int length, const uint8_t *key, int key_len, uint8_t *digest); +} +#define hmac_md5(A, B, C, D, E) ssl_hmac_md5(A, B, C, D, E) +#endif + +#define ADVANCE { buffer++; len--; } +#define ADVANCE_AND_BREAK { ADVANCE; break; } +#define ADVANCE_UNTIL_EOL do { \ + if(*(buffer - 1) == '\r' && *buffer == '\n') { ADVANCE_AND_BREAK; } \ + ADVANCE; \ +} while(len > 0); + +#define ADVANCE_UNTIL_EOL_OR_BREAK { ADVANCE_UNTIL_EOL; if(*(buffer-1) != '\n') { break;} } + +#define RETURN_ON_ERROR(SUCCESS_CODE) if(codeValue != SUCCESS_CODE) { memcpy(message, line, std::min(lineLength, SMTP_ERROR_LENGTH)); message[SMTP_ERROR_LENGTH]='\0'; return 0; } + +#define WAIT_FOR_STREAM(A) if(A != nullptr && !A->isFinished()) { break; } + + +SmtpClient::SmtpClient(bool autoDestroy /* =false */): TcpClient(autoDestroy), outgoingMail(nullptr) +{ +} + +SmtpClient::~SmtpClient() +{ + // TODO: clear all pointers... + delete stream; + delete outgoingMail; + stream = nullptr; + outgoingMail = nullptr; + do { + MailMessage *mail = mailQ.dequeue(); + if(mail == nullptr) { + break; + } + delete mail; + } + while(1); +} + +bool SmtpClient::connect(const URL& url) +{ + if (getConnectionState() != eTCS_Ready) { + close(); + } + + this->url = url; + if(!this->url.Port) { + this->url.Port = 25; + if(this->url.Protocol == SMTP_OVER_SSL_PROTOCOL) { + this->url.Protocol = 465; + } + } + + return TcpClient::connect(url.Host, url.Port, (url.Protocol == SMTP_OVER_SSL_PROTOCOL)); +} + +bool SmtpClient::send(const String& from, const String& to, + const String& subject, const String& body) +{ + MailMessage *mail = new MailMessage(); + + mail->to = to; + mail->from = from; + mail->subject = subject; + mail->setBody(body); + + return send(mail); +} + +MailMessage* SmtpClient::getCurrentMessage() +{ + return outgoingMail; +} + +bool SmtpClient::send(MailMessage* mail) +{ + if(!mailQ.enqueue(mail)) { + // the mail queue is full + delete mail; + return false; + } + + return true; +} + +void SmtpClient::quit() +{ + sendString("QUIT\r\n"); + state = eSMTP_Quitting; +} + +// Protected Methods +void SmtpClient::onReadyToSendData(TcpConnectionEvent sourceEvent) +{ + switch(state) { + case eSMTP_StartTLS: { + sendString("STARTTLS\n\n"); + state = eSMTP_Banner; + break; + } + + case eSMTP_SendAuth: { + if(authMethods.count()) { + // TODO: Simplify the code in that block... + Vector preferredOrder; + if(useSsl) { + preferredOrder.addElement("PLAIN"); + preferredOrder.addElement("CRAM-MD5"); + } + else { + preferredOrder.addElement("CRAM-MD5"); + preferredOrder.addElement("PLAIN"); + } + + for(int i=0; i< preferredOrder.count(); i++) { + if(authMethods.contains(preferredOrder[i])) { + if(preferredOrder[i] == "PLAIN") { + // base64('\0' + username + '\0' + password) + int tokenLength = url.User.length() + url.Password.length() + 2; + uint8_t token[tokenLength]; + memcpy((token+1), url.User.c_str(), url.User.length()); // copy user + memcpy((token + 2 + url.User.length()), url.Password.c_str(), url.Password.length()); // copy password + int hashLength = tokenLength * 4; + char hash[hashLength]; + base64_encode(tokenLength, token, hashLength, hash); + sendString("AUTH PLAIN "+String(hash)+"\r\n"); + + state = eSMTP_SendingAuth; + break; + } + else if( preferredOrder[i] == "CRAM-MD5"){ + // otherwise we can try the slow cram-md5 authentication... + sendString("AUTH CRAM-MD5\r\n"); + state = eSMTP_RequestingAuthChallenge; + break; + } + } + } + } /* authMethods.count */ + + if(state == eSMTP_SendAuth) { + state = eSMTP_Ready; + } + + break; + } + + case eSMTP_SendAuthResponse: { + // Calculate the CRAM-MD5 response + // base64.b64encode("user " +hmac.new(password, base64.b64decode(challenge), hashlib.md5).hexdigest()) + uint8_t digest[MD5_SIZE] = {0}; + hmac_md5((const uint8_t*)authChallenge.c_str(), authChallenge.length(), + (const uint8_t*)url.Password.c_str(), url.Password.length(), + digest); + + char hexdigest[MD5_SIZE*2+1] = {0}; + char *c = hexdigest; + for (int i = 0; i < MD5_SIZE; i++) { + ets_sprintf(c, "%02x", digest[i]); + c += 2; + } + *c = '\0'; + + String token = url.User + " " + hexdigest; + int hashLength = token.length() * 4; + char hash[hashLength]; + base64_encode(token.length(), (const unsigned char *)token.c_str(), hashLength, hash); + sendString(String(hash)+"\r\n"); + state = eSMTP_SendingAuth; + + break; + } + + case eSMTP_Ready: { + delete outgoingMail; + + debugf("Queue size: %d", mailQ.count()); + + outgoingMail = mailQ.dequeue(); + if(!outgoingMail) { + break; + } + + state = eSMTP_SendMail; + } + + case eSMTP_SendMail: { + sendString("MAIL FROM:" + outgoingMail->from + "\r\n"); + if(options & SMTP_OPT_PIPELINE) { + sendString("RCPT TO:" + outgoingMail->to + "\r\n"); + sendString("DATA\r\n"); + } + + state = eSMTP_SendingMail; + break; + } + + case eSMTP_SendRcpt: { + sendString("RCPT TO:" + outgoingMail->to+"\r\n"); + state = eSMTP_SendingRcpt; + break; + } + + case eSMTP_SendData: { + sendString("DATA\r\n"); + state = eSMTP_SendingData; + break; + } + + case eSMTP_SendHeader: { + WAIT_FOR_STREAM(stream); + + sendMailHeaders(outgoingMail); + + state = eSMTP_SendingHeaders; + } + + case eSMTP_SendingHeaders: { + WAIT_FOR_STREAM(stream); + + state = eSMTP_StartBody; + } + + case eSMTP_StartBody: { + sendMailBody(outgoingMail); + state = eSMTP_SendingBody; + } + + case eSMTP_SendingBody: { + WAIT_FOR_STREAM(stream); + + // send the final dot + state = eSMTP_Sent; + delete stream; + stream = nullptr; + + sendString("\r\n.\r\n"); + break; + } + + case eSMTP_Disconnect: { + close(); + return; + } + + } /* switch(state) */ + + TcpClient::onReadyToSendData(sourceEvent); +} + +HttpPartResult SmtpClient::multipartProducer() +{ + HttpPartResult result; + + if(outgoingMail->attachments.count()) { + result = outgoingMail->attachments[0]; + + if(!result.headers->contains("Content-Transfer-Encoding")) { + result.stream = new Base64OutputStream(result.stream); + (*result.headers)["Content-Transfer-Encoding"] = "base64"; + } + + outgoingMail->attachments.remove(0); + } + + return result; +} + +void SmtpClient::sendMailHeaders(MailMessage* mail) +{ + mail->getHeaders(); + + if(!mail->headers.contains("Content-Transfer-Encoding")) { + mail->headers["Content-Transfer-Encoding"] = "quoted-printable"; + mail->stream = new QuotedPrintableOutputStream(mail->stream); + } + + if(mail->attachments.count()) { + MultipartStream* mStream = new MultipartStream( + HttpPartProducerDelegate(&SmtpClient::multipartProducer, this)); + HttpPartResult text; + text.headers = new HttpHeaders(); + (*text.headers)["Content-Type"] = mail->headers["Content-Type"]; + (*text.headers)["Content-Transfer-Encoding"] = mail->headers["Content-Transfer-Encoding"]; + text.stream = mail->stream; + + mail->attachments.insertElementAt(text, 0); + + mail->headers.remove("Content-Transfer-Encoding"); + mail->headers["Content-Type"] = String("multipart/mixed; boundary=") + mStream->getBoundary(); + mail->stream = mStream; + } + + for(int i=0; i< mail->headers.count(); i++) { + String key = mail->headers.keyAt(i); + String value = mail->headers.valueAt(i); + sendString(key+": "+ value + "\r\n"); + } + sendString("\r\n"); +} + +bool SmtpClient::sendMailBody(MailMessage* mail) +{ + if(mail->stream == nullptr) { + return true; + } + + delete stream; + stream = mail->stream; // avoid intermediate buffers + mail->stream = nullptr; + + return false; +} + +err_t SmtpClient::onReceive(pbuf *buf) +{ + if (buf == nullptr) { + return TcpClient::onReceive(buf); + } + + pbuf *cur = buf; + int parsedBytes = 0; + while (cur != nullptr && cur->len > 0) { + parsedBytes += smtpParse((char*) cur->payload, cur->len); + cur = cur->next; + } + + if (parsedBytes != buf->tot_len) { + debug_e("Got error: %s:%s", code, message); + + if(!errorCallback || + errorCallback(*this, codeValue, message) != 0) { + // abort the connection if we cannot handle it. + TcpClient::onReceive(nullptr); + + return ERR_ABRT; + } + } + + TcpClient::onReceive(buf); + + return ERR_OK; +} + +int SmtpClient::smtpParse(char* buffer, size_t len) +{ + char *start = buffer; + while(len) { + char currentByte = *buffer; + // parse the code... + if(codeLength < 3) { + code[codeLength++] = currentByte; + ADVANCE; + continue; + } + else if (codeLength == 3) { + code[codeLength] = '\0'; + if(currentByte != ' ' && currentByte != '-') { + // the code must be followed by space or minus + return 0; + } + + char *tmp; + codeValue = strtol(code, &tmp, 10); + isLastLine = (currentByte == ' '); + codeLength++; + ADVANCE; + } + + char *line = buffer; + ADVANCE_UNTIL_EOL_OR_BREAK; + codeLength = 0; + int lineLength = (buffer-line) - 2; + + switch(state) { + case eSMTP_Banner: { + RETURN_ON_ERROR(SMTP_CODE_SERVICE_READY); + + if(!useSsl && (options & SMTP_OPT_STARTTLS)) { + useSsl = true; + TcpConnection::staticOnConnected((void *)this, tcp, ERR_OK); + } + + sendString("EHLO "+url.Host+"\r\n"); + state = eSMTP_Hello; + + break; + } + + case eSMTP_Hello: { + RETURN_ON_ERROR(SMTP_CODE_REQUEST_OK); + + if(strncmp(line, "PIPELINING", lineLength) == 0) { + // PIPELINING (see: https://tools.ietf.org/html/rfc2920) + options |= SMTP_OPT_PIPELINE; + } + else if(strncmp(line, "STARTTLS",lineLength) == 0) { + // STARTTLS (see: https://www.ietf.org/rfc/rfc3207.txt) + options |= SMTP_OPT_STARTTLS; + } + else if(strncmp(line, "AUTH ", 5) == 0) { + // Process authentication methods + // Ex: 250-AUTH CRAM-MD5 PLAIN LOGIN + // See: https://tools.ietf.org/html/rfc4954 + int offset = 0; + int pos = -1; + + String text(line + 5, lineLength - 5); + splitString(text,' ', authMethods); + } + + if(isLastLine) { + state = eSMTP_Ready; +#ifdef ENABLE_SSL + if(!useSsl && (options & SMTP_OPT_STARTTLS)) { + state = eSMTP_StartTLS; + } else +#endif + if(url.User && authMethods.count()) { + state = eSMTP_SendAuth; + } + } + + break; + } + + case eSMTP_RequestingAuthChallenge: { + RETURN_ON_ERROR(SMTP_CODE_AUTH_CHALLENGE); + uint8_t out[lineLength]; + int outlen = lineLength; + +// TODO: Unify the base64_[decode|encode]() signature in base64.cpp to match the one in axTLS crypt_misc.h +#ifdef ENABLE_SSL + base64_decode(line, lineLength, out, &outlen); +#else + // size_t in_len, const char *in, size_t out_len, unsigned char *out + outlen = base64_decode(lineLength, line, outlen, out); +#endif + authChallenge = String((const char*)out, outlen); + state = eSMTP_SendAuthResponse; + + break; + } + + case eSMTP_SendingAuth: { + RETURN_ON_ERROR(SMTP_CODE_AUTH_OK); + + authMethods.clear(); + + state = eSMTP_Ready; + + break; + } + + case eSMTP_SendingMail: { + RETURN_ON_ERROR(SMTP_CODE_REQUEST_OK); + + state = ((options & SMTP_OPT_PIPELINE) ? eSMTP_SendingRcpt: eSMTP_SendRcpt); + + break; + } + + case eSMTP_SendingRcpt:{ + RETURN_ON_ERROR(SMTP_CODE_REQUEST_OK); + + state = ((options & SMTP_OPT_PIPELINE) ? eSMTP_SendingData: eSMTP_SendData); + + break; + } + case eSMTP_SendingData: { + RETURN_ON_ERROR(SMTP_CODE_START_DATA); + + state = eSMTP_SendHeader; + + break; + } + case eSMTP_Sent: { + RETURN_ON_ERROR(SMTP_CODE_REQUEST_OK); + + state = eSMTP_Ready; + + if(messageSentCallback) { + messageSentCallback(*this, codeValue, message); + } + delete outgoingMail; + outgoingMail=nullptr; + + break; + } + + case eSMTP_Quitting: { + RETURN_ON_ERROR(SMTP_CODE_BYE); + close(); + state = eSMTP_Disconnect; + + break; + } + + default: + memcpy(message, line, std::min(lineLength, SMTP_ERROR_LENGTH)); + + } /* switch(state) */ + } + + return (buffer - start); +} diff --git a/Sming/SmingCore/Network/SmtpClient.h b/Sming/SmingCore/Network/SmtpClient.h new file mode 100644 index 0000000000..29725587e8 --- /dev/null +++ b/Sming/SmingCore/Network/SmtpClient.h @@ -0,0 +1,216 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * SmtpClient - asynchronous SmtpClient that supports the following features: + * - extended HELO command set + * - support for PIPELINING + * - support for STARTTLS (if the directive ENABLE_SSL=1 is set) + * - support for smtp connection over SSL (if the directive ENABLE_SSL=1 is set) + * - support for PLAIN and CRAM-MD5 authentication + * - support for multiple attachments + * - Support for base64 and quoted-printable transfer encoding + * + * Author: 2018 - Slavey Karadzhov + * + ****/ + +/** @defgroup smtpclient SMTP client + * @brief Provides SMTP/S client + * @ingroup tcpclient + * @{ + */ + +#pragma once + +#include "TcpClient.h" +#include "../Data/MailMessage.h" +#include "URL.h" +#include "../../Wiring/WString.h" +#include "../../Wiring/WVector.h" +#include "../Data/Stream/DataSourceStream.h" +#include "WebConstants.h" +#include "../Data/Structures.h" + +#include + +#define SMTP_PROTOCOL "smtp" +#define SMTP_OVER_SSL_PROTOCOL "smtps" + +/* Maximum waiting emails in the mail queue */ +#define SMTP_QUEUE_SIZE 5 + +/* Buffer size used to read the error messages */ +#define SMTP_ERROR_LENGTH 40 + +/** + * SMTP response codes + */ +#define SMTP_CODE_SERVICE_READY 220 +#define SMTP_CODE_BYE 221 +#define SMTP_CODE_AUTH_OK 235 +#define SMTP_CODE_REQUEST_OK 250 +#define SMTP_CODE_AUTH_CHALLENGE 334 +#define SMTP_CODE_START_DATA 354 + +#define SMTP_OPT_PIPELINE bit(0) +#define SMTP_OPT_STARTTLS bit(1) +#define SMTP_OPT_AUTH_PLAIN bit(2) +#define SMTP_OPT_AUTH_LOGIN bit(3) +#define SMTP_OPT_AUTH_CRAM_MD5 bit(4) + +enum SmtpState +{ + eSMTP_Banner = 0, + eSMTP_Hello, + eSMTP_StartTLS, + eSMTP_SendAuth, + eSMTP_SendingAuthLogin, + eSMTP_RequestingAuthChallenge, + eSMTP_SendAuthResponse, + eSMTP_SendingAuth, + eSMTP_Ready, + eSMTP_SendMail, + eSMTP_SendingMail, + eSMTP_SendRcpt, + eSMTP_SendingRcpt, + eSMTP_SendData, + eSMTP_SendingData, + eSMTP_SendHeader, + eSMTP_SendingHeaders, + eSMTP_StartBody, + eSMTP_SendingBody, + eSMTP_Sent, + eSMTP_Quitting, + eSMTP_Disconnect +}; + +class SmtpClient; + +typedef std::function SmtpClientCallback; + +class SmtpClient : protected TcpClient +{ +public: + SmtpClient(bool autoDestroy=false); + virtual ~SmtpClient(); + + /** + * @brief Connects to remote URL + * @param URL - provides the protocol, remote server, port and user credentials + * allowed protocols: + * - smtp - clear text SMTP + * - smtps - SMTP over SSL connection + */ + bool connect(const URL& url); + + /** + * @brief Queues a single message before it is sent later to the SMTP server + * + * @param String& from + * @param String& to + * @param String& subject + * @param String& body the body in plain text format + * + * @return true when the message was queued successfully, false otherwise + */ + bool send(const String& from, const String& to, const String& subject, const String& body); + + /** + * @brief Powerful method to queues a single message before it is sent later to the SMTP server + * @param MailMessage* message + * + * @return true when the message was queued successfully, false otherwise + */ + bool send(MailMessage* message); + + /** + * @brief Gets the current message + * + * @return MailMessage* message - the message, or NULL if none is scheduled + */ + MailMessage* getCurrentMessage(); + + inline size_t countPending() + { + return mailQ.count(); + } + + /** + * @brief Sends a quit command to the server and closes the TCP conneciton + */ + void quit(); + + /** + * @brief Returns the current state of the SmtpClient. + */ + SmtpState getState() { return state; } + + /** + * @brief Callback that will be called every time a message is sent successfully + * @param SmtpClientCallback callback + */ + inline void onMessageSent(SmtpClientCallback callback) + { + messageSentCallback = callback; + } + + /** + * @brief Callback that will be called every an error occurs + * @param SmtpClientCallback callback + */ + inline void onServerError(SmtpClientCallback callback) + { + errorCallback = callback; + } + + using TcpClient::setTimeOut; + +#ifdef ENABLE_SSL + using TcpClient::addSslOptions; + using TcpClient::addSslValidator; + using TcpClient::pinCertificate; + using TcpClient::setSslKeyCert; + using TcpClient::freeSslKeyCert; + using TcpClient::getSsl; +#endif + +protected: + virtual err_t onReceive(pbuf *buf); + virtual void onReadyToSendData(TcpConnectionEvent sourceEvent); + + void sendMailHeaders(MailMessage* mail); + bool sendMailBody(MailMessage* mail); + +private: + URL url; + Vector authMethods; + SimpleConcurrentQueue mailQ; + char code[4] = {0}; + int codeValue = 0; + String authChallenge; + char message[SMTP_ERROR_LENGTH + 1] = {0}; + bool isLastLine = false; + uint8_t codeLength = 0; + int options = 0; + MailMessage *outgoingMail = nullptr; + SmtpState state = eSMTP_Banner; + + SmtpClientCallback errorCallback = nullptr; + SmtpClientCallback messageSentCallback = nullptr; + +private: + /** + * @brief Simple and naive SMTP parser with a state machine + */ + int smtpParse(char* data, size_t len); + +private: + /** + * @brief Takes care to fetch the correct streams for a message + * @note The magic where all streams and attachments are packed together is happening here + */ + HttpPartResult multipartProducer(); +}; diff --git a/samples/SmtpClient/.project b/samples/SmtpClient/.project new file mode 100644 index 0000000000..90f03a2107 --- /dev/null +++ b/samples/SmtpClient/.project @@ -0,0 +1,28 @@ + + + SmtpClient + + + SmingFramework + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/samples/SmtpClient/Makefile b/samples/SmtpClient/Makefile new file mode 100644 index 0000000000..16d76cd676 --- /dev/null +++ b/samples/SmtpClient/Makefile @@ -0,0 +1,24 @@ +##################################################################### +#### Please don't change this file. Use Makefile-user.mk instead #### +##################################################################### +# Including user Makefile. +# Should be used to set project-specific parameters +include ./Makefile-user.mk + +# Important parameters check. +# We need to make sure SMING_HOME and ESP_HOME variables are set. +# You can use Makefile-user.mk in each project or use enviromental variables to set it globally. + +ifndef SMING_HOME +$(error SMING_HOME is not set. Please configure it in Makefile-user.mk) +endif +ifndef ESP_HOME +$(error ESP_HOME is not set. Please configure it in Makefile-user.mk) +endif + +# Include main Sming Makefile +ifeq ($(RBOOT_ENABLED), 1) +include $(SMING_HOME)/Makefile-rboot.mk +else +include $(SMING_HOME)/Makefile-project.mk +endif diff --git a/samples/SmtpClient/Makefile-user.mk b/samples/SmtpClient/Makefile-user.mk new file mode 100644 index 0000000000..9971cec20b --- /dev/null +++ b/samples/SmtpClient/Makefile-user.mk @@ -0,0 +1,43 @@ +## Local build configuration +## Parameters configured here will override default and ENV values. +## Uncomment and change examples: + +## Add your source directories here separated by space +# MODULES = app + +# EXTRA_INCDIR = include + +## ESP_HOME sets the path where ESP tools and SDK are located. +## Windows: +# ESP_HOME = c:/Espressif + +## MacOS / Linux: +# ESP_HOME = /opt/esp-open-sdk + +## SMING_HOME sets the path where Sming framework is located. +## Windows: +# SMING_HOME = c:/tools/sming/Sming + +## MacOS / Linux +# SMING_HOME = /opt/sming/Sming + +## COM port parameter is reqruied to flash firmware correctly. +## Windows: +COM_PORT = COM6 + +## MacOS / Linux: +# COM_PORT = /dev/tty.usbserial + +## Com port speed +COM_SPEED = 115200 + +## SPIFFS options +#DISABLE_SPIFFS = 1 +SPIFF_FILES = files + +ENABLE_SSL ?= 1 + +ifneq ($(ENABLE_SSL),1) +# Needed for hmac_md5, etc. + EXTRA_LIBS += ssl +endif \ No newline at end of file diff --git a/samples/SmtpClient/README.md b/samples/SmtpClient/README.md new file mode 100644 index 0000000000..f430a61377 --- /dev/null +++ b/samples/SmtpClient/README.md @@ -0,0 +1,23 @@ +# Purpose of this Sample Code +To show the basics of sending an email via SMTP + +# SMTP + +When an IOT device detects a malfunction, it is good to be able to notify the user. +We can do that either by sending them an email or via a service like MQTT. + +This sample shows how to send an email via SMTP directly from the ESP8266. + +smtp2go conveniently provides a free account. + +Create an account here: +https://www.smtp2go.com/setupguide/arduino/ . + +It needs some configuration: +* the name of the SMTP server, eg "mail.smtp2go.com" +* a username and password +* the name and email of the person from whom the email will be sent +* the name and email of the person to send the email to + +Edit the sample to replace these values and the SSID etc. + diff --git a/samples/SmtpClient/app/application.cpp b/samples/SmtpClient/app/application.cpp new file mode 100644 index 0000000000..4af04442a5 --- /dev/null +++ b/samples/SmtpClient/app/application.cpp @@ -0,0 +1,92 @@ +#include +#include "SmingCore/SmingCore.h" +#include "SmingCore/Network/SmtpClient.h" + +// If you want, you can define WiFi settings globally in Eclipse Environment Variables +#ifndef WIFI_SSID + #define WIFI_SSID "PleaseEnterSSID" // Put you SSID and Password here + #define WIFI_PWD "PleaseEnterPass" +#endif + +// Make sure to change those to your desired values +#define MAIL_FROM "admin@sming.com" +#define MAIL_TO "slav@attachix.com" +#define SMTP_USERNAME +#define SMTP_PASSWORD +#define SMTP_HOST "attachix.com" +#define SMTP_PORT 25 +#define SMTP_USE_SSL false + +SmtpClient client; + +int onServerError(SmtpClient& client, int code, char* status) +{ + debugf("Status: %s", status); + + return 0; // return non-zero value to abort the connection +} + +int onMailSent(SmtpClient& client, int code, char* status) +{ + // get the sent mail message + MailMessage* mail = client.getCurrentMessage(); + + // TODO: The status line contains the unique ID that was given to this email + debugf("Mail sent. Status: %s", status); + + // And if there are no more pending emails then you can disconnect from the server + if(!client.countPending()) { + debugf("No more mails to send. Quitting..."); + client.quit(); + } + + return 0; +} + +void onConnected(IPAddress ip, IPAddress mask, IPAddress gateway) +{ +#ifdef ENABLE_SSL + client.addSslOptions(SSL_SERVER_VERIFY_LATER); +#endif + + client.onServerError(onServerError); + + String dsn = "smtp" ; + if(SMTP_USE_SSL) { + dsn + "s"; + } + + dsn += String("://") + SMTP_USERNAME + ":" + SMTP_PASSWORD+"@" + SMTP_HOST + ":" + SMTP_PORT; + debugf("Connecting to SMTP server using: %s", dsn.c_str()); + + client.connect(URL(dsn)); + + MailMessage* mail = new MailMessage(); + mail->from = MAIL_FROM; + mail->to = MAIL_TO; + mail->subject = "Greetings from Sming"; + mail->setBody("Hello.\r\n.\r\n" + "This is test email from Sming " + "It contains attachment, Ümlauts, кирилица + etc"); + + FileStream* file= new FileStream("image.png"); + mail->addAttachment(file); + + client.onMessageSent(onMailSent); + client.send(mail); +} + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); + Serial.systemDebugOutput(true); + Serial.println("Sming: SmtpClient example!"); + + spiffs_mount(); + + // Setup the WIFI connection + WifiStation.enable(true); + WifiStation.config(WIFI_SSID, WIFI_PWD); // Put you SSID and Password here + + WifiEvents.onStationGotIP(onConnected); +} diff --git a/samples/SmtpClient/files/image.png b/samples/SmtpClient/files/image.png new file mode 100644 index 0000000000..369e55dc65 Binary files /dev/null and b/samples/SmtpClient/files/image.png differ diff --git a/samples/SmtpClient/include/ssl/cert.h b/samples/SmtpClient/include/ssl/cert.h new file mode 100644 index 0000000000..d38c8d2723 --- /dev/null +++ b/samples/SmtpClient/include/ssl/cert.h @@ -0,0 +1,43 @@ +unsigned char default_certificate[] = { + 0x30, 0x82, 0x01, 0xd7, 0x30, 0x82, 0x01, 0x40, 0x02, 0x09, 0x00, 0xa6, + 0xf3, 0xaa, 0xf6, 0xaf, 0xcd, 0x0b, 0x3a, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x34, + 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x29, 0x61, + 0x78, 0x54, 0x4c, 0x53, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x44, 0x6f, 0x64, 0x67, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x34, + 0x32, 0x34, 0x30, 0x39, 0x34, 0x38, 0x32, 0x39, 0x5a, 0x17, 0x0d, 0x33, + 0x32, 0x30, 0x31, 0x30, 0x31, 0x30, 0x39, 0x34, 0x38, 0x32, 0x39, 0x5a, + 0x30, 0x2c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x0d, 0x61, 0x78, 0x54, 0x4c, 0x53, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x30, 0x81, + 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, + 0x81, 0x81, 0x00, 0xa0, 0xbd, 0x8d, 0xf6, 0x21, 0x4b, 0x76, 0x92, 0xa8, + 0x13, 0xe8, 0x74, 0x90, 0x79, 0x05, 0x04, 0xfb, 0xf1, 0x4f, 0x4b, 0x02, + 0xc2, 0xd9, 0x6f, 0xc1, 0x82, 0xa7, 0xf2, 0x8b, 0x03, 0x15, 0xe3, 0x56, + 0xce, 0x37, 0x4a, 0x2d, 0xcb, 0x80, 0xd1, 0xc0, 0xa0, 0x84, 0x3d, 0x1b, + 0xd9, 0xda, 0x5c, 0xbc, 0x7a, 0x2c, 0x35, 0xda, 0x9e, 0xe7, 0x56, 0x4b, + 0xc5, 0x63, 0xa0, 0x6f, 0xa7, 0x39, 0x0c, 0x72, 0x56, 0x43, 0x34, 0xb0, + 0x9a, 0x59, 0xfb, 0x86, 0xa4, 0x3a, 0xdd, 0xfb, 0xf6, 0xfb, 0x3a, 0x26, + 0x69, 0xe8, 0x45, 0x03, 0x3b, 0xde, 0xa9, 0x64, 0x97, 0x8a, 0x95, 0xf1, + 0xfc, 0xd5, 0x77, 0x2d, 0x49, 0x4c, 0x72, 0xf5, 0xcf, 0xcc, 0xb8, 0x54, + 0x87, 0x40, 0x4a, 0x6f, 0x0f, 0x40, 0xd1, 0xba, 0xf1, 0x8c, 0x5b, 0xf9, + 0x5d, 0x94, 0xa6, 0xd4, 0xe9, 0x98, 0x38, 0x91, 0x77, 0x91, 0x9b, 0x02, + 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x19, + 0xe5, 0x9f, 0x6f, 0x26, 0x0a, 0x4b, 0xb4, 0xc2, 0x70, 0x5b, 0x2a, 0x31, + 0xe6, 0x0f, 0x37, 0x30, 0x1c, 0x9b, 0xaf, 0x0a, 0xe6, 0x12, 0xf8, 0xde, + 0x37, 0x2b, 0xd3, 0xb0, 0x56, 0x2e, 0xeb, 0x95, 0xdb, 0xd3, 0x2f, 0x70, + 0xd9, 0xa5, 0x4e, 0x01, 0xd4, 0x2a, 0xb1, 0xa1, 0x79, 0x71, 0xb5, 0xe8, + 0xf1, 0x0d, 0xb0, 0x95, 0x98, 0xb5, 0xc8, 0x7d, 0x1d, 0x10, 0x64, 0x54, + 0x16, 0x9f, 0x1e, 0x92, 0xc6, 0x26, 0xe6, 0xc5, 0xa7, 0xb0, 0x1d, 0x3c, + 0xd5, 0x92, 0xbf, 0xf3, 0xcf, 0xfe, 0x67, 0x03, 0x9d, 0x57, 0x76, 0x9a, + 0xa2, 0x4e, 0x55, 0xad, 0xc2, 0xcb, 0xbe, 0x7e, 0x83, 0xb1, 0xd8, 0x02, + 0x59, 0x49, 0x7b, 0x48, 0x70, 0xec, 0xe1, 0x17, 0x76, 0x8e, 0x00, 0x3e, + 0x28, 0xd6, 0x79, 0x84, 0x65, 0x7e, 0x77, 0x8b, 0x0b, 0x3a, 0x19, 0xbb, + 0x25, 0xec, 0x0a, 0xdd, 0x9a, 0xfe, 0x96 +}; +unsigned int default_certificate_len = 475; diff --git a/samples/SmtpClient/include/ssl/private_key.h b/samples/SmtpClient/include/ssl/private_key.h new file mode 100644 index 0000000000..ef6d60fc9a --- /dev/null +++ b/samples/SmtpClient/include/ssl/private_key.h @@ -0,0 +1,54 @@ +unsigned char default_private_key[] = { + 0x30, 0x82, 0x02, 0x5c, 0x02, 0x01, 0x00, 0x02, 0x81, 0x81, 0x00, 0xa0, + 0xbd, 0x8d, 0xf6, 0x21, 0x4b, 0x76, 0x92, 0xa8, 0x13, 0xe8, 0x74, 0x90, + 0x79, 0x05, 0x04, 0xfb, 0xf1, 0x4f, 0x4b, 0x02, 0xc2, 0xd9, 0x6f, 0xc1, + 0x82, 0xa7, 0xf2, 0x8b, 0x03, 0x15, 0xe3, 0x56, 0xce, 0x37, 0x4a, 0x2d, + 0xcb, 0x80, 0xd1, 0xc0, 0xa0, 0x84, 0x3d, 0x1b, 0xd9, 0xda, 0x5c, 0xbc, + 0x7a, 0x2c, 0x35, 0xda, 0x9e, 0xe7, 0x56, 0x4b, 0xc5, 0x63, 0xa0, 0x6f, + 0xa7, 0x39, 0x0c, 0x72, 0x56, 0x43, 0x34, 0xb0, 0x9a, 0x59, 0xfb, 0x86, + 0xa4, 0x3a, 0xdd, 0xfb, 0xf6, 0xfb, 0x3a, 0x26, 0x69, 0xe8, 0x45, 0x03, + 0x3b, 0xde, 0xa9, 0x64, 0x97, 0x8a, 0x95, 0xf1, 0xfc, 0xd5, 0x77, 0x2d, + 0x49, 0x4c, 0x72, 0xf5, 0xcf, 0xcc, 0xb8, 0x54, 0x87, 0x40, 0x4a, 0x6f, + 0x0f, 0x40, 0xd1, 0xba, 0xf1, 0x8c, 0x5b, 0xf9, 0x5d, 0x94, 0xa6, 0xd4, + 0xe9, 0x98, 0x38, 0x91, 0x77, 0x91, 0x9b, 0x02, 0x03, 0x01, 0x00, 0x01, + 0x02, 0x81, 0x80, 0x0d, 0x1f, 0xcd, 0x02, 0x86, 0xaf, 0x69, 0xac, 0x09, + 0xcb, 0x2e, 0x54, 0xae, 0x23, 0x23, 0x74, 0xc7, 0xb9, 0x69, 0x36, 0xff, + 0xaf, 0xb7, 0x1f, 0x37, 0xd6, 0x9a, 0x2d, 0xe4, 0x89, 0xc8, 0xf4, 0xb9, + 0xf6, 0xb6, 0x6e, 0xf9, 0x14, 0x3f, 0x9d, 0x60, 0xb3, 0xfa, 0x78, 0x1e, + 0xd9, 0x07, 0xca, 0x40, 0x9d, 0x5d, 0x14, 0xbc, 0x97, 0xf2, 0xdd, 0x89, + 0xec, 0x40, 0xf9, 0x2d, 0x84, 0xa2, 0xd4, 0xaf, 0x29, 0x42, 0x13, 0x74, + 0xa7, 0x8e, 0xe5, 0xe8, 0xec, 0xbc, 0xde, 0x64, 0xd9, 0xf5, 0x84, 0x9e, + 0x1d, 0x19, 0x4c, 0xf6, 0xdc, 0xb3, 0x7d, 0x66, 0x49, 0xef, 0x45, 0x57, + 0x49, 0xed, 0x4d, 0x4a, 0xe3, 0xec, 0xea, 0x5b, 0xe4, 0xdf, 0x0d, 0x72, + 0x3a, 0xd9, 0xbd, 0x9e, 0x37, 0x28, 0x9f, 0x5f, 0xa4, 0x74, 0xdd, 0xdb, + 0xeb, 0x65, 0xfa, 0x42, 0x36, 0x21, 0xd3, 0x3f, 0x47, 0x68, 0xc9, 0x02, + 0x41, 0x00, 0xd5, 0xcf, 0xdb, 0xed, 0xa4, 0x73, 0x47, 0xf9, 0x39, 0x40, + 0xaa, 0x7d, 0xc3, 0x18, 0x5d, 0x7b, 0x3b, 0x73, 0x48, 0x5b, 0x64, 0x5f, + 0xe9, 0xbb, 0x02, 0x83, 0xe3, 0xd8, 0xe7, 0x85, 0x70, 0x26, 0x0c, 0x92, + 0x57, 0xe9, 0x13, 0xdf, 0xae, 0x5d, 0x67, 0x5b, 0xdf, 0x0c, 0x64, 0x61, + 0x8b, 0x9f, 0x34, 0xc7, 0x85, 0xc5, 0x55, 0x1b, 0xed, 0xd1, 0xd2, 0xfc, + 0xbd, 0xb0, 0x1f, 0xd6, 0x31, 0xdf, 0x02, 0x41, 0x00, 0xc0, 0x74, 0xee, + 0x0f, 0x90, 0x6f, 0x0b, 0x9f, 0xa9, 0x5b, 0x70, 0xd7, 0x8d, 0x63, 0x80, + 0x95, 0x2b, 0x77, 0xa2, 0x28, 0x79, 0xfd, 0x49, 0x14, 0x61, 0x3f, 0x5a, + 0x09, 0x08, 0xc7, 0x74, 0x47, 0xe2, 0x9b, 0x1e, 0xe9, 0x9f, 0x61, 0x1f, + 0x70, 0xba, 0x89, 0xf6, 0xc5, 0xc5, 0x2c, 0xed, 0x19, 0x40, 0x46, 0x9c, + 0x51, 0x3b, 0xda, 0x9b, 0x20, 0x96, 0x8c, 0xd5, 0x7d, 0xd1, 0x6c, 0xef, + 0xc5, 0x02, 0x40, 0x16, 0x28, 0x9c, 0x9a, 0x5c, 0x58, 0xb6, 0x34, 0xd6, + 0x02, 0x25, 0xa9, 0x32, 0xf6, 0xeb, 0x79, 0x42, 0x08, 0x08, 0x8f, 0xb0, + 0x2f, 0x60, 0x81, 0xc9, 0x18, 0xf2, 0x1c, 0x20, 0xa2, 0x6b, 0xa5, 0x05, + 0xd8, 0x84, 0xd3, 0xdb, 0x03, 0x6b, 0x86, 0xb2, 0x97, 0x8a, 0xde, 0x35, + 0xe9, 0x06, 0x17, 0x51, 0xd8, 0xfb, 0xbc, 0x1f, 0xbd, 0xed, 0x3f, 0xb9, + 0xa6, 0x07, 0xe2, 0xa0, 0xea, 0x09, 0xf1, 0x02, 0x40, 0x68, 0xec, 0xd7, + 0x05, 0x61, 0x47, 0x49, 0x5d, 0x08, 0xa6, 0x33, 0xc5, 0x30, 0xee, 0x78, + 0xa1, 0xdb, 0x0a, 0xe4, 0x3b, 0x91, 0x16, 0x88, 0x0b, 0x36, 0x61, 0xa5, + 0xa2, 0x9b, 0x48, 0xb2, 0x9a, 0xa6, 0x6e, 0xcf, 0xd1, 0xaa, 0xf4, 0xf6, + 0x81, 0x2d, 0x12, 0x1e, 0x9a, 0x00, 0x3f, 0xd8, 0x1c, 0x16, 0x30, 0xe8, + 0xf4, 0x58, 0xdf, 0x7c, 0x07, 0xae, 0x4c, 0xa5, 0xf0, 0x6c, 0x87, 0x29, + 0xc9, 0x02, 0x41, 0x00, 0x94, 0x5f, 0x43, 0xe0, 0x51, 0x27, 0xe5, 0xe9, + 0x19, 0x0c, 0x22, 0x45, 0xe9, 0xd3, 0x88, 0xde, 0x15, 0x33, 0x30, 0x31, + 0x07, 0xd2, 0x7c, 0xd9, 0x5a, 0xeb, 0xc0, 0x7c, 0x39, 0x5e, 0xa5, 0x5f, + 0x98, 0x13, 0xed, 0xc9, 0x92, 0x1b, 0x50, 0xce, 0x01, 0xf7, 0x4f, 0xd1, + 0x36, 0x66, 0xd1, 0x48, 0xf7, 0xcb, 0x9b, 0x2c, 0x80, 0x48, 0xb0, 0xef, + 0x1e, 0xf3, 0xc4, 0xca, 0x1d, 0xd5, 0x41, 0x96 +}; +unsigned int default_private_key_len = 608;