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 */ 017 package org.apache.logging.log4j.core.net; 018 019 import java.io.ByteArrayOutputStream; 020 import java.io.IOException; 021 import java.io.OutputStream; 022 import java.util.Date; 023 import java.util.Properties; 024 025 import javax.activation.DataSource; 026 import javax.mail.Authenticator; 027 import javax.mail.Message; 028 import javax.mail.MessagingException; 029 import javax.mail.PasswordAuthentication; 030 import javax.mail.Session; 031 import javax.mail.Transport; 032 import javax.mail.internet.InternetHeaders; 033 import javax.mail.internet.MimeBodyPart; 034 import javax.mail.internet.MimeMessage; 035 import javax.mail.internet.MimeMultipart; 036 import javax.mail.internet.MimeUtility; 037 import javax.mail.util.ByteArrayDataSource; 038 039 import org.apache.logging.log4j.LoggingException; 040 import org.apache.logging.log4j.core.Layout; 041 import org.apache.logging.log4j.core.LogEvent; 042 import org.apache.logging.log4j.core.appender.AbstractManager; 043 import org.apache.logging.log4j.core.appender.ManagerFactory; 044 import org.apache.logging.log4j.core.util.CyclicBuffer; 045 import org.apache.logging.log4j.core.util.NameUtil; 046 import org.apache.logging.log4j.core.util.NetUtils; 047 import org.apache.logging.log4j.util.PropertiesUtil; 048 import org.apache.logging.log4j.util.Strings; 049 050 /** 051 * Manager for sending SMTP events. 052 */ 053 public class SmtpManager extends AbstractManager { 054 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory(); 055 056 private final Session session; 057 058 private final CyclicBuffer<LogEvent> buffer; 059 060 private volatile MimeMessage message; 061 062 private final FactoryData data; 063 064 protected SmtpManager(final String name, final Session session, final MimeMessage message, 065 final FactoryData data) { 066 super(name); 067 this.session = session; 068 this.message = message; 069 this.data = data; 070 this.buffer = new CyclicBuffer<LogEvent>(LogEvent.class, data.numElements); 071 } 072 073 public void add(final LogEvent event) { 074 buffer.add(event); 075 } 076 077 public static SmtpManager getSMTPManager(final String to, final String cc, final String bcc, 078 final String from, final String replyTo, 079 final String subject, String protocol, final String host, 080 final int port, final String username, final String password, 081 final boolean isDebug, final String filterName, final int numElements) { 082 if (Strings.isEmpty(protocol)) { 083 protocol = "smtp"; 084 } 085 086 final StringBuilder sb = new StringBuilder(); 087 if (to != null) { 088 sb.append(to); 089 } 090 sb.append(':'); 091 if (cc != null) { 092 sb.append(cc); 093 } 094 sb.append(':'); 095 if (bcc != null) { 096 sb.append(bcc); 097 } 098 sb.append(':'); 099 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 * Send the contents of the cyclic buffer as an e-mail message. 130 * @param layout The layout for formatting the events. 131 * @param appendEvent The event that triggered the send. 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 // LOG4J-310: log appendEvent even if priorEvents is empty 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 final OutputStream encoder = MimeUtility.encode(out, encoding); 218 encoder.write(bytes); 219 encoder.close(); 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 * Factory data. 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 * Factory to create the SMTP Manager. 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 // Prevent an UnknownHostException in Java 7 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 }