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