/* * This file is part of the MicroPython ESP32 project, https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo * * The MIT License (MIT) * * Copyright (c) 2018 LoBo (https://github.com/loboris) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "sdkconfig.h" #if defined(CONFIG_MICROPY_USE_CURL) #include "libs/curl_mail.h" #include #include #include #include #include #include #include #include "esp_log.h" #include "libs/espcurl.h" #include "py/mpthread.h" #define CRLF "\r\n" #define CRLFLENGTH 2 #define MIME_LINE_MAX_WIDTH 992 // Constants defining the email send state #define SEND_MAIL_STATE_INITIALIZE 0 #define SEND_MAIL_STATE_HEADER 1 #define SEND_MAIL_STATE_BODY 2 #define SEND_MAIL_STATE_BODY_DONE 3 #define SEND_MAIL_STATE_ATTACHMENT 4 #define SEND_MAIL_STATE_END 5 #define SEND_MAIL_STATE_DONE 6 static const char* default_mime_type = "text/plain"; static const char *MAIL_TAG = "[Curl_mail]"; static const char* MEMORY_ALLOCATION_ERROR = "Error allocating memory"; // ---- Base64 Encoding/Decoding Table --- static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //--------------- struct progress { double lastruntime; CURL *curl; }; //----------------------- struct email_attachment { char* filename; char* mimetype; FILE *handle; }; //------------------------ struct email_info_struct { int state; time_t timestamp; char *from; char *to; char *cc; char *bcc; char *subject; char *header; char *body; struct email_attachment* attachment[CURLMAIL_MAX_ATTACHMENTS]; int num_attach; char *buf; int buflen; char *boundary_body; char *boundary_attach; }; //----------------------------------------- static char* populate_boundary (char* data) { //replace all 0s in string with random digits char* p = data; while (*p) { if (*p == '0') *p = '0' + rand() % 10; p++; } return data; } //--------------------------------------------------------- char* append_to_string (char** in_str, const char* app_str) { //append a string to the end of an existing string int len = (*in_str ? strlen(*in_str) : 0); char *p = (char*)realloc(*in_str, len + strlen(app_str) + 1); if (p == NULL) { free(p); ESP_LOGE(MAIL_TAG, "%s", MEMORY_ALLOCATION_ERROR); return NULL; } *in_str = p; strcpy(*in_str + len, app_str); return *in_str; } //-------------------------------------------------------------- curl_mail curlmail_create(const char* from, const char* subject) { struct email_info_struct* mail_object; if ((mail_object = (struct email_info_struct*)malloc(sizeof(struct email_info_struct))) == NULL) { ESP_LOGE(MAIL_TAG, "%s", MEMORY_ALLOCATION_ERROR); return NULL; } mail_object->state = 0; mail_object->timestamp = time(NULL); mail_object->from = NULL; if (from) { append_to_string(&mail_object->from, "<"); append_to_string(&mail_object->from, from); append_to_string(&mail_object->from, ">"); } mail_object->to = NULL; mail_object->cc = NULL; mail_object->bcc = NULL; mail_object->subject = (subject ? strdup(subject) : NULL); mail_object->header = NULL; mail_object->body = NULL; for (int n=0; n < CURLMAIL_MAX_ATTACHMENTS; n++) { mail_object->attachment[n] = NULL; } mail_object->num_attach = 0; mail_object->buf = NULL; mail_object->buflen = 0; mail_object->boundary_body = NULL; mail_object->boundary_attach = NULL; srand(time(NULL)); return mail_object; } //------------------------------------------------------------------ static void curlmail_free_attachment(curl_mail mail_object, int idx) { if (mail_object->attachment[idx]) { if (mail_object->attachment[idx]->filename) { free(mail_object->attachment[idx]->filename); mail_object->attachment[idx]->filename = NULL; } if (mail_object->attachment[idx]->mimetype) { free(mail_object->attachment[idx]->mimetype); mail_object->attachment[idx]->mimetype = NULL; } free(mail_object->attachment[idx]); mail_object->attachment[idx] = NULL; } } //------------------------------------------ void curlmail_destroy(curl_mail mail_object) { if (mail_object->from) free(mail_object->from); if (mail_object->to) free(mail_object->to); if (mail_object->cc) free(mail_object->cc); if (mail_object->bcc) free(mail_object->bcc); if (mail_object->subject) free(mail_object->subject); if (mail_object->header) free(mail_object->header); if (mail_object->body) free(mail_object->body); if (mail_object->buf) free(mail_object->buf); if (mail_object->boundary_body) free(mail_object->boundary_body); if (mail_object->boundary_attach) free(mail_object->boundary_attach); for (int n=0; n < mail_object->num_attach; n++) { curlmail_free_attachment(mail_object, n); } free(mail_object); } //---------------------------------------------------------------- void curlmail_add_to(curl_mail mail_object, const char* mail_addr) { if ((mail_addr == NULL) || (*mail_addr == '\0')) return; if (mail_object->to) { append_to_string(&mail_object->to, ", <"); } else { append_to_string(&mail_object->to, "<"); } append_to_string(&mail_object->to, mail_addr); append_to_string(&mail_object->to, ">"); } //---------------------------------------------------------------- void curlmail_add_cc(curl_mail mail_object, const char* mail_addr) { if ((mail_addr == NULL) || (*mail_addr == '\0')) return; if (mail_object->cc) { append_to_string(&mail_object->cc, ", <"); } else { append_to_string(&mail_object->cc, "<"); } append_to_string(&mail_object->cc, mail_addr); append_to_string(&mail_object->cc, ">"); } //----------------------------------------------------------------- void curlmail_add_bcc(curl_mail mail_object, const char* mail_addr) { if ((mail_addr == NULL) || (*mail_addr == '\0')) return; if (mail_object->bcc) { append_to_string(&mail_object->bcc, ", <"); } else { append_to_string(&mail_object->bcc, "<"); } append_to_string(&mail_object->bcc, mail_addr); append_to_string(&mail_object->bcc, ">"); } //------------------------------------------------------------------- void curlmail_set_subject(curl_mail mail_object, const char* subject) { if (mail_object->subject) free(mail_object->subject); mail_object->subject = (subject ? strdup(subject) : NULL); } //--------------------------------------------------------------------- void curlmail_add_header(curl_mail mail_object, const char* headerline) { append_to_string(&mail_object->header, headerline); append_to_string(&mail_object->header, CRLF); } //--------------------------------------------------------------- void curlmail_set_body (curl_mail mail_object, const char* body) { if (mail_object->body) free(mail_object->body); mail_object->body = (body ? strdup(body) : NULL); } //---------------------------------------------------------------------------------------------- void curlmail_add_attachment_file(curl_mail mail_object, const char* path, const char* mimetype) { if (mail_object->num_attach >= CURLMAIL_MAX_ATTACHMENTS) return; mail_object->attachment[mail_object->num_attach] = (struct email_attachment*)malloc(sizeof(struct email_attachment)); if (mail_object->attachment[mail_object->num_attach]) { mail_object->attachment[mail_object->num_attach]->handle = NULL; mail_object->attachment[mail_object->num_attach]->filename = malloc(strlen(path)+1); if (mail_object->attachment[mail_object->num_attach]->filename == NULL) { free(mail_object->attachment[mail_object->num_attach]); return; } strcpy(mail_object->attachment[mail_object->num_attach]->filename, path); if (mimetype) { mail_object->attachment[mail_object->num_attach]->mimetype = malloc(strlen(mimetype)+1); if (mail_object->attachment[mail_object->num_attach]->mimetype == NULL) { free(mail_object->attachment[mail_object->num_attach]->filename); free(mail_object->attachment[mail_object->num_attach]); return; } strcpy(mail_object->attachment[mail_object->num_attach]->mimetype, mimetype); } else mail_object->attachment[mail_object->num_attach]->mimetype = NULL; mail_object->num_attach++; } } //------------------------------------------------- static const char *file_base_name(const char *path) { // get base filename const char* basename = path + strlen(path); while (basename != path) { basename--; if (*basename == '/') { basename++; break; } } return basename; } //-------------------------------------------------------------------------------- static size_t curlmail_get_data(void* ptr, size_t size, size_t nmemb, void* userp) { struct email_info_struct* mail_object = (struct email_info_struct*)userp; // Exit if no data is requested if (size * nmemb == 0) return 0; // Do some initialization on first run if (mail_object->state == SEND_MAIL_STATE_INITIALIZE) { if (mail_object->buf) free(mail_object->buf); mail_object->buf = NULL; mail_object->buflen = 0; if (mail_object->boundary_body) free(mail_object->boundary_body); mail_object->boundary_body = NULL; if (mail_object->boundary_attach) free(mail_object->boundary_attach); mail_object->boundary_attach = NULL; mail_object->state++; // -> SEND_MAIL_STATE_HEADER } // Check if partial data is pending while (mail_object->buflen == 0) { // ** No partial data is pending, process current part of mail //------------------------------------------------------------------------------- if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_HEADER) { // === HEADER, generate header part === char** p = &mail_object->buf; append_to_string(p, "User-Agent: MicroPython_ESP32_mail v1.0" CRLF); if (mail_object->timestamp != 0) { char timestamptext[32]; //format timestamp if (strftime(timestamptext, sizeof(timestamptext), "%a, %d %b %Y %H:%M:%S %z", localtime(&mail_object->timestamp))) { append_to_string(p, "Date: "); append_to_string(p, timestamptext); append_to_string(p, CRLF); } } if (mail_object->from && *mail_object->from) { append_to_string(p, "From: "); append_to_string(p, mail_object->from); append_to_string(p, CRLF); } if (mail_object->to && *mail_object->to) { append_to_string(p, "To: "); append_to_string(p, mail_object->to); append_to_string(p, CRLF); } if (mail_object->cc && *mail_object->cc) { append_to_string(p, "Cc: "); append_to_string(p, mail_object->cc); append_to_string(p, CRLF); } if (mail_object->bcc && *mail_object->bcc) { append_to_string(p, "Bcc: "); append_to_string(p, mail_object->bcc); append_to_string(p, CRLF); } if (mail_object->subject) { append_to_string(p, "Subject: "); append_to_string(p, mail_object->subject); append_to_string(p, CRLF); } if (mail_object->header) { append_to_string(p, mail_object->header); } if (mail_object->num_attach > 0) { append_to_string(p, "MIME-Version: 1.0" CRLF); mail_object->boundary_attach = populate_boundary(strdup("=PART_SEPARATOR_0000_0000_0000_0000_0000_0000")); append_to_string(p, "Content-Type: multipart/mixed; boundary=\""); append_to_string(p, mail_object->boundary_attach); append_to_string(p, "\"" CRLF CRLF "This is a multipart message in MIME format." CRLF CRLF "--"); append_to_string(p, mail_object->boundary_attach); append_to_string(p, CRLF); } if (mail_object->body) { mail_object->boundary_body = populate_boundary(strdup("=BODY_SEPARATOR_0000_0000_0000_0000_0000_0000")); append_to_string(p, "Content-Type: multipart/alternative; boundary=\""); append_to_string(p, mail_object->boundary_body); append_to_string(p, "\"" CRLF); } mail_object->buflen = (mail_object->buf ? strlen(mail_object->buf) : 0); mail_object->state++; // -> SEND_MAIL_STATE_BODY } //----------------------------------------------------------------------------- if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_BODY) { if (mail_object->body) { // === BODY === // generate body header if (mail_object->boundary_body) { mail_object->buf = append_to_string(&mail_object->buf, CRLF "--"); mail_object->buf = append_to_string(&mail_object->buf, mail_object->boundary_body); mail_object->buf = append_to_string(&mail_object->buf, CRLF); } mail_object->buf = append_to_string(&mail_object->buf, "Content-Type: "); mail_object->buf = append_to_string(&mail_object->buf, default_mime_type); mail_object->buf = append_to_string(&mail_object->buf, CRLF "Content-Transfer-Encoding: 8bit" CRLF "Content-Disposition: inline" CRLF CRLF); // append body mail_object->buf = append_to_string(&mail_object->buf, mail_object->body); mail_object->buflen = (mail_object->buf ? strlen(mail_object->buf) : 0); } mail_object->state++; // --> SEND_MAIL_STATE_BODY_DONE } //---------------------------------------------------------------------------------- if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_BODY_DONE) { if (mail_object->body) { // === BODY DONE === mail_object->buf = NULL; if (mail_object->boundary_body) { mail_object->buf = append_to_string(&mail_object->buf, CRLF "--"); mail_object->buf = append_to_string(&mail_object->buf, mail_object->boundary_body); mail_object->buf = append_to_string(&mail_object->buf, "--" CRLF); mail_object->buflen = strlen(mail_object->buf); free(mail_object->boundary_body); mail_object->boundary_body = NULL; } } mail_object->state++; // -> SEND_MAIL_STATE_ATTACHMENT } //----------------------------------------------------------------------------------- if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_ATTACHMENT) { // === ATTACHMENT === if ((mail_object->num_attach > 0) && (mail_object->attachment[mail_object->num_attach-1])) { if (!mail_object->attachment[mail_object->num_attach-1]->handle) { //open file to attach mail_object->attachment[mail_object->num_attach-1]->handle = fopen(mail_object->attachment[mail_object->num_attach-1]->filename, "rb"); if (mail_object->attachment[mail_object->num_attach-1]->handle) { //generate attachment header mail_object->buf = NULL; if (mail_object->boundary_attach) { mail_object->buf = append_to_string(&mail_object->buf, CRLF "--"); mail_object->buf = append_to_string(&mail_object->buf, mail_object->boundary_attach); mail_object->buf = append_to_string(&mail_object->buf, CRLF); } mail_object->buf = append_to_string(&mail_object->buf, "Content-Type: "); mail_object->buf = append_to_string(&mail_object->buf, (mail_object->attachment[mail_object->num_attach-1]->mimetype ? mail_object->attachment[mail_object->num_attach-1]->mimetype : "application/octet-stream")); mail_object->buf = append_to_string(&mail_object->buf, "; Name=\""); mail_object->buf = append_to_string(&mail_object->buf, (mail_object->attachment[mail_object->num_attach-1]->filename ? file_base_name(mail_object->attachment[mail_object->num_attach-1]->filename) : "ATTACHMENT")); mail_object->buf = append_to_string(&mail_object->buf, "\"" CRLF "Content-Disposition: attachment; filename=\""); mail_object->buf = append_to_string(&mail_object->buf, (mail_object->attachment[mail_object->num_attach-1]->filename ? file_base_name(mail_object->attachment[mail_object->num_attach-1]->filename) : "ATTACHMENT")); mail_object->buf = append_to_string(&mail_object->buf, "\"" CRLF "Content-Transfer-Encoding: base64" CRLF CRLF); mail_object->buflen = strlen(mail_object->buf); } } else { //generate next line of attachment data size_t n = 0, file_data = 0; int mimelinepos = 0; unsigned char igroup[3] = {0, 0, 0}; unsigned char ogroup[4]; mail_object->buflen = 0; if ((mail_object->buf = (char*)malloc(MIME_LINE_MAX_WIDTH + CRLFLENGTH + 1)) == NULL) { ESP_LOGE(MAIL_TAG, "%s", MEMORY_ALLOCATION_ERROR); n = 0; } else { while ((mimelinepos < MIME_LINE_MAX_WIDTH) && (n = fread(igroup, 1, 3, mail_object->attachment[mail_object->num_attach-1]->handle))) { // Encode data to base64 ogroup[0] = base64[igroup[0] >> 2]; ogroup[1] = base64[((igroup[0] & 3) << 4) | (igroup[1] >> 4)]; ogroup[2] = base64[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)]; ogroup[3] = base64[igroup[2] & 0x3F]; file_data += n; //pad with "=" characters if less than 3 characters were read if (n < 3) { ogroup[3] = '='; if (n < 2) ogroup[2] = '='; } memcpy(mail_object->buf + mimelinepos, ogroup, 4); mail_object->buflen += 4; mimelinepos += 4; } if (mimelinepos > 0) { memcpy(mail_object->buf + mimelinepos, CRLF, CRLFLENGTH); mail_object->buflen += CRLFLENGTH; } } if (n <= 0) { //end of file fclose(mail_object->attachment[mail_object->num_attach-1]->handle); mail_object->attachment[mail_object->num_attach-1]->handle = NULL; mail_object->state++; // -> SEND_MAIL_STATE_END } } } else mail_object->state = SEND_MAIL_STATE_DONE; } //---------------------------------------------------------------------------- if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_END) { if ((mail_object->num_attach > 0) && (mail_object->attachment[mail_object->num_attach-1])) { mail_object->buf = NULL; mail_object->buflen = 0; curlmail_free_attachment(mail_object, mail_object->num_attach-1); mail_object->num_attach--; if (mail_object->num_attach > 0) { // Send the next attachment mail_object->state--; // -> SEND_MAIL_STATE_ATTACHMENT } else { if (mail_object->boundary_attach) { // The last attachment was sent mail_object->buf = append_to_string(&mail_object->buf, CRLF "--"); mail_object->buf = append_to_string(&mail_object->buf, mail_object->boundary_attach); mail_object->buf = append_to_string(&mail_object->buf, "--" CRLF); mail_object->buflen = strlen(mail_object->buf); } free(mail_object->boundary_attach); mail_object->boundary_attach = NULL; mail_object->state++; // -> SEND_MAIL_STATE_DONE } } else mail_object->state = SEND_MAIL_STATE_DONE; } if (mail_object->buflen == 0 && mail_object->state == SEND_MAIL_STATE_DONE) { break; } } // while (mail_object->buflen == 0) // ----------------------------------------------- // Send any pending data from mail object's buffer // ----------------------------------------------- if (mail_object->buflen > 0) { int len = ((mail_object->buflen > (size * nmemb)) ? size * nmemb : mail_object->buflen); memcpy(ptr, mail_object->buf, len); if (len < mail_object->buflen) { // some data are still in buffer mail_object->buf = memmove(mail_object->buf, mail_object->buf + len, mail_object->buflen - len); mail_object->buflen -= len; } else { // Add data from buffer are sent, free the buffer free(mail_object->buf); mail_object->buf = NULL; mail_object->buflen = 0; } return len; } //if (mail_object->state != SEND_MAIL_STATE_DONE) // ** this should never be reached mail_object->state = 0; return 0; } //------------------------------------------------------------------------------------------------------ static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { struct progress *myp = (struct progress *)p; CURL *curl = myp->curl; double curtime = 0; curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime); /* under certain circumstances it may be desirable for certain functionality to only run every N seconds, in order to do this the transaction time can be used */ if ((curl_verbose) && ((curtime - myp->lastruntime) >= curl_progress)) { if (myp->lastruntime == 0) mp_printf(&mp_plat_print, "\n"); myp->lastruntime = curtime; mp_printf(&mp_plat_print, "TIME: %0.1f UP: %" CURL_FORMAT_CURL_OFF_T"\r", curtime, ulnow); } return 0; } //---------------------------------------------------------------------------------------------------------------------------------------------------------------- const char* curlmail_protocol_send(curl_mail mail_object, const char* smtpserver, unsigned int smtpport, int protocol, const char* username, const char* password) { // Curl based send mail CURL *curl; CURLcode result = CURLE_FAILED_INIT; struct progress prog; curl = curl_easy_init(); if (curl == NULL) return curl_easy_strerror(result); prog.lastruntime = 0; prog.curl = curl; struct curl_slist *recipients = NULL; //set destination URL char* addr; size_t len = strlen(smtpserver) + 14; if ((addr = (char*)malloc(len)) == NULL) { ESP_LOGE(MAIL_TAG, "Error allocating memory"); return MEMORY_ALLOCATION_ERROR; } snprintf(addr, len, "%s://%s:%u", (protocol == CURLMAIL_PROTOCOL_SMTPS ? "smtps" : "smtp"), smtpserver, smtpport); curl_easy_setopt(curl, CURLOPT_URL, addr); free(addr); // Try using Transport Layer Security (TLS), but continue anyway if it fails curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY); //don't fail if the TLS/SSL a certificate could not be verified //alternative: add the issuer certificate (or the host certificate if //the certificate is self-signed) to the set of certificates that are //known to libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); //set authentication credentials if provided if (username && *username) curl_easy_setopt(curl, CURLOPT_USERNAME, username); if (password) curl_easy_setopt(curl, CURLOPT_PASSWORD, password); //set from value for envelope reverse-path if (mail_object->from && *mail_object->from) curl_easy_setopt(curl, CURLOPT_MAIL_FROM, mail_object->from); //set recipient recipients = curl_slist_append(recipients, mail_object->to); if (mail_object->cc) recipients = curl_slist_append(recipients, mail_object->cc); if (mail_object->bcc) recipients = curl_slist_append(recipients, mail_object->bcc); curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); //set callback function for getting message body curl_easy_setopt(curl, CURLOPT_READFUNCTION, curlmail_get_data); curl_easy_setopt(curl, CURLOPT_READDATA, mail_object); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); //set CURLOPT_UPLOAD to 1 to not use VRFY and other unneeded commands //enable debugging if requested curl_easy_setopt(curl, CURLOPT_VERBOSE, curl_verbose); curl_easy_setopt(curl, CURLOPT_TIMEOUT, curl_timeout); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo); // pass the struct pointer into the xferinfo function, note that this is an alias to CURLOPT_PROGRESSDATA curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog); if (curl_progress) curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L); //send the message MP_THREAD_GIL_EXIT(); result = curl_easy_perform(curl); MP_THREAD_GIL_ENTER(); if (curl_verbose) mp_printf(&mp_plat_print, "\n"); //free the list of recipients and clean up curl_slist_free_all(recipients); curl_easy_cleanup(curl); return (result == CURLE_OK ? NULL : curl_easy_strerror(result)); } #endif