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