blob: 3be6600e03d4f691d0a4d6077b928eb1707bab80 [file] [log] [blame]
/*
* 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);
}