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
173 enum 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 */
180 enum 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 */
197 static 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
212 static 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
225 struct 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 */
233 struct 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 */
279 static char smtp_server[SMTP_MAX_SERVERNAME_LEN + 1];
280 /** TCP port of the server to use for next SMTP request */
281 static 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 */
284 static struct altcp_tls_config *smtp_server_tls_config;
285 #endif
286 /** Username to use for the next SMTP request */
287 static char *smtp_username;
288 /** Password to use for the next SMTP request */
289 static char *smtp_pass;
290 /** Username and password combined as necessary for PLAIN authentication */
291 static 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) */
293 static size_t smtp_auth_plain_len;
294
295 #if SMTP_CHECK_DATA
296 static err_t smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed);
297 #endif /* SMTP_CHECK_DATA */
298 static err_t smtp_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err);
299 static void smtp_tcp_err(void *arg, err_t err);
300 static err_t smtp_tcp_poll(void *arg, struct altcp_pcb *pcb);
301 static err_t smtp_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len);
302 static err_t smtp_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err);
303 #if LWIP_DNS
304 static 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
307 static 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 */
309 static enum smtp_session_state smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len);
310 static void smtp_send_body(struct smtp_session *s, struct altcp_pcb *pcb);
311 static void smtp_process(void *arg, struct altcp_pcb *pcb, struct pbuf *p);
312 #if SMTP_BODYDH
313 static 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 */
319 const char*
smtp_result_str(u8_t smtp_result)320 smtp_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 */
332 static const char*
smtp_pbuf_str(struct pbuf * p)333 smtp_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 */
348 err_t
smtp_set_server_addr(const char * server)349 smtp_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 */
374 void
smtp_set_server_port(u16_t port)375 smtp_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 */
387 void
smtp_set_tls_config(struct altcp_tls_config * tls_config)388 smtp_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 */
401 err_t
smtp_set_auth(const char * username,const char * pass)402 smtp_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
smtp_free_struct(struct smtp_session * s)445 static 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
456 static struct altcp_pcb*
smtp_setup_pcb(struct smtp_session * s,const ip_addr_t * remote_ip)457 smtp_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 */
483 static err_t
smtp_send_mail_alloced(struct smtp_session * s)484 smtp_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
560 deallocate_and_leave:
561 if (pcb != NULL) {
562 altcp_arg(pcb, NULL);
563 altcp_close(pcb);
564 }
565 leave:
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 occurred starting the connection
581 * (this does not mean the email has been successfully sent!)
582 * - another err_t on error.
583 */
584 err_t
smtp_send_mail(const char * from,const char * to,const char * subject,const char * body,smtp_result_fn callback_fn,void * callback_arg)585 smtp_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 */
639 err_t
smtp_send_mail_static(const char * from,const char * to,const char * subject,const char * body,smtp_result_fn callback_fn,void * callback_arg)640 smtp_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 */
694 void
smtp_send_mail_int(void * arg)695 smtp_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 */
720 static err_t
smtp_verify(const char * data,size_t data_len,u8_t linebreaks_allowed)721 smtp_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 */
756 static void
smtp_free(struct smtp_session * s,u8_t result,u16_t srv_err,err_t err)757 smtp_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 */
771 static void
smtp_close(struct smtp_session * s,struct altcp_pcb * pcb,u8_t result,u16_t srv_err,err_t err)772 smtp_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 */
793 static void
smtp_tcp_err(void * arg,err_t err)794 smtp_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 */
804 static err_t
smtp_tcp_poll(void * arg,struct altcp_pcb * pcb)805 smtp_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 */
818 static err_t
smtp_tcp_sent(void * arg,struct altcp_pcb * pcb,u16_t len)819 smtp_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 */
829 static err_t
smtp_tcp_recv(void * arg,struct altcp_pcb * pcb,struct pbuf * p,err_t err)830 smtp_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
843 static err_t
smtp_tcp_connected(void * arg,struct altcp_pcb * pcb,err_t err)844 smtp_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 */
862 static void
smtp_dns_found(const char * hostname,const ip_addr_t * ipaddr,void * arg)863 smtp_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 */
901 static 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 */
913 static size_t
smtp_base64_encode(char * target,size_t target_len,const char * source,size_t source_len)914 smtp_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 */
954 static u16_t
smtp_is_response(struct smtp_session * s)955 smtp_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 */
986 static err_t
smtp_is_response_finished(struct smtp_session * s)987 smtp_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;
997 again:
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 */
1030 static enum smtp_session_state
smtp_prepare_helo(struct smtp_session * s,u16_t * tx_buf_len,struct altcp_pcb * pcb)1031 smtp_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 */
1053 static enum smtp_session_state
smtp_prepare_auth_or_mail(struct smtp_session * s,u16_t * tx_buf_len)1054 smtp_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 */
1107 static enum smtp_session_state
smtp_prepare_auth_login_uname(struct smtp_session * s,u16_t * tx_buf_len)1108 smtp_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 */
1123 static enum smtp_session_state
smtp_prepare_auth_login_pass(struct smtp_session * s,u16_t * tx_buf_len)1124 smtp_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 */
1140 static enum smtp_session_state
smtp_prepare_mail(struct smtp_session * s,u16_t * tx_buf_len)1141 smtp_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 */
1157 static enum smtp_session_state
smtp_prepare_rcpt(struct smtp_session * s,u16_t * tx_buf_len)1158 smtp_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 */
1174 static enum smtp_session_state
smtp_prepare_header(struct smtp_session * s,u16_t * tx_buf_len)1175 smtp_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 */
1203 static enum smtp_session_state
smtp_prepare_quit(struct smtp_session * s,u16_t * tx_buf_len)1204 smtp_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 */
1214 static void
smtp_send_body(struct smtp_session * s,struct altcp_pcb * pcb)1215 smtp_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 */
1264 static void
smtp_process(void * arg,struct altcp_pcb * pcb,struct pbuf * p)1265 smtp_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 */
1447 static int
smtp_send_bodyh_data(struct altcp_pcb * pcb,const char ** from,u16_t * howmany)1448 smtp_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 */
1467 err_t
smtp_send_mail_bodycback(const char * from,const char * to,const char * subject,smtp_bodycback_fn bodycback_fn,smtp_result_fn callback_fn,void * callback_arg)1468 smtp_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 s->callback_fn = callback_fn;
1502 s->callback_arg = callback_arg;
1503 s->bodydh->callback_fn = bodycback_fn;
1504 s->bodydh->state = BDH_SENDING;
1505 /* call the actual implementation of this function */
1506 return smtp_send_mail_alloced(s);
1507 }
1508
1509 static void
smtp_send_body_data_handler(struct smtp_session * s,struct altcp_pcb * pcb)1510 smtp_send_body_data_handler(struct smtp_session *s, struct altcp_pcb *pcb)
1511 {
1512 struct smtp_bodydh_state *bdh;
1513 int res = 0, ret;
1514 LWIP_ASSERT("s != NULL", s != NULL);
1515 bdh = s->bodydh;
1516 LWIP_ASSERT("bodydh != NULL", bdh != NULL);
1517
1518 /* resume any leftovers from prior memory constraints */
1519 if (s->body_len) {
1520 LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: resume\n"));
1521 if((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len))
1522 != BDHALLDATASENT) {
1523 s->body_sent = s->body_len - 1;
1524 return;
1525 }
1526 }
1527 ret = res;
1528 /* all data on buffer has been queued, resume execution */
1529 if (bdh->state == BDH_SENDING) {
1530 LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: run\n"));
1531 do {
1532 ret |= res; /* remember if we once queued something to send */
1533 bdh->exposed.length = 0;
1534 if (bdh->callback_fn(s->callback_arg, &bdh->exposed) == BDH_DONE) {
1535 bdh->state = BDH_STOP;
1536 }
1537 s->body = bdh->exposed.buffer;
1538 s->body_len = bdh->exposed.length;
1539 LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: trying to send %u bytes\n", (unsigned int)s->body_len));
1540 } while (s->body_len &&
1541 ((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len)) == BDHALLDATASENT)
1542 && (bdh->state != BDH_STOP));
1543 }
1544 if ((bdh->state != BDH_SENDING) && (ret != BDHSOMEDATASENT)) {
1545 LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: stop\n"));
1546 s->body_sent = s->body_len;
1547 } else {
1548 LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: pause\n"));
1549 s->body_sent = s->body_len - 1;
1550 }
1551 }
1552 #endif /* SMTP_BODYDH */
1553
1554 #endif /* LWIP_TCP && LWIP_CALLBACK_API */
1555