情境描述:
儲存郵箱配置時自動探測郵箱配置參數是否正確,結果發現在探測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伺服器串連失敗,請檢查輸入資訊是否正確");
}
}