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