Guest User

Mondain

a guest
Sep 6th, 2010
2,064
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 33.27 KB | None | 0 0
  1. package com.infrared5.mail;
  2.  
  3. /*
  4.  * This code was created by Paul Gregoire for personal / professional projects.
  5.  * It is free to use by Infrared5, Inc. forever and may be used in any capacity
  6.  * without notification or approval of Paul Gregoire.
  7.  */
  8.  
  9. import java.io.BufferedReader;
  10. import java.io.IOException;
  11. import java.io.InputStreamReader;
  12. import java.io.PrintStream;
  13. import java.net.InetAddress;
  14. import java.net.Socket;
  15. import java.net.UnknownHostException;
  16. import java.security.Security;
  17. import java.text.SimpleDateFormat;
  18. import java.util.ArrayList;
  19. import java.util.Date;
  20. import java.util.List;
  21. import java.util.regex.Matcher;
  22. import java.util.regex.Pattern;
  23.  
  24. import org.apache.commons.codec.binary.Base64;
  25. import org.bouncycastle.crypto.tls.AlwaysValidVerifyer;
  26. import org.bouncycastle.crypto.tls.CertificateVerifyer;
  27. import org.bouncycastle.crypto.tls.TlsProtocolHandler;
  28. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;
  31. import org.springframework.beans.BeansException;
  32. import org.springframework.context.ApplicationContext;
  33. import org.springframework.context.ApplicationContextAware;
  34.  
  35. /**
  36.  * <p/>
  37.  * All purpose mailer object, provides programmatic interface to SMTP services.
  38.  * <br>
  39.  * Excellent guides:
  40.  * <a href=http://www.cs.cf.ac.uk/Dave/PERL/node175.html>Sending Mail (SMTP)</a> | <a href=http://perl.about.com/library/weekly/aa032402a.htm>SMTP 101</a>
  41.  * </p>
  42.  * <p/>
  43.  * <i>Quick reference SMTP reply codes:</i>
  44.  * <pre>
  45.  * 211 System status, or system help reply
  46.  * 214 Help message
  47.  * [Information on how to use the receiver or the meaning of a
  48.  * particular non-standard command; this reply is useful only
  49.  * to the human user]
  50.  * 220 <domain> Service ready
  51.  * 221 <domain> Service closing transmission channel
  52.  * 250 Requested mail action okay, completed
  53.  * 251 User not local; will forward to <forward-path>
  54.  * 354 Start mail input; end with <CRLF>.<CRLF>
  55.  * 421 <domain> Service not available,
  56.  * closing transmission channel
  57.  * [This may be a reply to any command if the service knows it
  58.  * must shut down]
  59.  * 450 Requested mail action not taken: mailbox unavailable
  60.  * [E.g., mailbox busy]
  61.  * 451 Requested action aborted: local error in processing
  62.  * 452 Requested action not taken: insufficient system storage
  63.  * 500 Syntax error, command unrecognized
  64.  * [This may include errors such as command line too long]
  65.  * 501 Syntax error in parameters or arguments
  66.  * 502 Command not implemented
  67.  * 503 Bad sequence of commands
  68.  * 504 Command parameter not implemented
  69.  * 550 Requested action not taken: mailbox unavailable
  70.  * [E.g., mailbox not found, no access]
  71.  * 551 User not local; please try <forward-path>
  72.  * 552 Requested mail action aborted: exceeded storage allocation
  73.  * 553 Requested action not taken: mailbox name not allowed
  74.  * [E.g., mailbox syntax incorrect]
  75.  * 554 Transaction failed / Service unavailable
  76.  * </pre>
  77.  *
  78.  * @author <a href="mailto:paul@gregoire.org">Paul Gregoire</a>
  79.  * @version $Revision: 31 $
  80.  */
  81. public class Mailer implements ApplicationContextAware {
  82.  
  83.     private static ApplicationContext applicationContext;
  84.  
  85.     public static final String VERSION = "Mailer v1.13 by Paul Gregoire";
  86.  
  87.     /**
  88.      * This header line should allow us to by pass spamcop and cause the spam
  89.      * blame to fall upon the actual user.
  90.      */
  91.     private static final String SMTP_SERVER_RECEIVED_FROM_HEADER = "Received: from [%s] by %s via HTTP; %s";
  92.  
  93.     private static final Pattern PAT_MESSAGE_ID = Pattern.compile("250 (Ok: queued as |[\\d]\\.[\\d]\\.[\\d] ){0,1}([a-zA-Z0-9]{4,32}).*",
  94.             Pattern.CANON_EQ);
  95.  
  96.     private static final Pattern PAT_TLS_READY = Pattern.compile("220 ([\\d]\\.[\\d]\\.[\\d] ){0,1}([R|r]eady).*",
  97.             Pattern.CANON_EQ);
  98.    
  99.     private static final Pattern PAT_SMTP_COMMAND = Pattern.compile("(HELO|RCPT|MAIL|DATA|VRFY|QUIT)");
  100.  
  101.     private static final String TEXT_HEADER_MIME = "Mime-Version: 1.0";
  102.  
  103.     private static final String TEXT_QUOTED_PLAIN = "Content-Type: text/plain; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: quoted-printable";
  104.  
  105.     private static final String TEXT_QUOTED_HTML = "Content-Type: text/html; charset=\"iso-8859-1\"\nContent-Transfer-Encoding: quoted-printable";
  106.  
  107.     private static final String TEXT_HEADER_CONTENT_CLASS = "Content-class: urn:content-classes:message";
  108.  
  109.     private static final String CRLF = "\r\n";
  110.  
  111.     private static final String DASH = "--";
  112.  
  113.     private static String SENDING_HOST = null;
  114.  
  115.     private Socket smtp;
  116.  
  117.     private BufferedReader input;
  118.  
  119.     private PrintStream output;
  120.  
  121.     private String serverReply;
  122.  
  123.     private String type = "html";
  124.  
  125.     private String boundary1 = "----=_PG0123456";
  126.  
  127.     private String boundary2 = "----=_GP7890123";
  128.  
  129.     private List<MailFile> files;
  130.  
  131.     private String messageId;
  132.  
  133.     private static String SMTP_SERVER;
  134.  
  135.     private static int SMTP_PORT = 25;
  136.  
  137.     private static String AUTH_USER;
  138.  
  139.     private static String AUTH_PASSWORD;
  140.    
  141.     private static final byte[] EMPTY_ARRAY;
  142.  
  143.     // rfc 2822
  144.     private SimpleDateFormat formatter = null;
  145.  
  146.     //socket read timeout, default 15s
  147.     private static int socketTimeout = 15000;
  148.  
  149.     //time between emails
  150.     private static long mailDelay = 1000;
  151.    
  152.     private static String authType = "none";
  153.  
  154.     private static Logger logger = LoggerFactory.getLogger(Mailer.class);
  155.  
  156.     static {
  157.         //add bouncycastle provider
  158.         Security.addProvider(new BouncyCastleProvider());
  159.         //prefill base64 line array
  160.         EMPTY_ARRAY = new byte[76];
  161.         for (int b = 0; b < 76; b++) {
  162.             EMPTY_ARRAY[b] = (byte) '=';
  163.         }
  164.     }
  165.  
  166.     /**
  167.      * Default ctor, uses DB configured SMTP server address.
  168.      */
  169.     public Mailer() {
  170.         if (null == SENDING_HOST) {
  171.             try {
  172.                 //get this boxes ip for use as sending host
  173.                 SENDING_HOST = InetAddress.getLocalHost().getHostAddress();
  174.             } catch (UnknownHostException ex) {
  175.                 logger.warn("Exception getting host address: {}", ex.getMessage());
  176.             }
  177.         }
  178.         formatter = new SimpleDateFormat("EEE, dd MMM yyyy kk:mm:ss Z");
  179.         files = new ArrayList<MailFile>(1);
  180.         //regenerate the boundaries
  181.         generateBoundaries();
  182.     }
  183.  
  184.     private void generateBoundaries() {
  185.         String prefix = "----=_";
  186.         StringBuilder sb = new StringBuilder();
  187.         //seed data
  188.         byte[] seed = (System.currentTimeMillis() + VERSION).getBytes();       
  189.         for (int i = 0; i < seed.length; i++) {
  190.             int positiveValue = seed[i] & 0x000000FF;
  191.             //logger.trace("Value: {}", positiveValue);
  192.             sb.append(Integer.toHexString(positiveValue));
  193.         }      
  194.         //the seed provides about 84 characters to work with
  195.         logger.trace("Seed data output length: {}", sb.length());
  196.         boundary1 = prefix + sb.substring(0, 16);
  197.         boundary2 = prefix + sb.substring(21, 37);
  198.         logger.debug("Generated mime boundaries: {} {}", boundary1, boundary2);
  199.     }
  200.  
  201.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  202.         Mailer.applicationContext = applicationContext;
  203.         logger.debug("App context: {}", Mailer.applicationContext.getDisplayName());
  204.     }
  205.    
  206.     public void setType(String emailtype) {
  207.         type = emailtype;
  208.     }
  209.  
  210.     public String getAuthType() {
  211.         return Mailer.authType;
  212.     }
  213.  
  214.     public void setAuthType(String authType) {
  215.         Mailer.authType = authType;
  216.     }
  217.  
  218.     /**
  219.      * Executes the SMTP VRFY (verify) command. This is not very well supported due
  220.      * to its use by spammers.
  221.      *
  222.      * @param mailto email address to check
  223.      * @throws Exception
  224.      * @throws MailerException
  225.      */
  226.     public void verify(String mailto) throws Exception, MailerException {
  227.         startSession();
  228.         submitCommand("VRFY " + mailto.subSequence(0, mailto.indexOf('@')));
  229.         endSession();
  230.     }
  231.  
  232.     /**
  233.      * Sends the email via SMTP
  234.      *
  235.      * @param email
  236.      * @throws MailerException
  237.      */
  238.     public void sendMail(Email email) throws Exception, MailerException {
  239.         if (email.hasPlainText && email.getHtmlText() == null) {
  240.             type = "plain";
  241.         }
  242.         String from = email.getFrom();
  243.         String originIp = email.getOriginIP();
  244.         String dateString = getFormattedDate();
  245.         String to = null;
  246.         while (email.getNumRecipients() > 0) {
  247.             logger.debug("Recipients: {} hasRecipients: {}", email.getNumRecipients(), email.hasRecipients());
  248.             //get the recipient
  249.             to = email.getTo();
  250.             if (null == to) {
  251.                 logger.warn("Recipient address was null");
  252.                 break;
  253.             }
  254.             //sender header
  255.             if (!submitCommand(String.format("MAIL FROM: <%s>", from))) {
  256.                 logger.warn("Mail from not accepted: {}", from);
  257.                 throw new MailerException(getClass().getName() + " error during mail transmission.");
  258.             }
  259.             //recipient header
  260.             if (!submitCommand(String.format("RCPT TO: <%s>", to))) {
  261.                 logger.warn("Mail to not accepted: {}", to);
  262.                 continue;
  263.             }
  264.             //start message
  265.             if (!submitCommand("DATA")) {
  266.                 logger.warn("Start of mail body not accepted");
  267.                 continue;
  268.             }
  269.             //do replacement of "special header fields"
  270.             //senders actual ip address, this host ip, date
  271.             String recvHeader = String.format(SMTP_SERVER_RECEIVED_FROM_HEADER, originIp, SENDING_HOST, dateString);
  272.             //construct the header and message body
  273.             StringBuilder sb = new StringBuilder(recvHeader);
  274.             sb.append(CRLF);
  275.             sb.append("From: \"");
  276.             sb.append(email.getNiceNameFrom());
  277.             sb.append("\" <");
  278.             sb.append(from);
  279.             sb.append(">");
  280.             sb.append(CRLF);
  281.             sb.append("To: ");
  282.             if (email.hasNiceNameTo()) {
  283.                 sb.append("\"");
  284.                 sb.append(email.getNiceNameTo());
  285.                 sb.append("\" <");
  286.                 sb.append(to);
  287.                 sb.append(">");
  288.             } else {
  289.                 sb.append(to);
  290.             }
  291.             sb.append(CRLF);
  292.             sb.append("Subject: ");
  293.             sb.append(email.getSubject());
  294.             sb.append(CRLF);
  295.             sb.append("Date: ");
  296.             sb.append(dateString);
  297.             sb.append(CRLF);
  298.             sb.append("Return-Path: <");
  299.             sb.append(from);
  300.             sb.append(">");
  301.             sb.append(CRLF);
  302.             sb.append(TEXT_HEADER_CONTENT_CLASS);
  303.             sb.append(CRLF);
  304.             if (type.equals("html")) {
  305.                 sb.append(TEXT_HEADER_MIME);
  306.                 sb.append(CRLF);
  307.                 sb.append(TEXT_QUOTED_HTML);
  308.                 sb.append(CRLF);
  309.                 getXHeaders(sb, originIp);
  310.                 //line feed before message
  311.                 sb.append(CRLF);
  312.                 //now add the message text
  313.                 //as quoted-printable
  314.                 sb.append(QuotedPrintable.encode(email.getHtmlText()));
  315.             } else if (type.equals("inline")) {
  316.                 sb.append(TEXT_HEADER_MIME);
  317.                 sb.append(CRLF);
  318.                 sb.append("Content-Type: multipart/related; type=\"multipart/alternative\";\n\tboundary=\"");
  319.                 sb.append(boundary1);
  320.                 sb.append("\"");
  321.                 sb.append(CRLF);
  322.                 getXHeaders(sb, originIp);
  323.                 sb.append(CRLF);
  324.                 sb.append(DASH);
  325.                 sb.append(boundary1);
  326.                 sb.append(CRLF);
  327.                 sb.append("Content-Type: multipart/alternative;\n\tboundary=\"");
  328.                 sb.append(boundary2);
  329.                 sb.append("\"");
  330.                 sb.append(CRLF);
  331.                 sb.append(CRLF);
  332.                 sb.append(DASH);
  333.                 sb.append(boundary2);
  334.                 sb.append(CRLF);
  335.                 // send the message plain text as quoted-printable
  336.                 sb.append(TEXT_QUOTED_PLAIN);
  337.                 sb.append(CRLF);
  338.                 sb.append(CRLF);
  339.                 if (email.hasPlainText()) {
  340.                     sb.append(QuotedPrintable.encode(email.getPlainText()));
  341.                 } else {
  342.                     sb.append("This is an HTML email please switch your view.");
  343.                 }
  344.                 sb.append(CRLF);
  345.                 sb.append(CRLF);
  346.                 sb.append(DASH);
  347.                 sb.append(boundary2);
  348.                 sb.append(CRLF);
  349.                 // send the message html text as quoted-printable
  350.                 sb.append(TEXT_QUOTED_HTML);
  351.                 sb.append(CRLF);
  352.                 sb.append(CRLF);
  353.                 sb.append(QuotedPrintable.encode(email.getHtmlText()));
  354.                 //line feed?
  355.                 sb.append(CRLF);
  356.                 // last line
  357.                 sb.append(DASH);
  358.                 sb.append(boundary2);
  359.                 sb.append(DASH);
  360.                 sb.append(CRLF);
  361.                 sb.append(CRLF);
  362.                 for (int f = 0; f < files.size(); f++) {
  363.                     MailFile file = files.get(f);
  364.                     //add each image
  365.                     addInline(sb, file);
  366.                 }
  367.                 // last line
  368.                 sb.append(DASH);
  369.                 sb.append(boundary1);
  370.                 sb.append(DASH);
  371.                 sb.append(CRLF);
  372.                 sb.append(CRLF);
  373.             } else {
  374.                 //line feed before message
  375.                 sb.append(CRLF);
  376.                 //now add the message text
  377.                 sb.append(email.getPlainText());
  378.             }
  379.             //tells the SMTP server that we are done with the data
  380.             sb.append(CRLF);
  381.             sb.append(".");
  382.             if (submitCommand(sb.toString())) {
  383.                 //some mail servers can't keep up so wait n seconds between mails
  384.                 try {
  385.                     Thread.sleep(mailDelay);
  386.                 } catch (Exception e) {
  387.                     logger.debug("Stack trace", e);
  388.                 }
  389.             } else {
  390.                 logger.error("Error during mail transmission {}", email);
  391.             }
  392.             //clear and re-use
  393.             sb.delete(0, sb.length() - 1);
  394.         }
  395.         //clear list
  396.         files.clear();
  397.     }
  398.  
  399.     /**
  400.      * Initiates the connection to the mail server.
  401.      *
  402.      * @throws MailerException
  403.      */
  404.     public void startSession() throws MailerException {
  405.         try {
  406.             createSocket();
  407.             input = new BufferedReader(new InputStreamReader(smtp.getInputStream()));
  408.             output = new PrintStream(smtp.getOutputStream());
  409.             serverReply = input.readLine();
  410.             if (serverReply.charAt(0) != '2') {
  411.                 logger.error("Error connecting to SMTP server {} on port {} reply {}", new Object[]{SMTP_SERVER, SMTP_PORT, serverReply});
  412.             }
  413.             submitCommand("HELO " + SENDING_HOST);
  414.         } catch (UnknownHostException e) {
  415.             throw new MailerException(getClass().getName() + " error - unknown host: " + SMTP_SERVER);
  416.         } catch (Exception e) {
  417.             throw new MailerException(getClass().getName() + " error: " + e.getMessage());
  418.         }
  419.     }
  420.  
  421.     /**
  422.      * Initiates the connection to an SMTP server using AUTH.
  423.      *
  424.      *<pre>
  425.         220 mail.example-nt.com (IMail 8.12 786621-2) NT-ESMTP Server X1
  426.         EHLO tech-flash
  427.         502 unimplemented command
  428.         EHLO tech-flash
  429.         250-mail.example.com says hello
  430.         250-SIZE 0
  431.         250-8BITMIME
  432.         250-DSN
  433.         250-ETRN
  434.         250-AUTH LOGIN CRAM-MD5
  435.         250-AUTH=LOGIN
  436.         250 EXPN
  437.         AUTH LOGIN
  438.         334 VXNlcm5hbWU6
  439.         root
  440.         334 UGFzc3dvcmQ6
  441.         ag98dg
  442.         500 failed authentication
  443.       </pre>
  444.      *
  445.      * @throws MailerException
  446.      */
  447.     public void startSessionWithAuth() throws MailerException {
  448.         try {
  449.             createSocket();
  450.             input = new BufferedReader(new InputStreamReader(smtp.getInputStream()));
  451.             output = new PrintStream(smtp.getOutputStream());
  452.             serverReply = input.readLine();
  453.             if (serverReply.charAt(0) != '2') {
  454.                 logger.warn("Error connecting to SMTP server: {} port: {} reply: {}", new Object[]{SMTP_SERVER, SMTP_PORT, serverReply});
  455.             }
  456.             submitCommand("EHLO " + SENDING_HOST);
  457.             submitCommand("AUTH LOGIN");
  458.            
  459.             submitCommand(Base64.encodeBase64URLSafeString(AUTH_USER.getBytes()));
  460.             submitCommand(Base64.encodeBase64URLSafeString(AUTH_PASSWORD.getBytes()));
  461.             //read server replies until we get auth message
  462.             serverReply = input.readLine();
  463.             //only wait thru 10 responses
  464.             int count = 1;
  465.             while (serverReply.indexOf("authenticated") < 0) {
  466.                 serverReply = input.readLine();
  467.                 logger.debug("Server reply in auth wait loop: {}", serverReply);
  468.                 if (count++ >= 10) {
  469.                     break;
  470.                 }
  471.             }
  472.         } catch (UnknownHostException e) {
  473.             throw new MailerException(getClass().getName() + " error - unknown host: " + SMTP_SERVER);
  474.         } catch (Exception e) {
  475.             throw new MailerException(getClass().getName() + " error: " + e.getMessage());
  476.         }
  477.     }
  478.  
  479.     /**
  480.      * Initiates a connection to an SMTP server using TLS.
  481.      * http://www.faqs.org/rfcs/rfc2487.html
  482.      * TLS http://www.faqs.org/rfcs/rfc2246.html
  483.      *
  484.      * @throws MailerException
  485.      */
  486.     public void startSessionWithTLS() throws MailerException {
  487.         try {
  488.             createSocket();
  489.            
  490.             input = new BufferedReader(new InputStreamReader(smtp.getInputStream()));
  491.             output = new PrintStream(smtp.getOutputStream());
  492.            
  493.             serverReply = input.readLine();
  494.             System.out.printf("1 Server reply: %s\n", serverReply);
  495.  
  496.             if (serverReply.charAt(0) != '2') {
  497.                 logger.warn("Error connecting to SMTP server: {} port: {} reply: {}", new Object[]{SMTP_SERVER, SMTP_PORT, serverReply});
  498.             }
  499.            
  500.             submitCommand("EHLO " + SENDING_HOST);
  501.  
  502.             //only wait thru 10 responses
  503.             int count = 1;
  504.             do {
  505.                 serverReply = input.readLine();
  506.                 System.out.printf("%s Server reply: %s\n", (count + 1), serverReply);
  507.                 if (serverReply.indexOf("STARTTLS") != -1) {      
  508.                     System.out.println("STARTTLS was received");
  509.                 }
  510.                 if (count++ >= 10) {
  511.                     break;
  512.                 }
  513.             } while (serverReply.startsWith("250-"));                
  514.             System.out.println("Loop exited");
  515.  
  516.             submitCommand("STARTTLS");
  517.  
  518.             //negotiate ciphers
  519.             //mandatory suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
  520.             TlsProtocolHandler tlsHandler = new TlsProtocolHandler(smtp.getInputStream(), smtp.getOutputStream());
  521.             CertificateVerifyer cv = new AlwaysValidVerifyer();
  522.             tlsHandler.connect(cv);
  523.            
  524.             //reassign the i/o streams using the tls generated i/o streams
  525.             input = new BufferedReader(new InputStreamReader(tlsHandler.getInputStream()));
  526.             output = new PrintStream(tlsHandler.getOutputStream());
  527.            
  528.             //hello must be resent after tls setup
  529.             submitCommand("EHLO " + SENDING_HOST);
  530.                        
  531.             serverReply = input.readLine();
  532.             System.out.printf("%s Server reply (2nd hello): %s\n", (count + 1), serverReply);          
  533.            
  534.             //read server replies looking for "220 ready for tls"
  535.             Matcher m = PAT_TLS_READY.matcher(serverReply);
  536.             if (!m.matches()) {
  537.                 logger.warn("Error in TLS start: {} port: {} reply: {}", new Object[]{SMTP_SERVER, SMTP_PORT, serverReply});
  538.             }
  539.  
  540.             count = 1;
  541.             do {
  542.                 serverReply = input.readLine();
  543.                 System.out.printf("%s Server reply: %s\n", (count + 1), serverReply);
  544.                 if (serverReply.indexOf("AUTH") > 0) {
  545.                     submitCommand("AUTH LOGIN");
  546.                 }
  547.                 if (count++ >= 10) {
  548.                     break;
  549.                 }
  550.             } while (serverReply.startsWith("250-"));                
  551.             System.out.println("Loop exited");
  552.            
  553.             String str = Base64.encodeBase64URLSafeString(AUTH_USER.getBytes());
  554.             System.out.printf("Username: %s\n", str);
  555.             submitCommand(str);
  556.             str = Base64.encodeBase64URLSafeString(AUTH_PASSWORD.getBytes());
  557.             System.out.printf("Password: %s\n", str);
  558.             submitCommand(str);
  559.  
  560.             //only wait thru 10 responses
  561.             count = 1;
  562.             do {
  563.                 serverReply = input.readLine();
  564.                 System.out.printf("Server reply in auth wait loop: %s\n", serverReply);
  565.                 //gmail sends 235 2.7.0 Accepted
  566.                 if (serverReply.indexOf("Accepted") > 0) {
  567.                     break;
  568.                 }
  569.                 if (serverReply.indexOf("authenticated") > 0) {
  570.                     break;
  571.                 }
  572.                 if (count++ >= 10) {
  573.                     break;
  574.                 }
  575.             } while (true);  
  576.         } catch (UnknownHostException e) {
  577.             throw new MailerException(getClass().getName() + " error - unknown host: " + SMTP_SERVER);
  578.         } catch (Exception e) {
  579.             throw new MailerException(getClass().getName() + " error: " + e.getMessage());
  580.         }
  581.     }
  582.  
  583.     /**
  584.      * Initiates a connection to an SMTP server using TLS/SSL.
  585.      * http://www.faqs.org/rfcs/rfc2487.html
  586.      * TLS http://www.faqs.org/rfcs/rfc2246.html
  587.      *
  588.      * @throws MailerException
  589.      */
  590.     public void startSessionWithSSL() throws MailerException {
  591.         try {
  592.             createSocket();
  593.            
  594.             //negotiate ciphers
  595.             //mandatory suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
  596.             TlsProtocolHandler tlsHandler = new TlsProtocolHandler(smtp.getInputStream(), smtp.getOutputStream());
  597.             CertificateVerifyer cv = new AlwaysValidVerifyer();
  598.             tlsHandler.connect(cv);
  599.            
  600.             //reassign the i/o streams using the tls generated i/o streams
  601.             input = new BufferedReader(new InputStreamReader(tlsHandler.getInputStream()));
  602.             output = new PrintStream(tlsHandler.getOutputStream());
  603.            
  604.             serverReply = input.readLine();
  605.             System.out.printf("1 Server reply: %s\n", serverReply);
  606.  
  607.             if (serverReply.charAt(0) != '2') {
  608.                 logger.warn("Error connecting to SMTP server: {} port: {} reply: {}", new Object[]{SMTP_SERVER, SMTP_PORT, serverReply});
  609.             }
  610.            
  611.             submitCommand("EHLO " + SENDING_HOST);
  612.            
  613.             //only wait thru 10 responses
  614.             int count = 1;
  615.             do {
  616.                 serverReply = input.readLine();
  617.                 System.out.printf("%s Server reply: %s\n", (count + 1), serverReply);
  618.                 if (serverReply.indexOf("AUTH") > 0) {
  619.                     submitCommand("AUTH LOGIN");
  620.                 }
  621.                 if (count++ >= 10) {
  622.                     break;
  623.                 }
  624.             } while (serverReply.startsWith("250-"));                
  625.             System.out.println("Loop exited");
  626.            
  627.             String str = Base64.encodeBase64URLSafeString(AUTH_USER.getBytes());
  628.             System.out.printf("Username: %s\n", str);
  629.             submitCommand(str);
  630.             str = Base64.encodeBase64URLSafeString(AUTH_PASSWORD.getBytes());
  631.             System.out.printf("Password: %s\n", str);
  632.             submitCommand(str);
  633.  
  634.             //only wait thru 10 responses
  635.             count = 1;
  636.             do {
  637.                 serverReply = input.readLine();
  638.                 System.out.printf("Server reply in auth wait loop: %s\n", serverReply);
  639.                 //gmail sends 235 2.7.0 Accepted
  640.                 if (serverReply.indexOf("Accepted") > 0) {
  641.                     break;
  642.                 }
  643.                 if (serverReply.indexOf("authenticated") > 0) {
  644.                     break;
  645.                 }
  646.                 if (count++ >= 10) {
  647.                     break;
  648.                 }
  649.             } while (true);  
  650.                
  651.         } catch (UnknownHostException e) {
  652.             throw new MailerException(getClass().getName() + " error - unknown host: " + SMTP_SERVER);
  653.         } catch (Exception e) {
  654.             throw new MailerException(getClass().getName() + " error: " + e.getMessage());
  655.         }
  656.     }    
  657.    
  658.     private void createSocket() throws UnknownHostException, IOException {
  659.         //check for null smtp server
  660.         if (null == SMTP_SERVER || SMTP_SERVER.length() < 3) {
  661.             //look in system props
  662.             String tmp = System.getProperty("mail.server");
  663.             if (null == tmp) {
  664.                 tmp = "mail.example.com";
  665.             }
  666.             SMTP_SERVER = tmp;
  667.         }
  668.         //connect to the MX
  669.         smtp = new Socket(SMTP_SERVER, SMTP_PORT);
  670.         //set a socket timeout
  671.         logger.debug("After socket creation, timeout: {}", smtp.getSoTimeout());
  672.         smtp.setSoTimeout(socketTimeout);
  673.     }    
  674.    
  675.     /**
  676.      * Flushes the stream and closes all aspects of the connection.
  677.      */
  678.     public void endSession() {
  679.         //disconnect
  680.         try {
  681.             submitCommand("QUIT");
  682.             if (input != null) {
  683.                 input.close();
  684.             }
  685.             if (output != null) {
  686.                 output.flush();
  687.                 output.close();
  688.             }
  689.             if (smtp != null) {
  690.                 smtp.close();
  691.             }
  692.         } catch (Exception e) {
  693.             logger.error("Logout error", e);
  694.         }
  695.     }
  696.  
  697.     /**
  698.      * Convenience method to allow a "quick" send.
  699.      *
  700.      * @param email
  701.      * @return true if no errors occur, false otherwise
  702.      */
  703.     public boolean sendMessage(Email email) {
  704.         boolean result = false;
  705.         try {
  706.             if ("none".equals(authType)) {
  707.                 startSession();
  708.             } else if ("ssl".equals(authType)) {
  709.                 startSessionWithSSL();
  710.             } else if ("tls".equals(authType)) {
  711.                 startSessionWithTLS();
  712.             }
  713.             sendMail(email);
  714.             result = true;
  715.         } catch (Exception ex) {
  716.             logger.error("Could not send email {}", ex);
  717.         } finally {
  718.             endSession();
  719.         }
  720.         return result;
  721.     }
  722.  
  723.     private void getXHeaders(StringBuilder sb, String originIp) {
  724.         sb.append("X-Coder: http://www.gregoire.org/");
  725.         sb.append(CRLF);
  726.         sb.append("X-Originating-IP: [");
  727.         sb.append(originIp);
  728.         sb.append("]");
  729.         sb.append(CRLF);
  730.     }
  731.  
  732.     /**
  733.      * Sends an SMTP command to the connected mail server.
  734.      *
  735.      * @param command valid SMTP command
  736.      * @return true if successful, false otherwise
  737.      * @throws MailerException
  738.      */
  739.     private boolean submitCommand(String command) throws MailerException {
  740.         boolean result = false;
  741.         try {
  742.             //to eliminate the "binary" logging
  743.             if (!command.startsWith("Received: from")) {
  744.                 logger.debug("Submit command: {}", command);
  745.             }
  746.  
  747.             output.print(command);
  748.             output.print(CRLF);
  749.             serverReply = input.readLine();
  750.             logger.debug("Server Reply: {}", serverReply);
  751.  
  752.             //if the command is message data, get the message id for storage in db
  753.             //when we get the reply
  754.             Matcher m = PAT_SMTP_COMMAND.matcher(command.subSequence(0, 4));
  755.             if (!m.matches()) {
  756.                 //reset the matcher
  757.                 m.reset();
  758.                 // Postfix = 250 Ok: queued as 2727171
  759.                 // MS Exchange = 250 Ok: queued as C6F496722E
  760.                 // Cox InterMail = 250 xH0R1V00j09Def40000000 mail accepted for delivery
  761.                 //                 250 2.0.0 hB0N1c00F411ueG03B0NQB mail accepted for delivery
  762.                 m = PAT_MESSAGE_ID.matcher(serverReply);
  763.                 if (m.find()) {
  764.                     logger.debug("Group 1: {}", m.group(1));
  765.                     logger.debug("Group 2: {}", m.group(2));               
  766.                     try {
  767.                         String messageId = serverReply.substring(m.start(2), m.end(2));
  768.                         logger.info("Found message id: {}", messageId);
  769.                     } catch (Exception e) {
  770.                     }
  771.                 } else {
  772.                     logger.debug("Didnt find message id");
  773.                 }
  774.             }
  775.  
  776.             //true if there is a failure of some kind
  777.             if (serverReply.charAt(0) == '4' || serverReply.charAt(0) == '5') {
  778.                 logger.warn("The mail server returned an error: {}", serverReply);
  779.             } else {
  780.                 result = true;
  781.             }
  782.         } catch (Exception e) {
  783.             logger.error("Error in mail command", e);
  784.             throw new MailerException(getClass().getName() + " error: " + e.getMessage() + " during " + command, serverReply);
  785.         }
  786.         return result;
  787.     }
  788.  
  789.     /**
  790.      * Returns a properly formatted date.
  791.      * The RFC that states this is 2822 and a link is: http://ftp.rfc-editor.org/in-notes/rfc2822.txt
  792.      * <br>
  793.      * ex: Mon, 21 Apr 2003 13:49:43 -0700
  794.      * <br>
  795.      *
  796.      * @return formatted date
  797.      */
  798.     public String getFormattedDate() {
  799.         return formatter.format(new Date());
  800.     }
  801.  
  802.     public void setInlineFiles(MailFile mailFile) {
  803.         if (mailFile != null) {
  804.             this.type = "inline";
  805.             files.add(mailFile);
  806.         }
  807.     }
  808.  
  809.     public void setInlineFiles(MailFile[] mailFiles) {
  810.         this.type = "inline";
  811.         for (int mf = 0; mf < mailFiles.length; mf++) {
  812.             files.add(mailFiles[mf]);
  813.         }
  814.     }
  815.  
  816.     /**
  817.      * Adds an inline file to the email
  818.      *
  819.      * @param sb   email text
  820.      * @param file object containing file data
  821.      */
  822.     public void addInline(StringBuilder sb, MailFile file) {
  823.         sb.append(DASH);
  824.         sb.append(boundary1);
  825.         sb.append(CRLF);
  826.         // image
  827.         sb.append("Content-Type: ");
  828.         sb.append(file.getMimeType());
  829.         sb.append("; name=\"");
  830.         sb.append(file.getFileName());
  831.         sb.append("\"\nContent-Transfer-Encoding: base64\nContent-ID: <");
  832.         sb.append(file.getFileName());
  833.         sb.append(">");
  834.         sb.append(CRLF);
  835.         sb.append(CRLF);
  836.         //Some mail servers will reject emails if they contain lines longer than 76 chars
  837.         byte[] byteLine = new byte[76];
  838.         byte[] buf = file.getDataBuffer();
  839.         int bufLen = buf.length;
  840.         int bufIndex = 0;
  841.         while (bufIndex < bufLen) {
  842.             for (int b = 0; b < 76; b++) {
  843.                 if (bufIndex >= bufLen) {
  844.                     break;
  845.                 }
  846.                 byteLine[b] = buf[bufIndex++];
  847.             }
  848.             sb.append(new String(byteLine)); //a byte line must be no longer than 76 bytes + CRLF
  849.             sb.append(CRLF);
  850.             //reset byteline
  851.             System.arraycopy(EMPTY_ARRAY, 0, byteLine, 0, 76);
  852.         }
  853.         sb.append(CRLF);
  854.     }
  855.  
  856.     /**
  857.      * The host name to use when executing the HELO command.
  858.      *
  859.      * @param sendHost
  860.      */
  861.     public void setSendingHost(String sendHost) {
  862.         Mailer.SENDING_HOST = sendHost;
  863.     }
  864.  
  865.     public void setSmtpPort(int port) {
  866.         Mailer.SMTP_PORT = port;
  867.     }
  868.  
  869.     public void setSmtpServer(String serverAddress) {
  870.         Mailer.SMTP_SERVER = serverAddress;
  871.     }    
  872.  
  873.     public void setAuthUser(String userName) {
  874.         Mailer.AUTH_USER = userName;
  875.     }    
  876.    
  877.     public void setAuthPassword(String password) {
  878.         Mailer.AUTH_PASSWORD = password;
  879.     }    
  880.    
  881.     public int getSocketTimeout() {
  882.         return Mailer.socketTimeout;
  883.     }
  884.  
  885.     public void setSocketTimeout(int socketTimeout) {
  886.         Mailer.socketTimeout = socketTimeout;
  887.     }
  888.  
  889.     /**
  890.      * Returns the message id assigned to the email by the mail server.
  891.      *
  892.      * @return 10 character message id
  893.      */
  894.     public String getMessageId() {
  895.         return messageId;
  896.     }
  897.  
  898.     public void setMessageId(String id) {
  899.         messageId = id;
  900.     }
  901.  
  902.     public static void main(String[] args) {
  903.         Mailer m = new Mailer();
  904.         m.setSendingHost("127.0.0.1");
  905.         m.setAuthUser("your-sending-gmail-email-address");
  906.         m.setAuthPassword("your-gmail-password");
  907.         m.setSmtpServer("smtp.gmail.com");
  908.         m.setSmtpPort(587); //tls
  909.         m.setType("plain");
  910.        
  911.     Email mail = new Email();
  912.     mail.setFrom("your-sending-gmail-email-address");
  913.     mail.setNiceNameFrom("Sally Sender");
  914.     mail.setTo("your-recipients-address");
  915.     mail.setNiceNameTo("John Q. Public");
  916.     mail.setSubject("Testing tls");
  917.     mail.setPlainText("This is a test of my tls impl.");
  918.     mail.setOriginIP("127.0.0.1"); //use a real origin ip
  919.        
  920.     try {
  921.             m.startSessionWithTLS();
  922.         m.sendMail(mail);
  923.         System.out.printf("Message id: %s", m.getMessageId());
  924.     } catch (Exception e) {
  925.         e.printStackTrace();
  926.     } finally {
  927.         m.endSession();
  928.     }
  929.        
  930.     }
  931.    
  932. }
Add Comment
Please, Sign In to add comment