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