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