From d9eec48d7c6d5d1cc44a8b7222de847ac87e4c62 Mon Sep 17 00:00:00 2001 From: Tina_Azure <-> Date: Thu, 25 May 2023 19:51:13 +0200 Subject: [PATCH 1/3] WIP:base implementation of replacement mail manager based on libcurl --- src/smtpManager.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/smtpManager.cpp diff --git a/src/smtpManager.cpp b/src/smtpManager.cpp new file mode 100644 index 0000000..a70eab9 --- /dev/null +++ b/src/smtpManager.cpp @@ -0,0 +1,157 @@ +#ifndef SMTP_MANAGER_CPP +#define SMTP_MANAGER_CPP + +#include +#include +#include +#include +#include + +#include "utilities.cpp" + + + +namespace SMTPManager { + static const std::string END_OF_LINE = "\r\n"; + static const std::string END_OF_MAIL = "\0"; + + struct payload { + int lines_read = 0; + std::vector payload_data; + }; + + /* + * Generates the Date Time Header Segment + * Date: {day}/{month}/{year} {hour}:{minute}:{second} {delta to UTC} \r\n + */ + std::string generateEmailSegmentDateTime() { + std::time_t time = std::time(nullptr); + std::tm localTime = *std::localtime(&time); + std::ostringstream oss; + oss << "Date: " << std::put_time(&localTime, "%d/%m/%Y %H:%M:%S %z") << END_OF_LINE; + return oss.str(); + } + + /* + * Generates the Recipient Header Segment + * To: {Recipient Name} <{email address}>\r\n + */ + std::string generateEmailSegmentRecipient(const std::string &recipientName, const std::string &recipientEmail) { + return "To: " + recipientName + " <" + recipientEmail + ">" + END_OF_LINE; + } + + /* + * Generates the Sender Header Segment + * From: {Sender Name} <{email address}>\r\n + */ + std::string generateEmailSegmentSender(const Utilities::config &configuration) { + return "From: " + configuration.emailAddressDisplay + " <" + configuration.emailAddress + ">" + END_OF_LINE; + } + + /* + * Generates the Subject Header Segment + * Subject: {subject}\r\n + */ + std::string generateEmailSegmentSubject(const std::string &emailSubject) { + return "Subject: " + emailSubject + END_OF_LINE; + } + + /* + * Generates the Content-Type Header Segment + */ + std::string generateEmailSegmentContentType() { + return "Content-Type: text/html; charset=UTF-8" + END_OF_LINE; + } + + + /* + * Generates a line for the body + * {bodyLine}\r\n + */ + std::string generateEmailSegmentBody(const std::string &body) { + return body + END_OF_LINE; + } + + void fillEmailPayloadData(struct payload &payload, const Utilities::config &configuration, const std::string &recipientEmail, const std::string &recipientName, const std::string &emailSubject, const std::string &body) { + payload.payload_data.emplace_back(generateEmailSegmentDateTime()); + payload.payload_data.emplace_back(generateEmailSegmentRecipient(recipientName, recipientEmail)); + payload.payload_data.emplace_back(generateEmailSegmentSender(configuration)); + payload.payload_data.emplace_back(generateEmailSegmentSubject(emailSubject)); + payload.payload_data.emplace_back(generateEmailSegmentContentType()); + payload.payload_data.emplace_back(END_OF_LINE); + payload.payload_data.emplace_back(generateEmailSegmentBody(body)); + payload.payload_data.emplace_back(END_OF_MAIL); + } + + /* + * curl read callback function + */ + size_t payload_source(void *ptr, size_t size, size_t nmemb, void *userp) + { + auto* payload_context = (struct payload*)userp; + const char *data; + + if((size == 0) || (nmemb == 0) || ((size*nmemb) < 1)) { + return 0; + } + + data = payload_context->payload_data[payload_context->lines_read].c_str(); + + if(data) { + size_t len = strlen(data); + memcpy(ptr, data, len); + payload_context->lines_read++; + return len; + } + return 0; + } + + /* + * Generates the SMTP URL + * "smtp://{address}:{port}" + */ + std::string generateMailServerURL(const Utilities::config &configuration) { + return "smtp://" + configuration.emailServerAddress + ":" + std::to_string(configuration.emailServerPort); + } + + /* + * todo:for some inexplicable reason the sent email contains an empty CC: field which needs some examination + * todo:validate header https://mailheader.org/show.cgi + * Sends an HTML Email using curl + * returns CURL Error Codes https://curl.se/libcurl/c/libcurl-errors.html + * 1 = OK + */ + int sendEmailNew(const Utilities::config &configuration, const std::string &recipientEmail, const std::string &emailSubject, const std::string &htmlBody) { + struct payload payload; + fillEmailPayloadData(payload, configuration, recipientEmail, "tmpString", emailSubject, htmlBody);//todo:change + CURL* curl; + CURLcode res = CURLE_OK; + struct curl_slist* recipients; + recipients = curl_slist_append(nullptr, "root@localhost"); + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_USERNAME, configuration.emailAddress.c_str()); + curl_easy_setopt(curl, CURLOPT_PASSWORD, configuration.emailPassword.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, generateMailServerURL(configuration).c_str()); + curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL); + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, configuration.emailAddress.c_str()); + recipients = curl_slist_append(recipients, recipientEmail.c_str()); + curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source); + curl_easy_setopt(curl, CURLOPT_READDATA, &payload); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + res = curl_easy_perform(curl); + + if (res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + + curl_slist_free_all(recipients); + + curl_easy_cleanup(curl); + } + return (int)res; + } +} +#endif \ No newline at end of file From 8102ea41dc0f93dbb1318079ae54a912dca7d2af Mon Sep 17 00:00:00 2001 From: Tina_Azure <-> Date: Mon, 29 May 2023 15:09:42 +0200 Subject: [PATCH 2/3] README Overhaul Config SSL made optional Minor Refactoring --- Makefile | 4 +--- README.md | 19 ++++++++++++++++--- src/default-cavecomm.conf | 4 +++- src/main.cpp | 5 +++-- src/utilities.cpp | 6 +++--- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 056f89f..d1cb6d1 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,6 @@ .POSIX: # Include path for Boost 1.81.0 and its libraries INC += -I/home/user/project/resources/boost_1_81_0 -L/home/user/project/resources/boost_1_81_0/stage/lib -# Include path for CPP-SMTPClient and its libraries -INC += -I/home/user/project/resources/CPP-SMTPClient-library/src -L/home/user/project/resources/CPP-SMTPClient-library # User include paths INC += -I/usr/include -L/usr/lib @@ -10,7 +8,7 @@ SRCFILES = src/main.cpp DEF += -DCROW_ENABLE_SSL -LIBS += -lpqxx -lfmt -l:libsmtpclient.a -lcrypto -lssl -pthread +LIBS += -lpqxx -lcurl -lcrypto -lssl -pthread db: ./setupdb.sh diff --git a/README.md b/README.md index 429fe7e..08040d1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ +## Running +./cavecomm {path to filled out cavecomm.conf} +if no path is given . will be considered the location + +It is possible to run without SSL support for that compilation has to occur without "-DCROW_ENABLE_SSL" ## Installation -Depends on: +Dependencies: +https://packages.debian.org/bullseye/openssl + +Postgress: https://packages.debian.org/bullseye/libpqxx-dev -https://packages.debian.org/bullseye/libfmt-dev -https://crowcpp.org + +SMTP: +https://packages.debian.org/bullseye/libcurlpp-dev + +CrowCPP: +https://crowcpp.org/ v1.0+5 +https://www.boost.org/ v1.81.0 **TODO: Write this** diff --git a/src/default-cavecomm.conf b/src/default-cavecomm.conf index ed7fc5a..7616834 100644 --- a/src/default-cavecomm.conf +++ b/src/default-cavecomm.conf @@ -5,9 +5,11 @@ # emailAddress= # emailPassword= # emailServerAddress= +# domain= + +# Optional if CROW_ENABLE_SSL not defined # sslCrtPath= # sslKeyPath= -# domain= # Optional: {default} # emailServerPort={587} diff --git a/src/main.cpp b/src/main.cpp index f491fe9..43e1b4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -682,7 +682,6 @@ int main(int argc, char *argv[]) { string postRequestBody = postRequest.body; Utilities::decodeString(postRequestBody); newTemplate.parseRequestBodyIntoItem(postRequestBody); - newTemplate.outputItem(); pqxx::connection databaseConnection(configuration.databaseConnectionString); Database::prepareStatement(databaseConnection, ID_INSERT_FREELANCER_TEMPLATE); @@ -790,7 +789,6 @@ int main(int argc, char *argv[]) { Utilities::templateItem toEditTemplate; Database::prepareStatement(databaseConnection, ID_UPDATE_EDIT_FREELANCER_TEMPLATE); toEditTemplate.parseRequestBodyIntoItem(postRequestBody); - toEditTemplate.outputItem(); bool updateSuccess = Database::executePreparedStatement_UPDATE_EDIT_FREELANCER_TEMPLATE( databaseConnection, toEditTemplate.name, toEditTemplate.content, toEditTemplate.contactdata, toEditTemplate.contactinformation, @@ -1021,6 +1019,9 @@ int main(int argc, char *argv[]) { */ //set the port, set the app to run on multiple threads, and run the app +#ifdef CROW_ENABLE_SSL + cout << "SSL Enabled" << endl; app.ssl_file(configuration.sslCrtPath, configuration.sslKeyPath); +#endif app.port(18080).multithreaded().run(); } diff --git a/src/utilities.cpp b/src/utilities.cpp index 5162b7e..af6443b 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -2,14 +2,12 @@ #define UTILITIES_CPP #include -#include #include #include #include #include #include #include -#include #include #include @@ -106,8 +104,10 @@ namespace Utilities { || emailPassword.empty() || emailServerAddress.empty() || databaseConnectionString.empty() + #ifdef CROW_ENABLE_SSL || sslCrtPath.empty() || sslKeyPath.empty() + #endif || domain.empty() ) return 1; @@ -282,7 +282,7 @@ namespace Utilities { priceondeliver = "0"; } - void outputItem() { + void outputItem() const { std::cout << name << " " << content << " " << contactdata << " " << contactinformation << " " << currencypreference << " " << priceupfront << "-" << priceondeliver << " " << std::endl; } From 3237b2af38f588c5374017d1133c7980ee390933 Mon Sep 17 00:00:00 2001 From: Tina_Azure <-> Date: Mon, 29 May 2023 15:12:40 +0200 Subject: [PATCH 3/3] RFC 28822 compliance changes. Move Mail Functionality into smtpManager.cpp --- src/emailTemplateCollection.cpp | 4 ++- src/main.cpp | 9 ++++-- src/smtpManager.cpp | 37 +++++++++++++++++++------ src/utilities.cpp | 49 +-------------------------------- 4 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/emailTemplateCollection.cpp b/src/emailTemplateCollection.cpp index cb4372d..aeb1257 100644 --- a/src/emailTemplateCollection.cpp +++ b/src/emailTemplateCollection.cpp @@ -4,7 +4,9 @@ #include namespace EmailTemplateCollection { - const std::string PASSWORD_RESET_EMAIL_SUBJECT = "Password Reset"; + static const std::string PASSWORD_RESET_EMAIL_SUBJECT = "Password Reset"; + static const std::string HTML_HEADER = ""; + static const std::string HTML_FOOTER = ""; std::string passwordResetEmail(const std::string& domain, const std::string& email, const std::string& passwordResetKey) { diff --git a/src/main.cpp b/src/main.cpp index 43e1b4d..ad00d3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "utilities.cpp" #include "regularTaskExecution.cpp" #include "templateConstCollection.cpp" +#include "smtpManager.cpp" using namespace std; using namespace TemplateConstCollection; @@ -26,6 +27,10 @@ int main(int argc, char *argv[]) { return 1; } + //simple email test, header validation via https://mailheader.org/ + //SMTPManager::sendEmail(configuration, "mail@mail.de", "testsubject", "

bluetest


blue
yellow"); + //return 0; + // Create app with Middleware crow::App app; @@ -239,7 +244,7 @@ int main(int argc, char *argv[]) { templateHTML = TEMPLATE_CUSTOMER_FREELANCER_TEMPLATE_REQUEST_FULFILMENT; pqxx::result resultEmailAddress = Database::executePreparedStatement_SELECT_FREELANCER_EMAIL(databaseConnection, newRequest.freelancerID); if (!resultEmailAddress.empty()) - Utilities::sendEmail(configuration, resultEmailAddress.at(0).at(0).c_str(), "NEW REQUEST", newRequest.toJSONString()); + SMTPManager::sendEmail(configuration, resultEmailAddress.at(0).at(0).c_str(), "NEW REQUEST", newRequest.toJSONString()); } else { ctx[MUSTACHE_ERROR_UNABLE_TO_CREATE_REQUEST] = true; @@ -355,7 +360,7 @@ int main(int argc, char *argv[]) { Database::executePreparedStatement_INSERT_FREELANCER_RESET_KEY(databaseConnection, email, passwordResetKey); - if (Utilities::sendPasswordResetEmail(configuration, email, passwordResetKey) == 0) + if (SMTPManager::sendPasswordResetEmail(configuration, email, passwordResetKey) == 1) ctx["passwordresetmailsent"] = true; } ctx["resetemail"] = email; diff --git a/src/smtpManager.cpp b/src/smtpManager.cpp index a70eab9..45f4cd5 100644 --- a/src/smtpManager.cpp +++ b/src/smtpManager.cpp @@ -7,6 +7,7 @@ #include #include +#include "emailTemplateCollection.cpp" #include "utilities.cpp" @@ -22,16 +23,24 @@ namespace SMTPManager { /* * Generates the Date Time Header Segment - * Date: {day}/{month}/{year} {hour}:{minute}:{second} {delta to UTC} \r\n + * Date: {Date Time in RFC 2822 Format} \r\n */ std::string generateEmailSegmentDateTime() { std::time_t time = std::time(nullptr); std::tm localTime = *std::localtime(&time); std::ostringstream oss; - oss << "Date: " << std::put_time(&localTime, "%d/%m/%Y %H:%M:%S %z") << END_OF_LINE; + oss << "Date: " << std::put_time(&localTime, "%a, %d %b %Y %T %z") << END_OF_LINE; return oss.str(); } + /* + * Generates the Message-ID Header Segment based on the RFC 2822 Format + * Message-ID: <{SHA256}@{email server address}>\r\n + */ + std::string generateEmailSegmentMessageId(const Utilities::config &configuration) { + return "Message-ID: <" + Utilities::generateRandomHashValueSHA256() + "@" + configuration.emailServerAddress + ">" + END_OF_LINE; + } + /* * Generates the Recipient Header Segment * To: {Recipient Name} <{email address}>\r\n @@ -74,6 +83,7 @@ namespace SMTPManager { void fillEmailPayloadData(struct payload &payload, const Utilities::config &configuration, const std::string &recipientEmail, const std::string &recipientName, const std::string &emailSubject, const std::string &body) { payload.payload_data.emplace_back(generateEmailSegmentDateTime()); + payload.payload_data.emplace_back(generateEmailSegmentMessageId(configuration)); payload.payload_data.emplace_back(generateEmailSegmentRecipient(recipientName, recipientEmail)); payload.payload_data.emplace_back(generateEmailSegmentSender(configuration)); payload.payload_data.emplace_back(generateEmailSegmentSubject(emailSubject)); @@ -115,19 +125,21 @@ namespace SMTPManager { } /* - * todo:for some inexplicable reason the sent email contains an empty CC: field which needs some examination - * todo:validate header https://mailheader.org/show.cgi * Sends an HTML Email using curl * returns CURL Error Codes https://curl.se/libcurl/c/libcurl-errors.html * 1 = OK */ - int sendEmailNew(const Utilities::config &configuration, const std::string &recipientEmail, const std::string &emailSubject, const std::string &htmlBody) { + int sendEmail(const Utilities::config& configuration, const std::string& recipientEmail, const std::string& emailSubject, const std::string& htmlBody, const std::string& recipientDisplayName = "") { struct payload payload; - fillEmailPayloadData(payload, configuration, recipientEmail, "tmpString", emailSubject, htmlBody);//todo:change + std::string recipientName; + if (!recipientDisplayName.empty()) + recipientName = recipientDisplayName; + else + recipientName = ""; + fillEmailPayloadData(payload, configuration, recipientEmail, recipientName, emailSubject, EmailTemplateCollection::HTML_HEADER + htmlBody + EmailTemplateCollection::HTML_FOOTER); CURL* curl; CURLcode res = CURLE_OK; - struct curl_slist* recipients; - recipients = curl_slist_append(nullptr, "root@localhost"); + struct curl_slist* recipients = nullptr; curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_USERNAME, configuration.emailAddress.c_str()); @@ -153,5 +165,14 @@ namespace SMTPManager { } return (int)res; } + + /* + * Sends a Password Reset Email + * return 1 at success + */ + int sendPasswordResetEmail(const Utilities::config& configuration, const std::string& email, const std::string& passwordResetKey) { + std::string emailContent = EmailTemplateCollection::passwordResetEmail(configuration.domain, email, passwordResetKey); + return sendEmail(configuration, email, EmailTemplateCollection::PASSWORD_RESET_EMAIL_SUBJECT, emailContent); + } } #endif \ No newline at end of file diff --git a/src/utilities.cpp b/src/utilities.cpp index af6443b..d96119a 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -15,13 +15,10 @@ #include #include "crow.h" #include "crow/middlewares/cookie_parser.h" -#include "cpp/opportunisticsecuresmtpclient.hpp" -#include "cpp/htmlmessage.hpp" -#include "emailTemplateCollection.cpp" + #include "templateConstCollection.cpp" -using namespace jed_utils::cpp; using namespace DatabaseStatementConstCollection; using namespace TemplateConstCollection; @@ -316,50 +313,6 @@ namespace Utilities { } } - /* - * Sends an HTML Email using CPP-SMTPClient-library - * return 0 at success, 1 at client fail, 2 at critical fail. - */ - int sendEmail(const config& configuration, const std::string& destinationEmail, std::string emailSubject, std::string htmlBody){ - OpportunisticSecureSMTPClient client(configuration.emailServerAddress, configuration.emailServerPort); - client.setCredentials(Credential(configuration.emailAddress, configuration.emailPassword)); - try { - const MessageAddress from(configuration.emailAddress, configuration.emailAddressDisplay); - const auto to = { MessageAddress(destinationEmail) }; - const auto subject = std::move(emailSubject); - const auto body = std::move(htmlBody); - const std::vector cc = {}; - const std::vector bcc = {}; - const std::vector attachments = {}; - HTMLMessage msg(from, to, subject, body, cc, bcc, attachments); - - int err_no = client.sendMail(msg); - if (err_no != 0) { - std::cerr << client.getCommunicationLog() << '\n'; - std::string errorMessage = OpportunisticSecureSMTPClient::getErrorMessage(err_no); - std::cerr << "An error occurred: " << errorMessage - << " (error no: " << err_no << ")" << '\n'; - return 1; - } - std::cout << client.getCommunicationLog() << '\n'; - std::cout << "Operation completed!" << std::endl; - } - catch (std::invalid_argument &err) { - std::cerr << err.what() << std::endl; - return 2; - } - return 0; - } - - /* - * Sends a Password Reset Email - * return 0 at success, 1 at client fail, 2 at critical fail. - */ - int sendPasswordResetEmail(const config& configuration, const std::string& email, const std::string& passwordResetKey) { - std::string emailContent = EmailTemplateCollection::passwordResetEmail(configuration.domain, email, passwordResetKey); - return sendEmail(configuration, email, EmailTemplateCollection::PASSWORD_RESET_EMAIL_SUBJECT, emailContent); - } - std::string createHashSha512(const std::string& str){ unsigned char hash[SHA512_DIGEST_LENGTH];