1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.classic.blackbox.net;
15
16 import java.io.ByteArrayInputStream;
17 import java.io.ByteArrayOutputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.util.concurrent.ExecutorService;
21 import java.util.concurrent.TimeUnit;
22
23 import ch.qos.logback.classic.Level;
24 import ch.qos.logback.classic.blackbox.BlackboxClassicTestConstants;
25 import ch.qos.logback.classic.net.SMTPAppender;
26 import ch.qos.logback.classic.util.LogbackMDCAdapter;
27 import ch.qos.logback.core.util.EnvUtil;
28 import com.icegreen.greenmail.util.*;
29 import org.dom4j.DocumentException;
30 import org.dom4j.io.SAXReader;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Disabled;
34 import org.junit.jupiter.api.Test;
35 import org.slf4j.LoggerFactory;
36 import org.slf4j.MDC;
37
38
39 import ch.qos.logback.classic.Logger;
40 import ch.qos.logback.classic.LoggerContext;
41 import ch.qos.logback.classic.PatternLayout;
42 import ch.qos.logback.classic.html.HTMLLayout;
43 import ch.qos.logback.classic.blackbox.html.XHTMLEntityResolver;
44 import ch.qos.logback.classic.joran.JoranConfigurator;
45 import ch.qos.logback.classic.spi.ILoggingEvent;
46 import ch.qos.logback.core.Layout;
47 import ch.qos.logback.core.joran.spi.JoranException;
48 import ch.qos.logback.core.status.OnConsoleStatusListener;
49 import ch.qos.logback.core.testUtil.RandomUtil;
50 import ch.qos.logback.core.util.StatusListenerConfigHelper;
51 import ch.qos.logback.core.util.StatusPrinter;
52 import jakarta.mail.MessagingException;
53 import jakarta.mail.internet.MimeMessage;
54 import jakarta.mail.internet.MimeMultipart;
55
56 import static org.junit.jupiter.api.Assertions.*;
57
58 public class SMTPAppender_GreenTest {
59
60 static boolean NO_SSL = false;
61 static boolean WITH_SSL = true;
62
63 static final String HEADER = "HEADER\n";
64 static final String FOOTER = "FOOTER\n";
65 static final String DEFAULT_PATTERN = "%-4relative %mdc [%thread] %-5level %class - %msg%n";
66
67 static final String JAVAX_NET_DEBUG_KEY = "javax.net.debug";
68 static final String SSH_HANDSHAKE_DEBUG_VAL = "ssl:handshake";
69
70 static final String CHECK_SERVER_IDENTITY_KEY = "mail.smtp.ssl.checkserveridentity";
71
72 static final boolean SYNCHRONOUS = false;
73 static final boolean ASYNCHRONOUS = true;
74 static int TIMEOUT = 3000;
75
76 int port = RandomUtil.getRandomServerPort();
77
78 GreenMail greenMailServer;
79
80 SMTPAppender smtpAppender;
81 LoggerContext loggerContext = new LoggerContext();
82 LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
83 Logger logger = loggerContext.getLogger(this.getClass());
84
85 static String REQUIRED_USERNAME = "alice";
86 static String REQUIRED_PASSWORD = "alicepass";
87
88
89 @BeforeEach
90 public void setUp() throws Exception {
91 loggerContext.setMDCAdapter(logbackMDCAdapter);
92 StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new OnConsoleStatusListener());
93 setGlobalLogbackLoggerForGreenmail(Level.INFO);
94 }
95
96
97 @AfterEach
98 public void tearDown() throws Exception {
99 greenMailServer.stop();
100 setGlobalLogbackLoggerForGreenmail(null);
101 }
102
103 private static void setGlobalLogbackLoggerForGreenmail(Level level) {
104 Logger logbackLogger = (Logger) LoggerFactory.getLogger("com.icegreen.greenmail");
105 logbackLogger.setLevel(level);
106 }
107
108
109 void startSMTPServer(boolean withSSL) {
110 ServerSetup serverSetup;
111
112 if (withSSL) {
113 serverSetup = new ServerSetup(port, null, ServerSetup.PROTOCOL_SMTPS);
114 } else {
115 serverSetup = new ServerSetup(port, null, ServerSetup.PROTOCOL_SMTP);
116 }
117 greenMailServer = new GreenMail(serverSetup);
118
119 greenMailServer.setUser(REQUIRED_USERNAME, REQUIRED_PASSWORD);
120 greenMailServer.start();
121
122 try {
123 Thread.sleep(10);
124 } catch (InterruptedException e) {
125 }
126 }
127
128
129 void buildSMTPAppender(String subject, boolean synchronicity) throws Exception {
130 smtpAppender = new SMTPAppender();
131 smtpAppender.setContext(loggerContext);
132 smtpAppender.setName("smtp");
133 smtpAppender.setFrom("user@host.dom");
134 smtpAppender.setSMTPHost("localhost");
135 smtpAppender.setSMTPPort(port);
136 smtpAppender.setSubject(subject);
137 smtpAppender.addTo("nospam@qos.ch");
138 smtpAppender.setAsynchronousSending(synchronicity);
139 }
140
141 private Layout<ILoggingEvent> buildPatternLayout(String pattern) {
142 PatternLayout layout = new PatternLayout();
143 layout.setContext(loggerContext);
144 layout.setFileHeader(HEADER);
145 layout.setOutputPatternAsHeader(false);
146 layout.setPattern(pattern);
147 layout.setFileFooter(FOOTER);
148 layout.start();
149 return layout;
150 }
151
152 private Layout<ILoggingEvent> buildHTMLLayout() {
153 HTMLLayout layout = new HTMLLayout();
154 layout.setContext(loggerContext);
155 layout.setPattern("%level%class%msg");
156 layout.start();
157 return layout;
158 }
159
160 private void waitForServerToReceiveEmails(int emailCount) throws InterruptedException {
161 greenMailServer.waitForIncomingEmail(5000, emailCount);
162 }
163
164 private MimeMultipart verifyAndExtractMimeMultipart(String subject)
165 throws MessagingException, IOException, InterruptedException {
166 int oldCount = 0;
167 int expectedEmailCount = 1;
168
169 waitForServerToReceiveEmails(expectedEmailCount);
170 MimeMessage[] mma = greenMailServer.getReceivedMessages();
171 assertNotNull(mma);
172 assertEquals(expectedEmailCount, mma.length);
173 MimeMessage mm = mma[oldCount];
174
175 assertEquals(subject, mm.getSubject());
176 return (MimeMultipart) mm.getContent();
177 }
178
179
180
181 void waitUntilEmailIsSent() throws InterruptedException {
182 ExecutorService es = loggerContext.getExecutorService();
183 es.shutdown();
184 boolean terminated = es.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS);
185
186
187 if(!terminated && !EnvUtil.isMacOs()) {
188 fail("executor elapsed before accorded delay " + System.getProperty("os.name"));
189 }
190
191 }
192
193 @Test
194 public void synchronousSmoke() throws Exception {
195 startSMTPServer(NO_SSL);
196 String subject = "synchronousSmoke";
197 buildSMTPAppender(subject, SYNCHRONOUS);
198
199 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
200
201 smtpAppender.start();
202
203 logger.addAppender(smtpAppender);
204 logger.debug("hello");
205 logger.error("en error", new Exception("an exception"));
206
207 MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
208 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
209 assertTrue(body.startsWith(HEADER.trim()));
210 assertTrue(body.endsWith(FOOTER.trim()));
211 }
212
213 @Test
214 public void asynchronousSmoke() throws Exception {
215 startSMTPServer(NO_SSL);
216
217 String subject = "asynchronousSmoke";
218 buildSMTPAppender(subject, ASYNCHRONOUS);
219 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
220 smtpAppender.start();
221
222 logger.addAppender(smtpAppender);
223 logger.debug("hello");
224 logger.error("en error", new Exception("an exception"));
225
226 waitUntilEmailIsSent();
227 MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
228 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
229 assertTrue(body.startsWith(HEADER.trim()));
230 assertTrue(body.endsWith(FOOTER.trim()));
231 }
232
233
234 @Test
235 public void callerDataShouldBeCorrectlySetWithAsynchronousSending() throws Exception {
236 startSMTPServer(NO_SSL);
237 String subject = "LOGBACK-734";
238 buildSMTPAppender("LOGBACK-734", ASYNCHRONOUS);
239 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
240 smtpAppender.setIncludeCallerData(true);
241 smtpAppender.start();
242 logger.addAppender(smtpAppender);
243 logger.debug("LOGBACK-734");
244 logger.error("callerData", new Exception("ShouldBeCorrectlySetWithAsynchronousSending"));
245
246 waitUntilEmailIsSent();
247 MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
248 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
249 assertTrue(body.contains("DEBUG " + this.getClass().getName() + " - LOGBACK-734"), "actual [" + body + "]");
250 }
251
252
253 @Test
254 public void LOGBACK_352() throws Exception {
255 startSMTPServer(NO_SSL);
256 String subject = "LOGBACK_352";
257 buildSMTPAppender(subject, SYNCHRONOUS);
258 smtpAppender.setAsynchronousSending(false);
259 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
260 smtpAppender.start();
261 logger.addAppender(smtpAppender);
262 logbackMDCAdapter.put("key", "val");
263 logger.debug("LBCLASSIC_104");
264 logbackMDCAdapter.clear();
265 logger.error("en error", new Exception("test"));
266
267 MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
268 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
269 assertTrue(body.startsWith(HEADER.trim()));
270 System.out.println(body);
271 assertTrue(body.contains("key=val"));
272 assertTrue(body.endsWith(FOOTER.trim()));
273 }
274
275 @Test
276 public void html() throws Exception {
277 startSMTPServer(NO_SSL);
278 String subject = "html";
279 buildSMTPAppender(subject, SYNCHRONOUS);
280 smtpAppender.setAsynchronousSending(false);
281 smtpAppender.setLayout(buildHTMLLayout());
282 smtpAppender.start();
283 logger.addAppender(smtpAppender);
284 logger.debug("html");
285 logger.error("en error", new Exception("an exception"));
286
287 MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
288
289
290 SAXReader reader = new SAXReader();
291 reader.setValidation(true);
292 reader.setEntityResolver(new XHTMLEntityResolver());
293 byte[] messageBytes = getAsByteArray(mp.getBodyPart(0).getInputStream());
294 ByteArrayInputStream bais = new ByteArrayInputStream(messageBytes);
295 try {
296 reader.read(bais);
297 } catch (DocumentException de) {
298 System.out.println("incoming message:");
299 System.out.println(new String(messageBytes));
300 throw de;
301 }
302 System.out.println("incoming message:");
303 System.out.println(new String(messageBytes));
304 }
305
306 private byte[] getAsByteArray(InputStream inputStream) throws IOException {
307 ByteArrayOutputStream baos = new ByteArrayOutputStream();
308
309 byte[] buffer = new byte[1024];
310 int n = -1;
311 while ((n = inputStream.read(buffer)) != -1) {
312 baos.write(buffer, 0, n);
313 }
314 return baos.toByteArray();
315 }
316
317 private void configure(String file) throws JoranException {
318 JoranConfigurator jc = new JoranConfigurator();
319 jc.setContext(loggerContext);
320 loggerContext.putProperty("port", "" + port);
321 jc.doConfigure(file);
322 }
323
324 @Test
325 public void testCustomEvaluator() throws Exception {
326 startSMTPServer(NO_SSL);
327 configure(BlackboxClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customEvaluator.xml");
328
329 logger.debug("test");
330 String msg2 = "CustomEvaluator";
331 logger.debug(msg2);
332 logger.debug("invisible");
333 waitUntilEmailIsSent();
334 MimeMultipart mp = verifyAndExtractMimeMultipart(
335 "testCustomEvaluator " + this.getClass().getName() + " - " + msg2);
336 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
337 assertEquals("testCustomEvaluator", body);
338 }
339
340 @Test
341 public void testCustomBufferSize() throws Exception {
342 startSMTPServer(NO_SSL);
343 configure(BlackboxClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customBufferSize.xml");
344
345 logger.debug("invisible1");
346 logger.debug("invisible2");
347 String msg = "hello";
348 logger.error(msg);
349 waitUntilEmailIsSent();
350 MimeMultipart mp = verifyAndExtractMimeMultipart(
351 "testCustomBufferSize " + this.getClass().getName() + " - " + msg);
352 String body = GreenMailUtil.getBody(mp.getBodyPart(0));
353 assertEquals(msg, body);
354 }
355
356
357 @Test
358 public void testMultipleTo() throws Exception {
359 startSMTPServer(NO_SSL);
360 buildSMTPAppender("testMultipleTo", SYNCHRONOUS);
361 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
362
363 smtpAppender.addTo("Test <test@example.com>, other-test@example.com");
364 smtpAppender.start();
365 logger.addAppender(smtpAppender);
366 logger.debug("testMultipleTo hello");
367 logger.error("testMultipleTo en error", new Exception("an exception"));
368 Thread.yield();
369 int expectedEmailCount = 3;
370 waitForServerToReceiveEmails(expectedEmailCount);
371 MimeMessage[] mma = greenMailServer.getReceivedMessages();
372 assertNotNull(mma);
373 assertEquals(expectedEmailCount, mma.length);
374 }
375
376
377 @Test
378 public void bufferShouldBeResetBetweenMessages() throws Exception {
379 startSMTPServer(NO_SSL);
380 buildSMTPAppender("bufferShouldBeResetBetweenMessages", SYNCHRONOUS);
381 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
382 smtpAppender.start();
383 logger.addAppender(smtpAppender);
384 String msg0 = "hello zero";
385 logger.debug(msg0);
386 logger.error("error zero");
387
388 String msg1 = "hello one";
389 logger.debug(msg1);
390 logger.error("error one");
391
392 Thread.yield();
393 int oldCount = 0;
394 int expectedEmailCount = oldCount + 2;
395 waitForServerToReceiveEmails(expectedEmailCount);
396
397 MimeMessage[] mma = greenMailServer.getReceivedMessages();
398 assertNotNull(mma);
399 assertEquals(expectedEmailCount, mma.length);
400
401 MimeMessage mm0 = mma[oldCount];
402 MimeMultipart content0 = (MimeMultipart) mm0.getContent();
403 @SuppressWarnings("unused")
404 String body0 = GreenMailUtil.getBody(content0.getBodyPart(0));
405
406 MimeMessage mm1 = mma[oldCount + 1];
407 MimeMultipart content1 = (MimeMultipart) mm1.getContent();
408 String body1 = GreenMailUtil.getBody(content1.getBodyPart(0));
409
410 assertFalse(body1.contains(msg0));
411 }
412
413 @Test
414 public void multiLineSubjectTruncatedAtFirstNewLine() throws Exception {
415 startSMTPServer(NO_SSL);
416 String line1 = "line 1 of subject";
417 String subject = line1 + "\nline 2 of subject\n";
418 buildSMTPAppender(subject, ASYNCHRONOUS);
419
420 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
421 smtpAppender.start();
422 logger.addAppender(smtpAppender);
423 logger.debug("hello");
424 logger.error("en error", new Exception("an exception"));
425
426 Thread.yield();
427 waitUntilEmailIsSent();
428 waitForServerToReceiveEmails(1);
429
430 MimeMessage[] mma = greenMailServer.getReceivedMessages();
431 assertEquals(1, mma.length);
432 assertEquals(line1, mma[0].getSubject());
433 }
434
435 @Test
436 public void authenticated() throws Exception {
437 startSMTPServer(NO_SSL);
438 buildSMTPAppender("testMultipleTo", SYNCHRONOUS);
439 smtpAppender.setUsername(REQUIRED_USERNAME);
440 smtpAppender.setPassword(REQUIRED_PASSWORD);
441
442 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
443 smtpAppender.start();
444
445 logger.addAppender(smtpAppender);
446 logger.debug("authenticated");
447 logger.error("authenticated en error", new Exception("an exception"));
448
449 waitUntilEmailIsSent();
450 waitForServerToReceiveEmails(1);
451
452 MimeMessage[] mma = greenMailServer.getReceivedMessages();
453 assertNotNull(mma);
454 assertTrue(mma.length == 1, "body should not be empty");
455 }
456
457 void setSystemPropertiesForStartTLS() {
458 String PREFIX = "mail.smtp.";
459 System.setProperty(PREFIX + "starttls.enable", "true");
460 System.setProperty(PREFIX + "socketFactory.class", DummySSLSocketFactory.class.getName());
461 System.setProperty(PREFIX + "socketFactory.fallback", "false");
462 }
463
464 void unsetSystemPropertiesForStartTLS() {
465 String PREFIX = "mail.smtp.";
466 System.clearProperty(PREFIX + "starttls.enable");
467 System.clearProperty(PREFIX + "socketFactory.class");
468 System.clearProperty(PREFIX + "socketFactory.fallback");
469 }
470
471 @Test
472 public void authenticatedSSL() throws Exception {
473
474 try {
475
476 System.setProperty(CHECK_SERVER_IDENTITY_KEY, "false");
477 setSystemPropertiesForStartTLS();
478
479 startSMTPServer(WITH_SSL);
480 buildSMTPAppender("testMultipleTo", SYNCHRONOUS);
481 smtpAppender.setUsername(REQUIRED_USERNAME);
482 smtpAppender.setPassword(REQUIRED_PASSWORD);
483 smtpAppender.setSTARTTLS(true);
484 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
485 smtpAppender.start();
486
487 logger.addAppender(smtpAppender);
488 logger.debug("authenticated");
489 logger.error("authenticated en error", new Exception("an exception"));
490
491 waitUntilEmailIsSent();
492 waitForServerToReceiveEmails(1);
493
494 MimeMessage[] mma = greenMailServer.getReceivedMessages();
495 assertNotNull(mma);
496 assertTrue(mma.length == 1, "body should not be empty");
497 } finally {
498 System.clearProperty(CHECK_SERVER_IDENTITY_KEY);
499 unsetSystemPropertiesForStartTLS();
500 }
501 }
502
503
504
505
506 static String GMAIL_USER_NAME = "xx@gmail.com";
507 static String GMAIL_PASSWORD = "xxx";
508
509 @Disabled
510 @Test
511 public void authenticatedGmailStartTLS() throws Exception {
512 smtpAppender.setSMTPHost("smtp.gmail.com");
513 smtpAppender.setSMTPPort(587);
514 smtpAppender.setAsynchronousSending(false);
515 smtpAppender.addTo(GMAIL_USER_NAME);
516
517 smtpAppender.setSTARTTLS(true);
518 smtpAppender.setUsername(GMAIL_USER_NAME);
519 smtpAppender.setPassword(GMAIL_PASSWORD);
520
521 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
522 smtpAppender.setSubject("authenticatedGmailStartTLS - %level %logger{20} - %m");
523 smtpAppender.start();
524 Logger logger = loggerContext.getLogger("authenticatedGmailSTARTTLS");
525 logger.addAppender(smtpAppender);
526 logger.debug("authenticatedGmailStartTLS =- hello");
527 logger.error("en error", new Exception("an exception"));
528
529 StatusPrinter.print(loggerContext);
530 }
531
532 @Disabled
533 @Test
534 public void authenticatedGmail_SSL() throws Exception {
535 smtpAppender.setSMTPHost("smtp.gmail.com");
536 smtpAppender.setSMTPPort(465);
537 smtpAppender.setSubject("authenticatedGmail_SSL - %level %logger{20} - %m");
538 smtpAppender.addTo(GMAIL_USER_NAME);
539 smtpAppender.setSSL(true);
540 smtpAppender.setUsername(GMAIL_USER_NAME);
541 smtpAppender.setPassword(GMAIL_PASSWORD);
542 smtpAppender.setAsynchronousSending(false);
543 smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
544 smtpAppender.start();
545 Logger logger = loggerContext.getLogger("authenticatedGmail_SSL");
546 logger.addAppender(smtpAppender);
547 logger.debug("hello" + new java.util.Date());
548 logger.error("en error", new Exception("an exception"));
549
550 StatusPrinter.print(loggerContext);
551
552 }
553 }