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.helpers.CyclicBuffer; 045 import org.apache.logging.log4j.core.helpers.NameUtil; 046 import org.apache.logging.log4j.core.helpers.NetUtils; 047 import org.apache.logging.log4j.util.PropertiesUtil; 048 049 /** 050 * Manager for sending SMTP events. 051 */ 052 public class SMTPManager extends AbstractManager { 053 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory(); 054 055 private final Session session; 056 057 private final CyclicBuffer<LogEvent> buffer; 058 059 private volatile MimeMessage message; 060 061 private final FactoryData data; 062 063 protected SMTPManager(final String name, final Session session, final MimeMessage message, 064 final FactoryData data) { 065 super(name); 066 this.session = session; 067 this.message = message; 068 this.data = data; 069 this.buffer = new CyclicBuffer<LogEvent>(LogEvent.class, data.numElements); 070 } 071 072 public void add(final LogEvent event) { 073 buffer.add(event); 074 } 075 076 public static SMTPManager getSMTPManager(final String to, final String cc, final String bcc, 077 final String from, final String replyTo, 078 final String subject, String protocol, final String host, 079 final int port, final String username, final String password, 080 final boolean isDebug, final String filterName, final int numElements) { 081 if (protocol == null || protocol.length() == 0) { 082 protocol = "smtp"; 083 } 084 085 final StringBuilder sb = new StringBuilder(); 086 if (to != null) { 087 sb.append(to); 088 } 089 sb.append(":"); 090 if (cc != null) { 091 sb.append(cc); 092 } 093 sb.append(":"); 094 if (bcc != null) { 095 sb.append(bcc); 096 } 097 sb.append(":"); 098 if (from != null) { 099 sb.append(from); 100 } 101 sb.append(":"); 102 if (replyTo != null) { 103 sb.append(replyTo); 104 } 105 sb.append(":"); 106 if (subject != null) { 107 sb.append(subject); 108 } 109 sb.append(":"); 110 sb.append(protocol).append(":").append(host).append(":").append("port").append(":"); 111 if (username != null) { 112 sb.append(username); 113 } 114 sb.append(":"); 115 if (password != null) { 116 sb.append(password); 117 } 118 sb.append(isDebug ? ":debug:" : "::"); 119 sb.append(filterName); 120 121 final String name = "SMTP:" + NameUtil.md5(sb.toString()); 122 123 return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subject, 124 protocol, host, port, username, password, isDebug, numElements)); 125 } 126 127 /** 128 * Send the contents of the cyclic buffer as an e-mail message. 129 * @param layout The layout for formatting the events. 130 * @param appendEvent The event that triggered the send. 131 */ 132 public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) { 133 if (message == null) { 134 connect(); 135 } 136 try { 137 final LogEvent[] priorEvents = buffer.removeAll(); 138 if (priorEvents == null || priorEvents.length == 0) { 139 return; // nothing to do, another thread already took all events 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 }