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