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