場景描述:
儲存郵件信箱設定時自動偵測郵箱設定參數是否正確,結果發現在偵測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伺服器連線失敗,請檢查輸入資訊是否正確");
}
}