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