001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 * Portions copyright 2015 Edan Idzerda 017 */ 018package org.opends.server.util; 019 020 021 022import java.io.BufferedReader; 023import java.io.File; 024import java.io.FileReader; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Properties; 030 031import javax.activation.DataHandler; 032import javax.activation.FileDataSource; 033import javax.mail.MessagingException; 034import javax.mail.SendFailedException; 035import javax.mail.Session; 036import javax.mail.Transport; 037import javax.mail.internet.InternetAddress; 038import javax.mail.internet.MimeBodyPart; 039import javax.mail.internet.MimeMessage; 040import javax.mail.internet.MimeMultipart; 041 042import org.forgerock.i18n.LocalizableMessage; 043import org.forgerock.i18n.LocalizableMessageBuilder; 044import org.opends.server.core.DirectoryServer; 045import org.forgerock.i18n.slf4j.LocalizedLogger; 046 047import com.forgerock.opendj.cli.ArgumentException; 048import com.forgerock.opendj.cli.ArgumentParser; 049import com.forgerock.opendj.cli.BooleanArgument; 050import com.forgerock.opendj.cli.StringArgument; 051 052import static org.opends.messages.ToolMessages.*; 053import static org.opends.messages.UtilityMessages.*; 054import static org.opends.server.util.ServerConstants.*; 055import static org.opends.server.util.StaticUtils.*; 056 057import static com.forgerock.opendj.cli.CommonArguments.*; 058 059 060 061/** 062 * This class defines an e-mail message that may be sent to one or more 063 * recipients via SMTP. This is a wrapper around JavaMail to make this process 064 * more convenient and fit better into the Directory Server framework. 065 */ 066@org.opends.server.types.PublicAPI( 067 stability=org.opends.server.types.StabilityLevel.VOLATILE, 068 mayInstantiate=true, 069 mayExtend=false, 070 mayInvoke=true) 071public final class EMailMessage 072{ 073 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 074 075 076 /** The addresses of the recipients to whom this message should be sent. */ 077 private List<String> recipients; 078 079 /** The set of attachments to include in this message. */ 080 private LinkedList<MimeBodyPart> attachments; 081 082 /** The MIME type for the message body. */ 083 private String bodyMIMEType; 084 085 /** The address of the sender for this message. */ 086 private String sender; 087 088 /** The subject for the mail message. */ 089 private String subject; 090 091 /** The body for the mail message. */ 092 private LocalizableMessageBuilder body; 093 094 095 096 /** 097 * Creates a new e-mail message with the provided information. 098 * 099 * @param sender The address of the sender for the message. 100 * @param recipient The address of the recipient for the message. 101 * @param subject The subject to use for the message. 102 */ 103 public EMailMessage(String sender, String recipient, String subject) 104 { 105 this.sender = sender; 106 this.subject = subject; 107 108 recipients = CollectionUtils.newArrayList(recipient); 109 110 body = new LocalizableMessageBuilder(); 111 attachments = new LinkedList<>(); 112 bodyMIMEType = "text/plain"; 113 } 114 115 116 117 /** 118 * Creates a new e-mail message with the provided information. 119 * 120 * @param sender The address of the sender for the message. 121 * @param recipients The addresses of the recipients for the message. 122 * @param subject The subject to use for the message. 123 */ 124 public EMailMessage(String sender, List<String> recipients, 125 String subject) 126 { 127 this.sender = sender; 128 this.recipients = recipients; 129 this.subject = subject; 130 131 body = new LocalizableMessageBuilder(); 132 attachments = new LinkedList<>(); 133 bodyMIMEType = "text/plain"; 134 } 135 136 137 138 /** 139 * Retrieves the sender for this message. 140 * 141 * @return The sender for this message. 142 */ 143 public String getSender() 144 { 145 return sender; 146 } 147 148 149 150 /** 151 * Specifies the sender for this message. 152 * 153 * @param sender The sender for this message. 154 */ 155 public void setSender(String sender) 156 { 157 this.sender = sender; 158 } 159 160 161 162 /** 163 * Retrieves the set of recipients for this message. This list may be 164 * directly manipulated by the caller. 165 * 166 * @return The set of recipients for this message. 167 */ 168 public List<String> getRecipients() 169 { 170 return recipients; 171 } 172 173 174 175 /** 176 * Specifies the set of recipients for this message. 177 * 178 * @param recipients The set of recipients for this message. 179 */ 180 public void setRecipients(ArrayList<String> recipients) 181 { 182 this.recipients = recipients; 183 } 184 185 186 187 /** 188 * Adds the specified recipient to this message. 189 * 190 * @param recipient The recipient to add to this message. 191 */ 192 public void addRecipient(String recipient) 193 { 194 recipients.add(recipient); 195 } 196 197 198 199 /** 200 * Retrieves the subject for this message. 201 * 202 * @return The subject for this message. 203 */ 204 public String getSubject() 205 { 206 return subject; 207 } 208 209 210 211 /** 212 * Specifies the subject for this message. 213 * 214 * @param subject The subject for this message. 215 */ 216 public void setSubject(String subject) 217 { 218 this.subject = subject; 219 } 220 221 222 /** 223 * Retrieves the MIME Type for the body of this message. 224 * 225 * @return The MIME Type for this message. 226 */ 227 public String getBodyMIMEType() 228 { 229 return bodyMIMEType; 230 } 231 232 /** 233 * Specifies the MIME Type for the body of this message. 234 * 235 * @param bodyMIMEType The MIME Type for this message. 236 */ 237 public void setBodyMIMEType(String bodyMIMEType) 238 { 239 this.bodyMIMEType = bodyMIMEType; 240 } 241 242 /** 243 * Retrieves the body for this message. It may be directly manipulated by the 244 * caller. 245 * 246 * @return The body for this message. 247 */ 248 public LocalizableMessageBuilder getBody() 249 { 250 return body; 251 } 252 253 254 255 /** 256 * Specifies the body for this message. 257 * 258 * @param body The body for this message. 259 */ 260 public void setBody(LocalizableMessageBuilder body) 261 { 262 this.body = body; 263 } 264 265 266 267 /** 268 * Specifies the body for this message. 269 * 270 * @param body The body for this message. 271 */ 272 public void setBody(LocalizableMessage body) 273 { 274 this.body = new LocalizableMessageBuilder(body); 275 } 276 277 278 279 /** 280 * Appends the provided text to the body of this message. 281 * 282 * @param text The text to append to the body of the message. 283 */ 284 public void appendToBody(String text) 285 { 286 body.append(text); 287 } 288 289 290 291 /** 292 * Retrieves the set of attachments for this message. This list may be 293 * directly modified by the caller if desired. 294 * 295 * @return The set of attachments for this message. 296 */ 297 public LinkedList<MimeBodyPart> getAttachments() 298 { 299 return attachments; 300 } 301 302 303 304 /** 305 * Adds the provided attachment to this mail message. 306 * 307 * @param attachment The attachment to add to this mail message. 308 */ 309 public void addAttachment(MimeBodyPart attachment) 310 { 311 attachments.add(attachment); 312 } 313 314 315 316 /** 317 * Adds an attachment to this mail message with the provided text. 318 * 319 * @param attachmentText The text to include in the attachment. 320 * 321 * @throws MessagingException If there is a problem of some type with the 322 * attachment. 323 */ 324 public void addAttachment(String attachmentText) 325 throws MessagingException 326 { 327 MimeBodyPart attachment = new MimeBodyPart(); 328 attachment.setText(attachmentText); 329 attachments.add(attachment); 330 } 331 332 333 334 /** 335 * Adds the provided attachment to this mail message. 336 * 337 * @param attachmentFile The file containing the attachment data. 338 * 339 * @throws MessagingException If there is a problem of some type with the 340 * attachment. 341 */ 342 public void addAttachment(File attachmentFile) 343 throws MessagingException 344 { 345 MimeBodyPart attachment = new MimeBodyPart(); 346 347 FileDataSource dataSource = new FileDataSource(attachmentFile); 348 attachment.setDataHandler(new DataHandler(dataSource)); 349 attachment.setFileName(attachmentFile.getName()); 350 351 attachments.add(attachment); 352 } 353 354 355 356 /** 357 * Attempts to send this message to the intended recipient(s). This will use 358 * the mail server(s) defined in the Directory Server mail handler 359 * configuration. If multiple servers are specified and the first is 360 * unavailable, then the other server(s) will be tried before returning a 361 * failure to the caller. 362 * 363 * @throws MessagingException If a problem occurred while attempting to send 364 * the message. 365 */ 366 public void send() 367 throws MessagingException 368 { 369 send(DirectoryServer.getMailServerPropertySets()); 370 } 371 372 373 374 /** 375 * Attempts to send this message to the intended recipient(s). If multiple 376 * servers are specified and the first is unavailable, then the other 377 * server(s) will be tried before returning a failure to the caller. 378 * 379 * @param mailServerPropertySets A list of property sets providing 380 * information about the mail servers to use 381 * when sending the message. 382 * 383 * @throws MessagingException If a problem occurred while attempting to send 384 * the message. 385 */ 386 public void send(List<Properties> mailServerPropertySets) 387 throws MessagingException 388 { 389 // Get information about the available mail servers that we can use. 390 MessagingException sendException = null; 391 for (Properties props : mailServerPropertySets) 392 { 393 // Get a session and use it to create a new message. 394 Session session = Session.getInstance(props); 395 MimeMessage message = new MimeMessage(session); 396 message.setSubject(subject); 397 message.setSentDate(new Date()); 398 399 400 // Add the sender address. If this fails, then it's a fatal problem we'll 401 // propagate to the caller. 402 try 403 { 404 message.setFrom(new InternetAddress(sender)); 405 } 406 catch (MessagingException me) 407 { 408 logger.traceException(me); 409 410 LocalizableMessage msg = ERR_EMAILMSG_INVALID_SENDER_ADDRESS.get(sender, me.getMessage()); 411 throw new MessagingException(msg.toString(), me); 412 } 413 414 415 // Add the recipient addresses. If any of them fail, then that's a fatal 416 // problem we'll propagate to the caller. 417 InternetAddress[] recipientAddresses = 418 new InternetAddress[recipients.size()]; 419 for (int i=0; i < recipientAddresses.length; i++) 420 { 421 String recipient = recipients.get(i); 422 423 try 424 { 425 recipientAddresses[i] = new InternetAddress(recipient); 426 } 427 catch (MessagingException me) 428 { 429 logger.traceException(me); 430 431 LocalizableMessage msg = ERR_EMAILMSG_INVALID_RECIPIENT_ADDRESS.get(recipient, me.getMessage()); 432 throw new MessagingException(msg.toString(), me); 433 } 434 } 435 message.setRecipients( 436 javax.mail.Message.RecipientType.TO, 437 recipientAddresses); 438 439 440 // If we have any attachments, then the whole thing needs to be 441 // multipart. Otherwise, just set the text of the message. 442 if (attachments.isEmpty()) 443 { 444 message.setContent(body.toString(), bodyMIMEType); 445 } 446 else 447 { 448 MimeMultipart multiPart = new MimeMultipart(); 449 450 MimeBodyPart bodyPart = new MimeBodyPart(); 451 bodyPart.setText(body.toString()); 452 multiPart.addBodyPart(bodyPart); 453 454 for (MimeBodyPart attachment : attachments) 455 { 456 multiPart.addBodyPart(attachment); 457 } 458 459 message.setContent(multiPart); 460 } 461 462 463 // Try to send the message. If this fails, it can be a complete failure 464 // or a partial one. If it's a complete failure then try rolling over to 465 // the next server. If it's a partial one, then that likely means that 466 // the message was sent but one or more recipients was rejected, so we'll 467 // propagate that back to the caller. 468 try 469 { 470 Transport.send(message); 471 return; 472 } 473 catch (SendFailedException sfe) 474 { 475 logger.traceException(sfe); 476 477 // We'll ignore this and hope that another server is available. If not, 478 // then at least save the exception so that we can throw it if all else 479 // fails. 480 if (sendException == null) 481 { 482 sendException = sfe; 483 } 484 } 485 // FIXME -- Are there any other types of MessagingException that we might 486 // want to catch so we could try again on another server? 487 } 488 489 490 // If we've gotten here, then we've tried all of the servers in the list and 491 // still failed. If we captured an earlier exception, then throw it. 492 // Otherwise, throw a generic exception. 493 if (sendException == null) 494 { 495 LocalizableMessage message = ERR_EMAILMSG_CANNOT_SEND.get(); 496 throw new MessagingException(message.toString()); 497 } 498 else 499 { 500 throw sendException; 501 } 502 } 503 504 505 506 /** 507 * Provide a command-line mechanism for sending an e-mail message via SMTP. 508 * 509 * @param args The command-line arguments provided to this program. 510 */ 511 public static void main(String[] args) 512 { 513 LocalizableMessage description = INFO_EMAIL_TOOL_DESCRIPTION.get(); 514 ArgumentParser argParser = new ArgumentParser(EMailMessage.class.getName(), 515 description, false); 516 517 BooleanArgument showUsage = null; 518 StringArgument attachFile = null; 519 StringArgument bodyFile = null; 520 StringArgument host = null; 521 StringArgument from = null; 522 StringArgument subject = null; 523 StringArgument to = null; 524 525 try 526 { 527 host = 528 StringArgument.builder("host") 529 .shortIdentifier('h') 530 .description(INFO_EMAIL_HOST_DESCRIPTION.get()) 531 .multiValued() 532 .required() 533 .defaultValue("127.0.0.1") 534 .valuePlaceholder(INFO_HOST_PLACEHOLDER.get()) 535 .buildAndAddToParser(argParser); 536 from = 537 StringArgument.builder("from") 538 .shortIdentifier('f') 539 .description(INFO_EMAIL_FROM_DESCRIPTION.get()) 540 .required() 541 .valuePlaceholder(INFO_ADDRESS_PLACEHOLDER.get()) 542 .buildAndAddToParser(argParser); 543 to = 544 StringArgument.builder("to") 545 .shortIdentifier('t') 546 .description(INFO_EMAIL_TO_DESCRIPTION.get()) 547 .multiValued() 548 .required() 549 .valuePlaceholder(INFO_ADDRESS_PLACEHOLDER.get()) 550 .buildAndAddToParser(argParser); 551 subject = 552 StringArgument.builder("subject") 553 .shortIdentifier('s') 554 .description(INFO_EMAIL_SUBJECT_DESCRIPTION.get()) 555 .required() 556 .valuePlaceholder(INFO_SUBJECT_PLACEHOLDER.get()) 557 .buildAndAddToParser(argParser); 558 bodyFile = 559 StringArgument.builder("body") 560 .shortIdentifier('b') 561 .description(INFO_EMAIL_BODY_DESCRIPTION.get()) 562 .multiValued() 563 .required() 564 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 565 .buildAndAddToParser(argParser); 566 attachFile = 567 StringArgument.builder("attach") 568 .shortIdentifier('a') 569 .description(INFO_EMAIL_ATTACH_DESCRIPTION.get()) 570 .multiValued() 571 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 572 .buildAndAddToParser(argParser); 573 574 showUsage = showUsageArgument(); 575 argParser.addArgument(showUsage); 576 argParser.setUsageArgument(showUsage); 577 } 578 catch (ArgumentException ae) 579 { 580 System.err.println(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 581 System.exit(1); 582 } 583 584 try 585 { 586 argParser.parseArguments(args); 587 } 588 catch (ArgumentException ae) 589 { 590 argParser.displayMessageAndUsageReference(System.err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 591 System.exit(1); 592 } 593 594 if (showUsage.isPresent()) 595 { 596 return; 597 } 598 599 LinkedList<Properties> mailServerProperties = new LinkedList<>(); 600 for (String s : host.getValues()) 601 { 602 Properties p = new Properties(); 603 p.setProperty(SMTP_PROPERTY_HOST, s); 604 mailServerProperties.add(p); 605 } 606 607 EMailMessage message = new EMailMessage(from.getValue(), to.getValues(), 608 subject.getValue()); 609 610 for (String s : bodyFile.getValues()) 611 { 612 try 613 { 614 File f = new File(s); 615 if (! f.exists()) 616 { 617 System.err.println(ERR_EMAIL_NO_SUCH_BODY_FILE.get(s)); 618 System.exit(1); 619 } 620 621 BufferedReader reader = new BufferedReader(new FileReader(f)); 622 while (true) 623 { 624 String line = reader.readLine(); 625 if (line == null) 626 { 627 break; 628 } 629 630 message.appendToBody(line); 631 message.appendToBody("\r\n"); // SMTP says we should use CRLF. 632 } 633 634 reader.close(); 635 } 636 catch (Exception e) 637 { 638 System.err.println(ERR_EMAIL_CANNOT_PROCESS_BODY_FILE.get(s, 639 getExceptionMessage(e))); 640 System.exit(1); 641 } 642 } 643 644 if (attachFile.isPresent()) 645 { 646 for (String s : attachFile.getValues()) 647 { 648 File f = new File(s); 649 if (! f.exists()) 650 { 651 System.err.println(ERR_EMAIL_NO_SUCH_ATTACHMENT_FILE.get(s)); 652 System.exit(1); 653 } 654 655 try 656 { 657 message.addAttachment(f); 658 } 659 catch (Exception e) 660 { 661 System.err.println(ERR_EMAIL_CANNOT_ATTACH_FILE.get(s, 662 getExceptionMessage(e))); 663 } 664 } 665 } 666 667 try 668 { 669 message.send(mailServerProperties); 670 } 671 catch (Exception e) 672 { 673 System.err.println(ERR_EMAIL_CANNOT_SEND_MESSAGE.get( 674 getExceptionMessage(e))); 675 System.exit(1); 676 } 677 } 678} 679