Skip to content

Commit

Permalink
Added SmtpClient and sample.
Browse files Browse the repository at this point in the history
  • Loading branch information
slav-at-attachix committed May 15, 2018
1 parent bf9caa6 commit 99b88b9
Show file tree
Hide file tree
Showing 10 changed files with 1,057 additions and 0 deletions.
534 changes: 534 additions & 0 deletions Sming/SmingCore/Network/SmtpClient.cpp

Large diffs are not rendered by default.

216 changes: 216 additions & 0 deletions Sming/SmingCore/Network/SmtpClient.h
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*
****/

/** @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 <functional>

#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<int(SmtpClient& client, int code, char* status)> 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<String> authMethods;
SimpleConcurrentQueue<MailMessage*,SMTP_QUEUE_SIZE> 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();
};
28 changes: 28 additions & 0 deletions samples/SmtpClient/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>SmtpClient</name>
<comment></comment>
<projects>
<project>SmingFramework</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>
24 changes: 24 additions & 0 deletions samples/SmtpClient/Makefile
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions samples/SmtpClient/Makefile-user.mk
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions samples/SmtpClient/README.md
Original file line number Diff line number Diff line change
@@ -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.

Loading

0 comments on commit 99b88b9

Please sign in to comment.