501 Command "HELO" requires an argument问题排查记录

本文介绍了一种在特定Linux服务器上出现的SMTP连接异常问题,详细记录了问题排查过程及解决方案,包括对JAVAMAIL源码的分析及对/etc/hosts文件的调整。

来源:http://blog.youkuaiyun.com/hbcui1984/article/details/5655657?reload

场景描述:
保存邮箱配置时自动探测邮箱配置参数是否正确,结果发现在探测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 = helo(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    mylocalserverdomain   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", "mylocalserverdomain");
                        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服务器连接失败,请检查输入信息是否正确");
                }
        }


### 解决 C# 中 SMTP 服务器拒绝 HELO 命令并提示需要完全限定主机名的问题 当在 C# 使用 SMTP 发送邮件时遇到 `Helo command rejected: need fully-qualified hostname` 错误,这通常是因为客户端未提供有效的完全限定域名 (FQDN),或者 SMTP 服务器配置严格要求 FQDN。 以下是解决方案: #### 1. 修改 C# 的 SMTP 客户端代码 可以通过设置 `Client.HostName` 属性来指定一个合法的 FQDN。如果当前环境无法自动解析到正确的 FQDN,则需手动定义它。以下是一个完整的示例代码片段[^3]: ```csharp using System; using System.Net.Mail; class Program { static void Main() { try { MailMessage mail = new MailMessage(); SmtpClient client = new SmtpClient(); // 设置发件人和收件人的邮箱地址 mail.From = new MailAddress("from@example.com"); mail.To.Add(new MailAddress("to@example.com")); // 邮件主题和正文 mail.Subject = "Test Email"; mail.Body = "This is a test email."; // 指定 SMTP 服务器及其端口 client.Host = "smtp.example.com"; // 替换为实际的 SMTP 服务器地址 client.Port = 587; // 或者其他支持的端口号 // 如果需要身份验证,请启用此部分 client.Credentials = new System.Net.NetworkCredential("username", "password"); // 手动设置 HostName 为一个有效 FQDN client.DeliveryMethod = SmtpDeliveryMethod.Network; client.EnableSsl = true; // 根据需求开启 SSL/TLS 加密 AppDomain.CurrentDomain.SetCustomHostname("your-valid-fqdn.com"); // 发送邮件 client.Send(mail); Console.WriteLine("Email sent successfully."); } catch (Exception ex) { Console.WriteLine($"Error sending email: {ex.Message}"); } } } ``` 注意:`AppDomain.CurrentDomain.SetCustomHostname()` 方法用于显式设置应用程序域中的主机名。如果没有该方法可用,可以尝试通过网络配置文件或其他方式强制设定主机名[^4]。 --- #### 2. 调整远程 SMTP 服务器的配置 如果问题是由于目标 SMTP 服务器过于严格的策略引起的,则可能还需要调整其配置参数。例如,在 Postfix 上可按如下操作处理[^2]: 编辑 `/etc/postfix/main.cf` 文件,定位至 `smtpd_helo_restrictions` 参数,并移除可能导致拒绝连接的部分规则(如 `reject_non_fqdn_helo_hostname`)。最终应类似于以下内容: ```bash smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, check_helo_access pcre:/etc/postfix/helo_access.pcre ``` 完成更改后重启服务以应用新设置: ```bash sudo systemctl restart postfix ``` 对于 IIS 和 Windows 平台上的 SMTP 组件,也可以进入属性窗口下的高级选项卡,确认是否启用了类似的限制逻辑[^1]。 --- #### 3. 确认本地系统的 DNS 配置无误 确保运行程序的操作系统能够返回一个合格的 FQDN 地址给外部查询工具。测试命令如下所示: ```bash hostname --fqdn nslookup $(hostname) ``` 假如以上任一指令未能给出预期的结果,则表明存在潜在问题亟待修正——比如更新 `/etc/hosts` 记录或是重新安装必要的软件包等。 --- ### 总结 综上所述,要彻底消除 “HELO Command Rejected” 类型错误的发生概率,既可以从源码层面着手改进调用流程;又或者是针对特定品牌型号设备作出针对性优化措施两者双管齐下效果更佳。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值