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