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