場景描述:
保存郵箱配置時自動探測郵箱配置參數是否正確,結果發現在探測SMTP時,系統報出如下異常:
javax.mail.MessagingException: 501 Command "HELO" requires an argument
at com.sun.mail.smtp.SMTPTransport.issueCommand(SMTPTransport.java:1363)
at com.sun.mail.smtp.SMTPTransport.helo(SMTPTransport.java:838)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:375)
at javax.mail.Service.connect(Service.java:275)
但是換一個windows服務器,發現就沒這樣的問題,僅在一臺Linux服務器上可以重現,直觀感覺就是這臺Linux服務器某些配置有問題。
排查步驟
1. 找一臺同樣操作系統的Linux服務器,驗證郵箱配置,ok,排除Linux操作系統特殊性的問題
2. 直接在Linux服務器上使用telnet連接對端郵件服務器的SMTP端口,OK,排除該服務器的網絡問題
3. 查找HELO指令解釋
HELO
-- Initiates a conversation with the mail server. When using this command you can specify your domain name so that the mail server knows who you are. For example, HELO mailhost2. cf.ac.uk.
發現HELO指令后面需要跟一個發起者的主機名,告訴SMTP服務器這個消息來源是哪里。
再看異常信息是"501 Command "HELO" requires an argument",很明顯,程序在跟SMTP SERVER交互過程中沒有傳遞源主機域名。4. 查看JAVA MAIL源碼
查找HELO指令,如下:
/**
* Issue the <code>HELO</code> command.
*
* @param domain
* our domain
*
* @since JavaMail 1.4.1
*/
protected void helo(String domain) throws MessagingException {
if (domain != null)
issueCommand("HELO " + domain, 250);
else
issueCommand("HELO", 250);
}
查找helo方法在哪里被調用,看看domain如何被傳遞過來的
if (useEhlo)
succeed = ehlo(getLocalHost());
if (!succeed)
helo(getLocalHost());
順理成章,接著找getLocalHost()方法,定義如下:
/**
* Get the name of the local host, for use in the EHLO and HELO commands.
* The property mail.smtp.localhost overrides mail.smtp.localaddress, which
* overrides what InetAddress would tell us.
*/
public synchronized String getLocalHost() {
try {
// get our hostname and cache it for future use
if (localHostName == null || localHostName.length() <= 0)
localHostName = session.getProperty("mail." + name + ".localhost");
if (localHostName == null || localHostName.length() <= 0)
localHostName = session.getProperty("mail." + name+ ".localaddress");
if (localHostName == null || localHostName.length() <= 0) {
InetAddress localHost = InetAddress.getLocalHost();
localHostName = localHost.getHostName();
// if we can't get our name, use local address literal
if (localHostName == null)
// XXX - not correct for IPv6
localHostName = "[" + localHost.getHostAddress() + "]";
}
} catch (UnknownHostException uhex) {
}
return localHostName;
}
可以看到hostname的獲取可以通過當前連接的session屬性中獲取,也可以從當前服務器的hosts配置中獲取,而我們程序是沒有在session中設置hostname的,因此原因肯定在于該臺Linux服務器的hosts文件被修改,造成JAVA程序無法自動獲得localhostName。5. 查看/etc/hosts文件,情況如下:
127.0.0.1 localhost.localdomain localhost
::1 localhost6.localdomain6 localhost6
簡單的將hosts文件修改如下:
127.0.0.1 localhost
::1 localhost6.localdomain6 localhost6
6. 重新測試,問題解決了。
其實,這種情況也可以通過程序避免,即在session連接中加入當前服務器的hostname屬性,程序示例:
public static void main(String[] args) {
try {
int smtpport = 25;
String smtpserver = "219.147.xxx.xxx";
Properties prop = System.getProperties();
prop.put("mail.smtp.auth", "true");
prop.put("mail.smtp.localhost", "myMailServer");
Session mailSession = Session.getInstance(prop, null);
Transport transport = mailSession.getTransport("smtp");
transport.connect(smtpserver,smtpport, "username", "password");
System.out.println("connect ok");
transport.close();
} catch (AuthenticationFailedException en) {
en.printStackTrace();
System.out.println("smtp服務器連接失敗,請檢查輸入信息是否正確");
} catch (NoSuchProviderException e) {
e.printStackTrace();
System.out.println("smtp服務器連接失敗,請檢查輸入信息是否正確");
} catch (MessagingException e) {
e.printStackTrace();
System.out.println("smtp服務器連接失敗,請檢查輸入信息是否正確");
}
}