| /** |
| * @file |
| * SMTP client module |
| * |
| * Author: Simon Goldschmidt |
| * |
| * @defgroup smtp SMTP client |
| * @ingroup apps |
| * |
| * This is simple SMTP client for raw API. |
| * It is a minimal implementation of SMTP as specified in RFC 5321. |
| * |
| * Example usage: |
| @code{.c} |
| 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); |
| } |
| @endcode |
| |
| * When using from any other thread than the tcpip_thread (for NO_SYS==0), use |
| * smtp_send_mail_int()! |
| * |
| * SMTP_BODYDH usage: |
| @code{.c} |
| int my_smtp_bodydh_fn(void *arg, struct smtp_bodydh *bdh) |
| { |
| if(bdh->state >= 10) { |
| return BDH_DONE; |
| } |
| sprintf(bdh->buffer,"Line #%2d\r\n",bdh->state); |
| bdh->length = strlen(bdh->buffer); |
| ++bdh->state; |
| return BDH_WORKING; |
| } |
| |
| smtp_send_mail_bodycback("sender", "recipient", "subject", |
| my_smtp_bodydh_fn, my_smtp_result_fn, some_argument); |
| @endcode |
| * |
| * @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... |
| * |
| */ |
| |
| #include "lwip/apps/smtp.h" |
| |
| #if LWIP_TCP && LWIP_CALLBACK_API |
| #include "lwip/sys.h" |
| #include "lwip/sockets.h" |
| #include "lwip/altcp.h" |
| #include "lwip/dns.h" |
| #include "lwip/mem.h" |
| #include "lwip/altcp_tcp.h" |
| #include "lwip/altcp_tls.h" |
| |
| #include <string.h> /* strlen, memcpy */ |
| #include <stdlib.h> |
| |
| /** 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 defined(SMTP_STAT_TX_BUF_MAX) && 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 */ |
| |
| #if SMTP_BODYDH |
| #ifndef SMTP_BODYDH_MALLOC |
| #define SMTP_BODYDH_MALLOC(size) mem_malloc(size) |
| #define SMTP_BODYDH_FREE(ptr) mem_free(ptr) |
| #endif |
| |
| /* Some internal state return values */ |
| #define BDHALLDATASENT 2 |
| #define BDHSOMEDATASENT 1 |
| |
| enum bdh_handler_state { |
| BDH_SENDING, /* Serving the user function generating body content */ |
| BDH_STOP /* User function stopped, closing */ |
| }; |
| #endif |
| |
| /** 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 */ |
| static 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", |
| }; |
| |
| static 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", |
| "SMTP_RESULT_ERR_MEM" |
| }; |
| #endif /* LWIP_DEBUG */ |
| |
| #if SMTP_BODYDH |
| struct smtp_bodydh_state { |
| smtp_bodycback_fn callback_fn; /* The function to call (again) */ |
| u16_t state; |
| struct smtp_bodydh exposed; /* the user function structure */ |
| }; |
| #endif /* SMTP_BODYDH */ |
| |
| /** 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 */ |
| #if SMTP_BODYDH |
| struct smtp_bodydh_state *bodydh; |
| #endif /* SMTP_BODYDH */ |
| }; |
| |
| /** 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; |
| #if LWIP_ALTCP && LWIP_ALTCP_TLS |
| /** If this is set, mail is sent using SMTPS */ |
| static struct altcp_tls_config *smtp_server_tls_config; |
| #endif |
| /** 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; |
| |
| #if SMTP_CHECK_DATA |
| static err_t smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed); |
| #endif /* SMTP_CHECK_DATA */ |
| static err_t smtp_tcp_recv(void *arg, struct altcp_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 altcp_pcb *pcb); |
| static err_t smtp_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len); |
| static err_t smtp_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err); |
| #if LWIP_DNS |
| static void smtp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg); |
| #endif /* LWIP_DNS */ |
| #if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN |
| static size_t smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len); |
| #endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */ |
| 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 altcp_pcb *pcb); |
| static void smtp_process(void *arg, struct altcp_pcb *pcb, struct pbuf *p); |
| #if SMTP_BODYDH |
| static void smtp_send_body_data_handler(struct smtp_session *s, struct altcp_pcb *pcb); |
| #endif /* SMTP_BODYDH */ |
| |
| |
| #ifdef LWIP_DEBUG |
| /** Convert an smtp result to a string */ |
| const char* |
| smtp_result_str(u8_t smtp_result) |
| { |
| if (smtp_result >= LWIP_ARRAYSIZE(smtp_result_strs)) { |
| 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 const char* |
| smtp_pbuf_str(struct pbuf* p) |
| { |
| if ((p == NULL) || (p->len == 0)) { |
| return ""; |
| } |
| ((char*)p->payload)[p->len] = 0; |
| return (const char*)p->payload; |
| } |
| #endif /* LWIP_DEBUG */ |
| |
| /** @ingroup smtp |
| * Set IP address or DNS name for next SMTP connection |
| * |
| * @param server IP address (in ASCII representation) or DNS name of the server |
| */ |
| err_t |
| smtp_set_server_addr(const char* server) |
| { |
| size_t len = 0; |
| |
| LWIP_ASSERT_CORE_LOCKED(); |
| |
| if (server != NULL) { |
| /* strlen: returns length WITHOUT terminating 0 byte */ |
| len = strlen(server); |
| } |
| if (len > SMTP_MAX_SERVERNAME_LEN) { |
| return ERR_MEM; |
| } |
| if (len != 0) { |
| MEMCPY(smtp_server, server, len); |
| } |
| smtp_server[len] = 0; /* always OK because of smtp_server[SMTP_MAX_SERVERNAME_LEN + 1] */ |
| return ERR_OK; |
| } |
| |
| /** @ingroup smtp |
| * Set TCP port for next SMTP connection |
| * |
| * @param port TCP port |
| */ |
| void |
| smtp_set_server_port(u16_t port) |
| { |
| LWIP_ASSERT_CORE_LOCKED(); |
| smtp_server_port = port; |
| } |
| |
| #if LWIP_ALTCP && LWIP_ALTCP_TLS |
| /** @ingroup smtp |
| * Set TLS configuration for next SMTP connection |
| * |
| * @param tls_config TLS configuration |
| */ |
| void |
| smtp_set_tls_config(struct altcp_tls_config *tls_config) |
| { |
| LWIP_ASSERT_CORE_LOCKED(); |
| smtp_server_tls_config = tls_config; |
| } |
| #endif |
| |
| /** @ingroup smtp |
| * 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; |
| |
| LWIP_ASSERT_CORE_LOCKED(); |
| |
| 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; |
| } |
| |
| #if SMTP_BODYDH |
| static void smtp_free_struct(struct smtp_session *s) |
| { |
| if (s->bodydh != NULL) { |
| SMTP_BODYDH_FREE(s->bodydh); |
| } |
| SMTP_STATE_FREE(s); |
| } |
| #else /* SMTP_BODYDH */ |
| #define smtp_free_struct(x) SMTP_STATE_FREE(x) |
| #endif /* SMTP_BODYDH */ |
| |
| static struct altcp_pcb* |
| smtp_setup_pcb(struct smtp_session *s, const ip_addr_t* remote_ip) |
| { |
| struct altcp_pcb* pcb; |
| LWIP_UNUSED_ARG(remote_ip); |
| |
| #if LWIP_ALTCP && LWIP_ALTCP_TLS |
| if (smtp_server_tls_config) { |
| pcb = altcp_tls_new(smtp_server_tls_config, IP_GET_TYPE(remote_ip)); |
| } else |
| #endif |
| { |
| pcb = altcp_tcp_new_ip_type(IP_GET_TYPE(remote_ip)); |
| } |
| if (pcb != NULL) { |
| altcp_arg(pcb, s); |
| altcp_recv(pcb, smtp_tcp_recv); |
| altcp_err(pcb, smtp_tcp_err); |
| altcp_poll(pcb, smtp_tcp_poll, SMTP_POLL_INTERVAL); |
| altcp_sent(pcb, smtp_tcp_sent); |
| } |
| return pcb; |
| } |
| |
| /** 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 altcp_pcb* pcb = NULL; |
| 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) { |
| err = ERR_ARG; |
| goto leave; |
| } |
| if (smtp_verify(s->from, s->from_len, 0) != ERR_OK) { |
| err = ERR_ARG; |
| goto leave; |
| } |
| if (smtp_verify(s->subject, s->subject_len, 0) != ERR_OK) { |
| err = ERR_ARG; |
| goto leave; |
| } |
| #if SMTP_BODYDH |
| if (s->bodydh == NULL) |
| #endif /* SMTP_BODYDH */ |
| { |
| if (smtp_verify(s->body, s->body_len, 0) != ERR_OK) { |
| err = ERR_ARG; |
| goto leave; |
| } |
| } |
| #endif /* SMTP_CHECK_DATA */ |
| |
| #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; |
| |
| #if LWIP_DNS |
| err = dns_gethostbyname(smtp_server, &addr, smtp_dns_found, s); |
| #else /* LWIP_DNS */ |
| err = ipaddr_aton(smtp_server, &addr) ? ERR_OK : ERR_ARG; |
| #endif /* LWIP_DNS */ |
| if (err == ERR_OK) { |
| pcb = smtp_setup_pcb(s, &addr); |
| if (pcb == NULL) { |
| err = ERR_MEM; |
| goto leave; |
| } |
| err = altcp_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) { |
| altcp_arg(pcb, NULL); |
| altcp_close(pcb); |
| } |
| leave: |
| smtp_free_struct(s); |
| /* no need to call the callback here since we return != ERR_OK */ |
| return err; |
| } |
| |
| /** @ingroup smtp |
| * 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 callback_fn callback function |
| * @param callback_arg user argument to callback_fn |
| * @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); |
| char *sfrom, *sto, *ssubject, *sbody; |
| |
| LWIP_ASSERT_CORE_LOCKED(); |
| |
| 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 *)SMTP_STATE_MALLOC((mem_size_t)mem_len); |
| if (s == NULL) { |
| return ERR_MEM; |
| } |
| /* initialize the structure */ |
| memset(s, 0, mem_len); |
| s->from = sfrom = (char*)s + sizeof(struct smtp_session); |
| s->from_len = (u16_t)from_len; |
| s->to = sto = sfrom + from_len + 1; |
| s->to_len = (u16_t)to_len; |
| s->subject = ssubject = sto + to_len + 1; |
| s->subject_len = (u16_t)subject_len; |
| s->body = sbody = ssubject + subject_len + 1; |
| s->body_len = (u16_t)body_len; |
| /* copy source and target email address */ |
| /* cast to size_t is a hack to cast away constness */ |
| MEMCPY(sfrom, from, from_len + 1); |
| MEMCPY(sto, to, to_len + 1); |
| MEMCPY(ssubject, subject, subject_len + 1); |
| MEMCPY(sbody, 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); |
| } |
| |
| /** @ingroup smtp |
| * 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; |
| |
| LWIP_ASSERT_CORE_LOCKED(); |
| |
| s = (struct smtp_session*)SMTP_STATE_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); |
| } |
| |
| |
| /** @ingroup smtp |
| * Same as smtp_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_CORE_LOCKED(); |
| 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 smtp_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); |
| } |
| smtp_free_struct(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 altcp_pcb *pcb, u8_t result, |
| u16_t srv_err, err_t err) |
| { |
| if (pcb != NULL) { |
| altcp_arg(pcb, NULL); |
| if (altcp_close(pcb) == ERR_OK) { |
| if (s != NULL) { |
| smtp_free(s, result, srv_err, err); |
| } |
| } else { |
| /* close failed, set back arg */ |
| altcp_arg(pcb, s); |
| } |
| } else { |
| if (s != NULL) { |
| smtp_free(s, result, srv_err, err); |
| } |
| } |
| } |
| |
| /** 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 altcp_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 altcp_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 altcp_pcb *pcb, struct pbuf *p, err_t err) |
| { |
| LWIP_UNUSED_ARG(err); |
| if (p != NULL) { |
| altcp_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((struct smtp_session*)arg, pcb, SMTP_RESULT_ERR_CLOSED, 0, err); |
| } |
| return ERR_OK; |
| } |
| |
| static err_t |
| smtp_tcp_connected(void *arg, struct altcp_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((struct smtp_session*)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, const ip_addr_t *ipaddr, void *arg) |
| { |
| struct smtp_session *s = (struct smtp_session*)arg; |
| struct altcp_pcb *pcb; |
| err_t err; |
| u8_t result; |
| |
| LWIP_UNUSED_ARG(hostname); |
| |
| if (ipaddr != NULL) { |
| pcb = smtp_setup_pcb(s, ipaddr); |
| if (pcb != NULL) { |
| LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: hostname resolved, connecting\n")); |
| err = altcp_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_STATE, ("smtp_dns_found: failed to allocate tcp pcb\n")); |
| result = SMTP_RESULT_ERR_MEM; |
| err = ERR_MEM; |
| } |
| } else { |
| LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_dns_found: failed to resolve hostname: %s\n", |
| hostname)); |
| pcb = NULL; |
| result = SMTP_RESULT_ERR_HOSTNAME; |
| err = ERR_ARG; |
| } |
| smtp_close(s, 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 */ |
| static const char 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 ? (u8_t)source[i] : 0); |
| for (j = 7; j >= 0; j--, x--) { |
| if ((b & (1 << j)) != 0) { |
| current = (u8_t)(current | (1U << 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 */ |
| if (offset > 0xFFFF - 4) { |
| /* would overflow */ |
| return ERR_VAL; |
| } |
| crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, (u16_t)(offset + 4)); |
| if (crlf == 0xFFFF) { |
| /* no CRLF found */ |
| return ERR_VAL; |
| } |
| sp = pbuf_get_at(s->p, (u16_t)(offset + 3)); |
| if (sp == '-') { |
| /* no space after response code -> try next line */ |
| offset = (u16_t)(crlf + 2); |
| if (offset < crlf) { |
| /* overflow */ |
| return ERR_VAL; |
| } |
| 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 altcp_pcb *pcb) |
| { |
| size_t ipa_len; |
| const char *ipa = ipaddr_ntoa(altcp_get_ip(pcb, 1)); |
| LWIP_ASSERT("ipaddr_ntoa returned NULL", ipa != NULL); |
| ipa_len = strlen(ipa); |
| LWIP_ASSERT("string too long", ipa_len <= (SMTP_TX_BUF_LEN-SMTP_CMD_EHLO_1_LEN-SMTP_CMD_EHLO_2_LEN)); |
| |
| *tx_buf_len = (u16_t)(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, (u16_t)(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 <= (SMTP_TX_BUF_LEN-SMTP_CMD_AUTHPLAIN_1_LEN-SMTP_CMD_AUTHPLAIN_2_LEN)); |
| *tx_buf_len = (u16_t)(SMTP_CMD_AUTHPLAIN_1_LEN + SMTP_CMD_AUTHPLAIN_2_LEN + (u16_t)auth_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_TX_BUF_LEN - SMTP_CRLF_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_TX_BUF_LEN - SMTP_CRLF_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; |
| LWIP_ASSERT("tx_buf overflow detected", s->from_len <= (SMTP_TX_BUF_LEN - SMTP_CMD_MAIL_1_LEN - SMTP_CMD_MAIL_2_LEN)); |
| *tx_buf_len = (u16_t)(SMTP_CMD_MAIL_1_LEN + SMTP_CMD_MAIL_2_LEN + s->from_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; |
| LWIP_ASSERT("tx_buf overflow detected", s->to_len <= (SMTP_TX_BUF_LEN - SMTP_CMD_RCPT_1_LEN - SMTP_CMD_RCPT_2_LEN)); |
| *tx_buf_len = (u16_t)(SMTP_CMD_RCPT_1_LEN + SMTP_CMD_RCPT_2_LEN + s->to_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; |
| int 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", len > 0 && len <= SMTP_TX_BUF_LEN); |
| *tx_buf_len = (u16_t)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 altcp_pcb *pcb) |
| { |
| err_t err; |
| |
| if (s->state == SMTP_BODY) { |
| #if SMTP_BODYDH |
| if (s->bodydh) { |
| smtp_send_body_data_handler(s, pcb); |
| } else |
| #endif /* SMTP_BODYDH */ |
| { |
| u16_t send_len = (u16_t)(s->body_len - s->body_sent); |
| if (send_len > 0) { |
| u16_t snd_buf = altcp_sndbuf(pcb); |
| if (send_len > snd_buf) { |
| send_len = snd_buf; |
| } |
| if (send_len > 0) { |
| /* try to send something out */ |
| err = altcp_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 = (u16_t)(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 = altcp_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 altcp_pcb *pcb, struct pbuf *p) |
| { |
| struct smtp_session* s = (struct smtp_session*)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 (altcp_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 */ |
| altcp_arg(pcb, NULL); |
| smtp_free(s, SMTP_RESULT_OK, 0, ERR_OK); |
| } |
| } |
| } |
| } |
| |
| #if SMTP_BODYDH |
| /** Elementary sub-function to send data |
| * |
| * @returns: BDHALLDATASENT all data has been written |
| * BDHSOMEDATASENT some data has been written |
| * 0 no data has been written |
| */ |
| static int |
| smtp_send_bodyh_data(struct altcp_pcb *pcb, const char **from, u16_t *howmany) |
| { |
| err_t err; |
| u16_t len = *howmany; |
| |
| len = (u16_t)LWIP_MIN(len, altcp_sndbuf(pcb)); |
| err = altcp_write(pcb, *from, len, TCP_WRITE_FLAG_COPY); |
| if (err == ERR_OK) { |
| *from += len; |
| if ((*howmany -= len) > 0) { |
| return BDHSOMEDATASENT; |
| } |
| return BDHALLDATASENT; |
| } |
| return 0; |
| } |
| |
| /** Same as smtp_send_mail_static, but uses a callback function to send body data |
| */ |
| err_t |
| smtp_send_mail_bodycback(const char *from, const char* to, const char* subject, |
| smtp_bodycback_fn bodycback_fn, smtp_result_fn callback_fn, void* callback_arg) |
| { |
| struct smtp_session* s; |
| size_t len; |
| |
| LWIP_ASSERT_CORE_LOCKED(); |
| |
| s = (struct smtp_session*)SMTP_STATE_MALLOC(sizeof(struct smtp_session)); |
| if (s == NULL) { |
| return ERR_MEM; |
| } |
| memset(s, 0, sizeof(struct smtp_session)); |
| s->bodydh = (struct smtp_bodydh_state*)SMTP_BODYDH_MALLOC(sizeof(struct smtp_bodydh_state)); |
| if (s->bodydh == NULL) { |
| SMTP_STATE_FREE(s); |
| return ERR_MEM; |
| } |
| memset(s->bodydh, 0, sizeof(struct smtp_bodydh_state)); |
| /* 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 = NULL; |
| LWIP_ASSERT("string is too long", len <= 0xffff); |
| s->callback_fn = callback_fn; |
| s->callback_arg = callback_arg; |
| s->bodydh->callback_fn = bodycback_fn; |
| s->bodydh->state = BDH_SENDING; |
| /* call the actual implementation of this function */ |
| return smtp_send_mail_alloced(s); |
| } |
| |
| static void |
| smtp_send_body_data_handler(struct smtp_session *s, struct altcp_pcb *pcb) |
| { |
| struct smtp_bodydh_state *bdh; |
| int res = 0, ret; |
| LWIP_ASSERT("s != NULL", s != NULL); |
| bdh = s->bodydh; |
| LWIP_ASSERT("bodydh != NULL", bdh != NULL); |
| |
| /* resume any leftovers from prior memory constraints */ |
| if (s->body_len) { |
| LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: resume\n")); |
| if((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len)) |
| != BDHALLDATASENT) { |
| s->body_sent = s->body_len - 1; |
| return; |
| } |
| } |
| ret = res; |
| /* all data on buffer has been queued, resume execution */ |
| if (bdh->state == BDH_SENDING) { |
| LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: run\n")); |
| do { |
| ret |= res; /* remember if we once queued something to send */ |
| bdh->exposed.length = 0; |
| if (bdh->callback_fn(s->callback_arg, &bdh->exposed) == BDH_DONE) { |
| bdh->state = BDH_STOP; |
| } |
| s->body = bdh->exposed.buffer; |
| s->body_len = bdh->exposed.length; |
| LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: trying to send %u bytes\n", (unsigned int)s->body_len)); |
| } while (s->body_len && |
| ((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len)) == BDHALLDATASENT) |
| && (bdh->state != BDH_STOP)); |
| } |
| if ((bdh->state != BDH_SENDING) && (ret != BDHSOMEDATASENT)) { |
| LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: stop\n")); |
| s->body_sent = s->body_len; |
| } else { |
| LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: pause\n")); |
| s->body_sent = s->body_len - 1; |
| } |
| } |
| #endif /* SMTP_BODYDH */ |
| |
| #endif /* LWIP_TCP && LWIP_CALLBACK_API */ |