1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.net;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.util.Date;
23 import java.util.Properties;
24
25 import javax.activation.DataSource;
26 import javax.mail.Authenticator;
27 import javax.mail.Message;
28 import javax.mail.MessagingException;
29 import javax.mail.PasswordAuthentication;
30 import javax.mail.Session;
31 import javax.mail.Transport;
32 import javax.mail.internet.InternetHeaders;
33 import javax.mail.internet.MimeBodyPart;
34 import javax.mail.internet.MimeMessage;
35 import javax.mail.internet.MimeMultipart;
36 import javax.mail.internet.MimeUtility;
37 import javax.mail.util.ByteArrayDataSource;
38 import javax.net.ssl.SSLSocketFactory;
39
40 import org.apache.logging.log4j.LoggingException;
41 import org.apache.logging.log4j.core.Layout;
42 import org.apache.logging.log4j.core.LogEvent;
43 import org.apache.logging.log4j.core.appender.AbstractManager;
44 import org.apache.logging.log4j.core.appender.ManagerFactory;
45 import org.apache.logging.log4j.core.config.Configuration;
46 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
47 import org.apache.logging.log4j.core.impl.MutableLogEvent;
48 import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer;
49 import org.apache.logging.log4j.core.layout.PatternLayout;
50 import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
51 import org.apache.logging.log4j.core.util.CyclicBuffer;
52 import org.apache.logging.log4j.core.util.NameUtil;
53 import org.apache.logging.log4j.core.util.NetUtils;
54 import org.apache.logging.log4j.message.ReusableMessage;
55 import org.apache.logging.log4j.util.PropertiesUtil;
56 import org.apache.logging.log4j.util.Strings;
57
58
59
60
61 public class SmtpManager extends AbstractManager {
62 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory();
63
64 private final Session session;
65
66 private final CyclicBuffer<LogEvent> buffer;
67
68 private volatile MimeMessage message;
69
70 private final FactoryData data;
71
72 private static MimeMessage createMimeMessage(final FactoryData data, final Session session, final LogEvent appendEvent)
73 throws MessagingException {
74 return new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
75 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
76 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject.toSerializable(appendEvent))
77 .build();
78 }
79
80 protected SmtpManager(final String name, final Session session, final MimeMessage message,
81 final FactoryData data) {
82 super(null, name);
83 this.session = session;
84 this.message = message;
85 this.data = data;
86 this.buffer = new CyclicBuffer<>(LogEvent.class, data.numElements);
87 }
88
89 public void add(LogEvent event) {
90 if (event instanceof Log4jLogEvent && event.getMessage() instanceof ReusableMessage) {
91 ((Log4jLogEvent) event).makeMessageImmutable();
92 } else if (event instanceof MutableLogEvent) {
93 event = ((MutableLogEvent) event).createMemento();
94 }
95 buffer.add(event);
96 }
97
98 public static SmtpManager getSmtpManager(
99 final Configuration config,
100 final String to, final String cc, final String bcc,
101 final String from, final String replyTo,
102 final String subject, String protocol, final String host,
103 final int port, final String username, final String password,
104 final boolean isDebug, final String filterName, final int numElements,
105 final SslConfiguration sslConfiguration) {
106 if (Strings.isEmpty(protocol)) {
107 protocol = "smtp";
108 }
109
110 final StringBuilder sb = new StringBuilder();
111 if (to != null) {
112 sb.append(to);
113 }
114 sb.append(':');
115 if (cc != null) {
116 sb.append(cc);
117 }
118 sb.append(':');
119 if (bcc != null) {
120 sb.append(bcc);
121 }
122 sb.append(':');
123 if (from != null) {
124 sb.append(from);
125 }
126 sb.append(':');
127 if (replyTo != null) {
128 sb.append(replyTo);
129 }
130 sb.append(':');
131 if (subject != null) {
132 sb.append(subject);
133 }
134 sb.append(':');
135 sb.append(protocol).append(':').append(host).append(':').append("port").append(':');
136 if (username != null) {
137 sb.append(username);
138 }
139 sb.append(':');
140 if (password != null) {
141 sb.append(password);
142 }
143 sb.append(isDebug ? ":debug:" : "::");
144 sb.append(filterName);
145
146 final String name = "SMTP:" + NameUtil.md5(sb.toString());
147 final Serializer subjectSerializer = PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(subject).build();
148
149 return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subjectSerializer,
150 protocol, host, port, username, password, isDebug, numElements, sslConfiguration));
151 }
152
153
154
155
156
157
158 public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) {
159 if (message == null) {
160 connect(appendEvent);
161 }
162 try {
163 final LogEvent[] priorEvents = buffer.removeAll();
164
165
166 final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout);
167
168 final String contentType = layout.getContentType();
169 final String encoding = getEncoding(rawBytes, contentType);
170 final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding);
171
172 final InternetHeaders headers = getHeaders(contentType, encoding);
173 final MimeMultipart mp = getMimeMultipart(encodedBytes, headers);
174
175 sendMultipartMessage(message, mp);
176 } catch (final MessagingException | IOException | RuntimeException e) {
177 logError("Caught exception while sending e-mail notification.", e);
178 throw new LoggingException("Error occurred while sending email", e);
179 }
180 }
181
182 protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent,
183 final Layout<?> layout) throws IOException {
184 final ByteArrayOutputStream raw = new ByteArrayOutputStream();
185 writeContent(priorEvents, appendEvent, layout, raw);
186 return raw.toByteArray();
187 }
188
189 private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
190 final ByteArrayOutputStream out)
191 throws IOException {
192 writeHeader(layout, out);
193 writeBuffer(priorEvents, appendEvent, layout, out);
194 writeFooter(layout, out);
195 }
196
197 protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException {
198 final byte[] header = layout.getHeader();
199 if (header != null) {
200 out.write(header);
201 }
202 }
203
204 protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
205 final OutputStream out) throws IOException {
206 for (final LogEvent priorEvent : priorEvents) {
207 final byte[] bytes = layout.toByteArray(priorEvent);
208 out.write(bytes);
209 }
210
211 final byte[] bytes = layout.toByteArray(appendEvent);
212 out.write(bytes);
213 }
214
215 protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException {
216 final byte[] footer = layout.getFooter();
217 if (footer != null) {
218 out.write(footer);
219 }
220 }
221
222 protected String getEncoding(final byte[] rawBytes, final String contentType) {
223 final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType);
224 return MimeUtility.getEncoding(dataSource);
225 }
226
227 protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding)
228 throws MessagingException, IOException {
229 final ByteArrayOutputStream encoded = new ByteArrayOutputStream();
230 encodeContent(rawBytes, encoding, encoded);
231 return encoded.toByteArray();
232 }
233
234 protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out)
235 throws MessagingException, IOException {
236 try (final OutputStream encoder = MimeUtility.encode(out, encoding)) {
237 encoder.write(bytes);
238 }
239 }
240
241 protected InternetHeaders getHeaders(final String contentType, final String encoding) {
242 final InternetHeaders headers = new InternetHeaders();
243 headers.setHeader("Content-Type", contentType + "; charset=UTF-8");
244 headers.setHeader("Content-Transfer-Encoding", encoding);
245 return headers;
246 }
247
248 protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers)
249 throws MessagingException {
250 final MimeMultipart mp = new MimeMultipart();
251 final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes);
252 mp.addBodyPart(part);
253 return mp;
254 }
255
256 protected void sendMultipartMessage(final MimeMessage msg, final MimeMultipart mp) throws MessagingException {
257 synchronized (msg) {
258 msg.setContent(mp);
259 msg.setSentDate(new Date());
260 Transport.send(msg);
261 }
262 }
263
264
265
266
267 private static class FactoryData {
268 private final String to;
269 private final String cc;
270 private final String bcc;
271 private final String from;
272 private final String replyto;
273 private final Serializer subject;
274 private final String protocol;
275 private final String host;
276 private final int port;
277 private final String username;
278 private final String password;
279 private final boolean isDebug;
280 private final int numElements;
281 private final SslConfiguration sslConfiguration;
282
283 public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo,
284 final Serializer subjectSerializer, final String protocol, final String host, final int port,
285 final String username, final String password, final boolean isDebug, final int numElements,
286 final SslConfiguration sslConfiguration) {
287 this.to = to;
288 this.cc = cc;
289 this.bcc = bcc;
290 this.from = from;
291 this.replyto = replyTo;
292 this.subject = subjectSerializer;
293 this.protocol = protocol;
294 this.host = host;
295 this.port = port;
296 this.username = username;
297 this.password = password;
298 this.isDebug = isDebug;
299 this.numElements = numElements;
300 this.sslConfiguration = sslConfiguration;
301 }
302 }
303
304 private synchronized void connect(final LogEvent appendEvent) {
305 if (message != null) {
306 return;
307 }
308 try {
309 message = createMimeMessage(data, session, appendEvent);
310 } catch (final MessagingException e) {
311 logError("Could not set SmtpAppender message options", e);
312 message = null;
313 }
314 }
315
316
317
318
319 private static class SMTPManagerFactory implements ManagerFactory<SmtpManager, FactoryData> {
320
321 @Override
322 public SmtpManager createManager(final String name, final FactoryData data) {
323 final String prefix = "mail." + data.protocol;
324
325 final Properties properties = PropertiesUtil.getSystemProperties();
326 properties.setProperty("mail.transport.protocol", data.protocol);
327 if (properties.getProperty("mail.host") == null) {
328
329 properties.setProperty("mail.host", NetUtils.getLocalHostname());
330 }
331
332 if (null != data.host) {
333 properties.setProperty(prefix + ".host", data.host);
334 }
335 if (data.port > 0) {
336 properties.setProperty(prefix + ".port", String.valueOf(data.port));
337 }
338
339 final Authenticator authenticator = buildAuthenticator(data.username, data.password);
340 if (null != authenticator) {
341 properties.setProperty(prefix + ".auth", "true");
342 }
343
344 if (data.protocol.equals("smtps")) {
345 final SslConfiguration sslConfiguration = data.sslConfiguration;
346 if (sslConfiguration != null) {
347 final SSLSocketFactory sslSocketFactory = sslConfiguration.getSslSocketFactory();
348 properties.put(prefix + ".ssl.socketFactory", sslSocketFactory);
349 properties.setProperty(prefix + ".ssl.checkserveridentity", Boolean.toString(sslConfiguration.isVerifyHostName()));
350 }
351 }
352
353 final Session session = Session.getInstance(properties, authenticator);
354 session.setProtocolForAddress("rfc822", data.protocol);
355 session.setDebug(data.isDebug);
356 return new SmtpManager(name, session, null, data);
357 }
358
359 private Authenticator buildAuthenticator(final String username, final String password) {
360 if (null != password && null != username) {
361 return new Authenticator() {
362 private final PasswordAuthentication passwordAuthentication =
363 new PasswordAuthentication(username, password);
364
365 @Override
366 protected PasswordAuthentication getPasswordAuthentication() {
367 return passwordAuthentication;
368 }
369 };
370 }
371 return null;
372 }
373 }
374 }