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