| /* |
| * 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 <log4cxx/net/smtpappender.h> |
| #include <log4cxx/level.h> |
| #include <log4cxx/helpers/loglog.h> |
| #include <log4cxx/helpers/optionconverter.h> |
| #include <log4cxx/spi/loggingevent.h> |
| #include <log4cxx/helpers/stringhelper.h> |
| #include <log4cxx/helpers/stringtokenizer.h> |
| #include <log4cxx/helpers/transcoder.h> |
| #include <log4cxx/helpers/synchronized.h> |
| #if !defined(LOG4CXX) |
| #define LOG4CXX 1 |
| #endif |
| #include <log4cxx/private/log4cxx_private.h> |
| |
| |
| |
| #include <apr_strings.h> |
| #include <vector> |
| |
| using namespace log4cxx; |
| using namespace log4cxx::helpers; |
| using namespace log4cxx::net; |
| using namespace log4cxx::spi; |
| |
| #if LOG4CXX_HAVE_LIBESMTP |
| #include <auth-client.h> |
| #include <libesmtp.h> |
| #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 <code>event</code> the e-mail triggering event? |
| <p>This method returns <code>true</code>, if the event level |
| has ERROR level or higher. Otherwise it returns |
| <code>false</code>. |
| */ |
| 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 <code>evaluator</code> 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. |
| <p>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 <code>false</code> 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 <b>EvaluatorClass</b> 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 <b>BufferSize</b> option takes a positive integer |
| representing the maximum number of logging events to collect in a |
| cyclic buffer. When the <code>BufferSize</code> 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 <b>EvaluatorClass</b> 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); |
| } |
| |