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