Missing files.
authorCarl Hetherington <cth@carlh.net>
Wed, 7 Oct 2015 22:04:05 +0000 (23:04 +0100)
committerCarl Hetherington <cth@carlh.net>
Fri, 9 Oct 2015 12:44:59 +0000 (13:44 +0100)
src/lib/emailer.cc [new file with mode: 0644]
src/lib/emailer.h [new file with mode: 0644]

diff --git a/src/lib/emailer.cc b/src/lib/emailer.cc
new file mode 100644 (file)
index 0000000..0febb56
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+    Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "compose.hpp"
+#include "job.h"
+#include "data.h"
+#include "config.h"
+#include "emailer.h"
+#include "exceptions.h"
+#include <curl/curl.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/date_time/c_local_time_adjustor.hpp>
+#include <boost/foreach.hpp>
+
+#include "i18n.h"
+
+using std::string;
+using std::stringstream;
+using std::min;
+using std::list;
+using std::cout;
+using std::pair;
+using boost::shared_ptr;
+
+Emailer::Emailer (string from, string to, string subject, string body)
+       : _from (from)
+       , _to (to)
+       , _subject (subject)
+       , _body (body)
+       , _offset (0)
+{
+       boost::algorithm::replace_all (_body, "\n", "\r\n");
+}
+
+void
+Emailer::add_cc (string cc)
+{
+       _cc.push_back (cc);
+}
+
+void
+Emailer::add_bcc (string bcc)
+{
+       _bcc.push_back (bcc);
+}
+
+void
+Emailer::add_attachment (boost::filesystem::path attachment, string mime_type)
+{
+       _attachments.push_back (make_pair (attachment, mime_type));
+}
+
+static size_t
+curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp)
+{
+       return reinterpret_cast<Emailer*>(userp)->get_data (ptr, size, nmemb);
+}
+
+size_t
+Emailer::get_data (void* ptr, size_t size, size_t nmemb)
+{
+       size_t const t = min (_email.length() - _offset, size * nmemb);
+       memcpy (ptr, _email.substr(_offset, size * nmemb).c_str(), size * nmemb);
+       _offset += t;
+       return t;
+}
+
+void
+Emailer::send (shared_ptr<Job> job)
+{
+       char date_buffer[32];
+       time_t now = time (0);
+       strftime (date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S ", localtime (&now));
+
+       boost::posix_time::ptime const utc_now = boost::posix_time::second_clock::universal_time ();
+       boost::posix_time::ptime const local_now = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local (utc_now);
+       boost::posix_time::time_duration offset = local_now - utc_now;
+       sprintf (date_buffer + strlen(date_buffer), "%s%02d%02d", (offset.hours() >= 0 ? "+" : "-"), abs (offset.hours()), offset.minutes());
+
+       stringstream email;
+
+       email << "Date: " << date_buffer << "\r\n"
+             << "To: " << _to << "\r\n"
+             << "From: " << _from << "\r\n";
+
+       if (!_cc.empty ()) {
+               email << "Cc: " << address_list (_cc);
+       }
+
+       if (!_bcc.empty ()) {
+               email << "Bcc: " << address_list (_bcc);
+       }
+
+       string const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+       string boundary;
+       for (int i = 0; i < 32; ++i) {
+               boundary += chars[rand() % chars.length()];
+       }
+
+       if (!_attachments.empty ()) {
+               email << "MIME-Version: 1.0\r\n"
+                     << "Content-Type: multipart/alternative; boundary=" << boundary << "\r\n";
+       }
+
+       email << "Subject: " << _subject << "\r\n"
+             << "User-Agent: DCP-o-matic\r\n"
+             << "\r\n";
+
+       if (!_attachments.empty ()) {
+               email << "--" << boundary << "\r\n"
+                     << "Content-Type: text/plain; charset=utf-8\r\n\r\n";
+       }
+
+       email << _body;
+
+       for (list<pair<boost::filesystem::path, string> >::const_iterator i = _attachments.begin(); i != _attachments.end(); ++i) {
+               email << "\r\n\r\n--" << boundary << "\r\n"
+                     << "Content-Type: " << i->second << "; name=" << i->first.leaf() << "\r\n"
+                     << "Content-Transfer-Encoding: Base64\r\n"
+                     << "Content-Disposition: attachment; filename=" << i->first.leaf() << "\r\n\r\n";
+
+               BIO* b64 = BIO_new (BIO_f_base64());
+
+               BIO* bio = BIO_new (BIO_s_mem());
+               bio = BIO_push (b64, bio);
+
+               Data data (i->first);
+               BIO_write (bio, data.data().get(), data.size());
+               (void) BIO_flush (bio);
+
+               char* out;
+               long int bytes = BIO_get_mem_data (bio, &out);
+               email << string (out, bytes);
+
+               BIO_free_all (b64);
+       }
+
+       if (!_attachments.empty ()) {
+               email << "\r\n--" << boundary << "--\r\n";
+       }
+
+       _email = email.str ();
+
+       curl_global_init (CURL_GLOBAL_DEFAULT);
+
+       CURL* curl = curl_easy_init ();
+       if (!curl) {
+               throw NetworkError ("Could not initialise libcurl");
+       }
+
+       CURLM* mcurl = curl_multi_init ();
+       if (!mcurl) {
+               throw NetworkError ("Could not initialise libcurl");
+       }
+
+       curl_easy_setopt (curl, CURLOPT_URL, String::compose (
+                                 "smtp://%1:%2",
+                                 Config::instance()->mail_server().c_str(),
+                                 Config::instance()->mail_port()
+                                 ).c_str());
+
+       if (!Config::instance()->mail_user().empty ()) {
+               curl_easy_setopt (curl, CURLOPT_USERNAME, Config::instance()->mail_user().c_str());
+       }
+       if (!Config::instance()->mail_password().empty ()) {
+               curl_easy_setopt (curl, CURLOPT_PASSWORD, Config::instance()->mail_password().c_str());
+       }
+
+       curl_easy_setopt (curl, CURLOPT_MAIL_FROM, _from.c_str());
+
+       struct curl_slist* recipients = curl_slist_append (0, _to.c_str());
+       BOOST_FOREACH (string i, _cc) {
+               recipients = curl_slist_append (recipients, i.c_str());
+       }
+       BOOST_FOREACH (string i, _bcc) {
+               recipients = curl_slist_append (recipients, i.c_str());
+       }
+
+       curl_easy_setopt (curl, CURLOPT_MAIL_RCPT, recipients);
+
+       curl_easy_setopt (curl, CURLOPT_READFUNCTION, curl_data_shim);
+       curl_easy_setopt (curl, CURLOPT_READDATA, this);
+       curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L);
+
+       curl_easy_setopt (curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY);
+       curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
+       curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
+       curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L);
+
+       _notes_buffer.reset (new char[65536]);
+       FILE* notes = fmemopen (_notes_buffer.get(), 65536, "w");
+       curl_easy_setopt (curl, CURLOPT_STDERR, notes);
+
+       curl_multi_add_handle (mcurl, curl);
+
+       time_t start = time (0);
+
+       int still_running = 1;
+       curl_multi_perform (mcurl, &still_running);
+
+       while (still_running) {
+
+               fd_set fdread;
+               fd_set fdwrite;
+               fd_set fdexcep;
+
+               FD_ZERO (&fdread);
+               FD_ZERO (&fdwrite);
+               FD_ZERO (&fdexcep);
+
+               struct timeval timeout;
+               timeout.tv_sec = 1;
+               timeout.tv_usec = 0;
+
+               long curl_timeout = -1;
+               curl_multi_timeout (mcurl, &curl_timeout);
+               if (curl_timeout >= 0) {
+                       timeout.tv_sec = curl_timeout / 1000;
+                       if (timeout.tv_sec > 1) {
+                               timeout.tv_sec = 1;
+                       } else {
+                               timeout.tv_usec = (curl_timeout % 1000) * 1000;
+                       }
+               }
+
+               int maxfd = -1;
+               CURLMcode mc = curl_multi_fdset (mcurl, &fdread, &fdwrite, &fdexcep, &maxfd);
+
+               if (mc != CURLM_OK) {
+                       fclose (notes);
+                       throw KDMError (String::compose ("Failed to send KDM email to %1", _to));
+               }
+
+               int rc;
+               if (maxfd == -1) {
+#ifdef DCPOMATIC_WINDOWS
+                       Sleep (100);
+                       rc = 0;
+#else
+                       struct timeval wait = { 0, 100 * 1000};
+                       rc = select (0, 0, 0, 0, &wait);
+#endif
+               } else {
+                       rc = select (maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
+               }
+
+               if (rc < 0) {
+                       throw KDMError ("Failed to send KDM email");
+               }
+
+               mc = curl_multi_perform (mcurl, &still_running);
+               if (mc != CURLM_OK) {
+                       fclose (notes);
+                       throw KDMError (String::compose ("Failed to send KDM email (%1)", curl_multi_strerror (mc)));
+               }
+
+               if (job) {
+                       job->set_progress_unknown ();
+               }
+
+               if ((time(0) - start) > 10) {
+                       fclose (notes);
+                       throw KDMError (_("Failed to send KDM email (timed out)"));
+               }
+       }
+
+       int messages;
+       do {
+               CURLMsg* m = curl_multi_info_read (mcurl, &messages);
+               if (m && m->data.result != CURLE_OK) {
+                       fclose (notes);
+                       throw KDMError (String::compose ("Failed to send KDM email (%1)", curl_easy_strerror (m->data.result)));
+               }
+       } while (messages > 0);
+
+       /* XXX: we should do this stuff when an exception is thrown, but curl_multi_remove_handle
+          seems to hang if we try that.
+       */
+
+       curl_slist_free_all (recipients);
+       curl_multi_remove_handle (mcurl, curl);
+       curl_multi_cleanup (mcurl);
+       curl_easy_cleanup (curl);
+       curl_global_cleanup ();
+
+       fclose (notes);
+}
+
+string
+Emailer::address_list (list<string> addresses)
+{
+       string o;
+       BOOST_FOREACH (string i, addresses) {
+               o += i + ", ";
+       }
+
+       return o.substr (0, o.length() - 2);
+}
+
+string
+Emailer::notes () const
+{
+       return string (_notes_buffer.get());
+}
diff --git a/src/lib/emailer.h b/src/lib/emailer.h
new file mode 100644 (file)
index 0000000..4b4d1f6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <curl/curl.h>
+#include <boost/scoped_array.hpp>
+
+class Emailer
+{
+public:
+       Emailer (std::string from, std::string to, std::string subject, std::string body);
+
+       void add_cc (std::string cc);
+       void add_bcc (std::string bcc);
+       void add_attachment (boost::filesystem::path attachment, std::string);
+
+       void send (boost::shared_ptr<Job> job);
+
+       std::string notes () const;
+
+       size_t get_data (void* ptr, size_t size, size_t nmemb);
+
+private:
+       static std::string address_list (std::list<std::string> addresses);
+
+       std::string _from;
+       std::string _to;
+       std::string _subject;
+       std::string _body;
+       std::list<std::string> _cc;
+       std::list<std::string> _bcc;
+       std::list<std::pair<boost::filesystem::path, std::string> > _attachments;
+       std::string _email;
+       size_t _offset;
+       boost::scoped_array<char> _notes_buffer;
+};