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 final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout); 139 140 final String contentType = layout.getContentType(); 141 final String encoding = getEncoding(rawBytes, contentType); 142 final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding); 143 144 final InternetHeaders headers = getHeaders(contentType, encoding); 145 final MimeMultipart mp = getMimeMultipart(encodedBytes, headers); 146 147 sendMultipartMessage(message, mp); 148 } catch (final MessagingException e) { 149 LOGGER.error("Error occurred while sending e-mail notification.", e); 150 throw new LoggingException("Error occurred while sending email", e); 151 } catch (final IOException 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 RuntimeException e) { 155 LOGGER.error("Error occurred while sending e-mail notification.", e); 156 throw new LoggingException("Error occurred while sending email", e); 157 } 158 } 159 160 protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent, 161 final Layout<?> layout) throws IOException { 162 final ByteArrayOutputStream raw = new ByteArrayOutputStream(); 163 writeContent(priorEvents, appendEvent, layout, raw); 164 return raw.toByteArray(); 165 } 166 167 private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout, 168 final ByteArrayOutputStream out) 169 throws IOException { 170 writeHeader(layout, out); 171 writeBuffer(priorEvents, appendEvent, layout, out); 172 writeFooter(layout, out); 173 } 174 175 protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException { 176 final byte[] header = layout.getHeader(); 177 if (header != null) { 178 out.write(header); 179 } 180 } 181 182 protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout, 183 final OutputStream out) throws IOException { 184 for (final LogEvent priorEvent : priorEvents) { 185 final byte[] bytes = layout.toByteArray(priorEvent); 186 out.write(bytes); 187 } 188 189 final byte[] bytes = layout.toByteArray(appendEvent); 190 out.write(bytes); 191 } 192 193 protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException { 194 final byte[] footer = layout.getFooter(); 195 if (footer != null) { 196 out.write(footer); 197 } 198 } 199 200 protected String getEncoding(final byte[] rawBytes, final String contentType) { 201 final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType); 202 return MimeUtility.getEncoding(dataSource); 203 } 204 205 protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding) 206 throws MessagingException, IOException { 207 final ByteArrayOutputStream encoded = new ByteArrayOutputStream(); 208 encodeContent(rawBytes, encoding, encoded); 209 return encoded.toByteArray(); 210 } 211 212 protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out) 213 throws MessagingException, IOException { 214 final OutputStream encoder = MimeUtility.encode(out, encoding); 215 encoder.write(bytes); 216 encoder.close(); 217 } 218 219 protected InternetHeaders getHeaders(final String contentType, final String encoding) { 220 final InternetHeaders headers = new InternetHeaders(); 221 headers.setHeader("Content-Type", contentType + "; charset=UTF-8"); 222 headers.setHeader("Content-Transfer-Encoding", encoding); 223 return headers; 224 } 225 226 protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers) 227 throws MessagingException { 228 final MimeMultipart mp = new MimeMultipart(); 229 final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes); 230 mp.addBodyPart(part); 231 return mp; 232 } 233 234 protected void sendMultipartMessage(final MimeMessage message, final MimeMultipart mp) throws MessagingException { 235 synchronized (message) { 236 message.setContent(mp); 237 message.setSentDate(new Date()); 238 Transport.send(message); 239 } 240 } 241 242 /** 243 * Factory data. 244 */ 245 private static class FactoryData { 246 private final String to; 247 private final String cc; 248 private final String bcc; 249 private final String from; 250 private final String replyto; 251 private final String subject; 252 private final String protocol; 253 private final String host; 254 private final int port; 255 private final String username; 256 private final String password; 257 private final boolean isDebug; 258 private final int numElements; 259 260 public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo, 261 final String subject, final String protocol, final String host, final int port, 262 final String username, final String password, final boolean isDebug, final int numElements) { 263 this.to = to; 264 this.cc = cc; 265 this.bcc = bcc; 266 this.from = from; 267 this.replyto = replyTo; 268 this.subject = subject; 269 this.protocol = protocol; 270 this.host = host; 271 this.port = port; 272 this.username = username; 273 this.password = password; 274 this.isDebug = isDebug; 275 this.numElements = numElements; 276 } 277 } 278 279 private synchronized void connect() { 280 if (message != null) { 281 return; 282 } 283 try { 284 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 285 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 286 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage(); 287 } catch (final MessagingException e) { 288 LOGGER.error("Could not set SMTPAppender message options.", e); 289 message = null; 290 } 291 } 292 293 /** 294 * Factory to create the SMTP Manager. 295 */ 296 private static class SMTPManagerFactory implements ManagerFactory<SMTPManager, FactoryData> { 297 298 public SMTPManager createManager(final String name, final FactoryData data) { 299 final String prefix = "mail." + data.protocol; 300 301 final Properties properties = PropertiesUtil.getSystemProperties(); 302 properties.put("mail.transport.protocol", data.protocol); 303 if (properties.getProperty("mail.host") == null) { 304 // Prevent an UnknownHostException in Java 7 305 properties.put("mail.host", NetUtils.getLocalHostname()); 306 } 307 308 if (null != data.host) { 309 properties.put(prefix + ".host", data.host); 310 } 311 if (data.port > 0) { 312 properties.put(prefix + ".port", String.valueOf(data.port)); 313 } 314 315 final Authenticator authenticator = buildAuthenticator(data.username, data.password); 316 if (null != authenticator) { 317 properties.put(prefix + ".auth", "true"); 318 } 319 320 final Session session = Session.getInstance(properties, authenticator); 321 session.setProtocolForAddress("rfc822", data.protocol); 322 session.setDebug(data.isDebug); 323 MimeMessage message; 324 325 try { 326 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 327 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 328 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage(); 329 } catch (final MessagingException e) { 330 LOGGER.error("Could not set SMTPAppender message options.", e); 331 message = null; 332 } 333 334 return new SMTPManager(name, session, message, data); 335 } 336 337 private Authenticator buildAuthenticator(final String username, final String password) { 338 if (null != password && null != username) { 339 return new Authenticator() { 340 private final PasswordAuthentication passwordAuthentication = 341 new PasswordAuthentication(username, password); 342 343 @Override 344 protected PasswordAuthentication getPasswordAuthentication() { 345 return passwordAuthentication; 346 } 347 }; 348 } 349 return null; 350 } 351 } 352 }