我正在尝试使用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
答案 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");
}
}