Mail subject or body are not mandatory, use NULL if not used.
There is one thing that could be optimized - using the ESP built-in base64_encode() function, but in does not seem reliable so far in terms of memory allocation/freeing.
There is also one possible memory leak - when the SMTP server is down, there is not callback for "connection failed", so there is no way to know the connection did not happen actually and free the allocated memory. But this is true for all TCP connections. A possible solution is to use static memory, but in this case the code is not re-entrant.
Any comments and suggestions are welcome.
Usage:
smtp_send("smtp.gmail.com", 465, "user@gmail.com", "password", "recipient@email.com", "subject", "body");
Enjoy it:
/* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Tito Boro(vski) <t_i_t_t_o@email.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
*
* 25.10.2015 Sofia, Bulgaria
* ----------------------------------------------------------------------------*/
#include <esp8266.h>
#define SMTP_MAX_STRING 32
#define SMTP_MAX_BODY 128
static void ICACHE_FLASH_ATTR smtp_dns_found_cb(const char *name, ip_addr_t *ipaddr, void *arg);
typedef enum
{
SMTP_STATE_HELO,
SMTP_STATE_AUTH,
SMTP_STATE_USER,
SMTP_STATE_PASS,
SMTP_STATE_FROM,
SMTP_STATE_RCPT,
SMTP_STATE_DATA,
SMTP_STATE_SUBJ_HEAD,
SMTP_STATE_SUBJ_BODY,
SMTP_STATE_SUBJ_FOOT,
SMTP_STATE_MAIL_BODY,
SMTP_STATE_MAIL_FOOT,
} SMTP_STATE;
typedef struct
{
struct espconn con; // MUST be first!!!
esp_tcp tcp;
SMTP_STATE state;
char server[SMTP_MAX_STRING];
char user[SMTP_MAX_STRING];
char pass[SMTP_MAX_STRING];
char rcpt[SMTP_MAX_STRING];
char subject[SMTP_MAX_STRING];
char body[SMTP_MAX_BODY];
} smtp_conn_t;
unsigned char *default_certificate;
unsigned int default_certificate_len = 0;
unsigned char *default_private_key;
unsigned int default_private_key_len = 0;
static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out)
{
unsigned ii, io;
uint32_t v;
unsigned rem;
for(io=0,ii=0,v=0,rem=0;ii<in_len;ii++)
{
unsigned char ch;
ch=in[ii];
v=(v<<8)|ch;
rem+=8;
while(rem>=6)
{
rem-=6;
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=base64enc_tab[(v>>rem)&63];
}
}
if(rem)
{
v<<=(6-rem);
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=base64enc_tab[v&63];
}
while(io&3)
{
if(io>=out_len) return -1; /* truncation is failure */
out[io++]='=';
}
if(io>=out_len) return -1; /* no room for null terminator */
out[io]=0;
return io;
}
static void smtp_send_data(smtp_conn_t *smtp_conn, char *data)
{
#ifdef PLATFORM_DEBUG
os_printf(data);
#endif
if (smtp_conn->con.proto.tcp->remote_port == 465)
espconn_secure_send(&smtp_conn->con, (uint8 *)data, strlen(data));
else
espconn_send(&smtp_conn->con, (uint8 *)data, strlen(data));
}
static void ICACHE_FLASH_ATTR smtp_receive_cb(void *arg, char *pdata, unsigned short len)
{
smtp_conn_t *smtp_conn = arg;
char buff[SMTP_MAX_STRING + 16];
#ifdef PLATFORM_DEBUG
pdata[len] = 0;
os_printf(pdata);
#endif
switch (smtp_conn->state)
{
case SMTP_STATE_HELO:
os_sprintf(buff, "HELO %s\r\n", smtp_conn->server);
break;
case SMTP_STATE_AUTH:
strncpy(buff, "AUTH LOGIN\r\n", sizeof(buff));
break;
case SMTP_STATE_USER:
base64_encode(strlen(smtp_conn->user), (unsigned char *)smtp_conn->user, sizeof(buff), buff);
strcat(buff, "\r\n");
break;
case SMTP_STATE_PASS:
base64_encode(strlen(smtp_conn->user), (unsigned char *)smtp_conn->pass, sizeof(buff), buff);
strcat(buff, "\r\n");
break;
case SMTP_STATE_FROM:
os_sprintf(buff, "MAIL FROM:<%s>\r\n", smtp_conn->user);
break;
case SMTP_STATE_RCPT:
os_sprintf(buff, "RCPT TO:<%s>\r\n", smtp_conn->rcpt);
break;
case SMTP_STATE_DATA:
strncpy(buff, "DATA\r\n", sizeof(buff));
break;
case SMTP_STATE_SUBJ_HEAD:
if (*smtp_conn->subject)
{
strncpy(buff, "Subject:", sizeof(buff));
}
else
{
smtp_conn->state = SMTP_STATE_MAIL_FOOT;
if (*smtp_conn->body)
{
smtp_send_data(smtp_conn, smtp_conn->body);
return;
}
else
strncpy(buff, "\r\n.\r\nQUIT\r\n", sizeof(buff));
}
break;
default:
return;
}
smtp_send_data(smtp_conn, buff);
smtp_conn->state++;
}
static void ICACHE_FLASH_ATTR smtp_sent_cb(void *arg)
{
smtp_conn_t *smtp_conn = arg;
char buff[SMTP_MAX_STRING + 16];
switch (smtp_conn->state)
{
case SMTP_STATE_SUBJ_BODY:
strncpy(buff, smtp_conn->subject, sizeof(buff));
if (!*smtp_conn->body)
smtp_conn->state = SMTP_STATE_MAIL_BODY;
break;
case SMTP_STATE_SUBJ_FOOT:
strncpy(buff, "\r\n", sizeof(buff));
break;
case SMTP_STATE_MAIL_BODY:
strncpy(buff, smtp_conn->body, sizeof(buff));
break;
case SMTP_STATE_MAIL_FOOT:
strncpy(buff, "\r\n.\r\nQUIT\r\n", sizeof(buff));
break;
default:
return;
}
smtp_send_data(smtp_conn, buff);
smtp_conn->state++;
}
static void ICACHE_FLASH_ATTR smtp_disconnect_cb(void *arg)
{
os_free(arg);
#ifdef PLATFORM_DEBUG
os_printf("SMTP disconnected\r\n");
#endif
}
static void ICACHE_FLASH_ATTR smtp_reconnect_cb(void *arg, sint8 err)
{
smtp_conn_t *smtp_conn = arg;
#ifdef PLATFORM_DEBUG
os_printf("SMTP disconnected, reconnecting\r\n");
#endif
smtp_dns_found_cb(smtp_conn->server, (ip_addr_t *)&smtp_conn->con.proto.tcp->remote_ip, smtp_conn);
}
static void ICACHE_FLASH_ATTR smtp_connect_cb(void *arg)
{
#ifdef PLATFORM_DEBUG
os_printf("SMTP connected\r\n");
#endif
espconn_regist_recvcb(arg, smtp_receive_cb);
espconn_regist_sentcb(arg, smtp_sent_cb);
espconn_regist_reconcb(arg, smtp_reconnect_cb);
espconn_regist_disconcb(arg, smtp_disconnect_cb);
}
static void ICACHE_FLASH_ATTR smtp_dns_found_cb(const char *name, ip_addr_t *ipaddr, void *arg)
{
smtp_conn_t *smtp_conn = arg;
if(ipaddr == NULL)
{
#ifdef PLATFORM_DEBUG
os_printf("SMTP DNS lookup failed\r\n");
#endif
smtp_disconnect_cb(smtp_conn);
return;
}
os_memcpy(smtp_conn->con.proto.tcp->remote_ip, &ipaddr->addr, 4);
espconn_regist_connectcb(&smtp_conn->con, smtp_connect_cb);
if (smtp_conn->con.proto.tcp->remote_port == 465)
{
espconn_secure_ca_disable(3);
espconn_secure_set_size(3, 8 * 1024);
espconn_secure_connect(&smtp_conn->con);
}
else
espconn_connect(&smtp_conn->con);
#ifdef PLATFORM_DEBUG
os_printf("SMTP connecting to " IPSTR ":%d\r\n",
IP2STR(smtp_conn->con.proto.tcp->remote_ip),
smtp_conn->con.proto.tcp->remote_port);
#endif
}
void ICACHE_FLASH_ATTR smtp_send(char *server, int server_port, char *user, char *pass, char *rcpt, char *subject, char *body)
{
smtp_conn_t *smtp_conn = (smtp_conn_t *)os_zalloc(sizeof(smtp_conn_t));
if (smtp_conn == NULL)
{
#ifdef PLATFORM_DEBUG
os_printf("SMTP mem alloc failed\r\n");
#endif
return;
}
strncpy(smtp_conn->server, server, sizeof(smtp_conn->server));
strncpy(smtp_conn->user, user, sizeof(smtp_conn->user));
strncpy(smtp_conn->pass, pass, sizeof(smtp_conn->pass));
strncpy(smtp_conn->rcpt, rcpt, sizeof(smtp_conn->rcpt));
strncpy(smtp_conn->subject, subject? subject: "", sizeof(smtp_conn->subject));
strncpy(smtp_conn->body, body? body: "", sizeof(smtp_conn->body));
smtp_conn->con.type = ESPCONN_TCP;
smtp_conn->con.state = ESPCONN_NONE;
smtp_conn->con.proto.tcp = &smtp_conn->tcp;
smtp_conn->con.proto.tcp->local_port = espconn_port();
smtp_conn->con.proto.tcp->remote_port = server_port;
ip_addr_t ip_addr;
switch (espconn_gethostbyname(&smtp_conn->con, server, &ip_addr, smtp_dns_found_cb))
{
case ESPCONN_INPROGRESS:
#ifdef PLATFORM_DEBUG
os_printf("SMTP DNS lookup for %s\r\n", server);
#endif
break;
case ESPCONN_OK:
smtp_dns_found_cb(server, &ip_addr, smtp_conn);
break;
case ESPCONN_ARG:
#ifdef PLATFORM_DEBUG
os_printf("SMTP DNS argument error %s\n", server);
#endif
break;
default:
#ifdef PLATFORM_DEBUG
os_printf("SMTP DNS lookup error\r\n");
#endif
break;
}
}