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