001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.net; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.OutputStream; 022import java.util.Date; 023import java.util.Properties; 024 025import javax.activation.DataSource; 026import javax.mail.Authenticator; 027import javax.mail.Message; 028import javax.mail.MessagingException; 029import javax.mail.PasswordAuthentication; 030import javax.mail.Session; 031import javax.mail.Transport; 032import javax.mail.internet.InternetHeaders; 033import javax.mail.internet.MimeBodyPart; 034import javax.mail.internet.MimeMessage; 035import javax.mail.internet.MimeMultipart; 036import javax.mail.internet.MimeUtility; 037import javax.mail.util.ByteArrayDataSource; 038import javax.net.ssl.SSLSocketFactory; 039 040import org.apache.logging.log4j.LoggingException; 041import org.apache.logging.log4j.core.Layout; 042import org.apache.logging.log4j.core.LogEvent; 043import org.apache.logging.log4j.core.appender.AbstractManager; 044import org.apache.logging.log4j.core.appender.ManagerFactory; 045import org.apache.logging.log4j.core.config.Configuration; 046import org.apache.logging.log4j.core.impl.Log4jLogEvent; 047import org.apache.logging.log4j.core.impl.MutableLogEvent; 048import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; 049import org.apache.logging.log4j.core.layout.PatternLayout; 050import org.apache.logging.log4j.core.net.ssl.SslConfiguration; 051import org.apache.logging.log4j.core.util.CyclicBuffer; 052import org.apache.logging.log4j.core.util.NameUtil; 053import org.apache.logging.log4j.core.util.NetUtils; 054import org.apache.logging.log4j.message.ReusableMessage; 055import org.apache.logging.log4j.util.PropertiesUtil; 056import org.apache.logging.log4j.util.Strings; 057 058/** 059 * Manager for sending SMTP events. 060 */ 061public class SmtpManager extends AbstractManager { 062 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory(); 063 064 private final Session session; 065 066 private final CyclicBuffer<LogEvent> buffer; 067 068 private volatile MimeMessage message; 069 070 private final FactoryData data; 071 072 private static MimeMessage createMimeMessage(final FactoryData data, final Session session, final LogEvent appendEvent) 073 throws MessagingException { 074 return new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 075 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 076 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject.toSerializable(appendEvent)) 077 .build(); 078 } 079 080 protected SmtpManager(final String name, final Session session, final MimeMessage message, 081 final FactoryData data) { 082 super(null, name); 083 this.session = session; 084 this.message = message; 085 this.data = data; 086 this.buffer = new CyclicBuffer<>(LogEvent.class, data.numElements); 087 } 088 089 public void add(LogEvent event) { 090 if (event instanceof Log4jLogEvent && event.getMessage() instanceof ReusableMessage) { 091 ((Log4jLogEvent) event).makeMessageImmutable(); 092 } else if (event instanceof MutableLogEvent) { 093 event = ((MutableLogEvent) event).createMemento(); 094 } 095 buffer.add(event); 096 } 097 098 public static SmtpManager getSmtpManager( 099 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 * Send the contents of the cyclic buffer as an e-mail message. 155 * @param layout The layout for formatting the events. 156 * @param appendEvent The event that triggered the send. 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 // LOG4J-310: log appendEvent even if priorEvents is empty 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 * Factory data. 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 * Factory to create the SMTP Manager. 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 // Prevent an UnknownHostException in Java 7 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}