/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #if !defined(LOG4CXX) #define LOG4CXX 1 #endif #include #include #include using namespace log4cxx; using namespace log4cxx::helpers; using namespace log4cxx::net; using namespace log4cxx::spi; #if LOG4CXX_HAVE_LIBESMTP #include #include #endif namespace log4cxx { namespace net { // // The following two classes implement an C++ SMTP wrapper over libesmtp. // The same signatures could be implemented over different SMTP implementations // or libesmtp could be combined with libgmime to enable support for non-ASCII // content. #if LOG4CXX_HAVE_LIBESMTP /** * SMTP Session. */ class SMTPSession { public: /** * Create new instance. */ SMTPSession(const LogString& smtpHost, int smtpPort, const LogString& smtpUsername, const LogString& smtpPassword, Pool& p) : session(0), authctx(0), user(toAscii(smtpUsername, p)), pwd(toAscii(smtpPassword, p)) { auth_client_init(); session = smtp_create_session(); if (session == 0) { throw Exception("Could not initialize session."); } std::string host(toAscii(smtpHost, p)); host.append(1, ':'); host.append(p.itoa(smtpPort)); smtp_set_server(session, host.c_str()); authctx = auth_create_context(); auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0); auth_set_interact_cb(authctx, authinteract, (void*) this); if (*user || *pwd) { smtp_auth_set_context(session, authctx); } } ~SMTPSession() { smtp_destroy_session(session); auth_destroy_context(authctx); } void send(Pool& p) { int status = smtp_start_session(session); if (!status) { size_t bufSize = 128; char* buf = p.pstralloc(bufSize); smtp_strerror(smtp_errno(), buf, bufSize); throw Exception(buf); } } operator smtp_session_t() { return session; } static char* toAscii(const LogString& str, Pool& p) { char* buf = p.pstralloc(str.length() + 1); char* current = buf; for(LogString::const_iterator iter = str.begin(); iter != str.end(); iter++) { unsigned int c = *iter; if (c > 0x7F) { c = '?'; } *current++ = c; } *current = 0; return buf; } private: SMTPSession(SMTPSession&); SMTPSession& operator=(SMTPSession&); smtp_session_t session; auth_context_t authctx; char* user; char* pwd; /** * This method is called if the SMTP server requests authentication. */ static int authinteract(auth_client_request_t request, char **result, int fields, void *arg) { SMTPSession* pThis = (SMTPSession*) arg; for (int i = 0; i < fields; i++) { int flag = request[i].flags & 0x07; if (flag == AUTH_USER) { result[i] = pThis->user; } else if(flag == AUTH_PASS) { result[i] = pThis->pwd; } } return 1; } }; /** * A message in an SMTP session. */ class SMTPMessage { public: SMTPMessage(SMTPSession& session, const LogString& from, const LogString& to, const LogString& cc, const LogString& bcc, const LogString& subject, const LogString msg, Pool& p) { message = smtp_add_message(session); body = current = toMessage(msg, p); messagecbState = 0; smtp_set_reverse_path(message, toAscii(from, p)); addRecipients(to, "To", p); addRecipients(cc, "Cc", p); addRecipients(bcc, "Bcc", p); if (!subject.empty()) { smtp_set_header(message, "Subject", toAscii(subject, p)); } smtp_set_messagecb(message, messagecb, this); } ~SMTPMessage() { } private: SMTPMessage(const SMTPMessage&); SMTPMessage& operator=(const SMTPMessage&); smtp_message_t message; const char* body; const char* current; int messagecbState; void addRecipients(const LogString& addresses, const char* field, Pool& p) { if (!addresses.empty()) { char* str = p.pstrdup(toAscii(addresses, p));; smtp_set_header(message, field, NULL, str); char* last; for(char* next = apr_strtok(str, ",", &last); next; next = apr_strtok(NULL, ",", &last)) { smtp_add_recipient(message, next); } } } static const char* toAscii(const LogString& str, Pool& p) { return SMTPSession::toAscii(str, p); } /** * Message bodies can only contain US-ASCII characters and * CR and LFs can only occur together. */ static const char* toMessage(const LogString& str, Pool& p) { // // count the number of carriage returns and line feeds // int feedCount = 0; for(size_t pos = str.find_first_of(LOG4CXX_STR("\n\r")); pos != LogString::npos; pos = str.find_first_of(LOG4CXX_STR("\n\r"), ++pos)) { feedCount++; } // // allocate sufficient space for the modified message char* retval = p.pstralloc(str.length() + feedCount + 1); char* current = retval; char* startOfLine = current; // // iterator through message // for(LogString::const_iterator iter = str.begin(); iter != str.end(); iter++) { unsigned int c = *iter; // // replace non-ASCII characters with '?' // if (c > 0x7F) { *current++ = 0x3F; // '?' } else if (c == 0x0A || c == 0x0D) { // // replace any stray CR or LF with CRLF // reset start of line *current++ = 0x0D; *current++ = 0x0A; startOfLine = current; LogString::const_iterator next = iter + 1; if (next != str.end() && (*next == 0x0A || *next == 0x0D)) { iter++; } } else { // // truncate any lines to 1000 characters (including CRLF) // as required by RFC. if (current < startOfLine + 998) { *current++ = (char) c; } } } *current = 0; return retval; } /** * Callback for message. */ static const char* messagecb(void** ctx, int* len, void* arg) { *ctx = 0; const char* retval = 0; SMTPMessage* pThis = (SMTPMessage*) arg; // rewind message if (len == NULL) { pThis->current = pThis->body; } else { // we are asked for headers, but we don't have any if ((pThis->messagecbState)++ == 0) { return NULL; } if (pThis->current) { *len = strlen(pThis->current); } retval = pThis->current; pThis->current = 0; } return retval; } }; #endif class LOG4CXX_EXPORT DefaultEvaluator : public virtual spi::TriggeringEventEvaluator, public virtual helpers::ObjectImpl { public: DECLARE_LOG4CXX_OBJECT(DefaultEvaluator) BEGIN_LOG4CXX_CAST_MAP() LOG4CXX_CAST_ENTRY(DefaultEvaluator) LOG4CXX_CAST_ENTRY(spi::TriggeringEventEvaluator) END_LOG4CXX_CAST_MAP() DefaultEvaluator(); /** Is this event the e-mail triggering event?

This method returns true, if the event level has ERROR level or higher. Otherwise it returns false. */ virtual bool isTriggeringEvent(const spi::LoggingEventPtr& event); private: DefaultEvaluator(const DefaultEvaluator&); DefaultEvaluator& operator=(const DefaultEvaluator&); }; // class DefaultEvaluator } } IMPLEMENT_LOG4CXX_OBJECT(DefaultEvaluator) IMPLEMENT_LOG4CXX_OBJECT(SMTPAppender) DefaultEvaluator::DefaultEvaluator() { } bool DefaultEvaluator::isTriggeringEvent(const spi::LoggingEventPtr& event) { return event->getLevel()->isGreaterOrEqual(Level::getError()); } SMTPAppender::SMTPAppender() : smtpPort(25), bufferSize(512), locationInfo(false), cb(bufferSize), evaluator(new DefaultEvaluator()) { } /** Use evaluator passed as parameter as the TriggeringEventEvaluator for this SMTPAppender. */ SMTPAppender::SMTPAppender(spi::TriggeringEventEvaluatorPtr evaluator) : smtpPort(25), bufferSize(512), locationInfo(false), cb(bufferSize), evaluator(evaluator) { } SMTPAppender::~SMTPAppender() { finalize(); } bool SMTPAppender::requiresLayout() const { return true; } LogString SMTPAppender::getFrom() const { return from; } void SMTPAppender::setFrom(const LogString& newVal) { from = newVal; } LogString SMTPAppender::getSubject() const { return subject; } void SMTPAppender::setSubject(const LogString& newVal) { subject = newVal; } LogString SMTPAppender::getSMTPHost() const { return smtpHost; } void SMTPAppender::setSMTPHost(const LogString& newVal) { smtpHost = newVal; } int SMTPAppender::getSMTPPort() const { return smtpPort; } void SMTPAppender::setSMTPPort(int newVal) { smtpPort = newVal; } bool SMTPAppender::getLocationInfo() const { return locationInfo; } void SMTPAppender::setLocationInfo(bool newVal) { locationInfo = newVal; } LogString SMTPAppender::getSMTPUsername() const { return smtpUsername; } void SMTPAppender::setSMTPUsername(const LogString& newVal) { smtpUsername = newVal; } LogString SMTPAppender::getSMTPPassword() const { return smtpPassword; } void SMTPAppender::setSMTPPassword(const LogString& newVal) { smtpPassword = newVal; } void SMTPAppender::setOption(const LogString& option, const LogString& value) { if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize"))) { setBufferSize(OptionConverter::toInt(value, 512)); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("EVALUATORCLASS"), LOG4CXX_STR("evaluatorclass"))) { setEvaluatorClass(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FROM"), LOG4CXX_STR("from"))) { setFrom(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPHOST"), LOG4CXX_STR("smtphost"))) { setSMTPHost(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPUSERNAME"), LOG4CXX_STR("smtpusername"))) { setSMTPUsername(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPASSWORD"), LOG4CXX_STR("smtppassword"))) { setSMTPPassword(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SUBJECT"), LOG4CXX_STR("subject"))) { setSubject(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("TO"), LOG4CXX_STR("to"))) { setTo(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CC"), LOG4CXX_STR("cc"))) { setCc(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BCC"), LOG4CXX_STR("bcc"))) { setBcc(value); } else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPORT"), LOG4CXX_STR("smtpport"))) { setSMTPPort(OptionConverter::toInt(value, 25)); } else { AppenderSkeleton::setOption(option, value); } } bool SMTPAppender::asciiCheck(const LogString& value, const LogString& field) { for(LogString::const_iterator iter = value.begin(); iter != value.end(); iter++) { if (0x7F < (unsigned int) *iter) { LogLog::warn(field + LOG4CXX_STR(" contains non-ASCII character")); return false; } } return true; } /** Activate the specified options, such as the smtp host, the recipient, from, etc. */ void SMTPAppender::activateOptions(Pool& p) { bool activate = true; if (layout == 0) { errorHandler->error(LOG4CXX_STR("No layout set for appender named [") +name+ LOG4CXX_STR("].")); activate = false; } if(evaluator == 0) { errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [")+ name+LOG4CXX_STR("].")); activate = false; } if(smtpHost.empty()) { errorHandler->error(LOG4CXX_STR("No smtpHost is set for appender [")+ name+LOG4CXX_STR("].")); activate = false; } if(to.empty() && cc.empty() && bcc.empty()) { errorHandler->error(LOG4CXX_STR("No recipient address is set for appender [")+ name+LOG4CXX_STR("].")); activate = false; } activate &= asciiCheck(to, LOG4CXX_STR("to")); activate &= asciiCheck(cc, LOG4CXX_STR("cc")); activate &= asciiCheck(bcc, LOG4CXX_STR("bcc")); activate &= asciiCheck(from, LOG4CXX_STR("from")); #if !LOG4CXX_HAVE_LIBESMTP errorHandler->error(LOG4CXX_STR("log4cxx built without SMTP support.")); activate = false; #endif if (activate) { AppenderSkeleton::activateOptions(p); } } /** Perform SMTPAppender specific appending actions, mainly adding the event to a cyclic buffer and checking if the event triggers an e-mail to be sent. */ void SMTPAppender::append(const spi::LoggingEventPtr& event, Pool& p) { if(!checkEntryConditions()) { return; } LogString ndc; event->getNDC(ndc); event->getThreadName(); // Get a copy of this thread's MDC. event->getMDCCopy(); cb.add(event); if(evaluator->isTriggeringEvent(event)) { sendBuffer(p); } } /** This method determines if there is a sense in attempting to append.

It checks whether there is a set output target and also if there is a set layout. If these checks fail, then the boolean value false is returned. */ bool SMTPAppender::checkEntryConditions() { #if LOG4CXX_HAVE_LIBESMTP if((to.empty() && cc.empty() && bcc.empty()) || from.empty() || smtpHost.empty()) { errorHandler->error(LOG4CXX_STR("Message not configured.")); return false; } if(evaluator == 0) { errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [")+ name+ LOG4CXX_STR("].")); return false; } if(layout == 0) { errorHandler->error(LOG4CXX_STR("No layout set for appender named [")+name+LOG4CXX_STR("].")); return false; } return true; #else return false; #endif } void SMTPAppender::close() { this->closed = true; } LogString SMTPAppender::getTo() const{ return to; } void SMTPAppender::setTo(const LogString& addressStr) { to = addressStr; } LogString SMTPAppender::getCc() const{ return cc; } void SMTPAppender::setCc(const LogString& addressStr) { cc = addressStr; } LogString SMTPAppender::getBcc() const{ return bcc; } void SMTPAppender::setBcc(const LogString& addressStr) { bcc = addressStr; } /** Send the contents of the cyclic buffer as an e-mail message. */ void SMTPAppender::sendBuffer(Pool& p) { #if LOG4CXX_HAVE_LIBESMTP // Note: this code already owns the monitor for this // appender. This frees us from needing to synchronize on 'cb'. try { LogString sbuf; layout->appendHeader(sbuf, p); int len = cb.length(); for(int i = 0; i < len; i++) { LoggingEventPtr event = cb.get(); layout->format(sbuf, event, p); } layout->appendFooter(sbuf, p); SMTPSession session(smtpHost, smtpPort, smtpUsername, smtpPassword, p); SMTPMessage message(session, from, to, cc, bcc, subject, sbuf, p); session.send(p); } catch(std::exception& e) { LogLog::error(LOG4CXX_STR("Error occured while sending e-mail notification."), e); } #endif } /** Returns value of the EvaluatorClass option. */ LogString SMTPAppender::getEvaluatorClass() { return evaluator == 0 ? LogString() : evaluator->getClass().getName(); } log4cxx::spi::TriggeringEventEvaluatorPtr SMTPAppender::getEvaluator() const { return evaluator; } void SMTPAppender::setEvaluator(log4cxx::spi::TriggeringEventEvaluatorPtr& trigger) { evaluator = trigger; } /** The BufferSize option takes a positive integer representing the maximum number of logging events to collect in a cyclic buffer. When the BufferSize is reached, oldest events are deleted as new events are added to the buffer. By default the size of the cyclic buffer is 512 events. */ void SMTPAppender::setBufferSize(int sz) { this->bufferSize = sz; cb.resize(sz); } /** The EvaluatorClass option takes a string value representing the name of the class implementing the {@link TriggeringEventEvaluator} interface. A corresponding object will be instantiated and assigned as the triggering event evaluator for the SMTPAppender. */ void SMTPAppender::setEvaluatorClass(const LogString& value) { evaluator = OptionConverter::instantiateByClassName(value, TriggeringEventEvaluator::getStaticClass(), evaluator); }