1 /**
2  * SMTP email client
3  *
4  * Adapted from the `ssl_mail_client` example in mbedtls.
5  *
6  * Original Copyright (C) 2006-2016, ARM Limited, All Rights Reserved, Apache 2.0 License.
7  * Additions Copyright (C) Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD, Apache 2.0 License.
8  *
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *     http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  */
22 
23 #include <string.h>
24 #include <stdlib.h>
25 #include "freertos/FreeRTOS.h"
26 #include "freertos/task.h"
27 #include "esp_event.h"
28 #include "esp_log.h"
29 #include "esp_system.h"
30 #include "nvs_flash.h"
31 #include "protocol_examples_common.h"
32 
33 #include "mbedtls/platform.h"
34 #include "mbedtls/net_sockets.h"
35 #include "mbedtls/esp_debug.h"
36 #include "mbedtls/ssl.h"
37 #include "mbedtls/entropy.h"
38 #include "mbedtls/ctr_drbg.h"
39 #include "mbedtls/error.h"
40 #include "mbedtls/certs.h"
41 #include <mbedtls/base64.h>
42 #include <sys/param.h>
43 
44 
45 /* Constants that are configurable in menuconfig */
46 #define MAIL_SERVER         CONFIG_SMTP_SERVER
47 #define MAIL_PORT           CONFIG_SMTP_PORT_NUMBER
48 #define SENDER_MAIL         CONFIG_SMTP_SENDER_MAIL
49 #define SENDER_PASSWORD     CONFIG_SMTP_SENDER_PASSWORD
50 #define RECIPIENT_MAIL      CONFIG_SMTP_RECIPIENT_MAIL
51 
52 #define SERVER_USES_STARTSSL 1
53 
54 static const char *TAG = "smtp_example";
55 
56 #define TASK_STACK_SIZE     (8 * 1024)
57 #define BUF_SIZE            512
58 
59 #define VALIDATE_MBEDTLS_RETURN(ret, min_valid_ret, max_valid_ret, goto_label)  \
60     do {                                                                        \
61         if (ret < min_valid_ret || ret > max_valid_ret) {                       \
62             goto goto_label;                                                    \
63         }                                                                       \
64     } while (0)                                                                 \
65 
66 /**
67  * Root cert for smtp.googlemail.com, taken from server_root_cert.pem
68  *
69  * The PEM file was extracted from the output of this command:
70  * openssl s_client -showcerts -connect smtp.googlemail.com:587 -starttls smtp
71  *
72  * The CA root cert is the last cert given in the chain of certs.
73  *
74  * To embed it in the app binary, the PEM file is named
75  * in the component.mk COMPONENT_EMBED_TXTFILES variable.
76  */
77 
78 extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
79 extern const uint8_t server_root_cert_pem_end[]   asm("_binary_server_root_cert_pem_end");
80 
81 extern const uint8_t esp_logo_png_start[] asm("_binary_esp_logo_png_start");
82 extern const uint8_t esp_logo_png_end[]   asm("_binary_esp_logo_png_end");
83 
write_and_get_response(mbedtls_net_context * sock_fd,unsigned char * buf,size_t len)84 static int write_and_get_response(mbedtls_net_context *sock_fd, unsigned char *buf, size_t len)
85 {
86     int ret;
87     const size_t DATA_SIZE = 128;
88     unsigned char data[DATA_SIZE];
89     char code[4];
90     size_t i, idx = 0;
91 
92     if (len) {
93         ESP_LOGD(TAG, "%s", buf);
94     }
95 
96     if (len && (ret = mbedtls_net_send(sock_fd, buf, len)) <= 0) {
97         ESP_LOGE(TAG, "mbedtls_net_send failed with error -0x%x", -ret);
98         return ret;
99     }
100 
101     do {
102         len = DATA_SIZE - 1;
103         ret = mbedtls_net_recv(sock_fd, data, len);
104 
105         if (ret <= 0) {
106             ESP_LOGE(TAG, "mbedtls_net_recv failed with error -0x%x", -ret);
107             goto exit;
108         }
109 
110         data[len] = '\0';
111         printf("\n%s", data);
112         len = ret;
113         for (i = 0; i < len; i++) {
114             if (data[i] != '\n') {
115                 if (idx < 4) {
116                     code[idx++] = data[i];
117                 }
118                 continue;
119             }
120 
121             if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') {
122                 code[3] = '\0';
123                 ret = atoi(code);
124                 goto exit;
125             }
126 
127             idx = 0;
128         }
129     } while (1);
130 
131 exit:
132     return ret;
133 }
134 
write_ssl_and_get_response(mbedtls_ssl_context * ssl,unsigned char * buf,size_t len)135 static int write_ssl_and_get_response(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
136 {
137     int ret;
138     const size_t DATA_SIZE = 128;
139     unsigned char data[DATA_SIZE];
140     char code[4];
141     size_t i, idx = 0;
142 
143     if (len) {
144         ESP_LOGD(TAG, "%s", buf);
145     }
146 
147     while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) {
148         if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
149             ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret);
150             goto exit;
151         }
152     }
153 
154     do {
155         len = DATA_SIZE - 1;
156         ret = mbedtls_ssl_read(ssl, data, len);
157 
158         if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
159             continue;
160         }
161 
162         if (ret <= 0) {
163             ESP_LOGE(TAG, "mbedtls_ssl_read failed with error -0x%x", -ret);
164             goto exit;
165         }
166 
167         ESP_LOGD(TAG, "%s", data);
168 
169         len = ret;
170         for (i = 0; i < len; i++) {
171             if (data[i] != '\n') {
172                 if (idx < 4) {
173                     code[idx++] = data[i];
174                 }
175                 continue;
176             }
177 
178             if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') {
179                 code[3] = '\0';
180                 ret = atoi(code);
181                 goto exit;
182             }
183 
184             idx = 0;
185         }
186     } while (1);
187 
188 exit:
189     return ret;
190 }
191 
write_ssl_data(mbedtls_ssl_context * ssl,unsigned char * buf,size_t len)192 static int write_ssl_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
193 {
194     int ret;
195 
196     if (len) {
197         ESP_LOGD(TAG, "%s", buf);
198     }
199 
200     while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) {
201         if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
202             ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret);
203             return ret;
204         }
205     }
206 
207     return 0;
208 }
209 
perform_tls_handshake(mbedtls_ssl_context * ssl)210 static int perform_tls_handshake(mbedtls_ssl_context *ssl)
211 {
212     int ret = -1;
213     uint32_t flags;
214     char *buf = NULL;
215     buf = (char *) calloc(1, BUF_SIZE);
216     if (buf == NULL) {
217         ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE);
218         goto exit;
219     }
220 
221     ESP_LOGI(TAG, "Performing the SSL/TLS handshake...");
222 
223     fflush(stdout);
224     while ((ret = mbedtls_ssl_handshake(ssl)) != 0) {
225         if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
226             ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
227             goto exit;
228         }
229     }
230 
231     ESP_LOGI(TAG, "Verifying peer X.509 certificate...");
232 
233     if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0) {
234         /* In real life, we probably want to close connection if ret != 0 */
235         ESP_LOGW(TAG, "Failed to verify peer certificate!");
236         mbedtls_x509_crt_verify_info(buf, BUF_SIZE, "  ! ", flags);
237         ESP_LOGW(TAG, "verification info: %s", buf);
238     } else {
239         ESP_LOGI(TAG, "Certificate verified.");
240     }
241 
242     ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(ssl));
243     ret = 0; /* No error */
244 
245 exit:
246     if (buf) {
247         free(buf);
248     }
249     return ret;
250 }
251 
smtp_client_task(void * pvParameters)252 static void smtp_client_task(void *pvParameters)
253 {
254     char *buf = NULL;
255     unsigned char base64_buffer[128];
256     int ret, len;
257     size_t base64_len;
258 
259     mbedtls_entropy_context entropy;
260     mbedtls_ctr_drbg_context ctr_drbg;
261     mbedtls_ssl_context ssl;
262     mbedtls_x509_crt cacert;
263     mbedtls_ssl_config conf;
264     mbedtls_net_context server_fd;
265 
266     mbedtls_ssl_init(&ssl);
267     mbedtls_x509_crt_init(&cacert);
268     mbedtls_ctr_drbg_init(&ctr_drbg);
269     ESP_LOGI(TAG, "Seeding the random number generator");
270 
271     mbedtls_ssl_config_init(&conf);
272 
273     mbedtls_entropy_init(&entropy);
274     if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
275                                      NULL, 0)) != 0) {
276         ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned -0x%x", -ret);
277         goto exit;
278     }
279 
280     ESP_LOGI(TAG, "Loading the CA root certificate...");
281 
282     ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start,
283                                  server_root_cert_pem_end - server_root_cert_pem_start);
284 
285     if (ret < 0) {
286         ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x", -ret);
287         goto exit;
288     }
289 
290     ESP_LOGI(TAG, "Setting hostname for TLS session...");
291 
292     /* Hostname set here should match CN in server certificate */
293     if ((ret = mbedtls_ssl_set_hostname(&ssl, MAIL_SERVER)) != 0) {
294         ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
295         goto exit;
296     }
297 
298     ESP_LOGI(TAG, "Setting up the SSL/TLS structure...");
299 
300     if ((ret = mbedtls_ssl_config_defaults(&conf,
301                                            MBEDTLS_SSL_IS_CLIENT,
302                                            MBEDTLS_SSL_TRANSPORT_STREAM,
303                                            MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
304         ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned -0x%x", -ret);
305         goto exit;
306     }
307 
308     mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
309     mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
310     mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
311 #ifdef CONFIG_MBEDTLS_DEBUG
312     mbedtls_esp_enable_debug_log(&conf, 4);
313 #endif
314 
315     if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) {
316         ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x", -ret);
317         goto exit;
318     }
319 
320     mbedtls_net_init(&server_fd);
321 
322     ESP_LOGI(TAG, "Connecting to %s:%s...", MAIL_SERVER, MAIL_PORT);
323 
324     if ((ret = mbedtls_net_connect(&server_fd, MAIL_SERVER,
325                                    MAIL_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) {
326         ESP_LOGE(TAG, "mbedtls_net_connect returned -0x%x", -ret);
327         goto exit;
328     }
329 
330     ESP_LOGI(TAG, "Connected.");
331 
332     mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
333 
334     buf = (char *) calloc(1, BUF_SIZE);
335     if (buf == NULL) {
336         ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE);
337         goto exit;
338     }
339 #if SERVER_USES_STARTSSL
340     /* Get response */
341     ret = write_and_get_response(&server_fd, (unsigned char *) buf, 0);
342     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
343 
344     ESP_LOGI(TAG, "Writing EHLO to server...");
345     len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32");
346     ret = write_and_get_response(&server_fd, (unsigned char *) buf, len);
347     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
348 
349     ESP_LOGI(TAG, "Writing STARTTLS to server...");
350     len = snprintf((char *) buf, BUF_SIZE, "STARTTLS\r\n");
351     ret = write_and_get_response(&server_fd, (unsigned char *) buf, len);
352     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
353 
354     ret = perform_tls_handshake(&ssl);
355     if (ret != 0) {
356         goto exit;
357     }
358 
359 #else /* SERVER_USES_STARTSSL */
360     ret = perform_tls_handshake(&ssl);
361     if (ret != 0) {
362         goto exit;
363     }
364 
365     /* Get response */
366     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, 0);
367     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
368     ESP_LOGI(TAG, "Writing EHLO to server...");
369 
370     len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32");
371     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
372     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
373 
374 #endif /* SERVER_USES_STARTSSL */
375 
376     /* Authentication */
377     ESP_LOGI(TAG, "Authentication...");
378 
379     ESP_LOGI(TAG, "Write AUTH LOGIN");
380     len = snprintf( (char *) buf, BUF_SIZE, "AUTH LOGIN\r\n" );
381     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
382     VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit);
383 
384     ESP_LOGI(TAG, "Write USER NAME");
385     ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
386                                 &base64_len, (unsigned char *) SENDER_MAIL, strlen(SENDER_MAIL));
387     if (ret != 0) {
388         ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
389         goto exit;
390     }
391     len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
392     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
393     VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit);
394 
395     ESP_LOGI(TAG, "Write PASSWORD");
396     ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
397                                 &base64_len, (unsigned char *) SENDER_PASSWORD, strlen(SENDER_PASSWORD));
398     if (ret != 0) {
399         ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
400         goto exit;
401     }
402     len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
403     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
404     VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit);
405 
406     /* Compose email */
407     ESP_LOGI(TAG, "Write MAIL FROM");
408     len = snprintf((char *) buf, BUF_SIZE, "MAIL FROM:<%s>\r\n", SENDER_MAIL);
409     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
410     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
411 
412     ESP_LOGI(TAG, "Write RCPT");
413     len = snprintf((char *) buf, BUF_SIZE, "RCPT TO:<%s>\r\n", RECIPIENT_MAIL);
414     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
415     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
416 
417     ESP_LOGI(TAG, "Write DATA");
418     len = snprintf((char *) buf, BUF_SIZE, "DATA\r\n");
419     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
420     VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit);
421 
422     ESP_LOGI(TAG, "Write Content");
423     /* We do not take action if message sending is partly failed. */
424     len = snprintf((char *) buf, BUF_SIZE,
425                    "From: %s\r\nSubject: mbed TLS Test mail\r\n"
426                    "To: %s\r\n"
427                    "MIME-Version: 1.0 (mime-construct 1.9)\n",
428                    "ESP32 SMTP Client", RECIPIENT_MAIL);
429 
430     /**
431      * Note: We are not validating return for some ssl_writes.
432      * If by chance, it's failed; at worst email will be incomplete!
433      */
434     ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
435 
436     /* Multipart boundary */
437     len = snprintf((char *) buf, BUF_SIZE,
438                    "Content-Type: multipart/mixed;boundary=XYZabcd1234\n"
439                    "--XYZabcd1234\n");
440     ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
441 
442     /* Text */
443     len = snprintf((char *) buf, BUF_SIZE,
444                    "Content-Type: text/plain\n"
445                    "This is a simple test mail from the SMTP client example.\r\n"
446                    "\r\n"
447                    "Enjoy!\n\n--XYZabcd1234\n");
448     ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
449 
450     /* Attachment */
451     len = snprintf((char *) buf, BUF_SIZE,
452                    "Content-Type: image/png;name=esp_logo.png\n"
453                    "Content-Transfer-Encoding: base64\n"
454                    "Content-Disposition:attachment;filename=\"esp_logo.png\"\r\n\n");
455     ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
456 
457     /* Image contents... */
458     const uint8_t *offset = esp_logo_png_start;
459     while (offset < esp_logo_png_end - 1) {
460         int read_bytes = MIN(((sizeof (base64_buffer) - 1) / 4) * 3, esp_logo_png_end - offset - 1);
461         ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
462                                     &base64_len, (unsigned char *) offset, read_bytes);
463         if (ret != 0) {
464             ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
465             goto exit;
466         }
467         offset += read_bytes;
468         len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
469         ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
470     }
471 
472     len = snprintf((char *) buf, BUF_SIZE, "\n--XYZabcd1234\n");
473     ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
474 
475     len = snprintf((char *) buf, BUF_SIZE, "\r\n.\r\n");
476     ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
477     VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
478     ESP_LOGI(TAG, "Email sent!");
479 
480     /* Close connection */
481     mbedtls_ssl_close_notify(&ssl);
482     ret = 0; /* No errors */
483 
484 exit:
485     mbedtls_ssl_session_reset(&ssl);
486     mbedtls_net_free(&server_fd);
487 
488     if (ret != 0) {
489         mbedtls_strerror(ret, buf, 100);
490         ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf);
491     }
492 
493     putchar('\n'); /* Just a new line */
494     if (buf) {
495         free(buf);
496     }
497     vTaskDelete(NULL);
498 }
499 
app_main(void)500 void app_main(void)
501 {
502     ESP_ERROR_CHECK(nvs_flash_init());
503     ESP_ERROR_CHECK(esp_netif_init());
504     ESP_ERROR_CHECK(esp_event_loop_create_default());
505 
506     /**
507      * This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
508      * Read "Establishing Wi-Fi or Ethernet Connection" section in
509      * examples/protocols/README.md for more information about this function.
510      */
511     ESP_ERROR_CHECK(example_connect());
512     xTaskCreate(&smtp_client_task, "smtp_client_task", TASK_STACK_SIZE, NULL, 5, NULL);
513 }
514