0

我正在尝试使用 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
4

2 回答 2

3

我是否在协商 TLS 连接以传输电子邮件的正确路径,还是我遗漏了一些明显的东西?

你错过了重要的步骤。

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

收到成功STARTTLS响应后,您必须启动并完成 SSL/TLS 握手,然后再发送任何进一步的 SMTP 命令。您没有执行该步骤(您注释掉了对 的调用SSLSocket.startHandshake())。服务器期待您的握手问候,但您正在发送一个新命令,服务器将其解释为错误的握手并关闭连接,当您发送命令EHLO时会向您报告。AUTH LOGIN

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

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

您需要更多类似的东西:

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("2wsxZAQ!".getBytes());

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

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

       if (sendCmd("EHLO aspmx.l.google.com") == 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("AUTH LOGIN", 334);
       if (sendCmd(username, new int[]{235, 334}) == 334)
           sendCmd(password, 235);

       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 sendCmd(String s) throws Exception
  {
      sendLine(s);

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

      int respCode = Integer.parseInt(line.substring(0, 3));
      while ((line.length() > 3) && (line.charAt(3) == '-'))
      {
          line = out.readLine();
          System.out.println("SERVER: " + line);                                    
      }

      return respCode;
  }

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

  private static int sendCmd(String s, int[] expectedRespCodes) throws Exception
  {
      int respCode = sendCmd(s);
      checkResponse(respCode, expectedRespCodes);
      return respCode;
  }

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

  private static void checkResponse(int actualRespCode, int[] expectedRespCodes)
  {
      for (int i = 0; i < expectedRespCodes.length; ++i)
      {
          if (actualRespCode == expectedRespCode)
              return;
      }
      throw new Exception("command failed");
  }
}
于 2015-12-11T03:38:01.043 回答
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");
  }
}
于 2015-12-14T02:14:17.013 回答