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