使用STARTTLS发送电子邮件

时间:2015-12-11 02:35:27

标签: sockets email ssl starttls

我正在尝试使用STARTTLS命令发送电子邮件。我在Gmail中设置了一个测试帐户,并将其设置为仅接受带有TLS连接的入站电子邮件。

由于我不想进入的原因,我无法使用JavaMail或其他电子邮件库。

我已经能够使用openssl向此测试帐户发送电子邮件。所以我知道该帐户已正确设置。

工作的示例:openssl s_client -starttls smtp -crlf -connect aspmx.l.google.com:25

我也可以使用包含TLS的.Net应用程序向此电子邮件帐户发送电子邮件。

我知道我的示例(下面)不是发送电子邮件的正确方法,因为我没有对服务器的响应作出反应,但我认为这是一个很好/简短的方法来创建一个示例来演示问题。

我一直在尝试让它发挥作用。我尝试连接不同的端口(465,587,25),结果相似。我得到的错误是在“AUTH LOGIN”命令上,但我在上一个命令“EHLO aspmx.l.google.com”中没有收到任何来自服务器的响应。

我得到的错误是:“错误:软件导致连接中止:套接字写入错误”。

我是否正在通过谈判TLS连接来传输电子邮件,或者我错过了一些明显的东西?

非常感谢任何帮助。

示例:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.bind.DatatypeConverter;

public class SendEmailWithTLSConnectionTest {

private static DataOutputStream dos;
private static BufferedReader out = null;

public static void main(String[] args) throws Exception
{   
    try
    {
       int delay = 1000;

       String username = DatatypeConverter.printBase64Binary("leo@tls.calcium.co.nz".getBytes());
       String password = DatatypeConverter.printBase64Binary("2wsxZAQ!".getBytes());

       Socket sock = new Socket("aspmx.l.google.com", 25);

       out = new BufferedReader(new InputStreamReader(sock.getInputStream()));

       (new Thread(new Runnable()
       {
            public void run()
            {
                while(true)
                {
                     try
                     {
                         if(out != null)
                         {
                              String line;

                              while((line = out.readLine()) != null)
                              {
                                   System.out.println("SERVER: "+line);                                    
                              }
                         }
                     }
                     catch (IOException e)
                     {
                         System.out.println("IOException SERVER! Error: " + e);

                         try {
                            Thread.sleep(1000 * 5);
                        } catch (InterruptedException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                     }
                }
            }
       })).start();

       dos = new DataOutputStream(sock.getOutputStream());

       send("EHLO aspmx.l.google.com\r\n");
       Thread.sleep(delay * 5);

       send("STARTTLS\r\n");
       Thread.sleep(delay * 5);

       SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(
               sock, 
               sock.getInetAddress().getHostAddress(), 
               587, 
               true);                 

       sslSocket.setUseClientMode(true);
       sslSocket.setEnableSessionCreation(true);        

       // Thread.sleep(delay * 5);
       // sslSocket.startHandshake();

       send("EHLO aspmx.l.google.com\r\n");
       Thread.sleep(delay * 5);

       send("AUTH LOGIN\r\n");
       Thread.sleep(delay * 5);

       send(username + "\r\n");
       Thread.sleep(delay * 5);

       send(password + "\r\n");
       Thread.sleep(delay * 5);

       send("MAIL FROM: <leo@tls.calcium.co.nz>\r\n");
       Thread.sleep(delay * 5);

       send("RCPT TO: <leo@tls.calcium.co.nz>\r\n");
       Thread.sleep(delay * 5);

       send("DATA\r\n");
       Thread.sleep(delay * 5);

       send("Test 1 2 3");
       Thread.sleep(delay * 5);

       send("\r\n.\r\n");
       Thread.sleep(delay * 5);

       send("QUIT\r\n");
     }
     catch(Exception ex)
     {
        System.out.println("Exception when sending out test. Error: " + ex.getMessage());
     }
  }

  private static void send(String s) throws Exception
  {
       dos.writeBytes(s);

       System.out.println("CLIENT: "+s);
  }   
}

输出:

SERVER: 220 mx.google.com ESMTP on10si24036122pac.132 - gsmtp
CLIENT: EHLO aspmx.l.google.com
SERVER: 250-mx.google.com at your service, [103.23.17.19]
SERVER: 250-SIZE 35882577
SERVER: 250-8BITMIME
SERVER: 250-STARTTLS
SERVER: 250-ENHANCEDSTATUSCODES
SERVER: 250-PIPELINING
SERVER: 250-CHUNKING
SERVER: 250 SMTPUTF8
CLIENT: STARTTLS
SERVER: 220 2.0.0 Ready to start TLS
CLIENT: EHLO aspmx.l.google.com
Exception when sending out test. Error: Software caused connection abort: socket write error

2 个答案:

答案 0 :(得分:2)

  

我是否正在通过谈判TLS连接来传输电子邮件,或者我错过了一些明显的东西?

您缺少重要的步骤。

大多数SMTP服务器仅在端口587上实现STARTTLS,但有些服务器也在端口25上实现它(Gmail确实如此)。您必须解析服务器的EHLO响应,以了解是否允许STARTTLS

收到成功STARTTLS响应后,您必须先启动并完成SSL / TLS握手,然后再发送任何SMTP命令。你没有这样做(你注释了对SSLSocket.startHandshake()的调用)。服务器期待您的握手问候,但您正在发送新的EHLO命令,服务器会将其解释为握手不良并关闭连接,当您发送{{1}时,会向您报告连接}命令。

此外,您正在连接到端口25,但随后告诉AUTH LOGIN您已连接到端口587。你需要保持一致。

此外,一旦您建立了SSL / TLS会话,您就不能再使用原始SSLSocketFactory进行阅读/发送。您可以将未加密的数据直接发送到服务器,并回读服务器的原始加密数据。您必须使用Socket,以便它可以加密您发送的任何内容并解密您阅读的内容。因此,您必须相应地重新初始化您的输入/输出流(并完全摆脱您的阅读线程,因为它不属于此代码.SMTP是同步的 - 发送命令,读取响应,发送命令,读取回应等。)

你需要更多的东西:

SSLSocket

答案 1 :(得分:0)

我已将上面的答案调整为工作版本。

我在下面插入,以便对其他人有用。感谢Remy Lebeau的指导。

package com.mailprimer.smtp.sender;

import java.io.BufferedReader;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.bind.DatatypeConverter;

public class SendEmailWithTLSConnectionTest {

private static DataOutputStream dos;
private static BufferedReader out = null;

public static void main(String[] args) throws Exception
{   
try
{
   String username = DatatypeConverter.printBase64Binary("leo@tls.calcium.co.nz".getBytes());
   String password = DatatypeConverter.printBase64Binary("XXXXXXXXXX".getBytes());

   Socket sock = new Socket("aspmx.l.google.com", 25);

   out = new BufferedReader(new InputStreamReader(sock.getInputStream()));    
   dos = new DataOutputStream(sock.getOutputStream());

   int responseCode = sendCommand("EHLO aspmx.l.google.com", 250);

   if ( responseCode == 250)
   {
       // TODO: parse response
       if (true/*response contains STARTTLS capability*/)
       {
           sendCmd("STARTTLS", 220);

           SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(
             sock, 
             sock.getInetAddress().getHostAddress(), 
             sock.getPort(), 
             true);                 

           sslSocket.setUseClientMode(true);
           sslSocket.setEnableSessionCreation(true);        

           System.out.println("CLIENT: securing connection");
           sslSocket.startHandshake();
           // on an initial handshake, startHandshake() blocks the calling
           // thread until the handshake is finished...
           System.out.println("CLIENT: secured");

           sock = sslSocket;
           out = new BufferedReader(new InputStreamReader(sock.getInputStream()));    
           dos = new DataOutputStream(sock.getOutputStream());

           sendCmd("EHLO aspmx.l.google.com", 250);
       }
   }
   else
   {
       sendCmd("HELO aspmx.l.google.com", 250);
   }

   sendCmd("MAIL FROM: <leo@tls.calcium.co.nz>", 250);
   sendCmd("RCPT TO: <leo@tls.calcium.co.nz>", new int[]{250, 251});
   sendCmd("DATA", 354);

   sendLine("From: <leo@tls.calcium.co.nz>");
   sendLine("To: <leo@tls.calcium.co.nz>");
   sendLine("Subject: test");
   sendLine("");
   sendLine("Test 1 2 3");
   sendCmd(".", 250);

   sendCmd("QUIT", 221);
 }
 catch(Exception ex)
 {
    System.out.println("Exception when sending out test. Error: " + ex.getMessage());
 } 

}



private static void sendLine(String s) throws Exception
  {
      dos.writeBytes(s + "\r\n");

      System.out.println("CLIENT: " + s);
  }



private static int sendCommand(String s, int expectedRespCode) throws Exception
  {
      sendLine(s);

  String line = out.readLine();
  System.out.println("SERVER: " + line);   

  // Need to wait a little longer until the other response is finished.
  Thread.sleep(100);

  int respCode = Integer.parseInt(line.substring(0, 3));

  if(expectedRespCode > 0)
  {
      while ((line.length() > 3) && ((line.charAt(3) == '-') || respCode != expectedRespCode))
      {
          line = out.readLine();
          System.out.println("SERVER: " + line);   

          respCode = Integer.parseInt(line.substring(0, 3));

          // Need to wait a little longer until the other response is finished.
          Thread.sleep(100);
      }
  }

  return respCode;


}



private static int sendCmd(String s, int expectedRespCode) throws Exception
  {
      int respCode = sendCommand(s, expectedRespCode);

  checkResponse(respCode, expectedRespCode);

  return respCode;


}

  private static int sendCmd(String s, int[] expectedRespCodes) throws Exception
  {
      int respCode = sendCommand(s, 0);

  checkResponse(respCode, expectedRespCodes);

  return respCode;


}

  private static void checkResponse(int actualRespCode, int expectedRespCode) throws Exception
  {
      if (actualRespCode != expectedRespCode)
          throw new Exception("command failed");
  }



private static void checkResponse(int actualRespCode, int[] expectedRespCodes) throws Exception
  {
      for (int i = 0; i < expectedRespCodes.length; ++i)
      {
          int expectedRespCode = expectedRespCodes[i];

          if (actualRespCode == expectedRespCode)
              return;
      }

      throw new Exception("command failed");
  }
}