NAWA 0.9
Web Application Framework for C++
SmtpMailer.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-2022 Tobias Flaig.
3 *
4 * This file is part of nawa.
5 *
6 * nawa is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License,
8 * version 3, as published by the Free Software Foundation.
9 *
10 * nawa is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with nawa. If not, see <https://www.gnu.org/licenses/>.
17 */
18
24#include <curl/curl.h>
25#include <nawa/Exception.h>
28#include <nawa/util/crypto.h>
29#include <nawa/util/utils.h>
30#include <random>
31
32using namespace nawa;
33using namespace std;
34
35namespace {
42 void addMissingHeaders(shared_ptr<mail::Email>& email, shared_ptr<mail::EmailAddress> const& from) {
43 if (!email->headers().count("Date")) {
44 email->headers()["Date"] = utils::makeSmtpTime(time(nullptr));
45 }
46 if (!email->headers().count("From") && !from->address().empty()) {
47 email->headers()["From"] = from->get();
48 }
49 unsigned long atPos;
50 if (!email->headers().count("Message-ID") && !from->address().empty() && (atPos = from->address().find_last_of('@')) != string::npos) {
51 stringstream mid;
52 stringstream base;
53 random_device rd;
54 timespec mtime{};
55 clock_gettime(CLOCK_REALTIME, &mtime);
56 base << mtime.tv_sec << mtime.tv_nsec << from->address() << rd();
57 mid << '<' << crypto::md5(base.str(), true) << '@' << from->address().substr(atPos + 1) << '>';
58 email->headers()["Message-ID"] = mid.str();
59 }
60 }
61
65 struct QueueElem {
66 std::shared_ptr<mail::Email const> email;
67 std::shared_ptr<mail::EmailAddress const> from;
68 std::vector<mail::EmailAddress> recipients;
69 std::shared_ptr<mail::ReplacementRules> replacementRules;
70 };
71}// namespace
72
73struct mail::SmtpMailer::Data {
74 std::string serverDomain;
75 unsigned int serverPort;
76 TlsMode serverTlsMode;
77 bool verifyServerTlsCert;
78 std::string authUsername;
79 std::string authPassword;
80 long connectionTimeout;
81 std::vector<QueueElem> queue;
82
83 Data(string serverDomain, unsigned int serverPort, TlsMode serverTlsMode, bool verifyServerTlsCert,
84 string authUsername, string authPassword, long connectionTimeout) : serverDomain(std::move(serverDomain)),
85 serverPort(serverPort),
86 serverTlsMode(serverTlsMode),
87 verifyServerTlsCert(verifyServerTlsCert),
88 authUsername(std::move(authUsername)),
89 authPassword(std::move(authPassword)),
90 connectionTimeout(connectionTimeout) {}
91};
92
94
95mail::SmtpMailer::SmtpMailer(string serverDomain, unsigned int serverPort, SmtpMailer::TlsMode serverTlsMode,
96 bool verifyServerTlsCert, string authUsername, string authPassword,
97 long connectionTimeout) {
98 data = make_unique<Data>(std::move(serverDomain), serverPort, serverTlsMode, verifyServerTlsCert, std::move(authUsername),
99 std::move(authPassword), connectionTimeout);
100}
101
102void mail::SmtpMailer::setServer(std::string domain, unsigned int port, SmtpMailer::TlsMode tlsMode, bool verifyTlsCert) {
103 data->serverDomain = std::move(domain);
104 data->serverPort = port;
105 data->serverTlsMode = tlsMode;
106 data->verifyServerTlsCert = verifyTlsCert;
107}
108
109void mail::SmtpMailer::setAuth(std::string username, std::string password) {
110 data->authUsername = std::move(username);
111 data->authPassword = std::move(password);
112}
113
114void mail::SmtpMailer::setConnectionTimeout(long timeout) {
115 data->connectionTimeout = timeout;
116}
117
118void mail::SmtpMailer::enqueue(std::shared_ptr<Email> email, EmailAddress to, std::shared_ptr<EmailAddress> from,
119 std::shared_ptr<ReplacementRules> replacementRules) {
120 bulkEnqueue(std::move(email), vector<EmailAddress>({std::move(to)}), std::move(from),
121 std::move(replacementRules));
122}
123
124void mail::SmtpMailer::bulkEnqueue(std::shared_ptr<Email> email, std::vector<EmailAddress> recipients,
125 std::shared_ptr<EmailAddress> from, std::shared_ptr<ReplacementRules> replacementRules) {
126 addMissingHeaders(email, from);
127 data->queue.push_back(QueueElem{.email = std::move(email), .from = std::move(from), .recipients = std::move(recipients), .replacementRules = std::move(replacementRules)});
128}
129
130void mail::SmtpMailer::clearQueue() {
131 data->queue.clear();
132}
133
134void mail::SmtpMailer::processQueue() const {
135 CURL* curl;
136 CURLcode res;
137
138 curl = curl_easy_init();
139 if (curl) {
140
141 // authentication, if requested
142 if (!data->authUsername.empty()) {
143 curl_easy_setopt(curl, CURLOPT_USERNAME, data->authUsername.c_str());
144 if (!data->authPassword.empty())
145 curl_easy_setopt(curl, CURLOPT_PASSWORD, data->authPassword.c_str());
146 }
147
148 // build URL for curl
149 {
150 stringstream curlUrl;
151 if (data->serverTlsMode == TlsMode::SMTPS) {
152 curlUrl << "smtps://";
153 } else {
154 curlUrl << "smtp://";
155 }
156 curlUrl << data->serverDomain << ":" << data->serverPort;
157 curl_easy_setopt(curl, CURLOPT_URL, curlUrl.str().c_str());
158 }
159
160 // set up TLS
161 if (data->serverTlsMode == TlsMode::REQUIRE_STARTTLS) {
162 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long) CURLUSESSL_ALL);
163 } else if (data->serverTlsMode == TlsMode::TRY_STARTTLS) {
164 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY);
165 }
166 if (data->serverTlsMode != TlsMode::NONE && !data->verifyServerTlsCert) {
167 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
168 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
169 }
170
171 // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
172 FILE* devNull = fopen("/dev/null", "wb");
173 curl_easy_setopt(curl, CURLOPT_STDERR, devNull);
174
175 // connection timeout
176 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, data->connectionTimeout);
177
178 // iterate queue
179 for (const auto& mail : data->queue) {
180
181 // set sender
182 curl_easy_setopt(curl, CURLOPT_MAIL_FROM, mail.from->get(false).c_str());
183
184 // set recipients
185 curl_slist* recipients = nullptr;
186 for (const auto& to : mail.recipients) {
187 recipients = curl_slist_append(recipients, to.get(false).c_str());
188 }
189 curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
190
191 // specify how to read the mail data
192 string payload = mail.email->getRaw(mail.replacementRules);
193 // fmemopen will create a FILE* to read from the string (curl expects that, unfortunately)
194 FILE* payloadFile = fmemopen((void*) payload.c_str(), payload.length(), "r");
195 curl_easy_setopt(curl, CURLOPT_READDATA, (void*) payloadFile);
196 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
197
198 // perform sending
199 res = curl_easy_perform(curl);
200
201 // free memory
202 curl_slist_free_all(recipients);
203 fclose(payloadFile);
204
205 // check for errors, throw exception if one happens
206 if (res != CURLE_OK) {
207 curl_easy_cleanup(curl);
208 throw Exception(__PRETTY_FUNCTION__, 1,
209 string("CURL error: ") + curl_easy_strerror(res));
210 }
211 }
212
213 curl_easy_cleanup(curl);
214 fclose(devNull);
215 }
216}
Structure representing an email address.
Exception class that can be used by apps to catch errors resulting from nawa function calls.
For establishing a connection to an SMTP server and sending emails.
A bunch of useful cryptographic functions (esp. hashing), acting as a wrapper to C crypto libraries.
#define NAWA_DEFAULT_DESTRUCTOR_IMPL_WITH_NS(Namespace, Class)
Definition: macros.h:37
std::string md5(std::string const &input, bool hex=true)
Definition: crypto.cpp:93
std::string makeSmtpTime(time_t time)
Definition: utils.cpp:392
Definition: AppInit.h:31
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.