lwip-contrib-mac/apps/smtp/smtp.c

1319 lines
40 KiB
C

/**
* @file
* SMTP client module
*
* Author: Simon Goldschmidt
*
* Example usage:
*
* void my_smtp_result_fn(void *arg, u8_t smtp_result, u16_t srv_err, err_t err)
* {
* printf("mail (%p) sent with results: 0x%02x, 0x%04x, 0x%08x\n", arg,
* smtp_result, srv_err, err);
* }
* static void my_smtp_test(void)
* {
* smtp_set_server_addr("mymailserver.org");
* -> set both username and password as NULL if no auth needed
* smtp_set_auth("username", "password");
* smtp_send_mail("sender", "recipient", "subject", "body", my_smtp_result_fn,
* some_argument);
* }
*
* When using from any other thread than the tcpip_thread (for NO_SYS==0), use
* smtp_send_mail_int()!
*/
#include "lwip/opt.h"
#include "smtp.h"
#if LWIP_TCP
#include "lwip/sys.h"
#include "lwip/sockets.h"
#include "lwip/tcp.h"
#include "lwip/dns.h"
#include <string.h>
#include <stdlib.h>
/** This is simple SMTP client for raw API.
* It is a minimal implementation of SMTP as specified in RFC 5321.
*
* @todo:
* - attachments (the main difficulty here is streaming base64-encoding to
* prevent having to allocate a buffer for the whole encoded file at once)
* - test with more mail servers...
*/
/**
* SMTP_DEBUG: Enable debugging for SNTP.
*/
#ifndef SMTP_DEBUG
#define SMTP_DEBUG LWIP_DBG_OFF
#endif
/** Maximum length reserved for server name */
#ifndef SMTP_MAX_SERVERNAME_LEN
#define SMTP_MAX_SERVERNAME_LEN 256
#endif
/** Maximum length reserved for username */
#ifndef SMTP_MAX_USERNAME_LEN
#define SMTP_MAX_USERNAME_LEN 32
#endif
/** Maximum length reserved for password */
#ifndef SMTP_MAX_PASS_LEN
#define SMTP_MAX_PASS_LEN 32
#endif
/** Set this to 0 if you know the authentication data will not change
* during the smtp session, which saves some heap space. */
#ifndef SMTP_COPY_AUTHDATA
#define SMTP_COPY_AUTHDATA 1
#endif
/** Set this to 0 to save some code space if you know for sure that all data
* passed to this module conforms to the requirements in the SMTP RFC.
* WARNING: use this with care!
*/
#ifndef SMTP_CHECK_DATA
#define SMTP_CHECK_DATA 1
#endif
/** Set this to 1 to enable AUTH PLAIN support */
#ifndef SMTP_SUPPORT_AUTH_PLAIN
#define SMTP_SUPPORT_AUTH_PLAIN 1
#endif
/** Set this to 1 to enable AUTH LOGIN support */
#ifndef SMTP_SUPPORT_AUTH_LOGIN
#define SMTP_SUPPORT_AUTH_LOGIN 1
#endif
/** TCP poll interval. Unit is 0.5 sec. */
#define SMTP_POLL_INTERVAL 4
/** TCP poll timeout while sending message body, reset after every
* successful write. 3 minutes */
#define SMTP_TIMEOUT_DATABLOCK ( 3 * 60 * SMTP_POLL_INTERVAL / 2)
/** TCP poll timeout while waiting for confirmation after sending the body.
* 10 minutes */
#define SMTP_TIMEOUT_DATATERM (10 * 60 * SMTP_POLL_INTERVAL / 2)
/** TCP poll timeout while not sending the body.
* This is somewhat lower than the RFC states (5 minutes for initial, MAIL
* and RCPT) but still OK for us here.
* 2 minutes */
#define SMTP_TIMEOUT ( 2 * 60 * SMTP_POLL_INTERVAL / 2)
/* the various debug levels for this file */
#define SMTP_DEBUG_TRACE (SMTP_DEBUG | LWIP_DBG_TRACE)
#define SMTP_DEBUG_STATE (SMTP_DEBUG | LWIP_DBG_STATE)
#define SMTP_DEBUG_WARN (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
#define SMTP_DEBUG_WARN_STATE (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
#define SMTP_DEBUG_SERIOUS (SMTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
#define SMTP_RX_BUF_LEN 255
#define SMTP_TX_BUF_LEN 255
#define SMTP_CRLF "\r\n"
#define SMTP_CRLF_LEN 2
#define SMTP_RESP_220 "220"
#define SMTP_RESP_235 "235"
#define SMTP_RESP_250 "250"
#define SMTP_RESP_334 "334"
#define SMTP_RESP_354 "354"
#define SMTP_RESP_LOGIN_UNAME "VXNlcm5hbWU6"
#define SMTP_RESP_LOGIN_PASS "UGFzc3dvcmQ6"
#define SMTP_KEYWORD_AUTH_SP "AUTH "
#define SMTP_KEYWORD_AUTH_EQ "AUTH="
#define SMTP_KEYWORD_AUTH_LEN 5
#define SMTP_AUTH_PARAM_PLAIN "PLAIN"
#define SMTP_AUTH_PARAM_LOGIN "LOGIN"
#define SMTP_CMD_EHLO_1 "EHLO ["
#define SMTP_CMD_EHLO_1_LEN 6
#define SMTP_CMD_EHLO_2 "]\r\n"
#define SMTP_CMD_EHLO_2_LEN 3
#define SMTP_CMD_AUTHPLAIN_1 "AUTH PLAIN "
#define SMTP_CMD_AUTHPLAIN_1_LEN 11
#define SMTP_CMD_AUTHPLAIN_2 "\r\n"
#define SMTP_CMD_AUTHPLAIN_2_LEN 2
#define SMTP_CMD_AUTHLOGIN "AUTH LOGIN\r\n"
#define SMTP_CMD_AUTHLOGIN_LEN 12
#define SMTP_CMD_MAIL_1 "MAIL FROM: <"
#define SMTP_CMD_MAIL_1_LEN 12
#define SMTP_CMD_MAIL_2 ">\r\n"
#define SMTP_CMD_MAIL_2_LEN 3
#define SMTP_CMD_RCPT_1 "RCPT TO: <"
#define SMTP_CMD_RCPT_1_LEN 10
#define SMTP_CMD_RCPT_2 ">\r\n"
#define SMTP_CMD_RCPT_2_LEN 3
#define SMTP_CMD_DATA "DATA\r\n"
#define SMTP_CMD_DATA_LEN 6
#define SMTP_CMD_HEADER_1 "From: <"
#define SMTP_CMD_HEADER_1_LEN 7
#define SMTP_CMD_HEADER_2 ">\r\nTo: <"
#define SMTP_CMD_HEADER_2_LEN 8
#define SMTP_CMD_HEADER_3 ">\r\nSubject: "
#define SMTP_CMD_HEADER_3_LEN 12
#define SMTP_CMD_HEADER_4 "\r\n\r\n"
#define SMTP_CMD_HEADER_4_LEN 4
#define SMTP_CMD_BODY_FINISHED "\r\n.\r\n"
#define SMTP_CMD_BODY_FINISHED_LEN 5
#define SMTP_CMD_QUIT "QUIT\r\n"
#define SMTP_CMD_QUIT_LEN 6
#if SMTP_STAT_TX_BUF_MAX
#define SMTP_TX_BUF_MAX(len) LWIP_MACRO(if((len) > smtp_tx_buf_len_max) smtp_tx_buf_len_max = (len);)
#else /* SMTP_STAT_TX_BUF_MAX */
#define SMTP_TX_BUF_MAX(len)
#endif /* SMTP_STAT_TX_BUF_MAX */
#if SMTP_COPY_AUTHDATA
#define SMTP_USERNAME(session) (session)->username
#define SMTP_PASS(session) (session)->pass
#define SMTP_AUTH_PLAIN_DATA(session) (session)->auth_plain
#define SMTP_AUTH_PLAIN_LEN(session) (session)->auth_plain_len
#else /* SMTP_COPY_AUTHDATA */
#define SMTP_USERNAME(session) smtp_username
#define SMTP_PASS(session) smtp_pass
#define SMTP_AUTH_PLAIN_DATA(session) smtp_auth_plain
#define SMTP_AUTH_PLAIN_LEN(session) smtp_auth_plain_len
#endif /* SMTP_COPY_AUTHDATA */
/** State for SMTP client state machine */
enum smtp_session_state {
SMTP_NULL,
SMTP_HELO,
SMTP_AUTH_PLAIN,
SMTP_AUTH_LOGIN_UNAME,
SMTP_AUTH_LOGIN_PASS,
SMTP_AUTH_LOGIN,
SMTP_MAIL,
SMTP_RCPT,
SMTP_DATA,
SMTP_BODY,
SMTP_QUIT,
SMTP_CLOSED
};
#ifdef LWIP_DEBUG
/** State-to-string table for debugging */
const char *smtp_state_str[] = {
"SMTP_NULL",
"SMTP_HELO",
"SMTP_AUTH_PLAIN",
"SMTP_AUTH_LOGIN_UNAME",
"SMTP_AUTH_LOGIN_PASS",
"SMTP_AUTH_LOGIN",
"SMTP_MAIL",
"SMTP_RCPT",
"SMTP_DATA",
"SMTP_BODY",
"SMTP_QUIT",
"SMTP_CLOSED",
};
const char *smtp_result_strs[] = {
"SMTP_RESULT_OK",
"SMTP_RESULT_ERR_UNKNOWN",
"SMTP_RESULT_ERR_CONNECT",
"SMTP_RESULT_ERR_HOSTNAME",
"SMTP_RESULT_ERR_CLOSED",
"SMTP_RESULT_ERR_TIMEOUT",
"SMTP_RESULT_ERR_SVR_RESP",
};
#endif /* LWIP_DEBUG */
/** struct keeping the body and state of an smtp session */
struct smtp_session {
/** keeping the state of the smtp session */
enum smtp_session_state state;
/** timeout handling, if this reaches 0, the connection is closed */
u16_t timer;
/** helper buffer for transmit, not used for sending body */
char tx_buf[SMTP_TX_BUF_LEN + 1];
struct pbuf* p;
/** source email address */
const char* from;
/** size of the sourceemail address */
u16_t from_len;
/** target email address */
const char* to;
/** size of the target email address */
u16_t to_len;
/** subject of the email */
const char *subject;
/** length of the subject string */
u16_t subject_len;
/** this is the body of the mail to be sent */
const char* body;
/** this is the length of the body to be sent */
u16_t body_len;
/** amount of data from body already sent */
u16_t body_sent;
/** callback function to call when closed */
smtp_result_fn callback_fn;
/** argument for callback function */
void *callback_arg;
#if SMTP_COPY_AUTHDATA
/** Username to use for this request */
char *username;
/** Password to use for this request */
char *pass;
/** Username and password combined as necessary for PLAIN authentication */
char auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
/** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
size_t auth_plain_len;
#endif /* SMTP_COPY_AUTHDATA */
};
/** IP address or DNS name of the server to use for next SMTP request */
static char smtp_server[SMTP_MAX_SERVERNAME_LEN + 1];
/** TCP port of the server to use for next SMTP request */
static u16_t smtp_server_port = SMTP_DEFAULT_PORT;
/** Username to use for the next SMTP request */
static char *smtp_username;
/** Password to use for the next SMTP request */
static char *smtp_pass;
/** Username and password combined as necessary for PLAIN authentication */
static char smtp_auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
/** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
static size_t smtp_auth_plain_len;
static err_t smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed);
static err_t smtp_tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static void smtp_tcp_err(void *arg, err_t err);
static err_t smtp_tcp_poll(void *arg, struct tcp_pcb *pcb);
static err_t smtp_tcp_sent(void *arg, struct tcp_pcb *pcb, u16_t len);
static err_t smtp_tcp_connected(void *arg, struct tcp_pcb *pcb, err_t err);
#if LWIP_DNS
static void smtp_dns_found(const char* hostname, ip_addr_t *ipaddr, void *arg);
#endif /* LWIP_DNS */
static size_t smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len);
static enum smtp_session_state smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len);
static void smtp_send_body(struct smtp_session *s, struct tcp_pcb *pcb);
static void smtp_process(void *arg, struct tcp_pcb *pcb, struct pbuf *p);
#ifdef LWIP_DEBUG
/** Convert an smtp result to a string */
const char*
smtp_result_str(u8_t smtp_result)
{
if (smtp_result > SMTP_RESULT_ERR_SVR_RESP) {
return "UNKNOWN";
}
return smtp_result_strs[smtp_result];
}
/** Null-terminates the payload of p for printing out messages.
* WARNING: use this only if p is not needed any more as the last byte of
* payload is deleted!
*/
static char*
smtp_pbuf_str(struct pbuf* p)
{
if ((p == NULL) || (p->len == 0)) {
return "";
}
((char*)p->payload)[p->len] = 0;
return p->payload;
}
#endif /* LWIP_DEBUG */
/** Set IP address or DNS name for next SMTP connection
*
* @param server IP address (in ASCII representation) or DNS name of the server
*/
void
smtp_set_server_addr(const char* server)
{
size_t len = 0;
if (server != NULL) {
len = strlen(server);
}
if (len > SMTP_MAX_SERVERNAME_LEN) {
len = SMTP_MAX_SERVERNAME_LEN;
}
memcpy(smtp_server, server, len);
}
/** Set TCP port for next SMTP connection
*
* @param port TCP port
*/
void
smtp_set_server_port(u16_t port)
{
smtp_server_port = port;
}
/** Set authentication parameters for next SMTP connection
*
* @param username login name as passed to the server
* @param pass password passed to the server together with username
*/
err_t
smtp_set_auth(const char* username, const char* pass)
{
size_t uname_len = 0;
size_t pass_len = 0;
memset(smtp_auth_plain, 0xfa, 64);
if (username != NULL) {
uname_len = strlen(username);
if (uname_len > SMTP_MAX_USERNAME_LEN) {
LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Username is too long, %d instead of %d\n",
(int)uname_len, SMTP_MAX_USERNAME_LEN));
return ERR_ARG;
}
}
if (pass != NULL) {
#if SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN
pass_len = strlen(pass);
if (pass_len > SMTP_MAX_PASS_LEN) {
LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Password is too long, %d instead of %d\n",
(int)uname_len, SMTP_MAX_USERNAME_LEN));
return ERR_ARG;
}
#else /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("Password not supported as no authentication methods are activated\n"));
#endif /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
}
*smtp_auth_plain = 0;
if (username != NULL) {
smtp_username = smtp_auth_plain + 1;
strcpy(smtp_username, username);
}
if (pass != NULL) {
smtp_pass = smtp_auth_plain + uname_len + 2;
strcpy(smtp_pass, pass);
}
smtp_auth_plain_len = uname_len + pass_len + 2;
return ERR_OK;
}
/** The actual mail-sending function, called by smtp_send_mail and
* smtp_send_mail_static after setting up the struct smtp_session.
*/
static err_t
smtp_send_mail_alloced(struct smtp_session *s)
{
err_t err;
struct tcp_pcb* pcb;
ip_addr_t addr;
LWIP_ASSERT("no smtp_session supplied", s != NULL);
#if SMTP_CHECK_DATA
/* check that body conforms to RFC:
* - convert all single-CR or -LF in body to CRLF
* - only 7-bit ASCII is allowed
*/
if (smtp_verify(s->to, s->to_len, 0) != ERR_OK) {
return ERR_ARG;
}
if (smtp_verify(s->from, s->from_len, 0) != ERR_OK) {
return ERR_ARG;
}
if (smtp_verify(s->subject, s->subject_len, 0) != ERR_OK) {
return ERR_ARG;
}
if (smtp_verify(s->body, s->body_len, 0) != ERR_OK) {
return ERR_ARG;
}
#endif /* SMTP_CHECK_DATA */
pcb = tcp_new();
if (pcb == NULL) {
err = ERR_MEM;
goto leave;
}
#if SMTP_COPY_AUTHDATA
/* copy auth data, ensuring the first byte is always zero */
memcpy(s->auth_plain + 1, smtp_auth_plain + 1, smtp_auth_plain_len - 1);
s->auth_plain_len = smtp_auth_plain_len;
/* default username and pass is empty string */
s->username = s->auth_plain;
s->pass = s->auth_plain;
if (smtp_username != NULL) {
s->username += smtp_username - smtp_auth_plain;
}
if (smtp_pass != NULL) {
s->pass += smtp_pass - smtp_auth_plain;
}
#endif /* SMTP_COPY_AUTHDATA */
s->state = SMTP_NULL;
s->timer = SMTP_TIMEOUT;
tcp_arg(pcb, s);
tcp_recv(pcb, smtp_tcp_recv);
tcp_err(pcb, smtp_tcp_err);
tcp_poll(pcb, smtp_tcp_poll, SMTP_POLL_INTERVAL);
tcp_sent(pcb, smtp_tcp_sent);
#if LWIP_DNS
err = dns_gethostbyname(smtp_server, &addr, smtp_dns_found, pcb);
#else /* LWIP_DNS */
addr.addr = ipaddr_addr(smtp_server);
err = addr.addr == IPADDR_NONE ? ERR_ARG : ERR_OK;
#endif /* LWIP_DNS */
if (err == ERR_OK) {
err = tcp_connect(pcb, &addr, smtp_server_port, smtp_tcp_connected);
if (err != ERR_OK) {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
goto deallocate_and_leave;
}
} else if (err != ERR_INPROGRESS) {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("dns_gethostbyname failed: %d\n", (int)err));
goto deallocate_and_leave;
}
return ERR_OK;
deallocate_and_leave:
if (pcb != NULL) {
tcp_arg(pcb, NULL);
tcp_close(pcb);
}
leave:
mem_free(s);
/* no need to call the callback here since we return != ERR_OK */
return err;
}
/** Send an email via the currently selected server, username and password.
*
* @param from source email address (must be NULL-terminated)
* @param to target email address (must be NULL-terminated)
* @param subject email subject (must be NULL-terminated)
* @param body email body (must be NULL-terminated)
* @param
* @returns - ERR_OK if structures were allocated and no error occured starting the connection
* (this does not mean the email has been successfully sent!)
* - another err_t on error.
*/
err_t
smtp_send_mail(const char* from, const char* to, const char* subject, const char* body,
smtp_result_fn callback_fn, void* callback_arg)
{
struct smtp_session* s;
size_t from_len = strlen(from);
size_t to_len = strlen(to);
size_t subject_len = strlen(subject);
size_t body_len = strlen(body);
size_t mem_len = sizeof(struct smtp_session);
mem_len += from_len + to_len + subject_len + body_len + 4;
if (mem_len > 0xffff) {
/* too long! */
return ERR_MEM;
}
/* Allocate memory to keep this email's session state */
s = (struct smtp_session *)mem_malloc((mem_size_t)mem_len);
if (s == NULL) {
return ERR_MEM;
}
/* initialize the structure */
memset(s, 0, mem_len);
s->from = (char*)s + sizeof(struct smtp_session);
s->from_len = (u16_t)from_len;
s->to = s->from + from_len + 1;
s->to_len = (u16_t)to_len;
s->subject = s->to + to_len + 1;
s->subject_len = (u16_t)subject_len;
s->body = s->subject + subject_len + 1;
s->body_len = (u16_t)body_len;
/* copy source and target email address */
memcpy((char*)s->from, from, from_len + 1);
memcpy((char*)s->to, to, to_len + 1);
memcpy((char*)s->subject, subject, subject_len + 1);
memcpy((char*)s->body, body, body_len + 1);
s->callback_fn = callback_fn;
s->callback_arg = callback_arg;
/* call the actual implementation of this function */
return smtp_send_mail_alloced(s);
}
/** Same as smtp_send_mail, but doesn't copy from, to, subject and body into
* an internal buffer to save memory.
* WARNING: the above data must stay untouched until the callback function is
* called (unless the function returns != ERR_OK)
*/
err_t
smtp_send_mail_static(const char *from, const char* to, const char* subject,
const char* body, smtp_result_fn callback_fn, void* callback_arg)
{
struct smtp_session* s;
size_t len;
s = (struct smtp_session*)mem_malloc(sizeof(struct smtp_session));
if (s == NULL) {
return ERR_MEM;
}
memset(s, 0, sizeof(struct smtp_session));
/* initialize the structure */
s->from = from;
len = strlen(from);
LWIP_ASSERT("string is too long", len <= 0xffff);
s->from_len = (u16_t)len;
s->to = to;
len = strlen(to);
LWIP_ASSERT("string is too long", len <= 0xffff);
s->to_len = (u16_t)len;
s->subject = subject;
len = strlen(subject);
LWIP_ASSERT("string is too long", len <= 0xffff);
s->subject_len = (u16_t)len;
s->body = body;
len = strlen(body);
LWIP_ASSERT("string is too long", len <= 0xffff);
s->body_len = (u16_t)len;
s->callback_fn = callback_fn;
s->callback_arg = callback_arg;
/* call the actual implementation of this function */
return smtp_send_mail_alloced(s);
}
/** Same as smpt_send_mail but takes a struct smtp_send_request as single
* parameter which contains all the other parameters.
* To be used with tcpip_callback to send mail from interrupt context or from
* another thread.
*
* WARNING: server and authentication must stay untouched until this function has run!
*
* Usage example:
* - allocate a struct smtp_send_request (in a way that is allowed in interrupt context)
* - fill the members of the struct as if calling smtp_send_mail
* - specify a callback_function
* - set callback_arg to the structure itself
* - call this function
* - wait for the callback function to be called
* - in the callback function, deallocate the structure (passed as arg)
*/
void
smtp_send_mail_int(void *arg)
{
struct smtp_send_request *req = (struct smtp_send_request*)arg;
err_t err;
LWIP_ASSERT("smtp_send_mail_int: no argument given", arg != NULL);
if (req->static_data) {
err = smtp_send_mail_static(req->from, req->to, req->subject, req->body,
req->callback_fn, req->callback_arg);
} else {
err = smtp_send_mail(req->from, req->to, req->subject, req->body,
req->callback_fn, req->callback_arg);
}
if ((err != ERR_OK) && (req->callback_fn != NULL)) {
req->callback_fn(req->callback_arg, SMTP_RESULT_ERR_UNKNOWN, 0, err);
}
}
#if SMTP_CHECK_DATA
/** Verify that a given string conforms to the SMTP rules
* (7-bit only, no single CR or LF,
* @todo: no line consisting of a single dot only)
*/
static err_t
smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed)
{
size_t i;
u8_t last_was_cr = 0;
for (i = 0; i < data_len; i++) {
char current = data[i];
if ((current & 0x80) != 0) {
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: no 8-bit data supported: %s\n", data));
return ERR_ARG;
}
if (current == '\r') {
if (!linebreaks_allowed) {
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found CR where no linebreaks allowed: %s\n", data));
return ERR_ARG;
}
if (last_was_cr) {
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found double CR: %s\n", data));
return ERR_ARG;
}
last_was_cr = 1;
} else {
if (current == '\n') {
if (!last_was_cr) {
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found LF without CR before: %s\n", data));
return ERR_ARG;
}
}
last_was_cr = 0;
}
}
return ERR_OK;
}
#endif /* SMTP_CHECK_DATA */
/** Frees the smpt_session and calls the callback function */
static void
smtp_free(struct smtp_session *s, u8_t result, u16_t srv_err, err_t err)
{
smtp_result_fn fn = s->callback_fn;
void *arg = s->callback_arg;
if (s->p != NULL) {
pbuf_free(s->p);
}
mem_free(s);
if (fn != NULL) {
fn(arg, result, srv_err, err);
}
}
/** Try to close a pcb and free the arg if successful */
static void
smtp_close(struct smtp_session *s, struct tcp_pcb *pcb, u8_t result,
u16_t srv_err, err_t err)
{
tcp_arg(pcb, NULL);
if (tcp_close(pcb) == ERR_OK) {
if (s != NULL) {
smtp_free(s, result, srv_err, err);
}
} else {
/* close failed, set back arg */
tcp_arg(pcb, s);
}
}
/** Raw API TCP err callback: pcb is already deallocated */
static void
smtp_tcp_err(void *arg, err_t err)
{
LWIP_UNUSED_ARG(err);
if (arg != NULL) {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_err: connection reset by remote host\n"));
smtp_free((struct smtp_session*)arg, SMTP_RESULT_ERR_CLOSED, 0, err);
}
}
/** Raw API TCP poll callback */
static err_t
smtp_tcp_poll(void *arg, struct tcp_pcb *pcb)
{
if (arg != NULL) {
struct smtp_session *s = (struct smtp_session*)arg;
if (s->timer != 0) {
s->timer--;
}
}
smtp_process(arg, pcb, NULL);
return ERR_OK;
}
/** Raw API TCP sent callback */
static err_t
smtp_tcp_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
LWIP_UNUSED_ARG(len);
smtp_process(arg, pcb, NULL);
return ERR_OK;
}
/** Raw API TCP recv callback */
static err_t
smtp_tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
LWIP_UNUSED_ARG(err);
if (p != NULL) {
tcp_recved(pcb, p->tot_len);
smtp_process(arg, pcb, p);
} else {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_recv: connection closed by remote host\n"));
smtp_close(arg, pcb, SMTP_RESULT_ERR_CLOSED, 0, err);
}
return ERR_OK;
}
static err_t
smtp_tcp_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{
LWIP_UNUSED_ARG(arg);
if (err == ERR_OK) {
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_connected: Waiting for 220\n"));
} else {
/* shouldn't happen, but we still check 'err', only to be sure */
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_connected: %d\n", (int)err));
smtp_close(arg, pcb, SMTP_RESULT_ERR_CONNECT, 0, err);
}
return ERR_OK;
}
#if LWIP_DNS
/** DNS callback
* If ipaddr is non-NULL, resolving succeeded, otherwise it failed.
*/
static void
smtp_dns_found(const char* hostname, ip_addr_t *ipaddr, void *arg)
{
struct tcp_pcb *pcb = arg;
err_t err;
u8_t result;
LWIP_UNUSED_ARG(hostname);
if (ipaddr != NULL) {
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: hostname resolved, connecting\n"));
err = tcp_connect(pcb, ipaddr, smtp_server_port, smtp_tcp_connected);
if (err == ERR_OK) {
return;
}
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
result = SMTP_RESULT_ERR_CONNECT;
} else {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_dns_found: failed to resolve hostname: %s\n",
hostname));
result = SMTP_RESULT_ERR_HOSTNAME;
err = ERR_ARG;
}
smtp_close(pcb->callback_arg, pcb, result, 0, err);
}
#endif /* LWIP_DNS */
#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
/** Table 6-bit-index-to-ASCII used for base64-encoding */
const u8_t base64_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',
'+', '/'
};
/** Base64 encoding */
static size_t
smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len)
{
size_t i;
s8_t j;
size_t target_idx = 0;
size_t longer = (source_len % 3) ? (3 - (source_len % 3)) : 0;
size_t source_len_b64 = source_len + longer;
size_t len = (((source_len_b64) * 4) / 3);
u8_t x = 5;
u8_t current = 0;
LWIP_UNUSED_ARG(target_len);
LWIP_ASSERT("target_len is too short", target_len >= len);
for (i = 0; i < source_len_b64; i++) {
u8_t b = (i < source_len ? source[i] : 0);
for (j = 7; j >= 0; j--, x--) {
u8_t shift = ((b & (1 << j)) != 0) ? 1 : 0;
current |= shift << x;
if (x == 0) {
target[target_idx++] = base64_table[current];
x = 6;
current = 0;
}
}
}
for (i = len - longer; i < len; i++) {
target[i] = '=';
}
return len;
}
#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
/** Parse pbuf to see if it contains the beginning of an answer.
* If so, it returns the contained response code as number between 1 and 999.
* If not, zero is returned.
*
* @param s smtp session struct
*/
static u16_t
smtp_is_response(struct smtp_session *s)
{
char digits[4];
long num;
if (s->p == NULL) {
return 0;
}
/* copy three digits and convert them to int */
if (pbuf_copy_partial(s->p, digits, 3, 0) != 3) {
/* pbuf was too short */
return 0;
}
digits[3] = 0;
num = strtol(digits, NULL, 10);
if ((num <= 0) || (num >= 1000)) {
/* failed to find response code at start of line */
return 0;
}
return (u16_t)num;
}
/** Parse pbuf to see if it contains a fully received answer.
* If one is found, ERR_OK is returned.
* If none is found, ERR_VAL is returned.
*
* A fully received answer is a 3-digit number followed by a space,
* some string and a CRLF as line ending.
*
* @param s smtp session struct
*/
static err_t
smtp_is_response_finished(struct smtp_session *s)
{
u8_t sp;
u16_t crlf;
u16_t offset;
if (s->p == NULL) {
return ERR_VAL;
}
offset = 0;
again:
/* We could check the response number here, but we trust the
* protocol definition which says the client can rely on it being
* the same on every line. */
/* find CRLF */
crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, offset + 4);
if (crlf == 0xFFFF) {
/* no CRLF found */
return ERR_VAL;
}
sp = pbuf_get_at(s->p, offset + 3);
if (sp == '-') {
/* no space after response code -> try next line */
offset = crlf + 2;
goto again;
} else if (sp == ' ') {
/* CRLF found after response code + space -> valid response */
return ERR_OK;
}
/* sp contains invalid character */
return ERR_VAL;
}
/** Prepare HELO/EHLO message */
static enum smtp_session_state
smtp_prepare_helo(struct smtp_session *s, u16_t *tx_buf_len, struct tcp_pcb *pcb)
{
size_t ipa_len;
char *ipa = ip_ntoa(&pcb->local_ip);
LWIP_ASSERT("ip_ntoa returned NULL", ipa != NULL);
ipa_len = strlen(ipa);
LWIP_ASSERT("string too long", ipa_len <= 0xffff);
*tx_buf_len = SMTP_CMD_EHLO_1_LEN + (u16_t)ipa_len + SMTP_CMD_EHLO_2_LEN;
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
SMEMCPY(s->tx_buf, SMTP_CMD_EHLO_1, SMTP_CMD_EHLO_1_LEN);
MEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN], ipa, ipa_len);
SMEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN + ipa_len], SMTP_CMD_EHLO_2, SMTP_CMD_EHLO_2_LEN);
return SMTP_HELO;
}
#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
/** Parse last server response (in rx_buf) for supported authentication method,
* create data to send out (to tx_buf), set tx_data_len correctly
* and return the next state.
*/
static enum smtp_session_state
smtp_prepare_auth_or_mail(struct smtp_session *s, u16_t *tx_buf_len)
{
/* check response for supported authentication method */
u16_t auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_SP);
if (auth == 0xFFFF) {
auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_EQ);
}
if (auth != 0xFFFF) {
u16_t crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, auth);
if ((crlf != 0xFFFF) && (crlf > auth)) {
/* use tx_buf temporarily */
u16_t copied = pbuf_copy_partial(s->p, s->tx_buf, crlf - auth, auth);
if (copied != 0) {
char *sep = s->tx_buf + SMTP_KEYWORD_AUTH_LEN;
s->tx_buf[copied] = 0;
#if SMTP_SUPPORT_AUTH_PLAIN
/* favour PLAIN over LOGIN since it involves less requests */
if (strstr(sep, SMTP_AUTH_PARAM_PLAIN) != NULL) {
size_t auth_len;
/* server supports AUTH PLAIN */
SMEMCPY(s->tx_buf, SMTP_CMD_AUTHPLAIN_1, SMTP_CMD_AUTHPLAIN_1_LEN);
/* add base64-encoded string "\0username\0password" */
auth_len = smtp_base64_encode(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN],
SMTP_TX_BUF_LEN - SMTP_CMD_AUTHPLAIN_1_LEN, SMTP_AUTH_PLAIN_DATA(s),
SMTP_AUTH_PLAIN_LEN(s));
LWIP_ASSERT("string too long", auth_len <= 0xffff);
*tx_buf_len = SMTP_CMD_AUTHPLAIN_1_LEN + SMTP_CMD_AUTHPLAIN_2_LEN + (u16_t)auth_len;
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
SMEMCPY(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN + auth_len], SMTP_CMD_AUTHPLAIN_2,
SMTP_CMD_AUTHPLAIN_2_LEN);
return SMTP_AUTH_PLAIN;
} else
#endif /* SMTP_SUPPORT_AUTH_PLAIN */
{
#if SMTP_SUPPORT_AUTH_LOGIN
if (strstr(sep, SMTP_AUTH_PARAM_LOGIN) != NULL) {
/* server supports AUTH LOGIN */
*tx_buf_len = SMTP_CMD_AUTHLOGIN_LEN;
SMEMCPY(s->tx_buf, SMTP_CMD_AUTHLOGIN, SMTP_CMD_AUTHLOGIN_LEN);
return SMTP_AUTH_LOGIN_UNAME;
}
#endif /* SMTP_SUPPORT_AUTH_LOGIN */
}
}
}
}
/* server didnt's send correct keywords for AUTH, try sending directly */
return smtp_prepare_mail(s, tx_buf_len);
}
#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
#if SMTP_SUPPORT_AUTH_LOGIN
/** Send base64-encoded username */
static enum smtp_session_state
smtp_prepare_auth_login_uname(struct smtp_session *s, u16_t *tx_buf_len)
{
size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
SMTP_USERNAME(s), strlen(SMTP_USERNAME(s)));
/* @todo: support base64-encoded longer than 64k */
LWIP_ASSERT("string too long", base64_len <= 0xffff);
LWIP_ASSERT("tx_buf overflow detected", base64_len + SMTP_CRLF_LEN <= SMTP_TX_BUF_LEN);
*tx_buf_len = (u16_t)base64_len + SMTP_CRLF_LEN;
SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
s->tx_buf[*tx_buf_len] = 0;
return SMTP_AUTH_LOGIN_PASS;
}
/** Send base64-encoded password */
static enum smtp_session_state
smtp_prepare_auth_login_pass(struct smtp_session *s, u16_t *tx_buf_len)
{
size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
SMTP_PASS(s), strlen(SMTP_PASS(s)));
/* @todo: support base64-encoded longer than 64k */
LWIP_ASSERT("string too long", base64_len <= 0xffff);
LWIP_ASSERT("tx_buf overflow detected", base64_len + SMTP_CRLF_LEN <= SMTP_TX_BUF_LEN);
*tx_buf_len = (u16_t)base64_len + SMTP_CRLF_LEN;
SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
s->tx_buf[*tx_buf_len] = 0;
return SMTP_AUTH_LOGIN;
}
#endif /* SMTP_SUPPORT_AUTH_LOGIN */
/** Prepare MAIL message */
static enum smtp_session_state
smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len)
{
char *target = s->tx_buf;
*tx_buf_len = SMTP_CMD_MAIL_1_LEN + SMTP_CMD_MAIL_2_LEN + s->from_len;
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
target[*tx_buf_len] = 0;
SMEMCPY(target, SMTP_CMD_MAIL_1, SMTP_CMD_MAIL_1_LEN);
target += SMTP_CMD_MAIL_1_LEN;
memcpy(target, s->from, s->from_len);
target += s->from_len;
SMEMCPY(target, SMTP_CMD_MAIL_2, SMTP_CMD_MAIL_2_LEN);
return SMTP_MAIL;
}
/** Prepare RCPT message */
static enum smtp_session_state
smtp_prepare_rcpt(struct smtp_session *s, u16_t *tx_buf_len)
{
char *target = s->tx_buf;
*tx_buf_len = SMTP_CMD_RCPT_1_LEN + SMTP_CMD_RCPT_2_LEN + s->to_len;
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
target[*tx_buf_len] = 0;
SMEMCPY(target, SMTP_CMD_RCPT_1, SMTP_CMD_RCPT_1_LEN);
target += SMTP_CMD_RCPT_1_LEN;
memcpy(target, s->to, s->to_len);
target += s->to_len;
SMEMCPY(target, SMTP_CMD_RCPT_2, SMTP_CMD_RCPT_2_LEN);
return SMTP_RCPT;
}
/** Prepare header of body */
static enum smtp_session_state
smtp_prepare_header(struct smtp_session *s, u16_t *tx_buf_len)
{
char *target = s->tx_buf;
*tx_buf_len = SMTP_CMD_HEADER_1_LEN + SMTP_CMD_HEADER_2_LEN +
SMTP_CMD_HEADER_3_LEN + SMTP_CMD_HEADER_4_LEN + s->from_len + s->to_len +
s->subject_len;
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
target[*tx_buf_len] = 0;
SMEMCPY(target, SMTP_CMD_HEADER_1, SMTP_CMD_HEADER_1_LEN);
target += SMTP_CMD_HEADER_1_LEN;
memcpy(target, s->from, s->from_len);
target += s->from_len;
SMEMCPY(target, SMTP_CMD_HEADER_2, SMTP_CMD_HEADER_2_LEN);
target += SMTP_CMD_HEADER_2_LEN;
memcpy(target, s->to, s->to_len);
target += s->to_len;
SMEMCPY(target, SMTP_CMD_HEADER_3, SMTP_CMD_HEADER_3_LEN);
target += SMTP_CMD_HEADER_3_LEN;
memcpy(target, s->subject, s->subject_len);
target += s->subject_len;
SMEMCPY(target, SMTP_CMD_HEADER_4, SMTP_CMD_HEADER_4_LEN);
return SMTP_BODY;
}
/** Prepare QUIT message */
static enum smtp_session_state
smtp_prepare_quit(struct smtp_session *s, u16_t *tx_buf_len)
{
*tx_buf_len = SMTP_CMD_QUIT_LEN;
s->tx_buf[*tx_buf_len] = 0;
SMEMCPY(s->tx_buf, SMTP_CMD_QUIT, SMTP_CMD_QUIT_LEN);
LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
return SMTP_CLOSED;
}
/** If in state SMTP_BODY, try to send more body data */
static void
smtp_send_body(struct smtp_session *s, struct tcp_pcb *pcb)
{
err_t err;
if (s->state == SMTP_BODY) {
u16_t send_len = s->body_len - s->body_sent;
if (send_len > 0) {
u16_t snd_buf = tcp_sndbuf(pcb);
if (send_len > snd_buf) {
send_len = snd_buf;
}
if (send_len > 0) {
/* try to send something out */
err = tcp_write(pcb, &s->body[s->body_sent], (u16_t)send_len, TCP_WRITE_FLAG_COPY);
if (err == ERR_OK) {
s->timer = SMTP_TIMEOUT_DATABLOCK;
s->body_sent += send_len;
if (s->body_sent < s->body_len) {
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: %d of %d bytes written\n",
s->body_sent, s->body_len));
}
}
}
}
if (s->body_sent == s->body_len) {
/* the whole body has been written, write last line */
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: body completely written (%d bytes), appending end-of-body\n",
s->body_len));
err = tcp_write(pcb, SMTP_CMD_BODY_FINISHED, SMTP_CMD_BODY_FINISHED_LEN, 0);
if (err == ERR_OK) {
s->timer = SMTP_TIMEOUT_DATATERM;
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: end-of-body written, changing state to %s\n",
smtp_state_str[SMTP_QUIT]));
/* last line written, change state, wait for confirmation */
s->state = SMTP_QUIT;
}
}
}
}
/** State machine-like implementation of an SMTP client.
*/
static void
smtp_process(void *arg, struct tcp_pcb *pcb, struct pbuf *p)
{
struct smtp_session* s = arg;
u16_t response_code = 0;
u16_t tx_buf_len = 0;
enum smtp_session_state next_state;
if (arg == NULL) {
/* already closed SMTP connection */
if (p != NULL) {
LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("Received %d bytes after closing: %s\n",
p->tot_len, smtp_pbuf_str(p)));
pbuf_free(p);
}
return;
}
next_state = s->state;
if (p != NULL) {
/* received data */
if (s->p == NULL) {
s->p = p;
} else {
pbuf_cat(s->p, p);
}
} else {
/* idle timer, close connection if timed out */
if (s->timer == 0) {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process: connection timed out, closing\n"));
smtp_close(s, pcb, SMTP_RESULT_ERR_TIMEOUT, 0, ERR_TIMEOUT);
return;
}
if (s->state == SMTP_BODY) {
smtp_send_body(s, pcb);
return;
}
}
response_code = smtp_is_response(s);
if (response_code) {
LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: received response code: %d\n", response_code));
if (smtp_is_response_finished(s) != ERR_OK) {
LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: partly received response code: %d\n", response_code));
/* wait for next packet to complete the respone */
return;
}
} else {
if (s->p != NULL) {
LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_process: unknown data received (%s)\n",
smtp_pbuf_str(s->p)));
pbuf_free(s->p);
s->p = NULL;
}
return;
}
switch(s->state)
{
case(SMTP_NULL):
/* wait for 220 */
if (response_code == 220) {
/* then send EHLO */
next_state = smtp_prepare_helo(s, &tx_buf_len, pcb);
}
break;
case(SMTP_HELO):
/* wait for 250 */
if (response_code == 250) {
#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
/* then send AUTH or MAIL */
next_state = smtp_prepare_auth_or_mail(s, &tx_buf_len);
}
break;
case(SMTP_AUTH_LOGIN):
case(SMTP_AUTH_PLAIN):
/* wait for 235 */
if (response_code == 235) {
#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
/* send MAIL */
next_state = smtp_prepare_mail(s, &tx_buf_len);
}
break;
#if SMTP_SUPPORT_AUTH_LOGIN
case(SMTP_AUTH_LOGIN_UNAME):
/* wait for 334 Username */
if (response_code == 334) {
if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_UNAME) != 0xFFFF) {
/* send username */
next_state = smtp_prepare_auth_login_uname(s, &tx_buf_len);
}
}
break;
case(SMTP_AUTH_LOGIN_PASS):
/* wait for 334 Password */
if (response_code == 334) {
if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_PASS) != 0xFFFF) {
/* send username */
next_state = smtp_prepare_auth_login_pass(s, &tx_buf_len);
}
}
break;
#endif /* SMTP_SUPPORT_AUTH_LOGIN */
case(SMTP_MAIL):
/* wait for 250 */
if (response_code == 250) {
/* send RCPT */
next_state = smtp_prepare_rcpt(s, &tx_buf_len);
}
break;
case(SMTP_RCPT):
/* wait for 250 */
if (response_code == 250) {
/* send DATA */
SMEMCPY(s->tx_buf, SMTP_CMD_DATA, SMTP_CMD_DATA_LEN);
tx_buf_len = SMTP_CMD_DATA_LEN;
next_state = SMTP_DATA;
}
break;
case(SMTP_DATA):
/* wait for 354 */
if (response_code == 354) {
/* send email header */
next_state = smtp_prepare_header(s, &tx_buf_len);
}
break;
case(SMTP_BODY):
/* nothing to be done here, handled somewhere else */
break;
case(SMTP_QUIT):
/* wait for 250 */
if (response_code == 250) {
/* send QUIT */
next_state = smtp_prepare_quit(s, &tx_buf_len);
}
break;
case(SMTP_CLOSED):
/* nothing to do, wait for connection closed from server */
return;
default:
LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Invalid state: %d/%s\n", (int)s->state,
smtp_state_str[s->state]));
break;
}
if (s->state == next_state) {
LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process[%s]: unexpected response_code, closing: %d (%s)\n",
smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
/* close connection */
smtp_close(s, pcb, SMTP_RESULT_ERR_SVR_RESP, response_code, ERR_OK);
return;
}
if (tx_buf_len > 0) {
SMTP_TX_BUF_MAX(tx_buf_len);
if (tcp_write(pcb, s->tx_buf, tx_buf_len, TCP_WRITE_FLAG_COPY) == ERR_OK) {
LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: received command %d (%s)\n",
smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: sent %"U16_F" bytes: \"%s\"\n",
smtp_state_str[s->state], tx_buf_len, s->tx_buf));
s->timer = SMTP_TIMEOUT;
pbuf_free(s->p);
s->p = NULL;
LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_process: changing state from %s to %s\n",
smtp_state_str[s->state], smtp_state_str[next_state]));
s->state = next_state;
if (next_state == SMTP_BODY) {
/* try to stream-send body data right now */
smtp_send_body(s, pcb);
} else if (next_state == SMTP_CLOSED) {
/* sent out all data, delete structure */
tcp_arg(pcb, NULL);
smtp_free(s, SMTP_RESULT_OK, 0, ERR_OK);
}
}
}
}
#endif /* LWIP_TCP */