001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.net;
015
016import static org.junit.Assert.assertEquals;
017import static org.junit.Assert.assertNotNull;
018import static org.junit.Assert.assertTrue;
019
020import java.io.ByteArrayOutputStream;
021import java.util.List;
022import java.util.Random;
023import java.util.concurrent.ThreadPoolExecutor;
024import java.util.concurrent.TimeUnit;
025
026import javax.mail.Part;
027import javax.mail.internet.MimeMessage;
028import javax.mail.internet.MimeMultipart;
029
030import org.dom4j.io.SAXReader;
031import org.junit.After;
032import org.junit.Before;
033import org.junit.Ignore;
034import org.junit.Test;
035import org.subethamail.smtp.auth.EasyAuthenticationHandlerFactory;
036import org.subethamail.smtp.auth.LoginFailedException;
037import org.subethamail.smtp.auth.UsernamePasswordValidator;
038import org.subethamail.wiser.Wiser;
039import org.subethamail.wiser.WiserMessage;
040
041import ch.qos.logback.classic.Logger;
042import ch.qos.logback.classic.LoggerContext;
043import ch.qos.logback.classic.PatternLayout;
044import ch.qos.logback.classic.html.HTMLLayout;
045import ch.qos.logback.classic.html.XHTMLEntityResolver;
046import ch.qos.logback.classic.spi.ILoggingEvent;
047import ch.qos.logback.core.CoreConstants;
048import ch.qos.logback.core.Layout;
049import ch.qos.logback.core.util.StatusPrinter;
050
051public class SMTPAppender_SubethaSMTPTest {
052    static final String TEST_SUBJECT = "test subject";
053    static final String HEADER = "HEADER\n";
054    static final String FOOTER = "FOOTER\n";
055
056    static int DIFF = 1024 + new Random().nextInt(30000);
057    static Wiser WISER;
058
059    SMTPAppender smtpAppender;
060    LoggerContext loggerContext = new LoggerContext();
061
062    int numberOfOldMessages;
063
064//    @BeforeClass
065//    static public void beforeClass() {
066//        WISER = new Wiser();
067//        WISER.setPort(DIFF);
068//        WISER.start();
069//    }
070
071//    @AfterClass
072//    static public void afterClass() throws Exception {
073//        WISER.stop();
074//    }
075
076    @Before
077    public void setUp() throws Exception {
078        WISER = new Wiser();
079        WISER.setPort(DIFF);
080        WISER.start();
081        numberOfOldMessages = WISER.getMessages().size();
082        buildSMTPAppender();
083    }
084
085    @After
086    public void tearDown() {
087        // clear any authentication handler factory
088        //WISER.getServer().setAuthenticationHandlerFactory(null);
089        WISER.stop();
090    }
091
092    void buildSMTPAppender() throws Exception {
093        smtpAppender = new SMTPAppender();
094        smtpAppender.setContext(loggerContext);
095        smtpAppender.setName("smtp");
096        smtpAppender.setFrom("user@host.dom");
097        smtpAppender.setSMTPHost("localhost");
098        smtpAppender.setSMTPPort(DIFF);
099        smtpAppender.setSubject(TEST_SUBJECT);
100        smtpAppender.addTo("noreply@qos.ch");
101    }
102
103    private Layout<ILoggingEvent> buildPatternLayout(LoggerContext lc) {
104        PatternLayout layout = new PatternLayout();
105        layout.setContext(lc);
106        layout.setFileHeader(HEADER);
107        layout.setPattern("%-4relative [%thread] %-5level %logger %class - %msg%n");
108        layout.setFileFooter(FOOTER);
109        layout.start();
110        return layout;
111    }
112
113    private Layout<ILoggingEvent> buildHTMLLayout(LoggerContext lc) {
114        HTMLLayout layout = new HTMLLayout();
115        layout.setContext(lc);
116        // layout.setFileHeader(HEADER);
117        layout.setPattern("%level%class%msg");
118        // layout.setFileFooter(FOOTER);
119        layout.start();
120        return layout;
121    }
122
123    private static String getWholeMessage(Part msg) {
124        try {
125            ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
126            msg.writeTo(bodyOut);
127            return bodyOut.toString("US-ASCII").trim();
128        } catch (Exception e) {
129            throw new RuntimeException(e);
130        }
131    }
132
133    void waitUntilEmailIsSent() throws Exception {
134        System.out.println("About to wait for sending thread to finish");
135        loggerContext.getExecutorService().shutdown();
136        loggerContext.getExecutorService().awaitTermination(3000, TimeUnit.MILLISECONDS);
137    }
138
139    private static String getBody(Part msg) {
140        String all = getWholeMessage(msg);
141        int i = all.indexOf("\r\n\r\n");
142        return all.substring(i + 4, all.length());
143    } 
144 
145    @Test
146    public void smoke() throws Exception {
147        smtpAppender.setLayout(buildPatternLayout(loggerContext));
148        smtpAppender.start();
149        Logger logger = loggerContext.getLogger("test");
150        logger.addAppender(smtpAppender);
151        logger.debug("hello");
152        logger.error("en error", new Exception("an exception"));
153
154        waitUntilEmailIsSent();
155        System.out.println("*** " + ((ThreadPoolExecutor) loggerContext.getExecutorService()).getCompletedTaskCount());
156        List<WiserMessage> wiserMsgList = WISER.getMessages();
157
158        assertNotNull(wiserMsgList);
159        assertEquals(numberOfOldMessages + 1, wiserMsgList.size());
160        WiserMessage wm = wiserMsgList.get(numberOfOldMessages);
161        // http://jira.qos.ch/browse/LBCLASSIC-67
162        MimeMessage mm = wm.getMimeMessage();
163        assertEquals(TEST_SUBJECT, mm.getSubject());
164
165        MimeMultipart mp = (MimeMultipart) mm.getContent();
166        String body = getBody(mp.getBodyPart(0));
167        System.out.println("[" + body);
168        assertTrue(body.startsWith(HEADER.trim()));
169        assertTrue(body.endsWith(FOOTER.trim()));
170    }
171
172    @Test
173    public void html() throws Exception {
174
175        smtpAppender.setLayout(buildHTMLLayout(loggerContext));
176        smtpAppender.start();
177        Logger logger = loggerContext.getLogger("test");
178        logger.addAppender(smtpAppender);
179        logger.debug("hello");
180        logger.error("en error", new Exception("an exception"));
181        waitUntilEmailIsSent();
182
183        List<WiserMessage> wiserMsgList = WISER.getMessages();
184
185        assertNotNull(wiserMsgList);
186        assertEquals(numberOfOldMessages + 1, wiserMsgList.size());
187        WiserMessage wm = wiserMsgList.get(numberOfOldMessages);
188        MimeMessage mm = wm.getMimeMessage();
189        assertEquals(TEST_SUBJECT, mm.getSubject());
190
191        MimeMultipart mp = (MimeMultipart) mm.getContent();
192
193        // verify strict adherence to xhtml1-strict.dtd
194        SAXReader reader = new SAXReader();
195        reader.setValidation(true);
196        reader.setEntityResolver(new XHTMLEntityResolver());
197        reader.read(mp.getBodyPart(0).getInputStream());
198        // System.out.println(GreenMailUtil.getBody(mp.getBodyPart(0)));
199    }
200
201    @Test
202    /**
203     * Checks that even when many events are processed, the output is still
204     * conforms to xhtml-strict.dtd.
205     *
206     * Note that SMTPAppender only keeps only 500 or so (=buffer size) events. So
207     * the generated output will be rather short.
208     */
209    public void htmlLong() throws Exception {
210        smtpAppender.setLayout(buildHTMLLayout(loggerContext));
211        smtpAppender.start();
212        Logger logger = loggerContext.getLogger("test");
213        logger.addAppender(smtpAppender);
214        for (int i = 0; i < CoreConstants.TABLE_ROW_LIMIT * 3; i++) {
215            logger.debug("hello " + i);
216        }
217        logger.error("en error", new Exception("an exception"));
218        waitUntilEmailIsSent();
219        List<WiserMessage> wiserMsgList = WISER.getMessages();
220
221        assertNotNull(wiserMsgList);
222        assertEquals(numberOfOldMessages + 1, wiserMsgList.size());
223        WiserMessage wm = wiserMsgList.get(numberOfOldMessages);
224        MimeMessage mm = wm.getMimeMessage();
225        assertEquals(TEST_SUBJECT, mm.getSubject());
226
227        MimeMultipart mp = (MimeMultipart) mm.getContent();
228
229        // verify strict adherence to xhtml1-strict.dtd
230        SAXReader reader = new SAXReader();
231        reader.setValidation(true);
232        reader.setEntityResolver(new XHTMLEntityResolver());
233        reader.read(mp.getBodyPart(0).getInputStream());
234    }
235
236    static String REQUIRED_USERNAME = "user";
237    static String REQUIRED_PASSWORD = "password";
238    
239    class RequiredUsernamePasswordValidator implements UsernamePasswordValidator {
240        public void login(String username, String password) throws LoginFailedException {
241            if (!username.equals(REQUIRED_USERNAME) || !password.equals(REQUIRED_PASSWORD)) {
242                throw new LoginFailedException();
243            }
244        }
245    }
246
247    
248    
249    
250    @Test
251    public void authenticated() throws Exception {
252        setAuthenticanHandlerFactory();
253        // MessageListenerAdapter mla = (MessageListenerAdapter) WISER.getServer().getMessageHandlerFactory();
254        // mla.setAuthenticationHandlerFactory(new TrivialAuthHandlerFactory());
255
256        smtpAppender.setUsername(REQUIRED_USERNAME);
257        smtpAppender.setPassword(REQUIRED_PASSWORD);
258
259        smtpAppender.setLayout(buildPatternLayout(loggerContext));
260        smtpAppender.start();
261        Logger logger = loggerContext.getLogger("test");
262        logger.addAppender(smtpAppender);
263        logger.debug("hello");
264        logger.error("en error", new Exception("an exception"));
265        waitUntilEmailIsSent();
266        List<WiserMessage> wiserMsgList = WISER.getMessages();
267
268        assertNotNull(wiserMsgList);
269        assertEquals(numberOfOldMessages + 1, wiserMsgList.size());
270        WiserMessage wm = wiserMsgList.get(numberOfOldMessages);
271        // http://jira.qos.ch/browse/LBCLASSIC-67
272        MimeMessage mm = wm.getMimeMessage();
273        assertEquals(TEST_SUBJECT, mm.getSubject());
274
275        MimeMultipart mp = (MimeMultipart) mm.getContent();
276        String body = getBody(mp.getBodyPart(0));
277        assertTrue(body.startsWith(HEADER.trim()));
278        assertTrue(body.endsWith(FOOTER.trim()));
279    }
280
281    private void setAuthenticanHandlerFactory() {
282        UsernamePasswordValidator validator = new RequiredUsernamePasswordValidator();
283        EasyAuthenticationHandlerFactory authenticationHandlerFactory = new EasyAuthenticationHandlerFactory(validator);
284        WISER.getServer().setAuthenticationHandlerFactory(authenticationHandlerFactory);
285    }
286
287    
288    // Unfortunately, there seems to be a problem with SubethaSMTP's implementation
289    // of startTLS. The same SMTPAppender code works fine when tested with gmail.
290    @Test
291    public void authenticatedSSL() throws Exception {
292        
293        setAuthenticanHandlerFactory();
294
295        smtpAppender.setSTARTTLS(true);
296        smtpAppender.setUsername(REQUIRED_USERNAME);
297        smtpAppender.setPassword(REQUIRED_PASSWORD);
298
299        smtpAppender.setLayout(buildPatternLayout(loggerContext));
300        smtpAppender.start();
301        Logger logger = loggerContext.getLogger("test");
302        logger.addAppender(smtpAppender);
303        logger.debug("hello");
304        logger.error("en error", new Exception("an exception"));
305
306        waitUntilEmailIsSent();
307        List<WiserMessage> wiserMsgList = WISER.getMessages();
308
309        assertNotNull(wiserMsgList);
310        assertEquals(1, wiserMsgList.size());
311    }
312
313    
314    static String GMAIL_USER_NAME = "xx@gmail.com";
315    static String GMAIL_PASSWORD = "xxx";
316    
317    @Ignore
318    @Test
319    public void authenticatedGmailStartTLS() throws Exception {
320        smtpAppender.setSMTPHost("smtp.gmail.com");
321        smtpAppender.setSMTPPort(587);
322        smtpAppender.setAsynchronousSending(false);
323        smtpAppender.addTo(GMAIL_USER_NAME);
324        
325        smtpAppender.setSTARTTLS(true);
326        smtpAppender.setUsername(GMAIL_USER_NAME);
327        smtpAppender.setPassword(GMAIL_PASSWORD);
328
329        smtpAppender.setLayout(buildPatternLayout(loggerContext));
330        smtpAppender.setSubject("authenticatedGmailStartTLS - %level %logger{20} - %m");
331        smtpAppender.start();
332        Logger logger = loggerContext.getLogger("authenticatedGmailSTARTTLS");
333        logger.addAppender(smtpAppender);
334        logger.debug("authenticatedGmailStartTLS =- hello");
335        logger.error("en error", new Exception("an exception"));
336
337        StatusPrinter.print(loggerContext);
338    }
339
340    @Ignore
341    @Test
342    public void authenticatedGmail_SSL() throws Exception {
343        smtpAppender.setSMTPHost("smtp.gmail.com");
344        smtpAppender.setSMTPPort(465);
345        smtpAppender.setSubject("authenticatedGmail_SSL - %level %logger{20} - %m");
346        smtpAppender.addTo(GMAIL_USER_NAME);
347        smtpAppender.setSSL(true);
348        smtpAppender.setUsername(GMAIL_USER_NAME);
349        smtpAppender.setPassword(GMAIL_PASSWORD);
350        smtpAppender.setAsynchronousSending(false);
351        smtpAppender.setLayout(buildPatternLayout(loggerContext));
352        smtpAppender.start();
353        Logger logger = loggerContext.getLogger("authenticatedGmail_SSL");
354        logger.addAppender(smtpAppender);
355        logger.debug("hello"+new java.util.Date());
356        logger.error("en error", new Exception("an exception"));
357
358        StatusPrinter.print(loggerContext);
359        
360        
361    }
362
363    @Test
364    public void testMultipleTo() throws Exception {
365        smtpAppender.setLayout(buildPatternLayout(loggerContext));
366        smtpAppender.addTo("Test <test@example.com>, other-test@example.com");
367        smtpAppender.start();
368        Logger logger = loggerContext.getLogger("test");
369        logger.addAppender(smtpAppender);
370        logger.debug("hello");
371        logger.error("en error", new Exception("an exception"));
372        waitUntilEmailIsSent();
373
374        List<WiserMessage> wiserMsgList = WISER.getMessages();
375        assertNotNull(wiserMsgList);
376        assertEquals(numberOfOldMessages + 3, wiserMsgList.size());
377    }
378
379//    public class TrivialAuthHandlerFactory implements AuthenticationHandlerFactory {
380//        public AuthenticationHandler create() {
381//            PluginAuthenticationHandler ret = new PluginAuthenticationHandler();
382//            UsernamePasswordValidator validator = new UsernamePasswordValidator() {
383//                public void login(String username, String password) throws LoginFailedException {
384//                    if (!username.equals(password)) {
385//                        throw new LoginFailedException("username=" + username + ", password=" + password);
386//                    }
387//                }
388//            };
389//            ret.addPlugin(new PlainAuthenticationHandler(validator));
390//            ret.addPlugin(new LoginAuthenticationHandler(validator));
391//            return ret;
392//        }
393//    }
394
395}