You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2018/01/28 21:40:16 UTC
[2/3] logging-log4j2 git commit: [LOG4J2-2230] Split off SMTP support
into a new module log4j-smtp.
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/pom.xml
----------------------------------------------------------------------
diff --git a/log4j-smtp/pom.xml b/log4j-smtp/pom.xml
new file mode 100644
index 0000000..203db56
--- /dev/null
+++ b/log4j-smtp/pom.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ 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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>2.10.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>log4j-smtp</artifactId>
+ <name>Apache Log4j SMTP</name>
+ <description>
+ Apache Log4j Simple Mail Transfer Protocol (SMTP) Appender.
+ </description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j SMTP Appender Documentation</docLabel>
+ <projectDir>/log4j-smtp</projectDir>
+ <module.name>org.apache.logging.log4j.smtp</module.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <!-- Required for SMTPAppender -->
+ <dependency>
+ <groupId>com.sun.mail</groupId>
+ <artifactId>javax.mail</artifactId>
+ </dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <type>test-jar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>org.apache.logging.log4j.core.appender.mom.jeromq</Fragment-Host>
+ <Export-Package>*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${findbugs.plugin.version}</version>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java
new file mode 100644
index 0000000..e3aac4d
--- /dev/null
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+package org.apache.logging.log4j.core.appender;
+
+import java.io.Serializable;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.core.net.SmtpManager;
+import org.apache.logging.log4j.core.util.Booleans;
+
+/**
+ * Send an e-mail when a specific logging event occurs, typically on errors or
+ * fatal errors.
+ *
+ * <p>
+ * The number of logging events delivered in this e-mail depend on the value of
+ * <b>BufferSize</b> option. The <code>SmtpAppender</code> keeps only the last
+ * <code>BufferSize</code> logging events in its cyclic buffer. This keeps
+ * memory requirements at a reasonable level while still delivering useful
+ * application context.
+ *
+ * By default, an email message will formatted as HTML. This can be modified by
+ * setting a layout for the appender.
+ *
+ * By default, an email message will be sent when an ERROR or higher severity
+ * message is appended. This can be modified by setting a filter for the
+ * appender.
+ */
+@Plugin(name = "SMTP", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
+public final class SmtpAppender extends AbstractAppender {
+
+ private static final int DEFAULT_BUFFER_SIZE = 512;
+
+ /** The SMTP Manager */
+ private final SmtpManager manager;
+
+ private SmtpAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final SmtpManager manager,
+ final boolean ignoreExceptions) {
+ super(name, filter, layout, ignoreExceptions);
+ this.manager = manager;
+ }
+
+ /**
+ * Create a SmtpAppender.
+ *
+ * @param name
+ * The name of the Appender.
+ * @param to
+ * The comma-separated list of recipient email addresses.
+ * @param cc
+ * The comma-separated list of CC email addresses.
+ * @param bcc
+ * The comma-separated list of BCC email addresses.
+ * @param from
+ * The email address of the sender.
+ * @param replyTo
+ * The comma-separated list of reply-to email addresses.
+ * @param subject The subject of the email message.
+ * @param smtpProtocol The SMTP transport protocol (such as "smtps", defaults to "smtp").
+ * @param smtpHost
+ * The SMTP hostname to send to.
+ * @param smtpPortStr
+ * The SMTP port to send to.
+ * @param smtpUsername
+ * The username required to authenticate against the SMTP server.
+ * @param smtpPassword
+ * The password required to authenticate against the SMTP server.
+ * @param smtpDebug
+ * Enable mail session debuging on STDOUT.
+ * @param bufferSizeStr
+ * How many log events should be buffered for inclusion in the
+ * message?
+ * @param layout
+ * The layout to use (defaults to HtmlLayout).
+ * @param filter
+ * The Filter or null (defaults to ThresholdFilter, level of
+ * ERROR).
+ * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
+ * they are propagated to the caller.
+ * @return The SmtpAppender.
+ */
+ @PluginFactory
+ public static SmtpAppender createAppender(
+ @PluginConfiguration final Configuration config,
+ @PluginAttribute("name") @Required final String name,
+ @PluginAttribute("to") final String to,
+ @PluginAttribute("cc") final String cc,
+ @PluginAttribute("bcc") final String bcc,
+ @PluginAttribute("from") final String from,
+ @PluginAttribute("replyTo") final String replyTo,
+ @PluginAttribute("subject") final String subject,
+ @PluginAttribute("smtpProtocol") final String smtpProtocol,
+ @PluginAttribute("smtpHost") final String smtpHost,
+ @PluginAttribute(value = "smtpPort", defaultString = "0") @ValidPort final String smtpPortStr,
+ @PluginAttribute("smtpUsername") final String smtpUsername,
+ @PluginAttribute(value = "smtpPassword", sensitive = true) final String smtpPassword,
+ @PluginAttribute("smtpDebug") final String smtpDebug,
+ @PluginAttribute("bufferSize") final String bufferSizeStr,
+ @PluginElement("Layout") Layout<? extends Serializable> layout,
+ @PluginElement("Filter") Filter filter,
+ @PluginAttribute("ignoreExceptions") final String ignore) {
+ if (name == null) {
+ LOGGER.error("No name provided for SmtpAppender");
+ return null;
+ }
+
+ final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
+ final int smtpPort = AbstractAppender.parseInt(smtpPortStr, 0);
+ final boolean isSmtpDebug = Boolean.parseBoolean(smtpDebug);
+ final int bufferSize = bufferSizeStr == null ? DEFAULT_BUFFER_SIZE : Integer.parseInt(bufferSizeStr);
+
+ if (layout == null) {
+ layout = HtmlLayout.createDefaultLayout();
+ }
+ if (filter == null) {
+ filter = ThresholdFilter.createFilter(null, null, null);
+ }
+ final Configuration configuration = config != null ? config : new DefaultConfiguration();
+
+ final SmtpManager manager = SmtpManager.getSmtpManager(configuration, to, cc, bcc, from, replyTo, subject, smtpProtocol,
+ smtpHost, smtpPort, smtpUsername, smtpPassword, isSmtpDebug, filter.toString(), bufferSize);
+ if (manager == null) {
+ return null;
+ }
+
+ return new SmtpAppender(name, filter, layout, manager, ignoreExceptions);
+ }
+
+ /**
+ * Capture all events in CyclicBuffer.
+ * @param event The Log event.
+ * @return true if the event should be filtered.
+ */
+ @Override
+ public boolean isFiltered(final LogEvent event) {
+ final boolean filtered = super.isFiltered(event);
+ if (filtered) {
+ manager.add(event);
+ }
+ return filtered;
+ }
+
+ /**
+ * 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.
+ * @param event The Log event.
+ */
+ @Override
+ public void append(final LogEvent event) {
+ manager.sendEvents(getLayout(), event);
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/MimeMessageBuilder.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/MimeMessageBuilder.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/MimeMessageBuilder.java
new file mode 100644
index 0000000..b4d2549
--- /dev/null
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/MimeMessageBuilder.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.net;
+
+import java.nio.charset.StandardCharsets;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.logging.log4j.core.util.Builder;
+
+/**
+ * Builder for {@link MimeMessage} instances.
+ */
+public class MimeMessageBuilder implements Builder<MimeMessage> {
+ private final MimeMessage message;
+
+ public MimeMessageBuilder(final Session session) {
+ message = new MimeMessage(session);
+ }
+
+ public MimeMessageBuilder setFrom(final String from) throws MessagingException {
+ final InternetAddress address = parseAddress(from);
+
+ if (null != address) {
+ message.setFrom(address);
+ } else {
+ try {
+ message.setFrom();
+ } catch (final Exception ex) {
+ message.setFrom((InternetAddress) null);
+ }
+ }
+ return this;
+ }
+
+ public MimeMessageBuilder setReplyTo(final String replyTo) throws MessagingException {
+ final InternetAddress[] addresses = parseAddresses(replyTo);
+
+ if (null != addresses) {
+ message.setReplyTo(addresses);
+ }
+ return this;
+ }
+
+ public MimeMessageBuilder setRecipients(final Message.RecipientType recipientType, final String recipients)
+ throws MessagingException {
+ final InternetAddress[] addresses = parseAddresses(recipients);
+
+ if (null != addresses) {
+ message.setRecipients(recipientType, addresses);
+ }
+ return this;
+ }
+
+ public MimeMessageBuilder setSubject(final String subject) throws MessagingException {
+ if (subject != null) {
+ message.setSubject(subject, StandardCharsets.UTF_8.name());
+ }
+ return this;
+ }
+
+ /**
+ * @deprecated Use {@link #build()}.
+ */
+ @Deprecated
+ public MimeMessage getMimeMessage() {
+ return build();
+ }
+
+ @Override
+ public MimeMessage build() {
+ return message;
+ }
+
+ private static InternetAddress parseAddress(final String address) throws AddressException {
+ return address == null ? null : new InternetAddress(address);
+ }
+
+ private static InternetAddress[] parseAddresses(final String addresses) throws AddressException {
+ return addresses == null ? null : InternetAddress.parse(addresses, true);
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java
new file mode 100644
index 0000000..84b07fe
--- /dev/null
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java
@@ -0,0 +1,359 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.net;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Properties;
+
+import javax.activation.DataSource;
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractManager;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.impl.MutableLogEvent;
+import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.util.CyclicBuffer;
+import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.message.ReusableMessage;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * Manager for sending SMTP events.
+ */
+public class SmtpManager extends AbstractManager {
+ private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory();
+
+ private final Session session;
+
+ private final CyclicBuffer<LogEvent> buffer;
+
+ private volatile MimeMessage message;
+
+ private final FactoryData data;
+
+ private static MimeMessage createMimeMessage(final FactoryData data, final Session session, final LogEvent appendEvent)
+ throws MessagingException {
+ return new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
+ .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
+ .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject.toSerializable(appendEvent))
+ .build();
+ }
+
+ protected SmtpManager(final String name, final Session session, final MimeMessage message,
+ final FactoryData data) {
+ super(null, name);
+ this.session = session;
+ this.message = message;
+ this.data = data;
+ this.buffer = new CyclicBuffer<>(LogEvent.class, data.numElements);
+ }
+
+ public void add(LogEvent event) {
+ if (event instanceof Log4jLogEvent && event.getMessage() instanceof ReusableMessage) {
+ ((Log4jLogEvent) event).makeMessageImmutable();
+ } else if (event instanceof MutableLogEvent) {
+ event = ((MutableLogEvent) event).createMemento();
+ }
+ buffer.add(event);
+ }
+
+ public static SmtpManager getSmtpManager(
+ final Configuration config,
+ final String to, final String cc, final String bcc,
+ final String from, final String replyTo,
+ final String subject, String protocol, final String host,
+ final int port, final String username, final String password,
+ final boolean isDebug, final String filterName, final int numElements) {
+ if (Strings.isEmpty(protocol)) {
+ protocol = "smtp";
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ if (to != null) {
+ sb.append(to);
+ }
+ sb.append(':');
+ if (cc != null) {
+ sb.append(cc);
+ }
+ sb.append(':');
+ if (bcc != null) {
+ sb.append(bcc);
+ }
+ sb.append(':');
+ if (from != null) {
+ sb.append(from);
+ }
+ sb.append(':');
+ if (replyTo != null) {
+ sb.append(replyTo);
+ }
+ sb.append(':');
+ if (subject != null) {
+ sb.append(subject);
+ }
+ sb.append(':');
+ sb.append(protocol).append(':').append(host).append(':').append("port").append(':');
+ if (username != null) {
+ sb.append(username);
+ }
+ sb.append(':');
+ if (password != null) {
+ sb.append(password);
+ }
+ sb.append(isDebug ? ":debug:" : "::");
+ sb.append(filterName);
+
+ final String name = "SMTP:" + NameUtil.md5(sb.toString());
+ final Serializer subjectSerializer = PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(subject).build();
+
+ return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subjectSerializer,
+ protocol, host, port, username, password, isDebug, numElements));
+ }
+
+ /**
+ * Send the contents of the cyclic buffer as an e-mail message.
+ * @param layout The layout for formatting the events.
+ * @param appendEvent The event that triggered the send.
+ */
+ public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) {
+ if (message == null) {
+ connect(appendEvent);
+ }
+ try {
+ final LogEvent[] priorEvents = buffer.removeAll();
+ // LOG4J-310: log appendEvent even if priorEvents is empty
+
+ final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout);
+
+ final String contentType = layout.getContentType();
+ final String encoding = getEncoding(rawBytes, contentType);
+ final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding);
+
+ final InternetHeaders headers = getHeaders(contentType, encoding);
+ final MimeMultipart mp = getMimeMultipart(encodedBytes, headers);
+
+ sendMultipartMessage(message, mp);
+ } catch (final MessagingException | IOException | RuntimeException e) {
+ logError("Caught exception while sending e-mail notification.", e);
+ throw new LoggingException("Error occurred while sending email", e);
+ }
+ }
+
+ protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent,
+ final Layout<?> layout) throws IOException {
+ final ByteArrayOutputStream raw = new ByteArrayOutputStream();
+ writeContent(priorEvents, appendEvent, layout, raw);
+ return raw.toByteArray();
+ }
+
+ private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
+ final ByteArrayOutputStream out)
+ throws IOException {
+ writeHeader(layout, out);
+ writeBuffer(priorEvents, appendEvent, layout, out);
+ writeFooter(layout, out);
+ }
+
+ protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException {
+ final byte[] header = layout.getHeader();
+ if (header != null) {
+ out.write(header);
+ }
+ }
+
+ protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
+ final OutputStream out) throws IOException {
+ for (final LogEvent priorEvent : priorEvents) {
+ final byte[] bytes = layout.toByteArray(priorEvent);
+ out.write(bytes);
+ }
+
+ final byte[] bytes = layout.toByteArray(appendEvent);
+ out.write(bytes);
+ }
+
+ protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException {
+ final byte[] footer = layout.getFooter();
+ if (footer != null) {
+ out.write(footer);
+ }
+ }
+
+ protected String getEncoding(final byte[] rawBytes, final String contentType) {
+ final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType);
+ return MimeUtility.getEncoding(dataSource);
+ }
+
+ protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding)
+ throws MessagingException, IOException {
+ final ByteArrayOutputStream encoded = new ByteArrayOutputStream();
+ encodeContent(rawBytes, encoding, encoded);
+ return encoded.toByteArray();
+ }
+
+ protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out)
+ throws MessagingException, IOException {
+ try (final OutputStream encoder = MimeUtility.encode(out, encoding)) {
+ encoder.write(bytes);
+ }
+ }
+
+ protected InternetHeaders getHeaders(final String contentType, final String encoding) {
+ final InternetHeaders headers = new InternetHeaders();
+ headers.setHeader("Content-Type", contentType + "; charset=UTF-8");
+ headers.setHeader("Content-Transfer-Encoding", encoding);
+ return headers;
+ }
+
+ protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers)
+ throws MessagingException {
+ final MimeMultipart mp = new MimeMultipart();
+ final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes);
+ mp.addBodyPart(part);
+ return mp;
+ }
+
+ protected void sendMultipartMessage(final MimeMessage msg, final MimeMultipart mp) throws MessagingException {
+ synchronized (msg) {
+ msg.setContent(mp);
+ msg.setSentDate(new Date());
+ Transport.send(msg);
+ }
+ }
+
+ /**
+ * Factory data.
+ */
+ private static class FactoryData {
+ private final String to;
+ private final String cc;
+ private final String bcc;
+ private final String from;
+ private final String replyto;
+ private final Serializer subject;
+ private final String protocol;
+ private final String host;
+ private final int port;
+ private final String username;
+ private final String password;
+ private final boolean isDebug;
+ private final int numElements;
+
+ public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo,
+ final Serializer subjectSerializer, final String protocol, final String host, final int port,
+ final String username, final String password, final boolean isDebug, final int numElements) {
+ this.to = to;
+ this.cc = cc;
+ this.bcc = bcc;
+ this.from = from;
+ this.replyto = replyTo;
+ this.subject = subjectSerializer;
+ this.protocol = protocol;
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.isDebug = isDebug;
+ this.numElements = numElements;
+ }
+ }
+
+ private synchronized void connect(final LogEvent appendEvent) {
+ if (message != null) {
+ return;
+ }
+ try {
+ message = createMimeMessage(data, session, appendEvent);
+ } catch (final MessagingException e) {
+ logError("Could not set SmtpAppender message options", e);
+ message = null;
+ }
+ }
+
+ /**
+ * Factory to create the SMTP Manager.
+ */
+ private static class SMTPManagerFactory implements ManagerFactory<SmtpManager, FactoryData> {
+
+ @Override
+ public SmtpManager createManager(final String name, final FactoryData data) {
+ final String prefix = "mail." + data.protocol;
+
+ final Properties properties = PropertiesUtil.getSystemProperties();
+ properties.put("mail.transport.protocol", data.protocol);
+ if (properties.getProperty("mail.host") == null) {
+ // Prevent an UnknownHostException in Java 7
+ properties.put("mail.host", NetUtils.getLocalHostname());
+ }
+
+ if (null != data.host) {
+ properties.put(prefix + ".host", data.host);
+ }
+ if (data.port > 0) {
+ properties.put(prefix + ".port", String.valueOf(data.port));
+ }
+
+ final Authenticator authenticator = buildAuthenticator(data.username, data.password);
+ if (null != authenticator) {
+ properties.put(prefix + ".auth", "true");
+ }
+
+ final Session session = Session.getInstance(properties, authenticator);
+ session.setProtocolForAddress("rfc822", data.protocol);
+ session.setDebug(data.isDebug);
+ return new SmtpManager(name, session, null, data);
+ }
+
+ private Authenticator buildAuthenticator(final String username, final String password) {
+ if (null != password && null != username) {
+ return new Authenticator() {
+ private final PasswordAuthentication passwordAuthentication =
+ new PasswordAuthentication(username, password);
+
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return passwordAuthentication;
+ }
+ };
+ }
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/site/manual/index.md
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/site/manual/index.md b/log4j-smtp/src/site/manual/index.md
new file mode 100644
index 0000000..318d365
--- /dev/null
+++ b/log4j-smtp/src/site/manual/index.md
@@ -0,0 +1,33 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ 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.
+-->
+
+# Apache Log4j Simple Mail Transfer Protocol module
+
+As of Log4j 2.11.0, Simple Mail Transfer Protocol (SMTP) support has moved from the existing module logj-core to the new module log4j-smtp.
+
+## Requirements
+
+This module was introduced in Log4j 2.11.0 and requires the jeromq JAR.
+
+Some features may require optional
+[dependencies](../runtime-dependencies.html). These dependencies are specified in the
+documentation for those features.
+
+Some Log4j features require external dependencies.
+See the [Dependency Tree](dependencies.html#Dependency_Tree)
+for the exact list of JAR files needed for these features.
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/site/site.xml
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/site/site.xml b/log4j-smtp/src/site/site.xml
new file mode 100644
index 0000000..6d4cddc
--- /dev/null
+++ b/log4j-smtp/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+
+-->
+<project name="Log4j Core"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java
new file mode 100644
index 0000000..e43811c
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+package org.apache.logging.dumbster.smtp;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * Dummy SMTP server for testing purposes.
+ *
+ * @todo constructor allowing user to pass preinitialized ServerSocket
+ */
+public class SimpleSmtpServer implements Runnable {
+ /**
+ * Stores all of the email received since this instance started up.
+ */
+ private final List<SmtpMessage> receivedMail;
+
+ /**
+ * Default SMTP port is 25.
+ */
+ public static final int DEFAULT_SMTP_PORT = 25;
+
+ /**
+ * Indicates whether this server is stopped or not.
+ */
+ private volatile boolean stopped = true;
+
+ /**
+ * Handle to the server socket this server listens to.
+ */
+ private ServerSocket serverSocket;
+
+ /**
+ * Port the server listens on - set to the default SMTP port initially.
+ */
+ private int port = DEFAULT_SMTP_PORT;
+
+ /**
+ * Timeout listening on server socket.
+ */
+ private static final int TIMEOUT = 500;
+
+ /**
+ * Constructor.
+ *
+ * @param port port number
+ */
+ public SimpleSmtpServer(final int port) {
+ receivedMail = new ArrayList<>();
+ this.port = port;
+ }
+
+ /**
+ * Main loop of the SMTP server.
+ */
+ @Override
+ public void run() {
+ stopped = false;
+ try {
+ try {
+ serverSocket = new ServerSocket(port);
+ serverSocket.setSoTimeout(TIMEOUT); // Block for maximum of 1.5 seconds
+ } finally {
+ synchronized (this) {
+ // Notify when server socket has been created
+ notifyAll();
+ }
+ }
+
+ // Server: loop until stopped
+ while (!isStopped()) {
+ // Start server socket and listen for client connections
+ Socket socket = null;
+ try {
+ socket = serverSocket.accept();
+ } catch (final Exception e) {
+ if (socket != null) {
+ socket.close();
+ }
+ continue; // Non-blocking socket timeout occurred: try accept() again
+ }
+
+ // Get the input and output streams
+ final BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ final PrintWriter out = new PrintWriter(socket.getOutputStream());
+
+ synchronized (this) {
+ /*
+ * We synchronize over the handle method and the list update because the client call completes
+ * inside the handle method and we have to prevent the client from reading the list until we've
+ * updated it. For higher concurrency, we could just change handle to return void and update the
+ * list inside the method to limit the duration that we hold the lock.
+ */
+ final List<SmtpMessage> msgs = handleTransaction(out, input);
+ receivedMail.addAll(msgs);
+ }
+ socket.close();
+ }
+ } catch (final Exception e) {
+ /** @todo Should throw an appropriate exception here. */
+ e.printStackTrace();
+ } finally {
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if the server has been placed in a stopped state. Allows another thread to
+ * stop the server safely.
+ *
+ * @return true if the server has been sent a stop signal, false otherwise
+ */
+ public synchronized boolean isStopped() {
+ return stopped;
+ }
+
+ /**
+ * Stops the server. Server is shutdown after processing of the current request is complete.
+ */
+ public synchronized void stop() {
+ // Mark us closed
+ stopped = true;
+ try {
+ // Kick the server accept loop
+ serverSocket.close();
+ } catch (final IOException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Handle an SMTP transaction, i.e. all activity between initial connect and QUIT command.
+ *
+ * @param out output stream
+ * @param input input stream
+ * @return List of SmtpMessage
+ * @throws IOException
+ */
+ private List<SmtpMessage> handleTransaction(final PrintWriter out, final BufferedReader input) throws IOException {
+ // Initialize the state machine
+ SmtpState smtpState = SmtpState.CONNECT;
+ final SmtpRequest smtpRequest = new SmtpRequest(SmtpActionType.CONNECT, Strings.EMPTY, smtpState);
+
+ // Execute the connection request
+ final SmtpResponse smtpResponse = smtpRequest.execute();
+
+ // Send initial response
+ sendResponse(out, smtpResponse);
+ smtpState = smtpResponse.getNextState();
+
+ final List<SmtpMessage> msgList = new ArrayList<>();
+ SmtpMessage msg = new SmtpMessage();
+
+ while (smtpState != SmtpState.CONNECT) {
+ final String line = input.readLine();
+
+ if (line == null) {
+ break;
+ }
+
+ // Create request from client input and current state
+ final SmtpRequest request = SmtpRequest.createRequest(line, smtpState);
+ // Execute request and create response object
+ final SmtpResponse response = request.execute();
+ // Move to next internal state
+ smtpState = response.getNextState();
+ // Send response to client
+ sendResponse(out, response);
+
+ // Store input in message
+ final String params = request.getParams();
+ msg.store(response, params);
+
+ // If message reception is complete save it
+ if (smtpState == SmtpState.QUIT) {
+ msgList.add(msg);
+ msg = new SmtpMessage();
+ }
+ }
+
+ return msgList;
+ }
+
+ /**
+ * Send response to client.
+ *
+ * @param out socket output stream
+ * @param smtpResponse response object
+ */
+ private static void sendResponse(final PrintWriter out, final SmtpResponse smtpResponse) {
+ if (smtpResponse.getCode() > 0) {
+ final int code = smtpResponse.getCode();
+ final String message = smtpResponse.getMessage();
+ out.print(code + " " + message + "\r\n");
+ out.flush();
+ }
+ }
+
+ /**
+ * Get email received by this instance since start up.
+ *
+ * @return List of String
+ */
+ public synchronized Iterator<SmtpMessage> getReceivedEmail() {
+ return receivedMail.iterator();
+ }
+
+ /**
+ * Get the number of messages received.
+ *
+ * @return size of received email list
+ */
+ public synchronized int getReceivedEmailSize() {
+ return receivedMail.size();
+ }
+
+ /**
+ * Creates an instance of SimpleSmtpServer and starts it. Will listen on the default port.
+ *
+ * @return a reference to the SMTP server
+ */
+ public static SimpleSmtpServer start() {
+ return start(DEFAULT_SMTP_PORT);
+ }
+
+ /**
+ * Creates an instance of SimpleSmtpServer and starts it.
+ *
+ * @param port port number the server should listen to
+ * @return a reference to the SMTP server
+ */
+ public static SimpleSmtpServer start(final int port) {
+ final SimpleSmtpServer server = new SimpleSmtpServer(port);
+ final Thread t = new Thread(server);
+
+
+ // Block until the server socket is created
+ synchronized (server) {
+ t.start();
+ try {
+ server.wait();
+ } catch (final InterruptedException e) {
+ // Ignore don't care.
+ }
+ }
+ return server;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java
new file mode 100644
index 0000000..8709e8c
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+package org.apache.logging.dumbster.smtp;
+
+/**
+ * Represents an SMTP action or command.
+ */
+public class SmtpActionType {
+ /**
+ * Internal value for the action type.
+ */
+ private final byte value;
+
+ /**
+ * Internal representation of the CONNECT action.
+ */
+ private static final byte CONNECT_BYTE = (byte) 1;
+ /**
+ * Internal representation of the EHLO action.
+ */
+ private static final byte EHLO_BYTE = (byte) 2;
+ /**
+ * Internal representation of the MAIL FROM action.
+ */
+ private static final byte MAIL_BYTE = (byte) 3;
+ /**
+ * Internal representation of the RCPT action.
+ */
+ private static final byte RCPT_BYTE = (byte) 4;
+ /**
+ * Internal representation of the DATA action.
+ */
+ private static final byte DATA_BYTE = (byte) 5;
+ /**
+ * Internal representation of the DATA END (.) action.
+ */
+ private static final byte DATA_END_BYTE = (byte) 6;
+ /**
+ * Internal representation of the QUIT action.
+ */
+ private static final byte QUIT_BYTE = (byte) 7;
+ /**
+ * Internal representation of an unrecognized action: body text gets this action type.
+ */
+ private static final byte UNREC_BYTE = (byte) 8;
+ /**
+ * Internal representation of the blank line action: separates headers and body text.
+ */
+ private static final byte BLANK_LINE_BYTE = (byte) 9;
+
+ /**
+ * Internal representation of the stateless RSET action.
+ */
+ private static final byte RSET_BYTE = (byte) -1;
+ /**
+ * Internal representation of the stateless VRFY action.
+ */
+ private static final byte VRFY_BYTE = (byte) -2;
+ /**
+ * Internal representation of the stateless EXPN action.
+ */
+ private static final byte EXPN_BYTE = (byte) -3;
+ /**
+ * Internal representation of the stateless HELP action.
+ */
+ private static final byte HELP_BYTE = (byte) -4;
+ /**
+ * Internal representation of the stateless NOOP action.
+ */
+ private static final byte NOOP_BYTE = (byte) -5;
+
+ /**
+ * CONNECT action.
+ */
+ public static final SmtpActionType CONNECT = new SmtpActionType(CONNECT_BYTE);
+ /**
+ * EHLO action.
+ */
+ public static final SmtpActionType EHLO = new SmtpActionType(EHLO_BYTE);
+ /**
+ * MAIL action.
+ */
+ public static final SmtpActionType MAIL = new SmtpActionType(MAIL_BYTE);
+ /**
+ * RCPT action.
+ */
+ public static final SmtpActionType RCPT = new SmtpActionType(RCPT_BYTE);
+ /**
+ * DATA action.
+ */
+ public static final SmtpActionType DATA = new SmtpActionType(DATA_BYTE);
+ /**
+ * "." action.
+ */
+ public static final SmtpActionType DATA_END = new SmtpActionType(DATA_END_BYTE);
+ /**
+ * Body text action.
+ */
+ public static final SmtpActionType UNRECOG = new SmtpActionType(UNREC_BYTE);
+ /**
+ * QUIT action.
+ */
+ public static final SmtpActionType QUIT = new SmtpActionType(QUIT_BYTE);
+ /**
+ * Header/body separator action.
+ */
+ public static final SmtpActionType BLANK_LINE = new SmtpActionType(BLANK_LINE_BYTE);
+
+ /**
+ * Stateless RSET action.
+ */
+ public static final SmtpActionType RSET = new SmtpActionType(RSET_BYTE);
+ /**
+ * Stateless VRFY action.
+ */
+ public static final SmtpActionType VRFY = new SmtpActionType(VRFY_BYTE);
+ /**
+ * Stateless EXPN action.
+ */
+ public static final SmtpActionType EXPN = new SmtpActionType(EXPN_BYTE);
+ /**
+ * Stateless HELP action.
+ */
+ public static final SmtpActionType HELP = new SmtpActionType(HELP_BYTE);
+ /**
+ * Stateless NOOP action.
+ */
+ public static final SmtpActionType NOOP = new SmtpActionType(NOOP_BYTE);
+
+ /**
+ * Create a new SMTP action type. Private to ensure no invalid values.
+ *
+ * @param value one of the _BYTE values
+ */
+ private SmtpActionType(final byte value) {
+ this.value = value;
+ }
+
+ /**
+ * Indicates whether the action is stateless or not.
+ *
+ * @return true iff the action is stateless
+ */
+ public boolean isStateless() {
+ return value < 0;
+ }
+
+ /**
+ * String representation of this SMTP action type.
+ *
+ * @return a String
+ */
+ @Override
+ public String toString() {
+ switch (value) {
+ case CONNECT_BYTE:
+ return "Connect";
+ case EHLO_BYTE:
+ return "EHLO";
+ case MAIL_BYTE:
+ return "MAIL";
+ case RCPT_BYTE:
+ return "RCPT";
+ case DATA_BYTE:
+ return "DATA";
+ case DATA_END_BYTE:
+ return ".";
+ case QUIT_BYTE:
+ return "QUIT";
+ case RSET_BYTE:
+ return "RSET";
+ case VRFY_BYTE:
+ return "VRFY";
+ case EXPN_BYTE:
+ return "EXPN";
+ case HELP_BYTE:
+ return "HELP";
+ case NOOP_BYTE:
+ return "NOOP";
+ case UNREC_BYTE:
+ return "Unrecognized command / data";
+ case BLANK_LINE_BYTE:
+ return "Blank line";
+ default:
+ return "Unknown";
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java
new file mode 100644
index 0000000..967bb5e
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+package org.apache.logging.dumbster.smtp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Container for a complete SMTP message - headers and message body.
+ */
+public class SmtpMessage {
+ /**
+ * Headers: Map of List of String hashed on header name.
+ */
+ private final Map<String, List<String>> headers;
+ /**
+ * Message body.
+ */
+ private final StringBuffer body;
+
+ /**
+ * Constructor. Initializes headers Map and body buffer.
+ */
+ public SmtpMessage() {
+ headers = new HashMap<>(10);
+ body = new StringBuffer();
+ }
+
+ /**
+ * Update the headers or body depending on the SmtpResponse object and line of input.
+ *
+ * @param response SmtpResponse object
+ * @param params remainder of input line after SMTP command has been removed
+ */
+ public void store(final SmtpResponse response, final String params) {
+ if (params != null) {
+ if (SmtpState.DATA_HDR.equals(response.getNextState())) {
+ final int headerNameEnd = params.indexOf(':');
+ if (headerNameEnd >= 0) {
+ final String name = params.substring(0, headerNameEnd).trim();
+ final String value = params.substring(headerNameEnd + 1).trim();
+ addHeader(name, value);
+ }
+ } else if (SmtpState.DATA_BODY == response.getNextState()) {
+ body.append(params);
+ }
+ }
+ }
+
+ /**
+ * Get an Iterator over the header names.
+ *
+ * @return an Iterator over the set of header names (String)
+ */
+ public Iterator<String> getHeaderNames() {
+ final Set<String> nameSet = headers.keySet();
+ return nameSet.iterator();
+ }
+
+ /**
+ * Get the value(s) associated with the given header name.
+ *
+ * @param name header name
+ * @return value(s) associated with the header name
+ */
+ public String[] getHeaderValues(final String name) {
+ final List<String> values = headers.get(name);
+ if (values == null) {
+ return new String[0];
+ }
+ return values.toArray(new String[values.size()]);
+ }
+
+ /**
+ * Get the first values associated with a given header name.
+ *
+ * @param name header name
+ * @return first value associated with the header name
+ */
+ public String getHeaderValue(final String name) {
+ final List<String> values = headers.get(name);
+ if (values == null) {
+ return null;
+ }
+ final Iterator<String> iterator = values.iterator();
+ return iterator.hasNext() ? iterator.next() : null;
+ }
+
+ /**
+ * Get the message body.
+ *
+ * @return message body
+ */
+ public String getBody() {
+ return body.toString();
+ }
+
+ /**
+ * Adds a header to the Map.
+ *
+ * @param name header name
+ * @param value header value
+ */
+ private void addHeader(final String name, final String value) {
+ List<String> valueList = headers.get(name);
+ if (valueList == null) {
+ valueList = new ArrayList<>(1);
+ headers.put(name, valueList);
+ }
+ valueList.add(value);
+ }
+
+ /**
+ * String representation of the SmtpMessage.
+ *
+ * @return a String
+ */
+ @Override
+ public String toString() {
+ final StringBuilder msg = new StringBuilder();
+ for (final Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ final String name = entry.getKey();
+ final List<String> values = entry.getValue();
+ for (final String value : values) {
+ msg.append(name);
+ msg.append(": ");
+ msg.append(value);
+ msg.append('\n');
+ }
+ }
+ msg.append('\n');
+ msg.append(body);
+ msg.append('\n');
+ return msg.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpRequest.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpRequest.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpRequest.java
new file mode 100644
index 0000000..7f4a965
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpRequest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+package org.apache.logging.dumbster.smtp;
+
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * Contains an SMTP client request. Handles state transitions using the following state transition table.
+ * <PRE>
+ * -----------+-------------------------------------------------------------------------------------------------
+ * | State
+ * Action +-------------+-----------+-----------+--------------+---------------+---------------+------------
+ * | CONNECT | GREET | MAIL | RCPT | DATA_HDR | DATA_BODY | QUIT
+ * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------
+ * connect | 220/GREET | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
+ * ehlo | 503/CONNECT | 250/MAIL | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
+ * mail | 503/CONNECT | 503/GREET | 250/RCPT | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/RCPT
+ * rcpt | 503/CONNECT | 503/GREET | 503/MAIL | 250/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
+ * data | 503/CONNECT | 503/GREET | 503/MAIL | 354/DATA_HDR | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
+ * data_end | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 250/QUIT | 250/QUIT | 503/QUIT
+ * unrecog | 500/CONNECT | 500/GREET | 500/MAIL | 500/RCPT | ---/DATA_HDR | ---/DATA_BODY | 500/QUIT
+ * quit | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/CONNECT
+ * blank_line | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT
+ * rset | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET
+ * vrfy | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT
+ * expn | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT
+ * help | 211/CONNECT | 211/GREET | 211/MAIL | 211/RCPT | 211/DATA_HDR | 211/DATA_BODY | 211/QUIT
+ * noop | 250/CONNECT | 250/GREET | 250/MAIL | 250/RCPT | 250|DATA_HDR | 250/DATA_BODY | 250/QUIT
+ * </PRE>
+ */
+public class SmtpRequest {
+ /**
+ * SMTP action received from client.
+ */
+ private final SmtpActionType action;
+ /**
+ * Current state of the SMTP state table.
+ */
+ private final SmtpState state;
+ /**
+ * Additional information passed from the client with the SMTP action.
+ */
+ private final String params;
+
+ /**
+ * Create a new SMTP client request.
+ *
+ * @param actionType type of action/command
+ * @param params remainder of command line once command is removed
+ * @param state current SMTP server state
+ */
+ public SmtpRequest(final SmtpActionType actionType, final String params, final SmtpState state) {
+ this.action = actionType;
+ this.state = state;
+ this.params = params;
+ }
+
+ /**
+ * Execute the SMTP request returning a response. This method models the state transition table for the SMTP server.
+ *
+ * @return reponse to the request
+ */
+ public SmtpResponse execute() {
+ SmtpResponse response = null;
+ if (action.isStateless()) {
+ if (SmtpActionType.EXPN == action || SmtpActionType.VRFY == action) {
+ response = new SmtpResponse(252, "Not supported", this.state);
+ } else if (SmtpActionType.HELP == action) {
+ response = new SmtpResponse(211, "No help available", this.state);
+ } else if (SmtpActionType.NOOP == action) {
+ response = new SmtpResponse(250, "OK", this.state);
+ } else if (SmtpActionType.VRFY == action) {
+ response = new SmtpResponse(252, "Not supported", this.state);
+ } else if (SmtpActionType.RSET == action) {
+ response = new SmtpResponse(250, "OK", SmtpState.GREET);
+ } else {
+ response = new SmtpResponse(500, "Command not recognized", this.state);
+ }
+ } else { // Stateful commands
+ if (SmtpActionType.CONNECT == action) {
+ if (SmtpState.CONNECT == state) {
+ response = new SmtpResponse(220, "localhost Dumbster SMTP service ready", SmtpState.GREET);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.EHLO == action) {
+ if (SmtpState.GREET == state) {
+ response = new SmtpResponse(250, "OK", SmtpState.MAIL);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.MAIL == action) {
+ if (SmtpState.MAIL == state || SmtpState.QUIT == state) {
+ response = new SmtpResponse(250, "OK", SmtpState.RCPT);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.RCPT == action) {
+ if (SmtpState.RCPT == state) {
+ response = new SmtpResponse(250, "OK", this.state);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.DATA == action) {
+ if (SmtpState.RCPT == state) {
+ response = new SmtpResponse(354, "Start mail input; end with <CRLF>.<CRLF>", SmtpState.DATA_HDR);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.UNRECOG == action) {
+ if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) {
+ response = new SmtpResponse(-1, Strings.EMPTY, this.state);
+ } else {
+ response = new SmtpResponse(500, "Command not recognized", this.state);
+ }
+ } else if (SmtpActionType.DATA_END == action) {
+ if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) {
+ response = new SmtpResponse(250, "OK", SmtpState.QUIT);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.BLANK_LINE == action) {
+ if (SmtpState.DATA_HDR == state) {
+ response = new SmtpResponse(-1, Strings.EMPTY, SmtpState.DATA_BODY);
+ } else if (SmtpState.DATA_BODY == state) {
+ response = new SmtpResponse(-1, Strings.EMPTY, this.state);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else if (SmtpActionType.QUIT == action) {
+ if (SmtpState.QUIT == state) {
+ response = new SmtpResponse(221, "localhost Dumbster service closing transmission channel",
+ SmtpState.CONNECT);
+ } else {
+ response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state);
+ }
+ } else {
+ response = new SmtpResponse(500, "Command not recognized", this.state);
+ }
+ }
+ return response;
+ }
+
+ /**
+ * Create an SMTP request object given a line of the input stream from the client and the current internal state.
+ *
+ * @param s line of input
+ * @param state current state
+ * @return a populated SmtpRequest object
+ */
+ public static SmtpRequest createRequest(final String s, final SmtpState state) {
+ SmtpActionType action = null;
+ String params = null;
+
+ if (state == SmtpState.DATA_HDR) {
+ if (s.equals(".")) {
+ action = SmtpActionType.DATA_END;
+ } else if (s.length() < 1) {
+ action = SmtpActionType.BLANK_LINE;
+ } else {
+ action = SmtpActionType.UNRECOG;
+ params = s;
+ }
+ } else if (state == SmtpState.DATA_BODY) {
+ if (s.equals(".")) {
+ action = SmtpActionType.DATA_END;
+ } else {
+ action = SmtpActionType.UNRECOG;
+ if (s.length() < 1) {
+ params = "\n";
+ } else {
+ params = s;
+ }
+ }
+ } else {
+ final String su = s.toUpperCase();
+ if (su.startsWith("EHLO ") || su.startsWith("HELO")) {
+ action = SmtpActionType.EHLO;
+ params = s.substring(5);
+ } else if (su.startsWith("MAIL FROM:")) {
+ action = SmtpActionType.MAIL;
+ params = s.substring(10);
+ } else if (su.startsWith("RCPT TO:")) {
+ action = SmtpActionType.RCPT;
+ params = s.substring(8);
+ } else if (su.startsWith("DATA")) {
+ action = SmtpActionType.DATA;
+ } else if (su.startsWith("QUIT")) {
+ action = SmtpActionType.QUIT;
+ } else if (su.startsWith("RSET")) {
+ action = SmtpActionType.RSET;
+ } else if (su.startsWith("NOOP")) {
+ action = SmtpActionType.NOOP;
+ } else if (su.startsWith("EXPN")) {
+ action = SmtpActionType.EXPN;
+ } else if (su.startsWith("VRFY")) {
+ action = SmtpActionType.VRFY;
+ } else if (su.startsWith("HELP")) {
+ action = SmtpActionType.HELP;
+ } else {
+ action = SmtpActionType.UNRECOG;
+ }
+ }
+
+ final SmtpRequest req = new SmtpRequest(action, params, state);
+ return req;
+ }
+
+ /**
+ * Get the parameters of this request (remainder of command line once the command is removed.
+ *
+ * @return parameters
+ */
+ public String getParams() {
+ return params;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java
new file mode 100644
index 0000000..8b72ba7
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package org.apache.logging.dumbster.smtp;
+
+/**
+ * SMTP response container.
+ */
+public class SmtpResponse {
+ /**
+ * Response code - see RFC-2821.
+ */
+ private final int code;
+ /**
+ * Response message.
+ */
+ private final String message;
+ /**
+ * New state of the SMTP server once the request has been executed.
+ */
+ private final SmtpState nextState;
+
+ /**
+ * Constructor.
+ *
+ * @param code response code
+ * @param message response message
+ * @param next next state of the SMTP server
+ */
+ public SmtpResponse(final int code, final String message, final SmtpState next) {
+ this.code = code;
+ this.message = message;
+ this.nextState = next;
+ }
+
+ /**
+ * Get the response code.
+ *
+ * @return response code
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * Get the response message.
+ *
+ * @return response message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Get the next SMTP server state.
+ *
+ * @return state
+ */
+ public SmtpState getNextState() {
+ return nextState;
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java
new file mode 100644
index 0000000..3c83545
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+package org.apache.logging.dumbster.smtp;
+
+/**
+ * SMTP server state.
+ */
+public class SmtpState {
+ /**
+ * Internal representation of the state.
+ */
+ private final byte value;
+
+ /**
+ * Internal representation of the CONNECT state.
+ */
+ private static final byte CONNECT_BYTE = (byte) 1;
+ /**
+ * Internal representation of the GREET state.
+ */
+ private static final byte GREET_BYTE = (byte) 2;
+ /**
+ * Internal representation of the MAIL state.
+ */
+ private static final byte MAIL_BYTE = (byte) 3;
+ /**
+ * Internal representation of the RCPT state.
+ */
+ private static final byte RCPT_BYTE = (byte) 4;
+ /**
+ * Internal representation of the DATA_HEADER state.
+ */
+ private static final byte DATA_HEADER_BYTE = (byte) 5;
+ /**
+ * Internal representation of the DATA_BODY state.
+ */
+ private static final byte DATA_BODY_BYTE = (byte) 6;
+ /**
+ * Internal representation of the QUIT state.
+ */
+ private static final byte QUIT_BYTE = (byte) 7;
+
+ /**
+ * CONNECT state: waiting for a client connection.
+ */
+ public static final SmtpState CONNECT = new SmtpState(CONNECT_BYTE);
+ /**
+ * GREET state: wating for a ELHO message.
+ */
+ public static final SmtpState GREET = new SmtpState(GREET_BYTE);
+ /**
+ * MAIL state: waiting for the MAIL FROM: command.
+ */
+ public static final SmtpState MAIL = new SmtpState(MAIL_BYTE);
+ /**
+ * RCPT state: waiting for a RCPT <email address> command.
+ */
+ public static final SmtpState RCPT = new SmtpState(RCPT_BYTE);
+ /**
+ * Waiting for headers.
+ */
+ public static final SmtpState DATA_HDR = new SmtpState(DATA_HEADER_BYTE);
+ /**
+ * Processing body text.
+ */
+ public static final SmtpState DATA_BODY = new SmtpState(DATA_BODY_BYTE);
+ /**
+ * End of client transmission.
+ */
+ public static final SmtpState QUIT = new SmtpState(QUIT_BYTE);
+
+ /**
+ * Create a new SmtpState object. Private to ensure that only valid states can be created.
+ *
+ * @param value one of the _BYTE values.
+ */
+ private SmtpState(final byte value) {
+ this.value = value;
+ }
+
+ /**
+ * String representation of this SmtpState.
+ *
+ * @return a String
+ */
+ @Override
+ public String toString() {
+ switch (value) {
+ case CONNECT_BYTE:
+ return "CONNECT";
+ case GREET_BYTE:
+ return "GREET";
+ case MAIL_BYTE:
+ return "MAIL";
+ case RCPT_BYTE:
+ return "RCPT";
+ case DATA_HEADER_BYTE:
+ return "DATA_HDR";
+ case DATA_BODY_BYTE:
+ return "DATA_BODY";
+ case QUIT_BYTE:
+ return "QUIT";
+ default:
+ return "Unknown";
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/readme.txt
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/readme.txt b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/readme.txt
new file mode 100644
index 0000000..aded490
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/dumbster/smtp/readme.txt
@@ -0,0 +1,4 @@
+Dumbster was an open source project hosted at Sourceforge. Unfortunately, that project has not had any commits
+to it since 2005. The code was copied here to be able to address a race condition in SimpleSmtpServer.
+
+Dumbster is Copyright 2004 Jason Paul Kitchen
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b667d0e1/log4j-smtp/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java
----------------------------------------------------------------------
diff --git a/log4j-smtp/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java b/log4j-smtp/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java
new file mode 100644
index 0000000..91503d3
--- /dev/null
+++ b/log4j-smtp/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.appender;
+
+import org.apache.logging.dumbster.smtp.SimpleSmtpServer;
+import org.apache.logging.dumbster.smtp.SmtpMessage;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.AvailablePortFinder;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class SmtpAppenderAsyncTest {
+
+ private static int PORT;
+
+ private SimpleSmtpServer smtpServer;
+
+ @BeforeClass
+ public static void setupClass() {
+ PORT = AvailablePortFinder.getNextAvailable();
+ System.setProperty("smtp.port", String.valueOf(PORT));
+ }
+
+ @Before
+ public void setup() {
+ smtpServer = SimpleSmtpServer.start(PORT);
+ }
+
+ @Rule
+ public LoggerContextRule ctx = new LoggerContextRule("SmtpAppenderAsyncTest.xml");
+
+ @Test
+ public void testSync() {
+ testSmtpAppender(ctx.getLogger("sync"));
+ }
+
+ @Test
+ public void testAsync() {
+ testSmtpAppender(ctx.getLogger("async"));
+ }
+
+ private void testSmtpAppender(Logger logger) {
+ ThreadContext.put("MDC1", "mdc1");
+ logger.error("the message");
+ ctx.getLoggerContext().stop();
+ smtpServer.stop();
+
+ assertEquals(1, smtpServer.getReceivedEmailSize());
+ final Iterator<SmtpMessage> messages = smtpServer.getReceivedEmail();
+ final SmtpMessage email = messages.next();
+
+ assertEquals("to@example.com", email.getHeaderValue("To"));
+ assertEquals("from@example.com", email.getHeaderValue("From"));
+ assertEquals("[mdc1]", email.getHeaderValue("Subject"));
+
+ String body = email.getBody();
+ if (!body.contains("Body:[mdc1]")) {
+ fail(body);
+ }
+ }
+
+ @After
+ public void teardown() {
+ if (smtpServer != null) {
+ smtpServer.stop();
+ }
+ }
+
+ @AfterClass
+ public static void teardownClass() {
+ System.clearProperty("smtp.port");
+ }
+}
\ No newline at end of file