Отправить письмо с STARTTLS
Я пытаюсь отправить электронное письмо с помощью команды STARTTLS. Я настроил тестовую учетную запись в Gmail и настроил ее на прием только входящей электронной почты с подключением TLS.
По причинам, по которым я не хочу вдаваться, я не могу использовать JavaMail или другие почтовые библиотеки.
Мне удалось отправить электронные письма на этот тестовый аккаунт с помощью openssl. Итак, я знаю, что учетная запись настроена правильно.
Работающий пример: openssl s_client -starttls smtp -crlf -connect aspmx.l.google.com:25
Я также был в состоянии послать электронные письма к этой учетной записи электронной почты, используя приложение.Net, включающее TLS.
Я знаю, что мой пример (ниже) не является правильным способом отправки электронных писем, потому что я не реагирую на ответ сервера, но я подумал, что это хороший / короткий способ создать пример для демонстрации проблемы.
Я некоторое время пытался заставить это работать. Я попытался соединиться с различными портами (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 ответа
Я на правильном пути для согласования соединения 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");
}
}
Я поправил ответ сверху до рабочей версии.
Я вставляю это ниже, так что это может быть полезно для кого-то еще. Спасибо Реми Лебо за ваше руководство.
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");
}
}