[logback-dev] svn commit: r1830 - in logback/trunk: logback-classic logback-classic/src/test/java/ch/qos/logback/classic/net logback-core/src/main/java/ch/qos/logback/core/net

noreply.ceki at qos.ch noreply.ceki at qos.ch
Wed Oct 15 00:03:28 CEST 2008


Author: ceki
Date: Wed Oct 15 00:03:28 2008
New Revision: 1830

Added:
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppender_SubethaSMTPTest.java
   logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/LoginAuthenticator.java
Modified:
   logback/trunk/logback-classic/pom.xml
   logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/SMTPAppenderBase.java

Log:
LBCORE-17

Added support for plain user password authentication. 

Furthermore, support for both STARTTLS and SSL connections were
added. Note that STARTTLS differs from SSL in that, in STARTTLS, the
connection is non-encrypted in the beginning and only after the STARTTLS 
command is issued by the client (if the server supports it) does the 
connection switch to SSL. In SSL mode, the connection is SSL from the 
start.


Modified: logback/trunk/logback-classic/pom.xml
==============================================================================
--- logback/trunk/logback-classic/pom.xml	(original)
+++ logback/trunk/logback-classic/pom.xml	Wed Oct 15 00:03:28 2008
@@ -100,10 +100,6 @@
       <optional>true</optional>
     </dependency>
 
-
-        
-
-
     <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>servlet-api</artifactId>
@@ -118,6 +114,18 @@
       <scope>test</scope>
     </dependency>
     
+    <dependency>
+      <groupId>org.subethamail</groupId>
+      <artifactId>subethasmtp</artifactId>
+      <version>2.1.0</version>
+      <scope>test</scope>
+       <exclusions>
+        <exclusion>  
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-api</artifactId>
+        </exclusion>
+      </exclusions> 
+    </dependency>    
   </dependencies>
 
   <build>

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppender_SubethaSMTPTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppender_SubethaSMTPTest.java	Wed Oct 15 00:03:28 2008
@@ -0,0 +1,314 @@
+package ch.qos.logback.classic.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.Random;
+
+import javax.mail.Part;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.dom4j.io.SAXReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.subethamail.smtp.AuthenticationHandler;
+import org.subethamail.smtp.AuthenticationHandlerFactory;
+import org.subethamail.smtp.auth.LoginAuthenticationHandler;
+import org.subethamail.smtp.auth.LoginFailedException;
+import org.subethamail.smtp.auth.PlainAuthenticationHandler;
+import org.subethamail.smtp.auth.PluginAuthenticationHandler;
+import org.subethamail.smtp.auth.UsernamePasswordValidator;
+import org.subethamail.smtp.server.MessageListenerAdapter;
+import org.subethamail.wiser.Wiser;
+import org.subethamail.wiser.WiserMessage;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.html.HTMLLayout;
+import ch.qos.logback.classic.html.XHTMLEntityResolver;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.CoreGlobal;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.util.StatusPrinter;
+
+public class SMTPAppender_SubethaSMTPTest {
+
+  int diff = 1024 + new Random().nextInt(10000);
+  Wiser wiser;
+
+  SMTPAppender smtpAppender;
+  LoggerContext lc = new LoggerContext();
+
+  static final String TEST_SUBJECT = "test subject";
+  static final String HEADER = "HEADER\n";
+  static final String FOOTER = "FOOTER\n";
+
+  @Before
+  public void setUp() throws Exception { 
+    wiser = new Wiser();
+    wiser.setPort(diff); 
+    wiser.getServer();
+    wiser.start();
+    //StartTLSCommand s;
+    buildSMTPAppender();
+  }
+
+  void buildSMTPAppender() throws Exception {
+    smtpAppender = new SMTPAppender();
+    smtpAppender.setContext(lc);
+    smtpAppender.setName("smtp");
+    smtpAppender.setFrom("user at host.dom");
+    smtpAppender.setSMTPHost("localhost");
+    smtpAppender.setSMTPPort(diff);
+    smtpAppender.setSubject(TEST_SUBJECT);
+    smtpAppender.addTo("noreply at qos.ch");
+  }
+
+  private Layout<LoggingEvent> buildPatternLayout(LoggerContext lc) {
+    PatternLayout layout = new PatternLayout();
+    layout.setContext(lc);
+    layout.setFileHeader(HEADER);
+    layout.setPattern("%-4relative [%thread] %-5level %logger %class - %msg%n");
+    layout.setFileFooter(FOOTER);
+    layout.start();
+    return layout;
+  }
+
+  private Layout<LoggingEvent> buildHTMLLayout(LoggerContext lc) {
+    HTMLLayout layout = new HTMLLayout();
+    layout.setContext(lc);
+    // layout.setFileHeader(HEADER);
+    layout.setPattern("%level%class%msg");
+    // layout.setFileFooter(FOOTER);
+    layout.start();
+    return layout;
+  }
+
+  @After
+  public void tearDown() throws Exception {
+  }
+
+  private static String getWholeMessage(Part msg) {
+    try {
+      ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
+      msg.writeTo(bodyOut);
+      return bodyOut.toString("US-ASCII").trim();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+  
+  private static String getBody(Part msg) {
+    String all = getWholeMessage(msg);
+    int i = all.indexOf("\r\n\r\n");
+    return all.substring(i + 4, all.length());
+}
+
+  @Test
+  public void smoke() throws Exception {
+    smtpAppender.setLayout(buildPatternLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("test");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+    List<WiserMessage> wiserMsgList = wiser.getMessages();
+    
+    assertNotNull(wiserMsgList);
+    assertEquals(1, wiserMsgList.size());
+    WiserMessage wm = wiserMsgList.get(0);
+    // http://jira.qos.ch/browse/LBCLASSIC-67
+    MimeMessage mm = wm.getMimeMessage();
+    assertEquals(TEST_SUBJECT, mm.getSubject());
+
+    MimeMultipart mp = (MimeMultipart) mm.getContent();
+    String body = getBody(mp.getBodyPart(0));
+    System.out.println("["+body);
+    assertTrue(body.startsWith(HEADER.trim()));
+    assertTrue(body.endsWith(FOOTER.trim()));
+  }
+
+  @Test
+  public void html() throws Exception {
+    smtpAppender.setLayout(buildHTMLLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("test");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+    
+    List<WiserMessage> wiserMsgList = wiser.getMessages();
+    
+    assertNotNull(wiserMsgList);
+    assertEquals(1, wiserMsgList.size());
+    WiserMessage wm = wiserMsgList.get(0);
+    MimeMessage mm = wm.getMimeMessage();
+    assertEquals(TEST_SUBJECT, mm.getSubject());
+
+    MimeMultipart mp = (MimeMultipart) mm.getContent();
+
+    // verify strict adherence to xhtml1-strict.dtd
+    SAXReader reader = new SAXReader();
+    reader.setValidation(true);
+    reader.setEntityResolver(new XHTMLEntityResolver());
+    reader.read(mp.getBodyPart(0).getInputStream());
+    // System.out.println(GreenMailUtil.getBody(mp.getBodyPart(0)));
+  }
+
+  @Test
+  /**
+   * Checks that even when many events are processed, the output is still
+   * conforms to xhtml-strict.dtd.
+   * 
+   * Note that SMTPAppender only keeps only 500 or so (=buffer size) events. So
+   * the generated output will be rather short.
+   */
+  public void htmlLong() throws Exception {
+    smtpAppender.setLayout(buildHTMLLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("test");
+    logger.addAppender(smtpAppender);
+    for (int i = 0; i < CoreGlobal.TABLE_ROW_LIMIT * 3; i++) {
+      logger.debug("hello " + i);
+    }
+    logger.error("en error", new Exception("an exception"));
+    List<WiserMessage> wiserMsgList = wiser.getMessages();
+    
+    assertNotNull(wiserMsgList);
+    assertEquals(1, wiserMsgList.size());
+    WiserMessage wm = wiserMsgList.get(0);
+    MimeMessage mm = wm.getMimeMessage();
+    assertEquals(TEST_SUBJECT, mm.getSubject());
+
+    MimeMultipart mp = (MimeMultipart) mm.getContent();
+
+    // verify strict adherence to xhtml1-strict.dtd
+    SAXReader reader = new SAXReader();
+    reader.setValidation(true);
+    reader.setEntityResolver(new XHTMLEntityResolver());
+    reader.read(mp.getBodyPart(0).getInputStream());
+  }
+  
+  @Test
+  public void authenticated() throws Exception {
+    MessageListenerAdapter mla = (MessageListenerAdapter)wiser.getServer().getMessageHandlerFactory();
+    mla.setAuthenticationHandlerFactory(new TrivialAuthHandlerFactory());
+
+    smtpAppender.setUsername("x");
+    smtpAppender.setPassword("x");
+    
+    smtpAppender.setLayout(buildPatternLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("test");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+
+    List<WiserMessage> wiserMsgList = wiser.getMessages();
+
+    assertNotNull(wiserMsgList);
+    assertEquals(1, wiserMsgList.size());
+    WiserMessage wm = wiserMsgList.get(0);
+    // http://jira.qos.ch/browse/LBCLASSIC-67
+    MimeMessage mm = wm.getMimeMessage();
+    assertEquals(TEST_SUBJECT, mm.getSubject());
+
+    MimeMultipart mp = (MimeMultipart) mm.getContent();
+    String body = getBody(mp.getBodyPart(0));
+    assertTrue(body.startsWith(HEADER.trim()));
+    assertTrue(body.endsWith(FOOTER.trim()));
+  }
+  
+  @Test
+  @Ignore 
+  // Unfortunately, there seems to be a problem with SubethaSMTP's implementation
+  // of startTLS. The same SMTPAppender code works fine when tested with gmail.
+  public void authenticatedSSL() throws Exception {
+    MessageListenerAdapter mla = (MessageListenerAdapter)wiser.getServer().getMessageHandlerFactory();
+    mla.setAuthenticationHandlerFactory(new TrivialAuthHandlerFactory());
+    
+    smtpAppender.setStartTLS(true);
+    smtpAppender.setUsername("xx");
+    smtpAppender.setPassword("xx");    
+  
+    smtpAppender.setLayout(buildPatternLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("test");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+
+    StatusPrinter.print(lc);
+    List<WiserMessage> wiserMsgList = wiser.getMessages();
+
+    assertNotNull(wiserMsgList);
+    assertEquals(1, wiserMsgList.size());
+  }
+  
+  @Test
+  @Ignore
+  public void authenticatedGmailStartTLS() throws Exception {
+    smtpAppender.setSMTPHost("smtp.gmail.com");
+    smtpAppender.setSMTPPort(587);
+    
+    smtpAppender.addTo("XXX at gmail.com");
+    smtpAppender.setStartTLS(true);
+    smtpAppender.setUsername("XXX at gmail.com");
+    smtpAppender.setPassword("XXX");    
+  
+    smtpAppender.setLayout(buildPatternLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("authenticatedGmailSTARTTLS");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+
+    StatusPrinter.print(lc);
+  }
+  
+  @Test
+  @Ignore
+  public void authenticatedGmail_SSL() throws Exception {
+    smtpAppender.setSMTPHost("smtp.gmail.com");
+    smtpAppender.setSMTPPort(465);
+    
+    smtpAppender.addTo("XXX at gmail.com");
+    smtpAppender.setSsl(true);
+    smtpAppender.setUsername("XXX at gmail.com");
+    smtpAppender.setPassword("XXX");    
+  
+    smtpAppender.setLayout(buildPatternLayout(lc));
+    smtpAppender.start();
+    Logger logger = lc.getLogger("authenticatedGmail_SSL");
+    logger.addAppender(smtpAppender);
+    logger.debug("hello");
+    logger.error("en error", new Exception("an exception"));
+
+    StatusPrinter.print(lc);
+  }
+  
+  public class TrivialAuthHandlerFactory implements AuthenticationHandlerFactory {
+    public AuthenticationHandler create() {
+      PluginAuthenticationHandler ret = new PluginAuthenticationHandler();
+      UsernamePasswordValidator validator = new UsernamePasswordValidator() {
+        public void login(String username, String password)
+            throws LoginFailedException {
+          if(!username.equals(password)) {
+            throw new LoginFailedException("username="+username+", password="+password);
+          }
+        }
+      };
+      ret.addPlugin(new PlainAuthenticationHandler(validator));
+      ret.addPlugin(new LoginAuthenticationHandler(validator));
+      return ret;
+    }
+  }
+
+}

Added: logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/LoginAuthenticator.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/LoginAuthenticator.java	Wed Oct 15 00:03:28 2008
@@ -0,0 +1,20 @@
+package ch.qos.logback.core.net;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+public class LoginAuthenticator extends Authenticator {
+
+  String username;
+  String password;
+  
+  LoginAuthenticator(String username, String password) {
+    this.username = username;
+    this.password = password;
+  }
+  
+  public PasswordAuthentication getPasswordAuthentication() {
+    return new PasswordAuthentication(username, password);
+}
+
+}

Modified: logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/SMTPAppenderBase.java
==============================================================================
--- logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/SMTPAppenderBase.java	(original)
+++ logback/trunk/logback-core/src/main/java/ch/qos/logback/core/net/SMTPAppenderBase.java	Wed Oct 15 00:03:28 2008
@@ -32,15 +32,24 @@
 import ch.qos.logback.core.boolex.EventEvaluator;
 
 /**
- * An abstract class that provides basic support for sending events to an email
+ * An abstract class that provides support for sending events to an email
  * address.
  * 
+ * <p>
+ * Authentication through plain user password is supported. Both STARTTLS and SSL are 
+ * also supported. Note that STARTTLS differs from SSL in that, in STARTTLS, the connection 
+ * is non-encrypted and only after the STARTTLS command is issued by the client 
+ * (if the server supports it) does the connection switch to SSL. In SSL mode, the connection 
+ * is SSL from the start.
+ * 
  * @author Ceki G&uuml;lc&uuml;
  * @author S&eacute;bastien Pennec
  * 
  */
 public abstract class SMTPAppenderBase<E> extends AppenderBase<E> {
 
+  // private final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
+
   protected Layout<E> layout;
   protected Layout<E> subjectLayout;
 
@@ -49,7 +58,12 @@
   private String subjectStr = null;
   private String smtpHost;
   private int smtpPort = 25;
-  
+  private boolean startTLS = false;
+  private boolean ssl = false;
+
+  String username;
+  String password;
+
   protected Message msg;
 
   protected EventEvaluator eventEvaluator;
@@ -74,10 +88,32 @@
       props.put("mail.smtp.host", smtpHost);
     }
     props.put("mail.smtp.port", Integer.toString(smtpPort));
+
+    LoginAuthenticator loginAuthenticator = null;
+
+    if (username != null) {
+      loginAuthenticator = new LoginAuthenticator(username, password);
+      props.put("mail.smtp.auth", "true");
+    }
+
+    if (isStartTLS() && isSsl()) {
+      addError("Both SSL and StartTLS cannot be enabled simultaneously");
+    } else {
+      if (isStartTLS()) {
+        props.setProperty("mail.smtp.auth", "true");
+        props.put("mail.smtp.starttls.enable", "true");
+      }
+      if (isSsl()) {
+        String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
+        props.put("mail.smtp.socketFactory.port", Integer.toString(smtpPort));
+        props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
+        props.put("mail.smtp.socketFactory.fallback", "true");
+      }
+    }
+
+    // props.put("mail.debug", "true");
     
-    
-    Session session = Session.getInstance(props, null);
-    // session.setDebug(true);
+    Session session = Session.getInstance(props, loginAuthenticator);
     msg = new MimeMessage(session);
 
     try {
@@ -131,10 +167,11 @@
    */
   public boolean checkEntryConditions() {
     if (!this.started) {
-      addError("Attempting to append to a non-started appender: " + this.getName());
+      addError("Attempting to append to a non-started appender: "
+          + this.getName());
       return false;
     }
-    
+
     if (this.msg == null) {
       addError("Message object not configured.");
       return false;
@@ -174,7 +211,7 @@
     for (int i = 0; i < addressList.size(); i++) {
       try {
         InternetAddress[] tmp = InternetAddress.parse(addressList.get(i), true);
-        //one <To> element should contain one email address
+        // one <To> element should contain one email address
         iaArray[i] = tmp[0];
       } catch (AddressException e) {
         addError("Could not parse address [" + addressList.get(i) + "].", e);
@@ -221,7 +258,6 @@
       if (footer != null) {
         sbuf.append(footer);
       }
- 
 
       if (subjectLayout != null) {
         msg.setSubject(subjectLayout.doLayout(lastEventObject));
@@ -288,14 +324,14 @@
   }
 
   /**
-   * The port where the SMTP server is running. Default value is 25. 
+   * The port where the SMTP server is running. Default value is 25.
    * 
    * @param port
    */
   public void setSMTPPort(int port) {
     this.smtpPort = port;
   }
-  
+
   /**
    * @see #setSMTPPort(int)
    * @return
@@ -303,10 +339,10 @@
   public int getSMTPPort() {
     return smtpPort;
   }
-  
+
   /**
-   * The <b>To</b> option takes a string value which should be 
-   * an e-mail address of one of the recipients.
+   * The <b>To</b> option takes a string value which should be an e-mail
+   * address of one of the recipients.
    */
   public void addTo(String to) {
     this.to.add(to);
@@ -322,6 +358,22 @@
     this.msg = msg;
   }
 
+  public boolean isStartTLS() {
+    return startTLS;
+  }
+
+  public void setStartTLS(boolean startTLS) {
+    this.startTLS = startTLS;
+  }
+
+  public boolean isSsl() {
+    return ssl;
+  }
+
+  public void setSsl(boolean ssl) {
+    this.ssl = ssl;
+  }
+
   /**
    * The <b>EventEvaluator</b> option takes a string value representing the
    * name of the class implementing the {@link EventEvaluators} interface. A
@@ -339,4 +391,21 @@
   public void setLayout(Layout<E> layout) {
     this.layout = layout;
   }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
 }


More information about the logback-dev mailing list