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; 038 039import org.apache.logging.log4j.LoggingException; 040import org.apache.logging.log4j.core.Layout; 041import org.apache.logging.log4j.core.LogEvent; 042import org.apache.logging.log4j.core.appender.AbstractManager; 043import org.apache.logging.log4j.core.appender.ManagerFactory; 044import org.apache.logging.log4j.core.config.Configuration; 045import org.apache.logging.log4j.core.impl.Log4jLogEvent; 046import org.apache.logging.log4j.core.impl.MutableLogEvent; 047import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; 048import org.apache.logging.log4j.core.layout.PatternLayout; 049import org.apache.logging.log4j.core.util.CyclicBuffer; 050import org.apache.logging.log4j.core.util.NameUtil; 051import org.apache.logging.log4j.core.util.NetUtils; 052import org.apache.logging.log4j.message.ReusableMessage; 053import org.apache.logging.log4j.util.PropertiesUtil; 054import org.apache.logging.log4j.util.Strings; 055 056/** 057 * Manager for sending SMTP events. 058 */ 059public class SmtpManager extends AbstractManager { 060 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory(); 061 062 private final Session session; 063 064 private final CyclicBuffer<LogEvent> buffer; 065 066 private volatile MimeMessage message; 067 068 private final FactoryData data; 069 070 private static MimeMessage createMimeMessage(final FactoryData data, final Session session, final LogEvent appendEvent) 071 throws MessagingException { 072 return new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 073 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 074 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject.toSerializable(appendEvent)) 075 .build(); 076 } 077 078 protected SmtpManager(final String name, final Session session, final MimeMessage message, 079 final FactoryData data) { 080 super(name); 081 this.session = session; 082 this.message = message; 083 this.data = data; 084 this.buffer = new CyclicBuffer<>(LogEvent.class, data.numElements); 085 } 086 087 public void add(LogEvent event) { 088 if (event instanceof Log4jLogEvent && event.getMessage() instanceof ReusableMessage) { 089 ((Log4jLogEvent) event).makeMessageImmutable(); 090 } else if (event instanceof MutableLogEvent) { 091 event = ((MutableLogEvent) event).createMemento(); 092 } 093 buffer.add(event); 094 } 095 096 public static SmtpManager getSmtpManager( 097 final Configuration config, 098 final String to, final String cc, final String bcc, 099 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.createSerializer(config, null, subject, null, null, false, false); 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 * Send the contents of the cyclic buffer as an e-mail message. 152 * @param layout The layout for formatting the events. 153 * @param appendEvent The event that triggered the send. 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 // LOG4J-310: log appendEvent even if priorEvents is empty 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 * Factory data. 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 * Factory to create the SMTP Manager. 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 // Prevent an UnknownHostException in Java 7 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}