From 99b88b9dab581e64defb7a3cd7228f42d39ba62c Mon Sep 17 00:00:00 2001 From: Slavey Karadzhov Date: Mon, 14 May 2018 16:02:29 +0200 Subject: [PATCH] Added SmtpClient and sample. --- Sming/SmingCore/Network/SmtpClient.cpp | 534 +++++++++++++++++++ Sming/SmingCore/Network/SmtpClient.h | 216 ++++++++ samples/SmtpClient/.project | 28 + samples/SmtpClient/Makefile | 24 + samples/SmtpClient/Makefile-user.mk | 43 ++ samples/SmtpClient/README.md | 23 + samples/SmtpClient/app/application.cpp | 92 ++++ samples/SmtpClient/files/image.png | Bin 0 -> 4320 bytes samples/SmtpClient/include/ssl/cert.h | 43 ++ samples/SmtpClient/include/ssl/private_key.h | 54 ++ 10 files changed, 1057 insertions(+) create mode 100644 Sming/SmingCore/Network/SmtpClient.cpp create mode 100644 Sming/SmingCore/Network/SmtpClient.h create mode 100644 samples/SmtpClient/.project create mode 100644 samples/SmtpClient/Makefile create mode 100644 samples/SmtpClient/Makefile-user.mk create mode 100644 samples/SmtpClient/README.md create mode 100644 samples/SmtpClient/app/application.cpp create mode 100644 samples/SmtpClient/files/image.png create mode 100644 samples/SmtpClient/include/ssl/cert.h create mode 100644 samples/SmtpClient/include/ssl/private_key.h 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 0000000000000000000000000000000000000000..369e55dc65f5826ebc16940c7f320ec88e19011e GIT binary patch literal 4320 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 O0000