00001 #include "cetlib/PluginTypeDeducer.h"
00002 #include "fhiclcpp/ParameterSet.h"
00003 #if MESSAGEFACILITY_HEX_VERSION >= 0x20103
00004 #include "fhiclcpp/types/ConfigurationTable.h"
00005 #include "fhiclcpp/types/Sequence.h"
00006 #include "fhiclcpp/types/TableFragment.h"
00007 #endif
00008
00009
00010 #include "messagefacility/MessageService/ELdestination.h"
00011 #include "messagefacility/Utilities/ELseverityLevel.h"
00012 #if MESSAGEFACILITY_HEX_VERSION < 0x20201 // v2_02_01 is s67
00013 # include "messagefacility/MessageService/MessageDrop.h"
00014 #else
00015 # include "messagefacility/MessageLogger/MessageLogger.h"
00016 #endif
00017 #include "messagefacility/Utilities/exception.h"
00018
00019
00020 #include <memory>
00021 #include <algorithm>
00022 #include <atomic>
00023 #include <mutex>
00024 #include <random>
00025 #include <arpa/inet.h>
00026 #include <ifaddrs.h>
00027 #include <netdb.h>
00028 #include <netinet/in.h>
00029 #include <boost/thread.hpp>
00030
00031 #include <QtCore/QString>
00032 #include "mfextensions/Destinations/detail/curl_send_message.h"
00033 #include "cetlib/compiler_macros.h"
00034
00035 namespace mfplugins
00036 {
00037 using mf::service::ELdestination;
00038 using mf::ELseverityLevel;
00039 using mf::ErrorObj;
00040
00044 class ELSMTP : public ELdestination
00045 {
00046 #if MESSAGEFACILITY_HEX_VERSION >= 0x20103
00047 struct Config
00048 {
00049 using strings_t = fhicl::Sequence<std::string>::default_type;
00050
00051 fhicl::TableFragment<ELdestination::Config> elDestConfig;
00052 fhicl::Atom<std::string> host{ fhicl::Name{ "host" },fhicl::Comment{ "SMTP Server hostname" }, "smtp.fnal.gov" };
00053 fhicl::Atom<int> port{ fhicl::Name{ "port" },fhicl::Comment{ "SMTP Server port" },25 };
00054 fhicl::Sequence<std::string> to{ fhicl::Name{ "to_addresses" }, fhicl::Comment{"The list of email addresses that SMTP mfPlugin should sent to" } , strings_t {} };
00055 fhicl::Atom<std::string> from{ fhicl::Name{ "from_address" },fhicl::Comment{ "Source email address" } };
00056 fhicl::Atom<std::string> subject{ fhicl::Name{ "subject" },fhicl::Comment{ "Subject of the email message" }, "MessageFacility SMTP Message Digest" };
00057 fhicl::Atom<std::string> messageHeader{ fhicl::Name{ "message_header" },fhicl::Comment{ "String to preface messages with in email body" }, "" };
00058 fhicl::Atom<bool> useSmtps{ fhicl::Name{ "use_smtps" },fhicl::Comment{ "Use SMTPS protocol" }, false };
00059 fhicl::Atom<std::string> user{ fhicl::Name{ "smtp_username" },fhicl::Comment{ "Username for SMTP server" }, "" };
00060 fhicl::Atom<std::string> pw{ fhicl::Name{ "smtp_password" },fhicl::Comment{ "Password for SMTP server" }, "" };
00061 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 };
00062 fhicl::Atom<size_t> sendInterval{ fhicl::Name{ "email_send_interval_seconds" },fhicl::Comment{ "Only send email every N seconds" }, 15 };
00063 };
00064 using Parameters = fhicl::WrappedTable<Config>;
00065 #endif
00066 public:
00071 #if MESSAGEFACILITY_HEX_VERSION < 0x20103 // v2_01_03 is s58, pre v2_01_03 is s50
00072 ELSMTP(const fhicl::ParameterSet& pset);
00073 #else
00074 ELSMTP(Parameters const& pset);
00075 #endif
00076
00077 ~ELSMTP()
00078 {
00079 abort_sleep_ = true;
00080 while (sending_thread_active_) usleep(1000);
00081 }
00082
00088 virtual void routePayload(const std::ostringstream& o, const ErrorObj& msg) override;
00089
00090 private:
00091 void send_message_();
00092 std::string generateMessageId_() const;
00093 std::string dateTimeNow_();
00094 std::string to_html(std::string msgString, const ErrorObj& msg);
00095
00096 std::string smtp_host_;
00097 int port_;
00098 std::vector<std::string> to_;
00099 std::string from_;
00100 std::string subject_;
00101 std::string message_prefix_;
00102
00103
00104 long pid_;
00105 std::string hostname_;
00106 std::string hostaddr_;
00107 std::string app_;
00108
00109 bool use_ssl_;
00110 std::string username_;
00111 std::string password_;
00112 bool ssl_verify_host_cert_;
00113
00114 std::atomic<bool> sending_thread_active_;
00115 std::atomic<bool> abort_sleep_;
00116 size_t send_interval_s_;
00117 mutable std::mutex message_mutex_;
00118 std::ostringstream message_contents_;
00119 };
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129 #if MESSAGEFACILITY_HEX_VERSION < 0x20103 // v2_01_03 is s58, pre v2_01_03 is s50
00130 ELSMTP::ELSMTP(const fhicl::ParameterSet& pset)
00131 : ELdestination(pset)
00132 , smtp_host_(pset.get<std::string>("host", "smtp.fnal.gov"))
00133 , port_(pset.get<int>("port", 25))
00134 , to_(pset.get<std::vector<std::string>>("to_addresses"))
00135 , from_(pset.get<std::string>("from_address"))
00136 , subject_(pset.get<std::string>("subject", "MessageFacility SMTP Message Digest"))
00137 , message_prefix_(pset.get<std::string>("message_header", ""))
00138 , pid_(static_cast<long>(getpid()))
00139 , use_ssl_(pset.get<bool>("use_smtps", false))
00140 , username_(pset.get<std::string>("smtp_username", ""))
00141 , password_(pset.get<std::string>("smtp_password", ""))
00142 , ssl_verify_host_cert_(pset.get<bool>("verify_host_ssl_certificate", true))
00143 , sending_thread_active_(false)
00144 , abort_sleep_(false)
00145 , send_interval_s_(pset.get<size_t>("email_send_interval_seconds", 15))
00146 #else
00147 ELSMTP::ELSMTP(Parameters const& pset)
00148 : ELdestination(pset().elDestConfig())
00149 , smtp_host_(pset().host())
00150 , port_(pset().port())
00151 , to_(pset().to())
00152 , from_(pset().from())
00153 , subject_(pset().subject())
00154 , message_prefix_(pset().messageHeader())
00155 , pid_(static_cast<long>(getpid()))
00156 , use_ssl_(pset().useSmtps())
00157 , username_(pset().user())
00158 , password_(pset().pw())
00159 , ssl_verify_host_cert_(pset().verifyCert())
00160 , sending_thread_active_(false)
00161 , abort_sleep_(false)
00162 , send_interval_s_(pset().sendInterval())
00163 #endif
00164 {
00165
00166 char hostname_c[1024];
00167 hostname_ = (gethostname(hostname_c, 1023) == 0) ? hostname_c : "Unkonwn Host";
00168
00169
00170 hostent* host = nullptr;
00171 host = gethostbyname(hostname_c);
00172
00173 if (host != nullptr)
00174 {
00175
00176 char* ip = inet_ntoa(*(struct in_addr *)host->h_addr);
00177 hostaddr_ = ip;
00178 }
00179 else
00180 {
00181
00182 struct ifaddrs* ifAddrStruct = nullptr;
00183 struct ifaddrs* ifa = nullptr;
00184 void* tmpAddrPtr = nullptr;
00185
00186 if (getifaddrs(&ifAddrStruct))
00187 {
00188
00189 hostaddr_ = "127.0.0.1";
00190 }
00191 else
00192 {
00193
00194 for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next)
00195 {
00196 if (ifa->ifa_addr->sa_family == AF_INET)
00197 {
00198
00199 tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
00200 char addressBuffer[INET_ADDRSTRLEN];
00201 inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
00202 hostaddr_ = addressBuffer;
00203 }
00204
00205 else if (ifa->ifa_addr->sa_family == AF_INET6)
00206 {
00207
00208 tmpAddrPtr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
00209 char addressBuffer[INET6_ADDRSTRLEN];
00210 inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
00211 hostaddr_ = addressBuffer;
00212 }
00213
00214
00215 if (!hostaddr_.empty()
00216 && hostaddr_.compare("127.0.0.1")
00217 && hostaddr_.compare("::1"))
00218 break;
00219 }
00220
00221 if (hostaddr_.empty())
00222 hostaddr_ = "127.0.0.1";
00223 }
00224 }
00225
00226
00227 std::string exe;
00228 std::ostringstream pid_ostr;
00229 pid_ostr << "/proc/" << pid_ << "/exe";
00230 exe = realpath(pid_ostr.str().c_str(), NULL);
00231
00232 size_t end = exe.find('\0');
00233 size_t start = exe.find_last_of('/', end);
00234
00235 app_ = exe.substr(start + 1, end - start - 1);
00236 }
00237
00238
00239 std::string ELSMTP::to_html(std::string msgString, const ErrorObj& msg)
00240 {
00241 auto sevid = msg.xid().severity().getLevel();
00242
00243 QString text_ = QString("<font color=");
00244
00245 switch (sevid)
00246 {
00247 case mf::ELseverityLevel::ELsev_success:
00248 case mf::ELseverityLevel::ELsev_zeroSeverity:
00249 case mf::ELseverityLevel::ELsev_unspecified:
00250 text_ += QString("#505050>");
00251 break;
00252
00253 case mf::ELseverityLevel::ELsev_info:
00254 text_ += QString("#008000>");
00255 break;
00256
00257 case mf::ELseverityLevel::ELsev_warning:
00258 text_ += QString("#E08000>");
00259 break;
00260
00261 case mf::ELseverityLevel::ELsev_error:
00262 case mf::ELseverityLevel::ELsev_severe:
00263 case mf::ELseverityLevel::ELsev_highestSeverity:
00264 text_ += QString("#FF0000>");
00265 break;
00266
00267 default: break;
00268 }
00269
00270
00271 text_ += QString("<pre>")
00272 + QString(msgString.c_str()).toHtmlEscaped()
00273 + QString("</pre>");
00274
00275 text_ += QString("</font>");
00276 return text_.toStdString();
00277 }
00278
00279
00280
00281
00282 void ELSMTP::routePayload(const std::ostringstream& oss, const ErrorObj& msg)
00283 {
00284 std::unique_lock<std::mutex>(message_mutex_);
00285 message_contents_ << to_html(oss.str(), msg);
00286
00287 if (!sending_thread_active_)
00288 {
00289 sending_thread_active_ = true;
00290 boost::thread t([=] { send_message_(); });
00291 t.detach();
00292 }
00293 }
00294
00295 void ELSMTP::send_message_()
00296 {
00297 size_t slept = 0;
00298 while (!abort_sleep_ && slept < send_interval_s_ * 1000000)
00299 {
00300 usleep(10000);
00301 slept += 10000;
00302 }
00303
00304 std::string payload;
00305 {
00306 std::unique_lock<std::mutex> lk(message_mutex_);
00307 payload = message_contents_.str();
00308 message_contents_.str("");
00309 }
00310 std::string destination = (use_ssl_ ? "smtps://" : "smtp://") + smtp_host_ + ":" + std::to_string(port_);
00311
00312
00313 std::vector<const char*> to;
00314 to.reserve(to_.size());
00315 std::string toString;
00316 for (size_t i = 0; i < to_.size(); ++i)
00317 {
00318 to.push_back(to_[i].c_str());
00319 toString += to_[i];
00320 if (i < to_.size() - 1)
00321 {
00322 toString += ", ";
00323 }
00324 }
00325
00326 std::ostringstream headers_builder;
00327
00328 headers_builder << "Date: " << dateTimeNow_() << "\r\n";
00329 headers_builder << "To: " << toString << "\r\n";
00330 headers_builder << "From: " << from_ << "\r\n";
00331 headers_builder << "Message-ID: <" + generateMessageId_() + "@" + from_.substr(from_.find('@') + 1) + ">\r\n";
00332 headers_builder << "Subject: " << subject_ << " @ " << dateTimeNow_() << " from PID " << getpid() << "\r\n";
00333 headers_builder << "Content-Type: text/html; charset=\"UTF-8\"\r\n";
00334 headers_builder << "\r\n";
00335
00336 std::string headers = headers_builder.str();
00337 std::ostringstream message_builder;
00338 message_builder << headers << "<html><body><p>" << message_prefix_ << "</p>" << payload << "</body></html>";
00339 std::string payloadWithHeaders = message_builder.str();
00340
00341
00342 if (use_ssl_)
00343 {
00344 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_);
00345 }
00346 else
00347 {
00348 send_message(destination.c_str(), &to[0], to_.size(), from_.c_str(), payloadWithHeaders.c_str(), payloadWithHeaders.size());
00349 }
00350 sending_thread_active_ = false;
00351 }
00352
00353
00354 std::string ELSMTP::generateMessageId_() const
00355 {
00356 const size_t MESSAGE_ID_LEN = 37;
00357 tm t;
00358 time_t tt;
00359 time(&tt);
00360 gmtime_r(&tt, &t);
00361
00362 std::string ret;
00363 ret.resize(MESSAGE_ID_LEN);
00364 size_t datelen = std::strftime(&ret[0], MESSAGE_ID_LEN, "%Y%m%d%H%M%S", &t);
00365 static const std::string alphaNum{
00366 "0123456789"
00367 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00368 "abcdefghijklmnopqrstuvwxyz" };
00369 std::mt19937 gen;
00370 std::uniform_int_distribution<> dis(0, alphaNum.length() - 1);
00371 std::generate_n(ret.begin() + datelen, MESSAGE_ID_LEN - datelen, [&]() { return alphaNum[dis(gen)]; });
00372 return ret;
00373 }
00374
00375 std::string ELSMTP::dateTimeNow_()
00376 {
00377 const int RFC5322_TIME_LEN = 32;
00378
00379 std::string ret;
00380 ret.resize(RFC5322_TIME_LEN);
00381
00382 time_t tt;
00383 tm tv, *t = &tv;
00384 tt = time(&tt);
00385 localtime_r(&tt, t);
00386
00387 strftime(&ret[0], RFC5322_TIME_LEN, "%a, %d %b %Y %H:%M:%S %z", t);
00388
00389 return ret;
00390 }
00391 }
00392
00393
00394
00395
00396
00397
00398
00399 #ifndef EXTERN_C_FUNC_DECLARE_START
00400 #define EXTERN_C_FUNC_DECLARE_START extern "C" {
00401 #endif
00402
00403 EXTERN_C_FUNC_DECLARE_START
00404 auto makePlugin(const std::string&,
00405 const fhicl::ParameterSet& pset)
00406 {
00407 return std::make_unique<mfplugins::ELSMTP>(pset);
00408 }
00409 }
00410
00411 DEFINE_BASIC_PLUGINTYPE_FUNC(mf::service::ELdestination)