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.net.ssl.SslConfiguration; 046import org.apache.logging.log4j.core.util.CyclicBuffer; 047import org.apache.logging.log4j.core.util.NameUtil; 048import org.apache.logging.log4j.core.util.NetUtils; 049import org.apache.logging.log4j.util.PropertiesUtil; 050import org.apache.logging.log4j.util.Strings; 051 052/** 053 * Manager for sending SMTP events. 054 */ 055public class SmtpManager extends AbstractManager { 056 private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory(); 057 058 private final Session session; 059 060 private final CyclicBuffer<LogEvent> buffer; 061 062 private volatile MimeMessage message; 063 064 private final FactoryData data; 065 066 protected SmtpManager(final String name, final Session session, final MimeMessage message, 067 final FactoryData data) { 068 super(name); 069 this.session = session; 070 this.message = message; 071 this.data = data; 072 this.buffer = new CyclicBuffer<LogEvent>(LogEvent.class, data.numElements); 073 } 074 075 public void add(final LogEvent event) { 076 buffer.add(event); 077 } 078 079 /** 080 * @deprecated use {@link #getSMTPManager(String, String, String, String, String, String, String, String, int, String, String, boolean, String, int, SslConfiguration)} 081 */ 082 @Deprecated 083 public static SmtpManager getSMTPManager( 084 final String to, final String cc, final String bcc, 085 final String from, final String replyTo, 086 final String subject, String protocol, final String host, 087 final int port, final String username, final String password, 088 final boolean isDebug, final String filterName, final int numElements) { 089 return getSMTPManager(to, cc, bcc, from, replyTo, subject, protocol, host, port, username, password, isDebug, 090 filterName, numElements, null); 091 } 092 093 public static SmtpManager getSMTPManager( 094 final String to, final String cc, final String bcc, 095 final String from, final String replyTo, 096 final String subject, String protocol, final String host, 097 final int port, final String username, final String password, 098 final boolean isDebug, final String filterName, final int numElements, 099 final SslConfiguration sslConfiguration) { 100 if (Strings.isEmpty(protocol)) { 101 protocol = "smtp"; 102 } 103 104 final StringBuilder sb = new StringBuilder(); 105 if (to != null) { 106 sb.append(to); 107 } 108 sb.append(':'); 109 if (cc != null) { 110 sb.append(cc); 111 } 112 sb.append(':'); 113 if (bcc != null) { 114 sb.append(bcc); 115 } 116 sb.append(':'); 117 if (from != null) { 118 sb.append(from); 119 } 120 sb.append(':'); 121 if (replyTo != null) { 122 sb.append(replyTo); 123 } 124 sb.append(':'); 125 if (subject != null) { 126 sb.append(subject); 127 } 128 sb.append(':'); 129 sb.append(protocol).append(':').append(host).append(':').append("port").append(':'); 130 if (username != null) { 131 sb.append(username); 132 } 133 sb.append(':'); 134 if (password != null) { 135 sb.append(password); 136 } 137 sb.append(isDebug ? ":debug:" : "::"); 138 sb.append(filterName); 139 140 final String name = "SMTP:" + NameUtil.md5(sb.toString()); 141 142 return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subject, 143 protocol, host, port, username, password, isDebug, numElements, sslConfiguration)); 144 } 145 146 /** 147 * Send the contents of the cyclic buffer as an e-mail message. 148 * @param layout The layout for formatting the events. 149 * @param appendEvent The event that triggered the send. 150 */ 151 public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) { 152 if (message == null) { 153 connect(); 154 } 155 try { 156 final LogEvent[] priorEvents = buffer.removeAll(); 157 // LOG4J-310: log appendEvent even if priorEvents is empty 158 159 final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout); 160 161 final String contentType = layout.getContentType(); 162 final String encoding = getEncoding(rawBytes, contentType); 163 final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding); 164 165 final InternetHeaders headers = getHeaders(contentType, encoding); 166 final MimeMultipart mp = getMimeMultipart(encodedBytes, headers); 167 168 sendMultipartMessage(message, mp); 169 } catch (final MessagingException e) { 170 LOGGER.error("Error occurred while sending e-mail notification.", e); 171 throw new LoggingException("Error occurred while sending email", e); 172 } catch (final IOException e) { 173 LOGGER.error("Error occurred while sending e-mail notification.", e); 174 throw new LoggingException("Error occurred while sending email", e); 175 } catch (final RuntimeException e) { 176 LOGGER.error("Error occurred while sending e-mail notification.", e); 177 throw new LoggingException("Error occurred while sending email", e); 178 } 179 } 180 181 protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent, 182 final Layout<?> layout) throws IOException { 183 final ByteArrayOutputStream raw = new ByteArrayOutputStream(); 184 writeContent(priorEvents, appendEvent, layout, raw); 185 return raw.toByteArray(); 186 } 187 188 private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout, 189 final ByteArrayOutputStream out) 190 throws IOException { 191 writeHeader(layout, out); 192 writeBuffer(priorEvents, appendEvent, layout, out); 193 writeFooter(layout, out); 194 } 195 196 protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException { 197 final byte[] header = layout.getHeader(); 198 if (header != null) { 199 out.write(header); 200 } 201 } 202 203 protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout, 204 final OutputStream out) throws IOException { 205 for (final LogEvent priorEvent : priorEvents) { 206 final byte[] bytes = layout.toByteArray(priorEvent); 207 out.write(bytes); 208 } 209 210 final byte[] bytes = layout.toByteArray(appendEvent); 211 out.write(bytes); 212 } 213 214 protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException { 215 final byte[] footer = layout.getFooter(); 216 if (footer != null) { 217 out.write(footer); 218 } 219 } 220 221 protected String getEncoding(final byte[] rawBytes, final String contentType) { 222 final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType); 223 return MimeUtility.getEncoding(dataSource); 224 } 225 226 protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding) 227 throws MessagingException, IOException { 228 final ByteArrayOutputStream encoded = new ByteArrayOutputStream(); 229 encodeContent(rawBytes, encoding, encoded); 230 return encoded.toByteArray(); 231 } 232 233 protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out) 234 throws MessagingException, IOException { 235 final OutputStream encoder = MimeUtility.encode(out, encoding); 236 encoder.write(bytes); 237 encoder.close(); 238 } 239 240 protected InternetHeaders getHeaders(final String contentType, final String encoding) { 241 final InternetHeaders headers = new InternetHeaders(); 242 headers.setHeader("Content-Type", contentType + "; charset=UTF-8"); 243 headers.setHeader("Content-Transfer-Encoding", encoding); 244 return headers; 245 } 246 247 protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers) 248 throws MessagingException { 249 final MimeMultipart mp = new MimeMultipart(); 250 final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes); 251 mp.addBodyPart(part); 252 return mp; 253 } 254 255 protected void sendMultipartMessage(final MimeMessage message, final MimeMultipart mp) throws MessagingException { 256 synchronized (message) { 257 message.setContent(mp); 258 message.setSentDate(new Date()); 259 Transport.send(message); 260 } 261 } 262 263 /** 264 * Factory data. 265 */ 266 private static class FactoryData { 267 private final String to; 268 private final String cc; 269 private final String bcc; 270 private final String from; 271 private final String replyto; 272 private final String subject; 273 private final String protocol; 274 private final String host; 275 private final int port; 276 private final String username; 277 private final String password; 278 private final boolean isDebug; 279 private final int numElements; 280 private final SslConfiguration sslConfiguration; 281 282 /** 283 * @deprecated use {@link #FactoryData(String, String, String, String, String, String, String, String, int, String, String, boolean, int, SslConfiguration)} 284 */ 285 @Deprecated 286 public FactoryData( 287 final String to, final String cc, final String bcc, final String from, final String replyTo, 288 final String subject, final String protocol, final String host, final int port, 289 final String username, final String password, final boolean isDebug, final int numElements) { 290 this(to, cc, bcc, from, replyTo, subject, protocol, host, port, username, password, isDebug, numElements, null); 291 } 292 293 public FactoryData( 294 final String to, final String cc, final String bcc, final String from, final String replyTo, 295 final String subject, final String protocol, final String host, final int port, 296 final String username, final String password, final boolean isDebug, final int numElements, 297 final SslConfiguration sslConfiguration) { 298 this.to = to; 299 this.cc = cc; 300 this.bcc = bcc; 301 this.from = from; 302 this.replyto = replyTo; 303 this.subject = subject; 304 this.protocol = protocol; 305 this.host = host; 306 this.port = port; 307 this.username = username; 308 this.password = password; 309 this.isDebug = isDebug; 310 this.numElements = numElements; 311 this.sslConfiguration = sslConfiguration; 312 } 313 } 314 315 private synchronized void connect() { 316 if (message != null) { 317 return; 318 } 319 try { 320 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 321 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 322 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage(); 323 } catch (final MessagingException e) { 324 LOGGER.error("Could not set SmtpAppender message options.", e); 325 message = null; 326 } 327 } 328 329 /** 330 * Factory to create the SMTP Manager. 331 */ 332 private static class SMTPManagerFactory implements ManagerFactory<SmtpManager, FactoryData> { 333 334 @Override 335 public SmtpManager createManager(final String name, final FactoryData data) { 336 final String prefix = "mail." + data.protocol; 337 338 final Properties properties = PropertiesUtil.getSystemProperties(); 339 properties.setProperty("mail.transport.protocol", data.protocol); 340 if (properties.getProperty("mail.host") == null) { 341 // Prevent an UnknownHostException in Java 7 342 properties.put("mail.host", NetUtils.getLocalHostname()); 343 } 344 345 if (null != data.host) { 346 properties.setProperty(prefix + ".host", data.host); 347 } 348 if (data.port > 0) { 349 properties.setProperty(prefix + ".port", Integer.toString(data.port)); 350 } 351 352 final Authenticator authenticator = buildAuthenticator(data.username, data.password); 353 if (null != authenticator) { 354 properties.setProperty(prefix + ".auth", "true"); 355 } 356 357 if (data.protocol.equals("smtps")) { 358 final SslConfiguration sslConfiguration = data.sslConfiguration; 359 if (sslConfiguration != null) { 360 final SSLSocketFactory sslSocketFactory = sslConfiguration.getSslSocketFactory(); 361 properties.put(prefix + ".ssl.socketFactory", sslSocketFactory); 362 properties.setProperty(prefix + ".ssl.checkserveridentity", Boolean.toString(sslConfiguration.isVerifyHostName())); 363 } 364 } 365 366 final Session session = Session.getInstance(properties, authenticator); 367 session.setProtocolForAddress("rfc822", data.protocol); 368 session.setDebug(data.isDebug); 369 MimeMessage message; 370 371 try { 372 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto) 373 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc) 374 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage(); 375 } catch (final MessagingException e) { 376 LOGGER.error("Could not set SmtpAppender message options.", e); 377 message = null; 378 } 379 380 return new SmtpManager(name, session, message, data); 381 } 382 383 private Authenticator buildAuthenticator(final String username, final String password) { 384 if (null != password && null != username) { 385 return new Authenticator() { 386 private final PasswordAuthentication passwordAuthentication = 387 new PasswordAuthentication(username, password); 388 389 @Override 390 protected PasswordAuthentication getPasswordAuthentication() { 391 return passwordAuthentication; 392 } 393 }; 394 } 395 return null; 396 } 397 } 398}