2 Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "compose.hpp"
24 #include "exceptions.h"
25 #include <curl/curl.h>
26 #include <boost/algorithm/string.hpp>
27 #include <boost/date_time/c_local_time_adjustor.hpp>
28 #include <boost/foreach.hpp>
37 using boost::shared_ptr;
40 Emailer::Emailer (string from, list<string> to, string subject, string body)
51 Emailer::fix (string s) const
53 boost::algorithm::replace_all (s, "\n", "\r\n");
54 boost::algorithm::replace_all (s, "\0", " ");
59 Emailer::add_cc (string cc)
65 Emailer::add_bcc (string bcc)
71 Emailer::add_attachment (boost::filesystem::path file, string name, string mime_type)
76 a.mime_type = mime_type;
77 _attachments.push_back (a);
81 curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp)
83 return reinterpret_cast<Emailer*>(userp)->get_data (ptr, size, nmemb);
87 curl_debug_shim (CURL* curl, curl_infotype type, char* data, size_t size, void* userp)
89 return reinterpret_cast<Emailer*>(userp)->debug (curl, type, data, size);
93 Emailer::get_data (void* ptr, size_t size, size_t nmemb)
95 size_t const t = min (_email.length() - _offset, size * nmemb);
96 memcpy (ptr, _email.substr (_offset, t).c_str(), t);
102 Emailer::send (string server, int port, string user, string password)
104 char date_buffer[128];
105 time_t now = time (0);
106 strftime (date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S ", localtime (&now));
108 boost::posix_time::ptime const utc_now = boost::posix_time::second_clock::universal_time ();
109 boost::posix_time::ptime const local_now = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local (utc_now);
110 boost::posix_time::time_duration offset = local_now - utc_now;
111 sprintf (date_buffer + strlen(date_buffer), "%s%02d%02d", (offset.hours() >= 0 ? "+" : "-"), int(abs(offset.hours())), int(offset.minutes()));
113 _email = "Date: " + string(date_buffer) + "\r\n"
114 "To: " + address_list (_to) + "\r\n"
115 "From: " + _from + "\r\n";
118 _email += "Cc: " + address_list (_cc) + "\r\n";
121 if (!_bcc.empty ()) {
122 _email += "Bcc: " + address_list (_bcc) + "\r\n";
125 string const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
127 for (int i = 0; i < 32; ++i) {
128 boundary += chars[rand() % chars.length()];
131 if (!_attachments.empty ()) {
132 _email += "MIME-Version: 1.0\r\n"
133 "Content-Type: multipart/mixed; boundary=" + boundary + "\r\n";
136 _email += "Subject: " + _subject + "\r\n"
137 "User-Agent: DCP-o-matic\r\n"
140 if (!_attachments.empty ()) {
141 _email += "--" + boundary + "\r\n"
142 + "Content-Type: text/plain; charset=utf-8\r\n\r\n";
147 BOOST_FOREACH (Attachment i, _attachments) {
148 _email += "\r\n\r\n--" + boundary + "\r\n"
149 "Content-Type: " + i.mime_type + "; name=" + i.name + "\r\n"
150 "Content-Transfer-Encoding: Base64\r\n"
151 "Content-Disposition: attachment; filename=" + i.name + "\r\n\r\n";
153 BIO* b64 = BIO_new (BIO_f_base64());
155 BIO* bio = BIO_new (BIO_s_mem());
156 bio = BIO_push (b64, bio);
159 BIO_write (bio, data.data().get(), data.size());
160 (void) BIO_flush (bio);
163 long int bytes = BIO_get_mem_data (bio, &out);
164 _email += fix (string (out, bytes));
169 if (!_attachments.empty ()) {
170 _email += "\r\n--" + boundary + "--\r\n";
173 curl_global_init (CURL_GLOBAL_DEFAULT);
175 CURL* curl = curl_easy_init ();
177 throw NetworkError ("Could not initialise libcurl");
181 /* "Implicit TLS"; I think curl wants us to use smtps here */
182 curl_easy_setopt (curl, CURLOPT_URL, String::compose ("smtps://%1:465", server).c_str());
184 curl_easy_setopt (curl, CURLOPT_URL, String::compose ("smtp://%1:%2", server, port).c_str());
187 if (!user.empty ()) {
188 curl_easy_setopt (curl, CURLOPT_USERNAME, user.c_str ());
190 if (!password.empty ()) {
191 curl_easy_setopt (curl, CURLOPT_PASSWORD, password.c_str());
194 curl_easy_setopt (curl, CURLOPT_MAIL_FROM, _from.c_str());
196 struct curl_slist* recipients = 0;
197 BOOST_FOREACH (string i, _to) {
198 recipients = curl_slist_append (recipients, i.c_str());
200 BOOST_FOREACH (string i, _cc) {
201 recipients = curl_slist_append (recipients, i.c_str());
203 BOOST_FOREACH (string i, _bcc) {
204 recipients = curl_slist_append (recipients, i.c_str());
207 curl_easy_setopt (curl, CURLOPT_MAIL_RCPT, recipients);
209 curl_easy_setopt (curl, CURLOPT_READFUNCTION, curl_data_shim);
210 curl_easy_setopt (curl, CURLOPT_READDATA, this);
211 curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
213 curl_easy_setopt (curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY);
214 curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
215 curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
216 curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L);
217 curl_easy_setopt (curl, CURLOPT_DEBUGFUNCTION, curl_debug_shim);
218 curl_easy_setopt (curl, CURLOPT_DEBUGDATA, this);
220 CURLcode const r = curl_easy_perform (curl);
222 throw KDMError (String::compose (_("Failed to send email (%1)"), curl_easy_strerror (r)));
225 curl_slist_free_all (recipients);
226 curl_easy_cleanup (curl);
227 curl_global_cleanup ();
231 Emailer::address_list (list<string> addresses)
234 BOOST_FOREACH (string i, addresses) {
238 return o.substr (0, o.length() - 2);
242 Emailer::debug (CURL *, curl_infotype type, char* data, size_t size)
244 if (type == CURLINFO_TEXT) {
245 _notes += string (data, size);
246 } else if (type == CURLINFO_HEADER_IN) {
247 _notes += "<- " + string (data, size);
248 } else if (type == CURLINFO_HEADER_OUT) {
249 _notes += "-> " + string (data, size);