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