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