1/**
2 * @file
3 * SMTP client module
4 *
5 * Author: Simon Goldschmidt
6 *
7 * Example usage:
8 *
9 * void my_smtp_result_fn(void *arg, u8_t smtp_result, u16_t srv_err, err_t err)
10 * {
11 *   printf("mail (%p) sent with results: 0x%02x, 0x%04x, 0x%08x\n", arg,
12 *          smtp_result, srv_err, err);
13 * }
14 * static void my_smtp_test(void)
15 * {
16 *   smtp_set_server_addr("mymailserver.org");
17 *   -> set both username and password as NULL if no auth needed
18 *   smtp_set_auth("username", "password");
19 *   smtp_send_mail("sender", "recipient", "subject", "body", my_smtp_result_fn,
20 *                  some_argument);
21 * }
22 *
23 * When using from any other thread than the tcpip_thread (for NO_SYS==0), use
24 * smtp_send_mail_int()!
25 *
26 *
27 * SMTP_BODYDH usage:
28 *
29 * int my_smtp_bodydh_fn(void *arg, struct smtp_bodydh *bdh)
30 * {
31 *    if(bdh->state >= 10) {
32 *       return BDH_DONE;
33 *    }
34 *    sprintf(bdh->buffer,"Line #%2d\r\n",bdh->state);
35 *    bdh->length = strlen(bdh->buffer);
36 *    ++bdh->state;
37 *    return BDH_WORKING;
38 * }
39 *
40 * smtp_send_mail_bodycback("sender", "recipient", "subject",
41 *                my_smtp_bodydh_fn, my_smtp_result_fn, some_argument);
42 *
43 */
44
45#include "smtp.h"
46
47#include "lwip/opt.h"
48
49#if LWIP_TCP
50#include "lwip/sys.h"
51#include "lwip/sockets.h"
52#include "lwip/tcp.h"
53#include "lwip/dns.h"
54
55#include <string.h>
56#include <stdlib.h>
57
58/** This is simple SMTP client for raw API.
59 * It is a minimal implementation of SMTP as specified in RFC 5321.
60 *
61 * @todo:
62 * - attachments (the main difficulty here is streaming base64-encoding to
63 *   prevent having to allocate a buffer for the whole encoded file at once)
64 * - test with more mail servers...
65 */
66
67/**
68 * SMTP_DEBUG: Enable debugging for SNTP.
69 */
70#ifndef SMTP_DEBUG
71#define SMTP_DEBUG                  LWIP_DBG_OFF
72#endif
73
74/** Maximum length reserved for server name */
75#ifndef SMTP_MAX_SERVERNAME_LEN
76#define SMTP_MAX_SERVERNAME_LEN 256
77#endif
78
79/** Maximum length reserved for username */
80#ifndef SMTP_MAX_USERNAME_LEN
81#define SMTP_MAX_USERNAME_LEN   32
82#endif
83
84/** Maximum length reserved for password */
85#ifndef SMTP_MAX_PASS_LEN
86#define SMTP_MAX_PASS_LEN       32
87#endif
88
89/** Set this to 0 if you know the authentication data will not change
90 * during the smtp session, which saves some heap space. */
91#ifndef SMTP_COPY_AUTHDATA
92#define SMTP_COPY_AUTHDATA      1
93#endif
94
95/** Set this to 0 to save some code space if you know for sure that all data
96 * passed to this module conforms to the requirements in the SMTP RFC.
97 * WARNING: use this with care!
98 */
99#ifndef SMTP_CHECK_DATA
100#define SMTP_CHECK_DATA         1
101#endif
102
103/** Set this to 1 to enable AUTH PLAIN support */
104#ifndef SMTP_SUPPORT_AUTH_PLAIN
105#define SMTP_SUPPORT_AUTH_PLAIN 1
106#endif
107
108/** Set this to 1 to enable AUTH LOGIN support */
109#ifndef SMTP_SUPPORT_AUTH_LOGIN
110#define SMTP_SUPPORT_AUTH_LOGIN 1
111#endif
112
113/** TCP poll interval. Unit is 0.5 sec. */
114#define SMTP_POLL_INTERVAL      4
115/** TCP poll timeout while sending message body, reset after every
116 * successful write. 3 minutes */
117#define SMTP_TIMEOUT_DATABLOCK  ( 3 * 60 * SMTP_POLL_INTERVAL / 2)
118/** TCP poll timeout while waiting for confirmation after sending the body.
119 * 10 minutes */
120#define SMTP_TIMEOUT_DATATERM   (10 * 60 * SMTP_POLL_INTERVAL / 2)
121/** TCP poll timeout while not sending the body.
122 * This is somewhat lower than the RFC states (5 minutes for initial, MAIL
123 * and RCPT) but still OK for us here.
124 * 2 minutes */
125#define SMTP_TIMEOUT            ( 2 * 60 * SMTP_POLL_INTERVAL / 2)
126
127/* the various debug levels for this file */
128#define SMTP_DEBUG_TRACE        (SMTP_DEBUG | LWIP_DBG_TRACE)
129#define SMTP_DEBUG_STATE        (SMTP_DEBUG | LWIP_DBG_STATE)
130#define SMTP_DEBUG_WARN         (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
131#define SMTP_DEBUG_WARN_STATE   (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
132#define SMTP_DEBUG_SERIOUS      (SMTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
133
134
135#define SMTP_RX_BUF_LEN         255
136#define SMTP_TX_BUF_LEN         255
137#define SMTP_CRLF               "\r\n"
138#define SMTP_CRLF_LEN           2
139
140#define SMTP_RESP_220           "220"
141#define SMTP_RESP_235           "235"
142#define SMTP_RESP_250           "250"
143#define SMTP_RESP_334           "334"
144#define SMTP_RESP_354           "354"
145#define SMTP_RESP_LOGIN_UNAME   "VXNlcm5hbWU6"
146#define SMTP_RESP_LOGIN_PASS    "UGFzc3dvcmQ6"
147
148#define SMTP_KEYWORD_AUTH_SP    "AUTH "
149#define SMTP_KEYWORD_AUTH_EQ    "AUTH="
150#define SMTP_KEYWORD_AUTH_LEN   5
151#define SMTP_AUTH_PARAM_PLAIN   "PLAIN"
152#define SMTP_AUTH_PARAM_LOGIN   "LOGIN"
153
154#define SMTP_CMD_EHLO_1           "EHLO ["
155#define SMTP_CMD_EHLO_1_LEN       6
156#define SMTP_CMD_EHLO_2           "]\r\n"
157#define SMTP_CMD_EHLO_2_LEN       3
158#define SMTP_CMD_AUTHPLAIN_1      "AUTH PLAIN "
159#define SMTP_CMD_AUTHPLAIN_1_LEN  11
160#define SMTP_CMD_AUTHPLAIN_2      "\r\n"
161#define SMTP_CMD_AUTHPLAIN_2_LEN  2
162#define SMTP_CMD_AUTHLOGIN        "AUTH LOGIN\r\n"
163#define SMTP_CMD_AUTHLOGIN_LEN    12
164#define SMTP_CMD_MAIL_1           "MAIL FROM: <"
165#define SMTP_CMD_MAIL_1_LEN       12
166#define SMTP_CMD_MAIL_2           ">\r\n"
167#define SMTP_CMD_MAIL_2_LEN       3
168#define SMTP_CMD_RCPT_1           "RCPT TO: <"
169#define SMTP_CMD_RCPT_1_LEN       10
170#define SMTP_CMD_RCPT_2           ">\r\n"
171#define SMTP_CMD_RCPT_2_LEN       3
172#define SMTP_CMD_DATA             "DATA\r\n"
173#define SMTP_CMD_DATA_LEN         6
174#define SMTP_CMD_HEADER_1         "From: <"
175#define SMTP_CMD_HEADER_1_LEN     7
176#define SMTP_CMD_HEADER_2         ">\r\nTo: <"
177#define SMTP_CMD_HEADER_2_LEN     8
178#define SMTP_CMD_HEADER_3         ">\r\nSubject: "
179#define SMTP_CMD_HEADER_3_LEN     12
180#define SMTP_CMD_HEADER_4         "\r\n\r\n"
181#define SMTP_CMD_HEADER_4_LEN     4
182#define SMTP_CMD_BODY_FINISHED    "\r\n.\r\n"
183#define SMTP_CMD_BODY_FINISHED_LEN 5
184#define SMTP_CMD_QUIT             "QUIT\r\n"
185#define SMTP_CMD_QUIT_LEN         6
186
187#if defined(SMTP_STAT_TX_BUF_MAX) && SMTP_STAT_TX_BUF_MAX
188#define SMTP_TX_BUF_MAX(len) LWIP_MACRO(if((len) > smtp_tx_buf_len_max) smtp_tx_buf_len_max = (len);)
189#else /* SMTP_STAT_TX_BUF_MAX */
190#define SMTP_TX_BUF_MAX(len)
191#endif /* SMTP_STAT_TX_BUF_MAX */
192
193#if SMTP_COPY_AUTHDATA
194#define SMTP_USERNAME(session)        (session)->username
195#define SMTP_PASS(session)            (session)->pass
196#define SMTP_AUTH_PLAIN_DATA(session) (session)->auth_plain
197#define SMTP_AUTH_PLAIN_LEN(session)  (session)->auth_plain_len
198#else /* SMTP_COPY_AUTHDATA */
199#define SMTP_USERNAME(session)        smtp_username
200#define SMTP_PASS(session)            smtp_pass
201#define SMTP_AUTH_PLAIN_DATA(session) smtp_auth_plain
202#define SMTP_AUTH_PLAIN_LEN(session)  smtp_auth_plain_len
203#endif /* SMTP_COPY_AUTHDATA */
204
205/* Memory allocation/deallocation can be overridden... */
206#ifndef SMTP_STATE_MALLOC
207#define SMTP_STATE_MALLOC(size)       mem_malloc(size)
208#define SMTP_STATE_FREE(ptr)          mem_free(ptr)
209#endif
210
211#if SMTP_BODYDH
212#ifndef SMTP_BODYDH_MALLOC
213#define SMTP_BODYDH_MALLOC(size)      mem_malloc(size)
214#define SMTP_BODYDH_FREE(ptr)         mem_free(ptr)
215#endif
216
217/* Some internal state return values */
218#define BDHALLDATASENT                2
219#define BDHSOMEDATASENT               1
220
221enum bdh_handler_state {
222  BDH_SENDING,         /* Serving the user function generating body content */
223  BDH_STOP             /* User function stopped, closing */
224};
225#endif
226
227/** State for SMTP client state machine */
228enum smtp_session_state {
229  SMTP_NULL,
230  SMTP_HELO,
231  SMTP_AUTH_PLAIN,
232  SMTP_AUTH_LOGIN_UNAME,
233  SMTP_AUTH_LOGIN_PASS,
234  SMTP_AUTH_LOGIN,
235  SMTP_MAIL,
236  SMTP_RCPT,
237  SMTP_DATA,
238  SMTP_BODY,
239  SMTP_QUIT,
240  SMTP_CLOSED
241};
242
243#ifdef LWIP_DEBUG
244/** State-to-string table for debugging */
245const char *smtp_state_str[] = {
246  "SMTP_NULL",
247  "SMTP_HELO",
248  "SMTP_AUTH_PLAIN",
249  "SMTP_AUTH_LOGIN_UNAME",
250  "SMTP_AUTH_LOGIN_PASS",
251  "SMTP_AUTH_LOGIN",
252  "SMTP_MAIL",
253  "SMTP_RCPT",
254  "SMTP_DATA",
255  "SMTP_BODY",
256  "SMTP_QUIT",
257  "SMTP_CLOSED",
258};
259
260const char *smtp_result_strs[] = {
261  "SMTP_RESULT_OK",
262  "SMTP_RESULT_ERR_UNKNOWN",
263  "SMTP_RESULT_ERR_CONNECT",
264  "SMTP_RESULT_ERR_HOSTNAME",
265  "SMTP_RESULT_ERR_CLOSED",
266  "SMTP_RESULT_ERR_TIMEOUT",
267  "SMTP_RESULT_ERR_SVR_RESP",
268};
269#endif /* LWIP_DEBUG */
270
271#if SMTP_BODYDH
272struct smtp_bodydh_state {
273  smtp_bodycback_fn callback_fn;  /* The function to call (again) */
274  u16_t state;
275  struct smtp_bodydh exposed;     /* the user function structure */
276};
277#endif /* SMTP_BODYDH */
278
279/** struct keeping the body and state of an smtp session */
280struct smtp_session {
281  /** keeping the state of the smtp session */
282  enum smtp_session_state state;
283  /** timeout handling, if this reaches 0, the connection is closed */
284  u16_t timer;
285  /** helper buffer for transmit, not used for sending body */
286  char tx_buf[SMTP_TX_BUF_LEN + 1];
287  struct pbuf* p;
288  /** source email address */
289  const char* from;
290  /** size of the sourceemail address */
291  u16_t from_len;
292  /** target email address */
293  const char* to;
294  /** size of the target email address */
295  u16_t to_len;
296  /** subject of the email */
297  const char *subject;
298  /** length of the subject string */
299  u16_t subject_len;
300  /** this is the body of the mail to be sent */
301  const char* body;
302  /** this is the length of the body to be sent */
303  u16_t body_len;
304  /** amount of data from body already sent */
305  u16_t body_sent;
306  /** callback function to call when closed */
307  smtp_result_fn callback_fn;
308  /** argument for callback function */
309  void *callback_arg;
310#if SMTP_COPY_AUTHDATA
311  /** Username to use for this request */
312  char *username;
313  /** Password to use for this request */
314  char *pass;
315  /** Username and password combined as necessary for PLAIN authentication */
316  char auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
317  /** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
318  size_t auth_plain_len;
319#endif /* SMTP_COPY_AUTHDATA */
320#if SMTP_BODYDH
321  struct smtp_bodydh_state *bodydh;
322#endif /* SMTP_BODYDH */
323};
324
325/** IP address or DNS name of the server to use for next SMTP request */
326static char smtp_server[SMTP_MAX_SERVERNAME_LEN + 1];
327/** TCP port of the server to use for next SMTP request */
328static u16_t smtp_server_port = SMTP_DEFAULT_PORT;
329/** Username to use for the next SMTP request */
330static char *smtp_username;
331/** Password to use for the next SMTP request */
332static char *smtp_pass;
333/** Username and password combined as necessary for PLAIN authentication */
334static char smtp_auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
335/** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
336static size_t smtp_auth_plain_len;
337
338static err_t  smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed);
339static err_t  smtp_tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
340static void   smtp_tcp_err(void *arg, err_t err);
341static err_t  smtp_tcp_poll(void *arg, struct tcp_pcb *pcb);
342static err_t  smtp_tcp_sent(void *arg, struct tcp_pcb *pcb, u16_t len);
343static err_t  smtp_tcp_connected(void *arg, struct tcp_pcb *pcb, err_t err);
344#if LWIP_DNS
345static void   smtp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg);
346#endif /* LWIP_DNS */
347static size_t smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len);
348static enum   smtp_session_state smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len);
349static void   smtp_send_body(struct smtp_session *s, struct tcp_pcb *pcb);
350static void   smtp_process(void *arg, struct tcp_pcb *pcb, struct pbuf *p);
351#if SMTP_BODYDH
352static void   smtp_send_body_data_handler(struct smtp_session *s, struct tcp_pcb *pcb);
353#endif /* SMTP_BODYDH */
354
355
356#ifdef LWIP_DEBUG
357/** Convert an smtp result to a string */
358const char*
359smtp_result_str(u8_t smtp_result)
360{
361  if (smtp_result > SMTP_RESULT_ERR_SVR_RESP) {
362    return "UNKNOWN";
363  }
364  return smtp_result_strs[smtp_result];
365}
366
367/** Null-terminates the payload of p for printing out messages.
368 * WARNING: use this only if p is not needed any more as the last byte of
369 *          payload is deleted!
370 */
371static const char*
372smtp_pbuf_str(struct pbuf* p)
373{
374  if ((p == NULL) || (p->len == 0)) {
375    return "";
376  }
377  ((char*)p->payload)[p->len] = 0;
378  return (const char*)p->payload;
379}
380#endif /* LWIP_DEBUG */
381
382/** Set IP address or DNS name for next SMTP connection
383 *
384 * @param server IP address (in ASCII representation) or DNS name of the server
385 */
386void
387smtp_set_server_addr(const char* server)
388{
389  size_t len = 0;
390  if (server != NULL) {
391    len = strlen(server);
392  }
393  if (len > SMTP_MAX_SERVERNAME_LEN) {
394    len = SMTP_MAX_SERVERNAME_LEN;
395  }
396  MEMCPY(smtp_server, server, len);
397}
398
399/** Set TCP port for next SMTP connection
400 *
401 * @param port TCP port
402 */
403void
404smtp_set_server_port(u16_t port)
405{
406  smtp_server_port = port;
407}
408
409/** Set authentication parameters for next SMTP connection
410 *
411 * @param username login name as passed to the server
412 * @param pass password passed to the server together with username
413 */
414err_t
415smtp_set_auth(const char* username, const char* pass)
416{
417  size_t uname_len = 0;
418  size_t pass_len = 0;
419
420  memset(smtp_auth_plain, 0xfa, 64);
421  if (username != NULL) {
422    uname_len = strlen(username);
423    if (uname_len > SMTP_MAX_USERNAME_LEN) {
424      LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Username is too long, %d instead of %d\n",
425        (int)uname_len, SMTP_MAX_USERNAME_LEN));
426      return ERR_ARG;
427    }
428  }
429  if (pass != NULL) {
430#if SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN
431    pass_len = strlen(pass);
432    if (pass_len > SMTP_MAX_PASS_LEN) {
433      LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Password is too long, %d instead of %d\n",
434        (int)uname_len, SMTP_MAX_USERNAME_LEN));
435      return ERR_ARG;
436    }
437#else /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
438    LWIP_DEBUGF(SMTP_DEBUG_WARN, ("Password not supported as no authentication methods are activated\n"));
439#endif /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
440  }
441  *smtp_auth_plain = 0;
442  if (username != NULL) {
443    smtp_username = smtp_auth_plain + 1;
444    strcpy(smtp_username, username);
445  }
446  if (pass != NULL) {
447    smtp_pass = smtp_auth_plain + uname_len + 2;
448    strcpy(smtp_pass, pass);
449  }
450  smtp_auth_plain_len = uname_len + pass_len + 2;
451
452  return ERR_OK;
453}
454
455#if SMTP_BODYDH
456static void smtp_free_struct(struct smtp_session *s)
457{
458  if (s->bodydh != NULL) {
459    SMTP_BODYDH_FREE(s->bodydh);
460  }
461  SMTP_STATE_FREE(s);
462}
463#else /* SMTP_BODYDH */
464#define smtp_free_struct(x) SMTP_STATE_FREE(x)
465#endif /* SMTP_BODYDH */
466
467static struct tcp_pcb*
468smtp_setup_pcb(struct smtp_session *s, const ip_addr_t* remote_ip)
469{
470  struct tcp_pcb* pcb;
471  LWIP_UNUSED_ARG(remote_ip);
472
473  pcb = tcp_new_ip_type(IP_GET_TYPE(remote_ip));
474  if (pcb != NULL) {
475    tcp_arg(pcb, s);
476    tcp_recv(pcb, smtp_tcp_recv);
477    tcp_err(pcb, smtp_tcp_err);
478    tcp_poll(pcb, smtp_tcp_poll, SMTP_POLL_INTERVAL);
479    tcp_sent(pcb, smtp_tcp_sent);
480  }
481  return pcb;
482}
483
484/** The actual mail-sending function, called by smtp_send_mail and
485 * smtp_send_mail_static after setting up the struct smtp_session.
486 */
487static err_t
488smtp_send_mail_alloced(struct smtp_session *s)
489{
490  err_t err;
491  struct tcp_pcb* pcb = NULL;
492  ip_addr_t addr;
493
494  LWIP_ASSERT("no smtp_session supplied", s != NULL);
495
496#if SMTP_CHECK_DATA
497  /* check that body conforms to RFC:
498   * - convert all single-CR or -LF in body to CRLF
499   * - only 7-bit ASCII is allowed
500   */
501  if (smtp_verify(s->to, s->to_len, 0) != ERR_OK) {
502    return ERR_ARG;
503  }
504  if (smtp_verify(s->from, s->from_len, 0) != ERR_OK) {
505    return ERR_ARG;
506  }
507  if (smtp_verify(s->subject, s->subject_len, 0) != ERR_OK) {
508    return ERR_ARG;
509  }
510#if SMTP_BODYDH
511  if (s->bodydh == NULL)
512#endif /* SMTP_BODYDH */
513  {
514  if (smtp_verify(s->body, s->body_len, 0) != ERR_OK) {
515    return ERR_ARG;
516  }
517  }
518#endif /* SMTP_CHECK_DATA */
519
520#if SMTP_COPY_AUTHDATA
521  /* copy auth data, ensuring the first byte is always zero */
522  MEMCPY(s->auth_plain + 1, smtp_auth_plain + 1, smtp_auth_plain_len - 1);
523  s->auth_plain_len = smtp_auth_plain_len;
524  /* default username and pass is empty string */
525  s->username = s->auth_plain;
526  s->pass = s->auth_plain;
527  if (smtp_username != NULL) {
528    s->username += smtp_username - smtp_auth_plain;
529  }
530  if (smtp_pass != NULL) {
531    s->pass += smtp_pass - smtp_auth_plain;
532  }
533#endif /* SMTP_COPY_AUTHDATA */
534
535  s->state = SMTP_NULL;
536  s->timer = SMTP_TIMEOUT;
537
538#if LWIP_DNS
539  err = dns_gethostbyname(smtp_server, &addr, smtp_dns_found, s);
540#else /* LWIP_DNS */
541  err = ipaddr_aton(smtp_server, &addr) ? ERR_OK : ERR_ARG;
542#endif /* LWIP_DNS */
543  if (err == ERR_OK) {
544    pcb = smtp_setup_pcb(s, &addr);
545    if (pcb == NULL) {
546      err = ERR_MEM;
547      goto leave;
548    }
549    err = tcp_connect(pcb, &addr, smtp_server_port, smtp_tcp_connected);
550    if (err != ERR_OK) {
551      LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
552      goto deallocate_and_leave;
553    }
554  } else if (err != ERR_INPROGRESS) {
555    LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("dns_gethostbyname failed: %d\n", (int)err));
556    goto deallocate_and_leave;
557  }
558  return ERR_OK;
559
560deallocate_and_leave:
561  if (pcb != NULL) {
562    tcp_arg(pcb, NULL);
563    tcp_close(pcb);
564  }
565leave:
566  smtp_free_struct(s);
567  /* no need to call the callback here since we return != ERR_OK */
568  return err;
569}
570
571/** Send an email via the currently selected server, username and password.
572 *
573 * @param from source email address (must be NULL-terminated)
574 * @param to target email address (must be NULL-terminated)
575 * @param subject email subject (must be NULL-terminated)
576 * @param body email body (must be NULL-terminated)
577 * @param callback_fn callback function
578 * @param callback_arg user argument to callback_fn
579 * @returns - ERR_OK if structures were allocated and no error occured starting the connection
580 *            (this does not mean the email has been successfully sent!)
581 *          - another err_t on error.
582 */
583err_t
584smtp_send_mail(const char* from, const char* to, const char* subject, const char* body,
585               smtp_result_fn callback_fn, void* callback_arg)
586{
587  struct smtp_session* s;
588  size_t from_len = strlen(from);
589  size_t to_len = strlen(to);
590  size_t subject_len = strlen(subject);
591  size_t body_len = strlen(body);
592  size_t mem_len = sizeof(struct smtp_session);
593  char *sfrom, *sto, *ssubject, *sbody;
594
595  mem_len += from_len + to_len + subject_len + body_len + 4;
596  if (mem_len > 0xffff) {
597    /* too long! */
598    return ERR_MEM;
599  }
600
601  /* Allocate memory to keep this email's session state */
602  s = (struct smtp_session *)SMTP_STATE_MALLOC((mem_size_t)mem_len);
603  if (s == NULL) {
604    return ERR_MEM;
605  }
606  /* initialize the structure */
607  memset(s, 0, mem_len);
608  s->from = sfrom = (char*)s + sizeof(struct smtp_session);
609  s->from_len = (u16_t)from_len;
610  s->to = sto = sfrom + from_len + 1;
611  s->to_len = (u16_t)to_len;
612  s->subject = ssubject = sto + to_len + 1;
613  s->subject_len = (u16_t)subject_len;
614  s->body = sbody = ssubject + subject_len + 1;
615  s->body_len = (u16_t)body_len;
616  /* copy source and target email address */
617  /* cast to size_t is a hack to cast away constness */
618  MEMCPY(sfrom, from, from_len + 1);
619  MEMCPY(sto, to, to_len + 1);
620  MEMCPY(ssubject, subject, subject_len + 1);
621  MEMCPY(sbody, body, body_len + 1);
622
623  s->callback_fn = callback_fn;
624  s->callback_arg = callback_arg;
625
626  /* call the actual implementation of this function */
627  return smtp_send_mail_alloced(s);
628}
629
630/** Same as smtp_send_mail, but doesn't copy from, to, subject and body into
631 * an internal buffer to save memory.
632 * WARNING: the above data must stay untouched until the callback function is
633 *          called (unless the function returns != ERR_OK)
634 */
635err_t
636smtp_send_mail_static(const char *from, const char* to, const char* subject,
637  const char* body, smtp_result_fn callback_fn, void* callback_arg)
638{
639  struct smtp_session* s;
640  size_t len;
641
642  s = (struct smtp_session*)SMTP_STATE_MALLOC(sizeof(struct smtp_session));
643  if (s == NULL) {
644    return ERR_MEM;
645  }
646  memset(s, 0, sizeof(struct smtp_session));
647  /* initialize the structure */
648  s->from = from;
649  len = strlen(from);
650  LWIP_ASSERT("string is too long", len <= 0xffff);
651  s->from_len = (u16_t)len;
652  s->to = to;
653  len = strlen(to);
654  LWIP_ASSERT("string is too long", len <= 0xffff);
655  s->to_len = (u16_t)len;
656  s->subject = subject;
657  len = strlen(subject);
658  LWIP_ASSERT("string is too long", len <= 0xffff);
659  s->subject_len = (u16_t)len;
660  s->body = body;
661  len = strlen(body);
662  LWIP_ASSERT("string is too long", len <= 0xffff);
663  s->body_len = (u16_t)len;
664  s->callback_fn = callback_fn;
665  s->callback_arg = callback_arg;
666  /* call the actual implementation of this function */
667  return smtp_send_mail_alloced(s);
668}
669
670
671/** Same as smpt_send_mail but takes a struct smtp_send_request as single
672 * parameter which contains all the other parameters.
673 * To be used with tcpip_callback to send mail from interrupt context or from
674 * another thread.
675 *
676 * WARNING: server and authentication must stay untouched until this function has run!
677 *
678 * Usage example:
679 * - allocate a struct smtp_send_request (in a way that is allowed in interrupt context)
680 * - fill the members of the struct as if calling smtp_send_mail
681 * - specify a callback_function
682 * - set callback_arg to the structure itself
683 * - call this function
684 * - wait for the callback function to be called
685 * - in the callback function, deallocate the structure (passed as arg)
686 */
687void
688smtp_send_mail_int(void *arg)
689{
690  struct smtp_send_request *req = (struct smtp_send_request*)arg;
691  err_t err;
692
693  LWIP_ASSERT("smtp_send_mail_int: no argument given", arg != NULL);
694
695  if (req->static_data) {
696    err = smtp_send_mail_static(req->from, req->to, req->subject, req->body,
697      req->callback_fn, req->callback_arg);
698  } else {
699    err = smtp_send_mail(req->from, req->to, req->subject, req->body,
700      req->callback_fn, req->callback_arg);
701  }
702  if ((err != ERR_OK) && (req->callback_fn != NULL)) {
703    req->callback_fn(req->callback_arg, SMTP_RESULT_ERR_UNKNOWN, 0, err);
704  }
705}
706
707#if SMTP_CHECK_DATA
708/** Verify that a given string conforms to the SMTP rules
709 * (7-bit only, no single CR or LF,
710 *  @todo: no line consisting of a single dot only)
711 */
712static err_t
713smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed)
714{
715  size_t i;
716  u8_t last_was_cr = 0;
717  for (i = 0; i < data_len; i++) {
718    char current = data[i];
719    if ((current & 0x80) != 0) {
720      LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: no 8-bit data supported: %s\n", data));
721      return ERR_ARG;
722    }
723    if (current == '\r') {
724      if (!linebreaks_allowed) {
725        LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found CR where no linebreaks allowed: %s\n", data));
726        return ERR_ARG;
727      }
728      if (last_was_cr) {
729        LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found double CR: %s\n", data));
730        return ERR_ARG;
731      }
732      last_was_cr = 1;
733    } else {
734      if (current == '\n') {
735        if (!last_was_cr) {
736          LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found LF without CR before: %s\n", data));
737          return ERR_ARG;
738        }
739      }
740      last_was_cr = 0;
741    }
742  }
743  return ERR_OK;
744}
745#endif /* SMTP_CHECK_DATA */
746
747/** Frees the smpt_session and calls the callback function */
748static void
749smtp_free(struct smtp_session *s, u8_t result, u16_t srv_err, err_t err)
750{
751  smtp_result_fn fn = s->callback_fn;
752  void *arg = s->callback_arg;
753  if (s->p != NULL) {
754    pbuf_free(s->p);
755  }
756  smtp_free_struct(s);
757  if (fn != NULL) {
758    fn(arg, result, srv_err, err);
759  }
760}
761
762/** Try to close a pcb and free the arg if successful */
763static void
764smtp_close(struct smtp_session *s, struct tcp_pcb *pcb, u8_t result,
765           u16_t srv_err, err_t err)
766{
767  if (pcb != NULL) {
768     tcp_arg(pcb, NULL);
769     if (tcp_close(pcb) == ERR_OK) {
770       if (s != NULL) {
771         smtp_free(s, result, srv_err, err);
772       }
773     } else {
774       /* close failed, set back arg */
775       tcp_arg(pcb, s);
776     }
777  } else {
778    if (s != NULL) {
779      smtp_free(s, result, srv_err, err);
780    }
781  }
782}
783
784/** Raw API TCP err callback: pcb is already deallocated */
785static void
786smtp_tcp_err(void *arg, err_t err)
787{
788  LWIP_UNUSED_ARG(err);
789  if (arg != NULL) {
790    LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_err: connection reset by remote host\n"));
791    smtp_free((struct smtp_session*)arg, SMTP_RESULT_ERR_CLOSED, 0, err);
792  }
793}
794
795/** Raw API TCP poll callback */
796static err_t
797smtp_tcp_poll(void *arg, struct tcp_pcb *pcb)
798{
799  if (arg != NULL) {
800    struct smtp_session *s = (struct smtp_session*)arg;
801    if (s->timer != 0) {
802      s->timer--;
803    }
804  }
805  smtp_process(arg, pcb, NULL);
806  return ERR_OK;
807}
808
809/** Raw API TCP sent callback */
810static err_t
811smtp_tcp_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
812{
813  LWIP_UNUSED_ARG(len);
814
815  smtp_process(arg, pcb, NULL);
816
817  return ERR_OK;
818}
819
820/** Raw API TCP recv callback */
821static err_t
822smtp_tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
823{
824  LWIP_UNUSED_ARG(err);
825  if (p != NULL) {
826    tcp_recved(pcb, p->tot_len);
827    smtp_process(arg, pcb, p);
828  } else {
829    LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_recv: connection closed by remote host\n"));
830    smtp_close((struct smtp_session*)arg, pcb, SMTP_RESULT_ERR_CLOSED, 0, err);
831  }
832  return ERR_OK;
833}
834
835static err_t
836smtp_tcp_connected(void *arg, struct tcp_pcb *pcb, err_t err)
837{
838  LWIP_UNUSED_ARG(arg);
839
840  if (err == ERR_OK) {
841    LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_connected: Waiting for 220\n"));
842  } else {
843    /* shouldn't happen, but we still check 'err', only to be sure */
844    LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_connected: %d\n", (int)err));
845    smtp_close((struct smtp_session*)arg, pcb, SMTP_RESULT_ERR_CONNECT, 0, err);
846  }
847  return ERR_OK;
848}
849
850#if LWIP_DNS
851/** DNS callback
852 * If ipaddr is non-NULL, resolving succeeded, otherwise it failed.
853 */
854static void
855smtp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
856{
857  struct smtp_session *s = (struct smtp_session*)arg;
858  struct tcp_pcb *pcb;
859  err_t err;
860  u8_t result;
861
862  LWIP_UNUSED_ARG(hostname);
863
864  if (ipaddr != NULL) {
865    pcb = smtp_setup_pcb(s, ipaddr);
866    if (pcb != NULL) {
867      LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: hostname resolved, connecting\n"));
868      err = tcp_connect(pcb, ipaddr, smtp_server_port, smtp_tcp_connected);
869      if (err == ERR_OK) {
870        return;
871      }
872      LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
873      result = SMTP_RESULT_ERR_CONNECT;
874    } else {
875      LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: failed to allocate tcp pcb\n"));
876      result = SMTP_RESULT_ERR_MEM;
877      err = ERR_MEM;
878    }
879  } else {
880    LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_dns_found: failed to resolve hostname: %s\n",
881      hostname));
882    pcb = NULL;
883    result = SMTP_RESULT_ERR_HOSTNAME;
884    err = ERR_ARG;
885  }
886  smtp_close(s, pcb, result, 0, err);
887}
888#endif /* LWIP_DNS */
889
890#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
891
892/** Table 6-bit-index-to-ASCII used for base64-encoding */
893const u8_t base64_table[] = {
894  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
895  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
896  'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
897  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
898  'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
899  'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
900  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
901  '+', '/'
902};
903
904/** Base64 encoding */
905static size_t
906smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len)
907{
908  size_t i;
909  s8_t j;
910  size_t target_idx = 0;
911  size_t longer = (source_len % 3) ? (3 - (source_len % 3)) : 0;
912  size_t source_len_b64 = source_len + longer;
913  size_t len = (((source_len_b64) * 4) / 3);
914  u8_t x = 5;
915  u8_t current = 0;
916  LWIP_UNUSED_ARG(target_len);
917
918  LWIP_ASSERT("target_len is too short", target_len >= len);
919
920  for (i = 0; i < source_len_b64; i++) {
921    u8_t b = (i < source_len ? source[i] : 0);
922    for (j = 7; j >= 0; j--, x--) {
923      u8_t shift = ((b & (1 << j)) != 0) ? 1 : 0;
924      current |= shift << x;
925      if (x == 0) {
926        target[target_idx++] = base64_table[current];
927        x = 6;
928        current = 0;
929      }
930    }
931  }
932  for (i = len - longer; i < len; i++) {
933    target[i] = '=';
934  }
935  return len;
936}
937#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
938
939/** Parse pbuf to see if it contains the beginning of an answer.
940 * If so, it returns the contained response code as number between 1 and 999.
941 * If not, zero is returned.
942 *
943 * @param s smtp session struct
944 */
945static u16_t
946smtp_is_response(struct smtp_session *s)
947{
948  char digits[4];
949  long num;
950
951  if (s->p == NULL) {
952    return 0;
953  }
954  /* copy three digits and convert them to int */
955  if (pbuf_copy_partial(s->p, digits, 3, 0) != 3) {
956    /* pbuf was too short */
957    return 0;
958  }
959  digits[3] = 0;
960  num = strtol(digits, NULL, 10);
961  if ((num <= 0) || (num >= 1000)) {
962    /* failed to find response code at start of line */
963    return 0;
964  }
965  return (u16_t)num;
966}
967
968/** Parse pbuf to see if it contains a fully received answer.
969 * If one is found, ERR_OK is returned.
970 * If none is found, ERR_VAL is returned.
971 *
972 * A fully received answer is a 3-digit number followed by a space,
973 * some string and a CRLF as line ending.
974 *
975 * @param s smtp session struct
976 */
977static err_t
978smtp_is_response_finished(struct smtp_session *s)
979{
980  u8_t sp;
981  u16_t crlf;
982  u16_t offset;
983
984  if (s->p == NULL) {
985    return ERR_VAL;
986  }
987  offset = 0;
988again:
989  /* We could check the response number here, but we trust the
990   * protocol definition which says the client can rely on it being
991   * the same on every line. */
992
993  /* find CRLF */
994  crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, offset + 4);
995  if (crlf == 0xFFFF) {
996    /* no CRLF found */
997    return ERR_VAL;
998  }
999  sp = pbuf_get_at(s->p, offset + 3);
1000  if (sp == '-') {
1001    /* no space after response code -> try next line */
1002    offset = crlf + 2;
1003    goto again;
1004  } else if (sp == ' ') {
1005    /* CRLF found after response code + space -> valid response */
1006    return ERR_OK;
1007  }
1008  /* sp contains invalid character */
1009  return ERR_VAL;
1010}
1011
1012/** Prepare HELO/EHLO message */
1013static enum smtp_session_state
1014smtp_prepare_helo(struct smtp_session *s, u16_t *tx_buf_len, struct tcp_pcb *pcb)
1015{
1016  size_t ipa_len;
1017  const char *ipa = ipaddr_ntoa(&pcb->local_ip);
1018  LWIP_ASSERT("ipaddr_ntoa returned NULL", ipa != NULL);
1019  ipa_len = strlen(ipa);
1020  LWIP_ASSERT("string too long", ipa_len <= (SMTP_TX_BUF_LEN-SMTP_CMD_EHLO_1_LEN-SMTP_CMD_EHLO_2_LEN));
1021
1022  *tx_buf_len = SMTP_CMD_EHLO_1_LEN + (u16_t)ipa_len + SMTP_CMD_EHLO_2_LEN;
1023  LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
1024
1025  SMEMCPY(s->tx_buf, SMTP_CMD_EHLO_1, SMTP_CMD_EHLO_1_LEN);
1026  MEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN], ipa, ipa_len);
1027  SMEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN + ipa_len], SMTP_CMD_EHLO_2, SMTP_CMD_EHLO_2_LEN);
1028  return SMTP_HELO;
1029}
1030
1031#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
1032/** Parse last server response (in rx_buf) for supported authentication method,
1033 * create data to send out (to tx_buf), set tx_data_len correctly
1034 * and return the next state.
1035 */
1036static enum smtp_session_state
1037smtp_prepare_auth_or_mail(struct smtp_session *s, u16_t *tx_buf_len)
1038{
1039  /* check response for supported authentication method */
1040  u16_t auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_SP);
1041  if (auth == 0xFFFF) {
1042    auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_EQ);
1043  }
1044  if (auth != 0xFFFF) {
1045    u16_t crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, auth);
1046    if ((crlf != 0xFFFF) && (crlf > auth)) {
1047      /* use tx_buf temporarily */
1048      u16_t copied = pbuf_copy_partial(s->p, s->tx_buf, crlf - auth, auth);
1049      if (copied != 0) {
1050        char *sep = s->tx_buf + SMTP_KEYWORD_AUTH_LEN;
1051        s->tx_buf[copied] = 0;
1052#if SMTP_SUPPORT_AUTH_PLAIN
1053        /* favour PLAIN over LOGIN since it involves less requests */
1054        if (strstr(sep, SMTP_AUTH_PARAM_PLAIN) != NULL) {
1055          size_t auth_len;
1056          /* server supports AUTH PLAIN */
1057          SMEMCPY(s->tx_buf, SMTP_CMD_AUTHPLAIN_1, SMTP_CMD_AUTHPLAIN_1_LEN);
1058
1059          /* add base64-encoded string "\0username\0password" */
1060          auth_len = smtp_base64_encode(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN],
1061            SMTP_TX_BUF_LEN - SMTP_CMD_AUTHPLAIN_1_LEN, SMTP_AUTH_PLAIN_DATA(s),
1062            SMTP_AUTH_PLAIN_LEN(s));
1063          LWIP_ASSERT("string too long", auth_len <= (SMTP_TX_BUF_LEN-SMTP_CMD_AUTHPLAIN_1_LEN-SMTP_CMD_AUTHPLAIN_2_LEN));
1064          *tx_buf_len = SMTP_CMD_AUTHPLAIN_1_LEN + SMTP_CMD_AUTHPLAIN_2_LEN + (u16_t)auth_len;
1065          SMEMCPY(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN + auth_len], SMTP_CMD_AUTHPLAIN_2,
1066            SMTP_CMD_AUTHPLAIN_2_LEN);
1067          return SMTP_AUTH_PLAIN;
1068        } else
1069#endif /* SMTP_SUPPORT_AUTH_PLAIN */
1070        {
1071#if SMTP_SUPPORT_AUTH_LOGIN
1072          if (strstr(sep, SMTP_AUTH_PARAM_LOGIN) != NULL) {
1073            /* server supports AUTH LOGIN */
1074            *tx_buf_len = SMTP_CMD_AUTHLOGIN_LEN;
1075            SMEMCPY(s->tx_buf, SMTP_CMD_AUTHLOGIN, SMTP_CMD_AUTHLOGIN_LEN);
1076            return SMTP_AUTH_LOGIN_UNAME;
1077          }
1078#endif /* SMTP_SUPPORT_AUTH_LOGIN */
1079        }
1080      }
1081    }
1082  }
1083  /* server didnt's send correct keywords for AUTH, try sending directly */
1084  return smtp_prepare_mail(s, tx_buf_len);
1085}
1086#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
1087
1088#if SMTP_SUPPORT_AUTH_LOGIN
1089/** Send base64-encoded username */
1090static enum smtp_session_state
1091smtp_prepare_auth_login_uname(struct smtp_session *s, u16_t *tx_buf_len)
1092{
1093  size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
1094    SMTP_USERNAME(s), strlen(SMTP_USERNAME(s)));
1095  /* @todo: support base64-encoded longer than 64k */
1096  LWIP_ASSERT("string too long", base64_len <= 0xffff);
1097  LWIP_ASSERT("tx_buf overflow detected", base64_len + SMTP_CRLF_LEN <= SMTP_TX_BUF_LEN);
1098  *tx_buf_len = (u16_t)base64_len + SMTP_CRLF_LEN;
1099
1100  SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
1101  s->tx_buf[*tx_buf_len] = 0;
1102  return SMTP_AUTH_LOGIN_PASS;
1103}
1104
1105/** Send base64-encoded password */
1106static enum smtp_session_state
1107smtp_prepare_auth_login_pass(struct smtp_session *s, u16_t *tx_buf_len)
1108{
1109  size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
1110    SMTP_PASS(s), strlen(SMTP_PASS(s)));
1111  /* @todo: support base64-encoded longer than 64k */
1112  LWIP_ASSERT("string too long", base64_len <= 0xffff);
1113  LWIP_ASSERT("tx_buf overflow detected", base64_len + SMTP_CRLF_LEN <= SMTP_TX_BUF_LEN);
1114  *tx_buf_len = (u16_t)base64_len + SMTP_CRLF_LEN;
1115
1116  SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
1117  s->tx_buf[*tx_buf_len] = 0;
1118  return SMTP_AUTH_LOGIN;
1119}
1120#endif /* SMTP_SUPPORT_AUTH_LOGIN */
1121
1122/** Prepare MAIL message */
1123static enum smtp_session_state
1124smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len)
1125{
1126  char *target = s->tx_buf;
1127  *tx_buf_len = SMTP_CMD_MAIL_1_LEN + SMTP_CMD_MAIL_2_LEN + s->from_len;
1128  LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
1129  target[*tx_buf_len] = 0;
1130
1131  SMEMCPY(target, SMTP_CMD_MAIL_1, SMTP_CMD_MAIL_1_LEN);
1132  target += SMTP_CMD_MAIL_1_LEN;
1133  MEMCPY(target, s->from, s->from_len);
1134  target += s->from_len;
1135  SMEMCPY(target, SMTP_CMD_MAIL_2, SMTP_CMD_MAIL_2_LEN);
1136  return SMTP_MAIL;
1137}
1138
1139/** Prepare RCPT message */
1140static enum smtp_session_state
1141smtp_prepare_rcpt(struct smtp_session *s, u16_t *tx_buf_len)
1142{
1143  char *target = s->tx_buf;
1144  *tx_buf_len = SMTP_CMD_RCPT_1_LEN + SMTP_CMD_RCPT_2_LEN + s->to_len;
1145  LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
1146  target[*tx_buf_len] = 0;
1147
1148  SMEMCPY(target, SMTP_CMD_RCPT_1, SMTP_CMD_RCPT_1_LEN);
1149  target += SMTP_CMD_RCPT_1_LEN;
1150  MEMCPY(target, s->to, s->to_len);
1151  target += s->to_len;
1152  SMEMCPY(target, SMTP_CMD_RCPT_2, SMTP_CMD_RCPT_2_LEN);
1153  return SMTP_RCPT;
1154}
1155
1156/** Prepare header of body */
1157static enum smtp_session_state
1158smtp_prepare_header(struct smtp_session *s, u16_t *tx_buf_len)
1159{
1160  char *target = s->tx_buf;
1161  *tx_buf_len = SMTP_CMD_HEADER_1_LEN + SMTP_CMD_HEADER_2_LEN +
1162    SMTP_CMD_HEADER_3_LEN + SMTP_CMD_HEADER_4_LEN + s->from_len + s->to_len +
1163    s->subject_len;
1164  LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
1165  target[*tx_buf_len] = 0;
1166
1167  SMEMCPY(target, SMTP_CMD_HEADER_1, SMTP_CMD_HEADER_1_LEN);
1168  target += SMTP_CMD_HEADER_1_LEN;
1169  MEMCPY(target, s->from, s->from_len);
1170  target += s->from_len;
1171  SMEMCPY(target, SMTP_CMD_HEADER_2, SMTP_CMD_HEADER_2_LEN);
1172  target += SMTP_CMD_HEADER_2_LEN;
1173  MEMCPY(target, s->to, s->to_len);
1174  target += s->to_len;
1175  SMEMCPY(target, SMTP_CMD_HEADER_3, SMTP_CMD_HEADER_3_LEN);
1176  target += SMTP_CMD_HEADER_3_LEN;
1177  MEMCPY(target, s->subject, s->subject_len);
1178  target += s->subject_len;
1179  SMEMCPY(target, SMTP_CMD_HEADER_4, SMTP_CMD_HEADER_4_LEN);
1180
1181  return SMTP_BODY;
1182}
1183
1184/** Prepare QUIT message */
1185static enum smtp_session_state
1186smtp_prepare_quit(struct smtp_session *s, u16_t *tx_buf_len)
1187{
1188  *tx_buf_len = SMTP_CMD_QUIT_LEN;
1189  s->tx_buf[*tx_buf_len] = 0;
1190  SMEMCPY(s->tx_buf, SMTP_CMD_QUIT, SMTP_CMD_QUIT_LEN);
1191  LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
1192  return SMTP_CLOSED;
1193}
1194
1195/** If in state SMTP_BODY, try to send more body data */
1196static void
1197smtp_send_body(struct smtp_session *s, struct tcp_pcb *pcb)
1198{
1199  err_t err;
1200
1201  if (s->state == SMTP_BODY) {
1202#if SMTP_BODYDH
1203    if (s->bodydh) {
1204      smtp_send_body_data_handler(s, pcb);
1205    } else
1206#endif /* SMTP_BODYDH */
1207    {
1208      u16_t send_len = s->body_len - s->body_sent;
1209      if (send_len > 0) {
1210        u16_t snd_buf = tcp_sndbuf(pcb);
1211        if (send_len > snd_buf) {
1212          send_len = snd_buf;
1213        }
1214        if (send_len > 0) {
1215          /* try to send something out */
1216          err = tcp_write(pcb, &s->body[s->body_sent], (u16_t)send_len, TCP_WRITE_FLAG_COPY);
1217          if (err == ERR_OK) {
1218            s->timer = SMTP_TIMEOUT_DATABLOCK;
1219            s->body_sent += send_len;
1220            if (s->body_sent < s->body_len) {
1221              LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: %d of %d bytes written\n",
1222                s->body_sent, s->body_len));
1223            }
1224          }
1225        }
1226      }
1227    }
1228    if (s->body_sent == s->body_len) {
1229      /* the whole body has been written, write last line */
1230      LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: body completely written (%d bytes), appending end-of-body\n",
1231        s->body_len));
1232      err = tcp_write(pcb, SMTP_CMD_BODY_FINISHED, SMTP_CMD_BODY_FINISHED_LEN, 0);
1233      if (err == ERR_OK) {
1234        s->timer = SMTP_TIMEOUT_DATATERM;
1235        LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: end-of-body written, changing state to %s\n",
1236          smtp_state_str[SMTP_QUIT]));
1237        /* last line written, change state, wait for confirmation */
1238        s->state = SMTP_QUIT;
1239      }
1240    }
1241  }
1242}
1243
1244/** State machine-like implementation of an SMTP client.
1245 */
1246static void
1247smtp_process(void *arg, struct tcp_pcb *pcb, struct pbuf *p)
1248{
1249  struct smtp_session* s = (struct smtp_session*)arg;
1250  u16_t response_code = 0;
1251  u16_t tx_buf_len = 0;
1252  enum smtp_session_state next_state;
1253
1254  if (arg == NULL) {
1255    /* already closed SMTP connection */
1256    if (p != NULL) {
1257      LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("Received %d bytes after closing: %s\n",
1258        p->tot_len, smtp_pbuf_str(p)));
1259      pbuf_free(p);
1260    }
1261    return;
1262  }
1263
1264  next_state = s->state;
1265
1266  if (p != NULL) {
1267    /* received data */
1268    if (s->p == NULL) {
1269      s->p = p;
1270    } else {
1271      pbuf_cat(s->p, p);
1272    }
1273  } else {
1274    /* idle timer, close connection if timed out */
1275    if (s->timer == 0) {
1276      LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process: connection timed out, closing\n"));
1277      smtp_close(s, pcb, SMTP_RESULT_ERR_TIMEOUT, 0, ERR_TIMEOUT);
1278      return;
1279    }
1280    if (s->state == SMTP_BODY) {
1281      smtp_send_body(s, pcb);
1282      return;
1283    }
1284  }
1285  response_code = smtp_is_response(s);
1286  if (response_code) {
1287    LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: received response code: %d\n", response_code));
1288    if (smtp_is_response_finished(s) != ERR_OK) {
1289      LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: partly received response code: %d\n", response_code));
1290      /* wait for next packet to complete the respone */
1291      return;
1292    }
1293  } else {
1294    if (s->p != NULL) {
1295      LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_process: unknown data received (%s)\n",
1296        smtp_pbuf_str(s->p)));
1297      pbuf_free(s->p);
1298      s->p = NULL;
1299    }
1300    return;
1301  }
1302
1303  switch(s->state)
1304  {
1305  case(SMTP_NULL):
1306    /* wait for 220 */
1307    if (response_code == 220) {
1308      /* then send EHLO */
1309      next_state = smtp_prepare_helo(s, &tx_buf_len, pcb);
1310    }
1311    break;
1312  case(SMTP_HELO):
1313    /* wait for 250 */
1314    if (response_code == 250) {
1315#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
1316      /* then send AUTH or MAIL */
1317      next_state = smtp_prepare_auth_or_mail(s, &tx_buf_len);
1318    }
1319    break;
1320  case(SMTP_AUTH_LOGIN):
1321  case(SMTP_AUTH_PLAIN):
1322    /* wait for 235 */
1323    if (response_code == 235) {
1324#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
1325      /* send MAIL */
1326      next_state = smtp_prepare_mail(s, &tx_buf_len);
1327    }
1328    break;
1329#if SMTP_SUPPORT_AUTH_LOGIN
1330  case(SMTP_AUTH_LOGIN_UNAME):
1331    /* wait for 334 Username */
1332    if (response_code == 334) {
1333      if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_UNAME) != 0xFFFF) {
1334        /* send username */
1335        next_state = smtp_prepare_auth_login_uname(s, &tx_buf_len);
1336      }
1337    }
1338    break;
1339  case(SMTP_AUTH_LOGIN_PASS):
1340    /* wait for 334 Password */
1341    if (response_code == 334) {
1342      if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_PASS) != 0xFFFF) {
1343        /* send username */
1344        next_state = smtp_prepare_auth_login_pass(s, &tx_buf_len);
1345      }
1346    }
1347    break;
1348#endif /* SMTP_SUPPORT_AUTH_LOGIN */
1349  case(SMTP_MAIL):
1350    /* wait for 250 */
1351    if (response_code == 250) {
1352      /* send RCPT */
1353      next_state = smtp_prepare_rcpt(s, &tx_buf_len);
1354    }
1355    break;
1356  case(SMTP_RCPT):
1357    /* wait for 250 */
1358    if (response_code == 250) {
1359      /* send DATA */
1360      SMEMCPY(s->tx_buf, SMTP_CMD_DATA, SMTP_CMD_DATA_LEN);
1361      tx_buf_len = SMTP_CMD_DATA_LEN;
1362      next_state = SMTP_DATA;
1363    }
1364    break;
1365  case(SMTP_DATA):
1366    /* wait for 354 */
1367    if (response_code == 354) {
1368      /* send email header */
1369      next_state = smtp_prepare_header(s, &tx_buf_len);
1370    }
1371    break;
1372  case(SMTP_BODY):
1373    /* nothing to be done here, handled somewhere else */
1374    break;
1375  case(SMTP_QUIT):
1376    /* wait for 250 */
1377    if (response_code == 250) {
1378      /* send QUIT */
1379      next_state = smtp_prepare_quit(s, &tx_buf_len);
1380    }
1381    break;
1382  case(SMTP_CLOSED):
1383    /* nothing to do, wait for connection closed from server */
1384    return;
1385  default:
1386    LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Invalid state: %d/%s\n", (int)s->state,
1387      smtp_state_str[s->state]));
1388    break;
1389  }
1390  if (s->state == next_state) {
1391    LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process[%s]: unexpected response_code, closing: %d (%s)\n",
1392      smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
1393    /* close connection */
1394    smtp_close(s, pcb, SMTP_RESULT_ERR_SVR_RESP, response_code, ERR_OK);
1395    return;
1396  }
1397  if (tx_buf_len > 0) {
1398    SMTP_TX_BUF_MAX(tx_buf_len);
1399    if (tcp_write(pcb, s->tx_buf, tx_buf_len, TCP_WRITE_FLAG_COPY) == ERR_OK) {
1400      LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: received command %d (%s)\n",
1401        smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
1402      LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: sent %"U16_F" bytes: \"%s\"\n",
1403        smtp_state_str[s->state], tx_buf_len, s->tx_buf));
1404      s->timer = SMTP_TIMEOUT;
1405      pbuf_free(s->p);
1406      s->p = NULL;
1407      LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_process: changing state from %s to %s\n",
1408        smtp_state_str[s->state], smtp_state_str[next_state]));
1409      s->state = next_state;
1410      if (next_state == SMTP_BODY) {
1411        /* try to stream-send body data right now */
1412        smtp_send_body(s, pcb);
1413      } else if (next_state == SMTP_CLOSED) {
1414        /* sent out all data, delete structure */
1415        tcp_arg(pcb, NULL);
1416        smtp_free(s, SMTP_RESULT_OK, 0, ERR_OK);
1417      }
1418    }
1419  }
1420}
1421
1422#if SMTP_BODYDH
1423/** Elementary sub-function to send data
1424 *
1425 * @returns: BDHALLDATASENT all data has been written
1426 *           BDHSOMEDATASENT some data has been written
1427 *           0 no data has been written
1428 */
1429static int
1430smtp_send_bodyh_data(struct tcp_pcb *pcb, char **from, u16_t *howmany)
1431{
1432  err_t err;
1433  u16_t len = *howmany;
1434
1435  len = (u16_t)LWIP_MIN(len, tcp_sndbuf(pcb));
1436  err = tcp_write(pcb, *from, len, TCP_WRITE_FLAG_COPY);
1437  if (err == ERR_OK) {
1438    *from += len;
1439    if ((*howmany -= len) > 0) {
1440      return BDHSOMEDATASENT;
1441    }
1442    return BDHALLDATASENT;
1443  }
1444  return 0;
1445}
1446
1447/** Same as smtp_send_mail_static, but uses a callback function to send body data
1448 */
1449err_t
1450smtp_send_mail_bodycback(const char *from, const char* to, const char* subject,
1451  smtp_bodycback_fn bodycback_fn, smtp_result_fn callback_fn, void* callback_arg)
1452{
1453  struct smtp_session* s;
1454  size_t len;
1455
1456  s = (struct smtp_session*)SMTP_STATE_MALLOC(sizeof(struct smtp_session));
1457  if (s == NULL) {
1458    return ERR_MEM;
1459  }
1460  memset(s, 0, sizeof(struct smtp_session));
1461  s->bodydh = (struct smtp_bodydh_state*)SMTP_BODYDH_MALLOC(sizeof(struct smtp_bodydh_state));
1462  if (s->bodydh == NULL) {
1463    return ERR_MEM;
1464  }
1465  memset(s->bodydh, 0, sizeof(struct smtp_bodydh));
1466  /* initialize the structure */
1467  s->from = from;
1468  len = strlen(from);
1469  LWIP_ASSERT("string is too long", len <= 0xffff);
1470  s->from_len = (u16_t)len;
1471  s->to = to;
1472  len = strlen(to);
1473  LWIP_ASSERT("string is too long", len <= 0xffff);
1474  s->to_len = (u16_t)len;
1475  s->subject = subject;
1476  len = strlen(subject);
1477  LWIP_ASSERT("string is too long", len <= 0xffff);
1478  s->subject_len = (u16_t)len;
1479  s->body = NULL;
1480  LWIP_ASSERT("string is too long", len <= 0xffff);
1481  s->callback_fn = callback_fn;
1482  s->callback_arg = callback_arg;
1483  s->bodydh->callback_fn = bodycback_fn;
1484  s->bodydh->state = BDH_SENDING;
1485  /* call the actual implementation of this function */
1486  return smtp_send_mail_alloced(s);
1487}
1488
1489static void
1490smtp_send_body_data_handler(struct smtp_session *s, struct tcp_pcb *pcb)
1491{
1492  struct smtp_bodydh_state *bdh = s->bodydh;
1493  int res = 0, ret;
1494  LWIP_ASSERT("s != NULL", s != NULL);
1495  LWIP_ASSERT("bodydh != NULL", bdh != NULL);
1496
1497  /* resume any leftovers from prior memory constraints */
1498  if (s->body_len) {
1499    LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: resume\n"));
1500    if((res = smtp_send_bodyh_data(pcb, (char **)&s->body, &s->body_len))
1501        != BDHALLDATASENT) {
1502      s->body_sent = s->body_len - 1;
1503      return;
1504    }
1505  }
1506  ret = res;
1507  /* all data on buffer has been queued, resume execution */
1508  if (bdh->state == BDH_SENDING) {
1509    LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: run\n"));
1510    do {
1511      ret |= res; /* remember if we once queued something to send */
1512      bdh->exposed.length = 0;
1513      if (bdh->callback_fn(s->callback_arg, &bdh->exposed) == BDH_DONE) {
1514        bdh->state = BDH_STOP;
1515      }
1516      s->body = bdh->exposed.buffer;
1517      s->body_len = bdh->exposed.length;
1518      LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: trying to send %u bytes\n", (unsigned int)s->body_len));
1519    } while (s->body_len &&
1520            ((res = smtp_send_bodyh_data(pcb, (char **)&s->body, &s->body_len)) == BDHALLDATASENT)
1521            && (bdh->state != BDH_STOP));
1522  }
1523  if ((bdh->state != BDH_SENDING) && (ret != BDHSOMEDATASENT)) {
1524    LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: stop\n"));
1525    s->body_sent = s->body_len;
1526  } else {
1527    LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: pause\n"));
1528    s->body_sent = s->body_len - 1;
1529  }
1530}
1531#endif /* SMTP_BODYDH */
1532
1533#endif /* LWIP_TCP */
1534