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