2015-12-11 21 views
0

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

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

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

例子工作:OpenSSL的的s_client.First -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("[email protected]".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: <[email protected]>\r\n"); 
     Thread.sleep(delay * 5); 

     send("RCPT TO: <[email protected]>\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

Am I on the right path to negotiating a TLS connection to transmit an email or am I missing something obvious?

你缺少的重要步骤。

大多数SMTP服务器仅在端口587上实现STARTTLS,尽管有些服务器也在端口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("[email protected]".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: <[email protected]>", 250); 
     sendCmd("RCPT TO: <[email protected]>", new int[]{250, 251}); 
     sendCmd("DATA", 354); 

     sendLine("From: <[email protected]>"); 
     sendLine("To: <[email protected]>"); 
     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"); 
    } 
} 
+0

感谢您的反馈。对于域“aspmx.l.google.com”端口587不起作用,所以不得不使用端口25.然后,startHandshake()命令给了我一个错误,但第二次它工作正常。我不得不改变你的示例代码,以便在我们发送下一个命令之前允许完成服务器的响应。但它现在正在完美工作。非常感谢你! – Leo

+0

为什么您使用'aspmx.l.google.com'而不是'smtp.gmail.com'?为什么在EHLO命令中指定'aspmx.l.google.com'?您应该发送自己的客户端主机名。无论如何,我给你的代码已经等待一个完整的响应到达,然后才允许发送下一个命令。 'sendCmd()'发送一个命令行,然后读取响应,直到到达最终响应终止符,然后它将响应代码返回给调用者。 –

+0

我并非试图将电子邮件转交给Gmail来转发邮件。我正在尝试将其他邮件服务器的电子邮件发送到Gmail用户的邮箱。如果您执行tls.calcium.co.nz的mxlookup,您将获得gmail域名“aspmx.l.google.com”而不是smtp域名“smtp.gmail.com”。 – Leo

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("[email protected]".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: <[email protected]>", 250); 
    sendCmd("RCPT TO: <[email protected]>", new int[]{250, 251}); 
    sendCmd("DATA", 354); 

    sendLine("From: <[email protected]>"); 
    sendLine("To: <[email protected]>"); 
    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"); 
    } 
}