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