artdaq_mfextensions  v1_03_02
SMTP_mfPlugin.cc
1 #include "cetlib/PluginTypeDeducer.h"
2 #include "fhiclcpp/ParameterSet.h"
3 #if MESSAGEFACILITY_HEX_VERSION >= 0x20103
4 #include "fhiclcpp/types/ConfigurationTable.h"
5 #include "fhiclcpp/types/Sequence.h"
6 #include "fhiclcpp/types/TableFragment.h"
7 #endif
8 
9 
10 #include "messagefacility/MessageService/ELdestination.h"
11 #include "messagefacility/Utilities/ELseverityLevel.h"
12 #if MESSAGEFACILITY_HEX_VERSION < 0x20201 // v2_02_01 is s67
13 # include "messagefacility/MessageService/MessageDrop.h"
14 #else
15 # include "messagefacility/MessageLogger/MessageLogger.h"
16 #endif
17 #include "messagefacility/Utilities/exception.h"
18 
19 // C/C++ includes
20 #include <memory>
21 #include <algorithm>
22 #include <atomic>
23 #include <mutex>
24 #include <random>
25 #include <arpa/inet.h>
26 #include <ifaddrs.h>
27 #include <netdb.h>
28 #include <netinet/in.h>
29 #include <boost/thread.hpp>
30 
31 #include <QtCore/QString>
33 #include "cetlib/compiler_macros.h"
34 
35 namespace mfplugins
36 {
37  using mf::service::ELdestination;
38  using mf::ELseverityLevel;
39  using mf::ErrorObj;
40 
44  class ELSMTP : public ELdestination
45  {
46 #if MESSAGEFACILITY_HEX_VERSION >= 0x20103
47  struct Config
48  {
49  using strings_t = fhicl::Sequence<std::string>::default_type;
50 
51  fhicl::TableFragment<ELdestination::Config> elDestConfig;
52  fhicl::Atom<std::string> host{ fhicl::Name{ "host" },fhicl::Comment{ "SMTP Server hostname" }, "smtp.fnal.gov" };
53  fhicl::Atom<int> port{ fhicl::Name{ "port" },fhicl::Comment{ "SMTP Server port" },25 };
54  fhicl::Sequence<std::string> to{ fhicl::Name{ "to_addresses" }, fhicl::Comment{"The list of email addresses that SMTP mfPlugin should sent to" } , strings_t {} };
55  fhicl::Atom<std::string> from{ fhicl::Name{ "from_address" },fhicl::Comment{ "Source email address" } };
56  fhicl::Atom<std::string> subject{ fhicl::Name{ "subject" },fhicl::Comment{ "Subject of the email message" }, "MessageFacility SMTP Message Digest" };
57  fhicl::Atom<std::string> messageHeader{ fhicl::Name{ "message_header" },fhicl::Comment{ "String to preface messages with in email body" }, "" };
58  fhicl::Atom<bool> useSmtps{ fhicl::Name{ "use_smtps" },fhicl::Comment{ "Use SMTPS protocol" }, false };
59  fhicl::Atom<std::string> user{ fhicl::Name{ "smtp_username" },fhicl::Comment{ "Username for SMTP server" }, "" };
60  fhicl::Atom<std::string> pw{ fhicl::Name{ "smtp_password" },fhicl::Comment{ "Password for SMTP server" }, "" };
61  fhicl::Atom<bool> verifyCert{ fhicl::Name{ "verify_host_ssl_certificate" },fhicl::Comment{ "Whether to run full SSL verify on SMTP server in SMTPS mode" }, true };
62  fhicl::Atom<size_t> sendInterval{ fhicl::Name{ "email_send_interval_seconds" },fhicl::Comment{ "Only send email every N seconds" }, 15 };
63  };
64  using Parameters = fhicl::WrappedTable<Config>;
65 #endif
66  public:
71 #if MESSAGEFACILITY_HEX_VERSION < 0x20103 // v2_01_03 is s58, pre v2_01_03 is s50
72  ELSMTP(const fhicl::ParameterSet& pset);
73 #else
74  ELSMTP(Parameters const& pset);
75 #endif
76 
77  ~ELSMTP()
78  {
79  abort_sleep_ = true;
80  while (sending_thread_active_) usleep(1000);
81  }
82 
88  virtual void routePayload(const std::ostringstream& o, const ErrorObj& msg) override;
89 
90  private:
91  void send_message_();
92  std::string generateMessageId_() const;
93  std::string dateTimeNow_();
94  std::string to_html(std::string msgString, const ErrorObj& msg);
95 
96  std::string smtp_host_;
97  int port_;
98  std::vector<std::string> to_;
99  std::string from_;
100  std::string subject_;
101  std::string message_prefix_;
102 
103  // Message information
104  long pid_;
105  std::string hostname_;
106  std::string hostaddr_;
107  std::string app_;
108 
109  bool use_ssl_;
110  std::string username_;
111  std::string password_;
112  bool ssl_verify_host_cert_;
113 
114  std::atomic<bool> sending_thread_active_;
115  std::atomic<bool> abort_sleep_;
116  size_t send_interval_s_;
117  mutable std::mutex message_mutex_;
118  std::ostringstream message_contents_;
119  };
120 
121  // END DECLARATION
122  //======================================================================
123  // BEGIN IMPLEMENTATION
124 
125  //======================================================================
126  // ELSMTP c'tor
127  //======================================================================
128 
129 #if MESSAGEFACILITY_HEX_VERSION < 0x20103 // v2_01_03 is s58, pre v2_01_03 is s50
130  ELSMTP::ELSMTP(const fhicl::ParameterSet& pset)
131  : ELdestination(pset)
132  , smtp_host_(pset.get<std::string>("host", "smtp.fnal.gov"))
133  , port_(pset.get<int>("port", 25))
134  , to_(pset.get<std::vector<std::string>>("to_addresses"))
135  , from_(pset.get<std::string>("from_address"))
136  , subject_(pset.get<std::string>("subject", "MessageFacility SMTP Message Digest"))
137  , message_prefix_(pset.get<std::string>("message_header", ""))
138  , pid_(static_cast<long>(getpid()))
139  , use_ssl_(pset.get<bool>("use_smtps", false))
140  , username_(pset.get<std::string>("smtp_username", ""))
141  , password_(pset.get<std::string>("smtp_password", ""))
142  , ssl_verify_host_cert_(pset.get<bool>("verify_host_ssl_certificate", true))
143  , sending_thread_active_(false)
144  , abort_sleep_(false)
145  , send_interval_s_(pset.get<size_t>("email_send_interval_seconds", 15))
146 #else
147  ELSMTP::ELSMTP(Parameters const& pset)
148  : ELdestination(pset().elDestConfig())
149  , smtp_host_(pset().host())
150  , port_(pset().port())
151  , to_(pset().to())
152  , from_(pset().from())
153  , subject_(pset().subject())
154  , message_prefix_(pset().messageHeader())
155  , pid_(static_cast<long>(getpid()))
156  , use_ssl_(pset().useSmtps())
157  , username_(pset().user())
158  , password_(pset().pw())
159  , ssl_verify_host_cert_(pset().verifyCert())
160  , sending_thread_active_(false)
161  , abort_sleep_(false)
162  , send_interval_s_(pset().sendInterval())
163 #endif
164  {
165  // hostname
166  char hostname_c[1024];
167  hostname_ = (gethostname(hostname_c, 1023) == 0) ? hostname_c : "Unkonwn Host";
168 
169  // host ip address
170  hostent* host = nullptr;
171  host = gethostbyname(hostname_c);
172 
173  if (host != nullptr)
174  {
175  // ip address from hostname if the entry exists in /etc/hosts
176  char* ip = inet_ntoa(*(struct in_addr *)host->h_addr);
177  hostaddr_ = ip;
178  }
179  else
180  {
181  // enumerate all network interfaces
182  struct ifaddrs* ifAddrStruct = nullptr;
183  struct ifaddrs* ifa = nullptr;
184  void* tmpAddrPtr = nullptr;
185 
186  if (getifaddrs(&ifAddrStruct))
187  {
188  // failed to get addr struct
189  hostaddr_ = "127.0.0.1";
190  }
191  else
192  {
193  // iterate through all interfaces
194  for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next)
195  {
196  if (ifa->ifa_addr->sa_family == AF_INET)
197  {
198  // a valid IPv4 addres
199  tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
200  char addressBuffer[INET_ADDRSTRLEN];
201  inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
202  hostaddr_ = addressBuffer;
203  }
204 
205  else if (ifa->ifa_addr->sa_family == AF_INET6)
206  {
207  // a valid IPv6 address
208  tmpAddrPtr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
209  char addressBuffer[INET6_ADDRSTRLEN];
210  inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
211  hostaddr_ = addressBuffer;
212  }
213 
214  // find first non-local address
215  if (!hostaddr_.empty()
216  && hostaddr_.compare("127.0.0.1")
217  && hostaddr_.compare("::1"))
218  break;
219  }
220 
221  if (hostaddr_.empty()) // failed to find anything
222  hostaddr_ = "127.0.0.1";
223  }
224  }
225 
226  // get process name from '/proc/pid/exe'
227  std::string exe;
228  std::ostringstream pid_ostr;
229  pid_ostr << "/proc/" << pid_ << "/exe";
230  exe = realpath(pid_ostr.str().c_str(), NULL);
231 
232  size_t end = exe.find('\0');
233  size_t start = exe.find_last_of('/', end);
234 
235  app_ = exe.substr(start + 1, end - start - 1);
236  }
237 
238 
239  std::string ELSMTP::to_html(std::string msgString, const ErrorObj& msg)
240  {
241  auto sevid = msg.xid().severity().getLevel();
242 
243  QString text_ = QString("<font color=");
244 
245  switch (sevid)
246  {
247  case mf::ELseverityLevel::ELsev_success:
248  case mf::ELseverityLevel::ELsev_zeroSeverity:
249  case mf::ELseverityLevel::ELsev_unspecified:
250  text_ += QString("#505050>");
251  break;
252 
253  case mf::ELseverityLevel::ELsev_info:
254  text_ += QString("#008000>");
255  break;
256 
257  case mf::ELseverityLevel::ELsev_warning:
258  text_ += QString("#E08000>");
259  break;
260 
261  case mf::ELseverityLevel::ELsev_error:
262  case mf::ELseverityLevel::ELsev_severe:
263  case mf::ELseverityLevel::ELsev_highestSeverity:
264  text_ += QString("#FF0000>");
265  break;
266 
267  default: break;
268  }
269 
270  //std::cout << "qt_mf_msg.cc:" << msg.message() << std::endl;
271  text_ += QString("<pre>")
272  + QString(msgString.c_str()).toHtmlEscaped() // + "<br>"
273  + QString("</pre>");
274 
275  text_ += QString("</font>");
276  return text_.toStdString();
277  }
278 
279  //======================================================================
280  // Message router ( overriddes ELdestination::routePayload )
281  //======================================================================
282  void ELSMTP::routePayload(const std::ostringstream& oss, const ErrorObj& msg)
283  {
284  std::unique_lock<std::mutex>(message_mutex_);
285  message_contents_ << to_html(oss.str(), msg);
286 
287  if (!sending_thread_active_)
288  {
289  sending_thread_active_ = true;
290  boost::thread t([=] { send_message_(); });
291  t.detach();
292  }
293  }
294 
295  void ELSMTP::send_message_()
296  {
297  size_t slept = 0;
298  while (!abort_sleep_ && slept < send_interval_s_ * 1000000)
299  {
300  usleep(10000);
301  slept += 10000;
302  }
303 
304  std::string payload;
305  {
306  std::unique_lock<std::mutex> lk(message_mutex_);
307  payload = message_contents_.str();
308  message_contents_.str("");
309  }
310  std::string destination = (use_ssl_ ? "smtps://" : "smtp://") + smtp_host_ + ":" + std::to_string(port_);
311 
312 
313  std::vector<const char*> to;
314  to.reserve(to_.size());
315  std::string toString;
316  for (size_t i = 0; i < to_.size(); ++i)
317  {
318  to.push_back(to_[i].c_str());
319  toString += to_[i];
320  if (i < to_.size() - 1)
321  {
322  toString += ", ";
323  }
324  }
325 
326  std::ostringstream headers_builder;
327 
328  headers_builder << "Date: " << dateTimeNow_() << "\r\n";
329  headers_builder << "To: " << toString << "\r\n";
330  headers_builder << "From: " << from_ << "\r\n";
331  headers_builder << "Message-ID: <" + generateMessageId_() + "@" + from_.substr(from_.find('@') + 1) + ">\r\n";
332  headers_builder << "Subject: " << subject_ << " @ " << dateTimeNow_() << " from PID " << getpid() << "\r\n";
333  headers_builder << "Content-Type: text/html; charset=\"UTF-8\"\r\n";
334  headers_builder << "\r\n";
335 
336  std::string headers = headers_builder.str();
337  std::ostringstream message_builder;
338  message_builder << headers << "<html><body><p>" << message_prefix_ << "</p>" << payload << "</body></html>";
339  std::string payloadWithHeaders = message_builder.str();
340 
341 
342  if (use_ssl_)
343  {
344  send_message_ssl(destination.c_str(), &to[0], to_.size(), from_.c_str(), payloadWithHeaders.c_str(), payloadWithHeaders.size(), username_.c_str(), password_.c_str(), !ssl_verify_host_cert_);
345  }
346  else
347  {
348  send_message(destination.c_str(), &to[0], to_.size(), from_.c_str(), payloadWithHeaders.c_str(), payloadWithHeaders.size());
349  }
350  sending_thread_active_ = false;
351  }
352 
353  //https://codereview.stackexchange.com/questions/140409/sending-email-using-libcurl-follow-up/140562
354  std::string ELSMTP::generateMessageId_() const
355  {
356  const size_t MESSAGE_ID_LEN = 37;
357  tm t;
358  time_t tt;
359  time(&tt);
360  gmtime_r(&tt, &t);
361 
362  std::string ret;
363  ret.resize(MESSAGE_ID_LEN);
364  size_t datelen = std::strftime(&ret[0], MESSAGE_ID_LEN, "%Y%m%d%H%M%S", &t);
365  static const std::string alphaNum{
366  "0123456789"
367  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
368  "abcdefghijklmnopqrstuvwxyz" };
369  std::mt19937 gen;
370  std::uniform_int_distribution<> dis(0, alphaNum.length() - 1);
371  std::generate_n(ret.begin() + datelen, MESSAGE_ID_LEN - datelen, [&]() { return alphaNum[dis(gen)]; });
372  return ret;
373  }
374 
375  std::string ELSMTP::dateTimeNow_()
376  {
377  const int RFC5322_TIME_LEN = 32;
378 
379  std::string ret;
380  ret.resize(RFC5322_TIME_LEN);
381 
382  time_t tt;
383  tm tv, *t = &tv;
384  tt = time(&tt);
385  localtime_r(&tt, t);
386 
387  strftime(&ret[0], RFC5322_TIME_LEN, "%a, %d %b %Y %H:%M:%S %z", t);
388 
389  return ret;
390  }
391 } // end namespace mfplugins
392 
393  //======================================================================
394  //
395  // makePlugin function
396  //
397  //======================================================================
398 
399 #ifndef EXTERN_C_FUNC_DECLARE_START
400 #define EXTERN_C_FUNC_DECLARE_START extern "C" {
401 #endif
402 
403 EXTERN_C_FUNC_DECLARE_START
404 auto makePlugin(const std::string&,
405  const fhicl::ParameterSet& pset)
406 {
407  return std::make_unique<mfplugins::ELSMTP>(pset);
408 }
409 }
410 
411 DEFINE_BASIC_PLUGINTYPE_FUNC(mf::service::ELdestination)
void send_message_ssl(const char *dest, const char *to[], size_t to_size, const char *from, const char *payload, size_t payload_size, const char *username, const char *pw, int disableVerify)
Sends a message to the given SMTP server, using SSL encryption.
SMTP Message Facility destination plugin (Using libcurl)
void send_message(const char *dest, const char *to[], size_t to_size, const char *from, const char *payload, size_t payload_size)
Sends a message to the given SMTP server.
virtual void routePayload(const std::ostringstream &o, const ErrorObj &msg) override
Serialize a MessageFacility message to the output.
ELSMTP(const fhicl::ParameterSet &pset)
ELSMTP Constructor