esp-idf/examples/protocols/smtp_client/main/smtp_client_example_main.c
Aditya Patwardhan 45122533e0 mbedtls-3 update:
1) Fix build issue in mbedtls
2) skip the public headers check in IDF
3)Update Kconfig Macros
4)Remove deprecated config options
5) Update the sha API according to new nomenclature
6) Update mbedtls_rsa_init usage
7) Include mbedtls/build_info.h instead of mbedtls/config.h
8) Dont include check_config.h
9) Add additional error message in esp_blufi_api.h
2022-03-03 01:37:10 +05:30

513 lines
17 KiB
C

/**
* SMTP email client
*
* Adapted from the `ssl_mail_client` example in mbedtls.
*
* Original Copyright (C) 2006-2016, ARM Limited, All Rights Reserved, Apache 2.0 License.
* Additions Copyright (C) Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD, Apache 2.0 License.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/esp_debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include <mbedtls/base64.h>
#include <sys/param.h>
/* Constants that are configurable in menuconfig */
#define MAIL_SERVER CONFIG_SMTP_SERVER
#define MAIL_PORT CONFIG_SMTP_PORT_NUMBER
#define SENDER_MAIL CONFIG_SMTP_SENDER_MAIL
#define SENDER_PASSWORD CONFIG_SMTP_SENDER_PASSWORD
#define RECIPIENT_MAIL CONFIG_SMTP_RECIPIENT_MAIL
#define SERVER_USES_STARTSSL 1
static const char *TAG = "smtp_example";
#define TASK_STACK_SIZE (8 * 1024)
#define BUF_SIZE 512
#define VALIDATE_MBEDTLS_RETURN(ret, min_valid_ret, max_valid_ret, goto_label) \
do { \
if (ret < min_valid_ret || ret > max_valid_ret) { \
goto goto_label; \
} \
} while (0) \
/**
* Root cert for smtp.googlemail.com, taken from server_root_cert.pem
*
* The PEM file was extracted from the output of this command:
* openssl s_client -showcerts -connect smtp.googlemail.com:587 -starttls smtp
*
* The CA root cert is the last cert given in the chain of certs.
*
* To embed it in the app binary, the PEM file is named
* in the component.mk COMPONENT_EMBED_TXTFILES variable.
*/
extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end");
extern const uint8_t esp_logo_png_start[] asm("_binary_esp_logo_png_start");
extern const uint8_t esp_logo_png_end[] asm("_binary_esp_logo_png_end");
static int write_and_get_response(mbedtls_net_context *sock_fd, unsigned char *buf, size_t len)
{
int ret;
const size_t DATA_SIZE = 128;
unsigned char data[DATA_SIZE];
char code[4];
size_t i, idx = 0;
if (len) {
ESP_LOGD(TAG, "%s", buf);
}
if (len && (ret = mbedtls_net_send(sock_fd, buf, len)) <= 0) {
ESP_LOGE(TAG, "mbedtls_net_send failed with error -0x%x", -ret);
return ret;
}
do {
len = DATA_SIZE - 1;
ret = mbedtls_net_recv(sock_fd, data, len);
if (ret <= 0) {
ESP_LOGE(TAG, "mbedtls_net_recv failed with error -0x%x", -ret);
goto exit;
}
data[len] = '\0';
printf("\n%s", data);
len = ret;
for (i = 0; i < len; i++) {
if (data[i] != '\n') {
if (idx < 4) {
code[idx++] = data[i];
}
continue;
}
if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') {
code[3] = '\0';
ret = atoi(code);
goto exit;
}
idx = 0;
}
} while (1);
exit:
return ret;
}
static int write_ssl_and_get_response(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
{
int ret;
const size_t DATA_SIZE = 128;
unsigned char data[DATA_SIZE];
char code[4];
size_t i, idx = 0;
if (len) {
ESP_LOGD(TAG, "%s", buf);
}
while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret);
goto exit;
}
}
do {
len = DATA_SIZE - 1;
ret = mbedtls_ssl_read(ssl, data, len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
continue;
}
if (ret <= 0) {
ESP_LOGE(TAG, "mbedtls_ssl_read failed with error -0x%x", -ret);
goto exit;
}
ESP_LOGD(TAG, "%s", data);
len = ret;
for (i = 0; i < len; i++) {
if (data[i] != '\n') {
if (idx < 4) {
code[idx++] = data[i];
}
continue;
}
if (idx == 4 && code[0] >= '0' && code[0] <= '9' && code[3] == ' ') {
code[3] = '\0';
ret = atoi(code);
goto exit;
}
idx = 0;
}
} while (1);
exit:
return ret;
}
static int write_ssl_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
{
int ret;
if (len) {
ESP_LOGD(TAG, "%s", buf);
}
while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
ESP_LOGE(TAG, "mbedtls_ssl_write failed with error -0x%x", -ret);
return ret;
}
}
return 0;
}
static int perform_tls_handshake(mbedtls_ssl_context *ssl)
{
int ret = -1;
uint32_t flags;
char *buf = NULL;
buf = (char *) calloc(1, BUF_SIZE);
if (buf == NULL) {
ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE);
goto exit;
}
ESP_LOGI(TAG, "Performing the SSL/TLS handshake...");
fflush(stdout);
while ((ret = mbedtls_ssl_handshake(ssl)) != 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
goto exit;
}
}
ESP_LOGI(TAG, "Verifying peer X.509 certificate...");
if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0) {
/* In real life, we probably want to close connection if ret != 0 */
ESP_LOGW(TAG, "Failed to verify peer certificate!");
mbedtls_x509_crt_verify_info(buf, BUF_SIZE, " ! ", flags);
ESP_LOGW(TAG, "verification info: %s", buf);
} else {
ESP_LOGI(TAG, "Certificate verified.");
}
ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(ssl));
ret = 0; /* No error */
exit:
if (buf) {
free(buf);
}
return ret;
}
static void smtp_client_task(void *pvParameters)
{
char *buf = NULL;
unsigned char base64_buffer[128];
int ret, len;
size_t base64_len;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context ssl;
mbedtls_x509_crt cacert;
mbedtls_ssl_config conf;
mbedtls_net_context server_fd;
mbedtls_ssl_init(&ssl);
mbedtls_x509_crt_init(&cacert);
mbedtls_ctr_drbg_init(&ctr_drbg);
ESP_LOGI(TAG, "Seeding the random number generator");
mbedtls_ssl_config_init(&conf);
mbedtls_entropy_init(&entropy);
if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
NULL, 0)) != 0) {
ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned -0x%x", -ret);
goto exit;
}
ESP_LOGI(TAG, "Loading the CA root certificate...");
ret = mbedtls_x509_crt_parse(&cacert, server_root_cert_pem_start,
server_root_cert_pem_end - server_root_cert_pem_start);
if (ret < 0) {
ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x", -ret);
goto exit;
}
ESP_LOGI(TAG, "Setting hostname for TLS session...");
/* Hostname set here should match CN in server certificate */
if ((ret = mbedtls_ssl_set_hostname(&ssl, MAIL_SERVER)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
goto exit;
}
ESP_LOGI(TAG, "Setting up the SSL/TLS structure...");
if ((ret = mbedtls_ssl_config_defaults(&conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned -0x%x", -ret);
goto exit;
}
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
#ifdef CONFIG_MBEDTLS_DEBUG
mbedtls_esp_enable_debug_log(&conf, 4);
#endif
if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) {
ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x", -ret);
goto exit;
}
mbedtls_net_init(&server_fd);
ESP_LOGI(TAG, "Connecting to %s:%s...", MAIL_SERVER, MAIL_PORT);
if ((ret = mbedtls_net_connect(&server_fd, MAIL_SERVER,
MAIL_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) {
ESP_LOGE(TAG, "mbedtls_net_connect returned -0x%x", -ret);
goto exit;
}
ESP_LOGI(TAG, "Connected.");
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
buf = (char *) calloc(1, BUF_SIZE);
if (buf == NULL) {
ESP_LOGE(TAG, "calloc failed for size %d", BUF_SIZE);
goto exit;
}
#if SERVER_USES_STARTSSL
/* Get response */
ret = write_and_get_response(&server_fd, (unsigned char *) buf, 0);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Writing EHLO to server...");
len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32");
ret = write_and_get_response(&server_fd, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Writing STARTTLS to server...");
len = snprintf((char *) buf, BUF_SIZE, "STARTTLS\r\n");
ret = write_and_get_response(&server_fd, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ret = perform_tls_handshake(&ssl);
if (ret != 0) {
goto exit;
}
#else /* SERVER_USES_STARTSSL */
ret = perform_tls_handshake(&ssl);
if (ret != 0) {
goto exit;
}
/* Get response */
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, 0);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Writing EHLO to server...");
len = snprintf((char *) buf, BUF_SIZE, "EHLO %s\r\n", "ESP32");
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
#endif /* SERVER_USES_STARTSSL */
/* Authentication */
ESP_LOGI(TAG, "Authentication...");
ESP_LOGI(TAG, "Write AUTH LOGIN");
len = snprintf( (char *) buf, BUF_SIZE, "AUTH LOGIN\r\n" );
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit);
ESP_LOGI(TAG, "Write USER NAME");
ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
&base64_len, (unsigned char *) SENDER_MAIL, strlen(SENDER_MAIL));
if (ret != 0) {
ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
goto exit;
}
len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit);
ESP_LOGI(TAG, "Write PASSWORD");
ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
&base64_len, (unsigned char *) SENDER_PASSWORD, strlen(SENDER_PASSWORD));
if (ret != 0) {
ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
goto exit;
}
len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 399, exit);
/* Compose email */
ESP_LOGI(TAG, "Write MAIL FROM");
len = snprintf((char *) buf, BUF_SIZE, "MAIL FROM:<%s>\r\n", SENDER_MAIL);
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Write RCPT");
len = snprintf((char *) buf, BUF_SIZE, "RCPT TO:<%s>\r\n", RECIPIENT_MAIL);
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Write DATA");
len = snprintf((char *) buf, BUF_SIZE, "DATA\r\n");
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 300, 399, exit);
ESP_LOGI(TAG, "Write Content");
/* We do not take action if message sending is partly failed. */
len = snprintf((char *) buf, BUF_SIZE,
"From: %s\r\nSubject: mbed TLS Test mail\r\n"
"To: %s\r\n"
"MIME-Version: 1.0 (mime-construct 1.9)\n",
"ESP32 SMTP Client", RECIPIENT_MAIL);
/**
* Note: We are not validating return for some ssl_writes.
* If by chance, it's failed; at worst email will be incomplete!
*/
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
/* Multipart boundary */
len = snprintf((char *) buf, BUF_SIZE,
"Content-Type: multipart/mixed;boundary=XYZabcd1234\n"
"--XYZabcd1234\n");
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
/* Text */
len = snprintf((char *) buf, BUF_SIZE,
"Content-Type: text/plain\n"
"This is a simple test mail from the SMTP client example.\r\n"
"\r\n"
"Enjoy!\n\n--XYZabcd1234\n");
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
/* Attachment */
len = snprintf((char *) buf, BUF_SIZE,
"Content-Type: image/png;name=esp_logo.png\n"
"Content-Transfer-Encoding: base64\n"
"Content-Disposition:attachment;filename=\"esp_logo.png\"\r\n\n");
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
/* Image contents... */
const uint8_t *offset = esp_logo_png_start;
while (offset < esp_logo_png_end - 1) {
int read_bytes = MIN(((sizeof (base64_buffer) - 1) / 4) * 3, esp_logo_png_end - offset - 1);
ret = mbedtls_base64_encode((unsigned char *) base64_buffer, sizeof(base64_buffer),
&base64_len, (unsigned char *) offset, read_bytes);
if (ret != 0) {
ESP_LOGE(TAG, "Error in mbedtls encode! ret = -0x%x", -ret);
goto exit;
}
offset += read_bytes;
len = snprintf((char *) buf, BUF_SIZE, "%s\r\n", base64_buffer);
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
}
len = snprintf((char *) buf, BUF_SIZE, "\n--XYZabcd1234\n");
ret = write_ssl_data(&ssl, (unsigned char *) buf, len);
len = snprintf((char *) buf, BUF_SIZE, "\r\n.\r\n");
ret = write_ssl_and_get_response(&ssl, (unsigned char *) buf, len);
VALIDATE_MBEDTLS_RETURN(ret, 200, 299, exit);
ESP_LOGI(TAG, "Email sent!");
/* Close connection */
mbedtls_ssl_close_notify(&ssl);
ret = 0; /* No errors */
exit:
mbedtls_ssl_session_reset(&ssl);
mbedtls_net_free(&server_fd);
if (ret != 0) {
mbedtls_strerror(ret, buf, 100);
ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf);
}
putchar('\n'); /* Just a new line */
if (buf) {
free(buf);
}
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/**
* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
xTaskCreate(&smtp_client_task, "smtp_client_task", TASK_STACK_SIZE, NULL, 5, NULL);
}