【问题标题】:mail submission agent (MSA) server hostname from email address via c++来自电子邮件地址的邮件提交代理(MSA)服务器主机名通过 C++
【发布时间】:2019-01-09 14:01:19
【问题描述】:

我正在寻找一种通用的方法来获取邮件提交服务器的主机名,方法是只使用 c/c++ 的电子邮件地址,这样我就可以连接到端口 587 和/或 465 smtp 提交。

例如example@gmail.com -> smtp.gmail.com 或 gmail-smtp-msa.l.google.com

基本上,我代表客户发送一些特定的电子邮件。所以最后看起来他们已经发送了电子邮件。到目前为止,我仍在使用一个简单的域来主机名映射,例如 {gmail.com,smtp.gmail.com},我会在需要时对其进行扩展。但是,我非常希望避免使用此地图并自动执行此过程。

前段时间我发了c++ sockets - smtp server hostname from email address,应该是同一个问题,可能还不够清楚。在答案中,我被建议执行 DNS 查找并询问 MX 记录。一本书和一个实现后来我注意到这些mx记录不适合邮件提交(无法连接到端口587,只有25)。我检查了 resolv.h 中定义的所有其他类型并检查了 nslookup (它可能正在使用 resolv.h ),现在我真的认为这通过 DNS 查找是不可行的。如果有人想尝试,请检查 nslookup -type=mx gmail.com 你不会看到任何 -msa... 的东西。

我现在有点绝望,因为我真的很想完成它。但是,我也不想在上面花费数周时间。如果有一个答案告诉我在合理的时间内不可行,我也会非常高兴。


编辑:

以下是我刚刚通过 DNS MX 方法非常快速地制作的一个最小示例,并尝试连接到端口 25、465、587。

/*c++1z, Don't forget -lresolv*/
#include <resolv.h>
#include <string>
#include <map>
#include <sstream>
#include <cerrno>
#include <netdb.h>
#include <iostream>
#include <cstddef>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/tcp.h>

//like assert but throws instead
#define CHECK(condition)\
    if(!(condition)){\
        std::ostringstream oss;\
        oss << __FILE__ << ":" << __LINE__ << "\nerrno:" << errno << "\nh_errno:" << h_errno << std::endl;\
        throw std::runtime_error(oss.str());\
    }

//!
//! \brief mxRecords performs dns T_MX lookup
//! \param domain the domain part of the email e.g. example@gmail.com -> gmail.com
//! \return multimap of {priority, server hostname} pairs
//!
auto mxRecords(std::string const&domain)
->std::multimap<size_t/*priority*/,std::string/*server name*/>
{
    std::basic_string<unsigned char> response(PACKETSZ,'0');
    int response_size = res_search(domain.data(),C_IN,T_MX,response.data(),response.size());
    CHECK(response_size != -1);
    response.resize(response_size);

    ns_msg handle;
    CHECK(ns_initparse(response.data(),response.size(),&handle) != -1);

    std::multimap<size_t,std::string> ret;
    for(int i = 0; i < ns_msg_count(handle,ns_s_an); ++i)
    {
        ns_rr rr;
        if(ns_parserr(&handle,ns_s_an,i,&rr) == -1)
            continue;
        if(ns_rr_type(rr) != ns_t_mx)
            continue;
        std::string exp_dn(MAXDNAME,0);
        if(ns_name_uncompress(ns_msg_base(handle),ns_msg_end(handle),ns_rr_rdata(rr)+NS_INT16SZ,exp_dn.data(),exp_dn.size()) == -1)
            continue;
        ret.emplace(ns_get16(ns_rr_rdata(rr)),exp_dn.data());
    }
    return ret;
}
bool __connect(std::string const&host, std::string const&port)
{
    addrinfo const __req{0,AF_UNSPEC,SOCK_STREAM,IPPROTO_TCP,0,nullptr,nullptr,nullptr};
    addrinfo *__pai(nullptr);
    if(getaddrinfo(host.data(),port.data(),&__req,&__pai) != 0)
        return false;
    for(addrinfo *iter = __pai; iter != nullptr; iter = iter->ai_next)
    {
        int fd = socket(iter->ai_family, iter->ai_socktype, iter->ai_protocol);
        if(fd == -1)
            continue;
        int retries = 1;
        setsockopt(fd, IPPROTO_TCP, TCP_SYNCNT, &retries, sizeof(retries));
        if(::connect(fd, iter->ai_addr, iter->ai_addrlen) == -1)
        {
            close(fd);
            continue;
        }
        freeaddrinfo(__pai);
        close(fd);
        return true;
    }
    freeaddrinfo(__pai);
    return false;
}
int main()
{
    std::string domain = "gmail.com";

    //get mx records and try to connect for port 25,465,587
    auto mx_records = mxRecords(domain);
    std::cout << "----------MX RECORDS <priority|host|port25|port465|port587> for " << domain << std::endl;
    for(auto iter = mx_records.begin(); iter != mx_records.end(); ++iter)
    {
        std::cout << iter->first << " | " << iter->second;
        //try port 25|port 465 | port 587
        for(auto port : {"25","465","587"})
        {
            if(__connect(iter->second,port))
                std::cout << " | y";
            else
                std::cout << " | n";
        }
        std::cout << std::endl;
    }
    return 0;
}

作为输出给出

----------MX RECORDS <priority|host|port25|port465|port587> for gmail.com
5 | gmail-smtp-in.l.google.com | y | n | n
10 | alt1.gmail-smtp-in.l.google.com | y | n | n
20 | alt2.gmail-smtp-in.l.google.com | y | n | n
30 | alt3.gmail-smtp-in.l.google.com | y | n | n
40 | alt4.gmail-smtp-in.l.google.com | y | n | n

请注意这些主机名中的“in”。我对 smtp.gmail.com 的 cname 进行了 dns 查询,它给了我 gmail-smtp-msa.l.google.com 所以 "msa",对端口 587/465 开放。我真的认为这些 mx 记录现在只适用于端口 25,而不适用于邮件提交。


编辑#2:

互联网上的 25、465、587 端口存在很多混淆。只是为了为我澄清这些端口在 ssl/tls 中没有区别。端口 25 例如当您想从 example@yourDomain.com 向 example@gmail.com 发送电子邮件时。端口 587 例如当您想从 example1@gmail.com 向 example2@gmail.com 发送电子邮件时。最后,端口 465[deprecated] 与 587 相同,只是它已经以 ssl/tls 开头。如果我错了,请纠正我,但我很确定我不是!从我的真实应用程序中运行 3 EHLO 的日志。首先使用 mx 记录主机名和端口 25,第二个使用“smtp.gmail.com”和端口 587,最后一个使用“smtp.gmail.com”和端口 465。请注意 587/465 之后具有 AUTH 命令选项的区别安全连接和 25 没有 AUTH 选项。因此,当通过端口 25 发送电子邮件时,收件人会收到通知,发件人未经过验证,您通常很快就会被阻止(垃圾邮件)。无论如何,我需要这个 AUTH 命令,应用程序是完全合法的,没有垃圾邮件等。这就是为什么我需要打开 587 端口的 smtp 服务器,我不认为这些 MX 服务器是正确的。

PORT-25

S:220 mx.google.com ESMTP ############### - gsmtp
C:EHLO ###############
S:250-mx.google.com at your service, ###############
  250-SIZE 157286400
  250-8BITMIME
  250-STARTTLS
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8
C:STARTTLS
S:220 2.0.0 Ready to start TLS
C:EHLO ###############
S:250-mx.google.com at your service, ###############
  250-SIZE 157286400
  250-8BITMIME
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8
C:QUIT
S:221 2.0.0 closing connection ############### - gsmtp

PORT-587

S:220 smtp.gmail.com ESMTP ############### - gsmtp
C:EHLO ###############
S:250-smtp.gmail.com at your service, ###############
  250-SIZE 35882577
  250-8BITMIME
  250-STARTTLS
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8
C:STARTTLS
S:220 2.0.0 Ready to start TLS
C:EHLO ###############
S:250-smtp.gmail.com at your service, ###############
  250-SIZE 35882577
  250-8BITMIME
  250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8
C:QUIT
S:221 2.0.0 closing connection ############### - gsmtp

PORT-465

S:220 smtp.gmail.com ESMTP ############### - gsmtp
C:EHLO ###############
S:250-smtp.gmail.com at your service, ###############
  250-SIZE 35882577
  250-8BITMIME
  250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
  250-ENHANCEDSTATUSCODES
  250-PIPELINING
  250-CHUNKING
  250 SMTPUTF8
C:QUIT
S:221 2.0.0 closing connection ############### - gsmtp

【问题讨论】:

  • 这个答案是我给你的,我之前所说的仍然适用于同样的问题。是的,您对电子邮件域的 MX 记录执行 DNS 查找。这就是您发现 SMTP 服务器地址的方式,然后您可以使用 TCP 正常连接到该地址。该端口不由 DNS 处理,这就是您在 DNS 数据中看不到它的原因。您决定连接到哪个端口,具体取决于您是否需要通过隐式 SSL(端口 465)、显式 TLS(端口 25 或 587,使用 STARTTLS 命令)或不需要(端口 25)与 SMTP 服务器通信。
  • @RemyLebeau 再次感谢您尝试帮助我!我在答案中包含了一个示例,该示例遵循您上一个答案的建议。我认为 mx 记录中列出的这些服务器未对其他端口开放,而是对 25 开放。
  • 我删除了“c++”标签,它似乎不相关。我不知道为什么这被否决了。对我来说,它看起来是一个非常有效的技术问题,我赞成它,所以你又回到了 0 :)。此外,通常应该给反对票一个理由
  • @RemyLebeau,OP 正在代表客户行事,因此在这种情况下伪装成 MUA 是有必要的。您的答案是正确的,但它是对不同问题的答案。在 SaaS 的世界里,一切都是颠倒的! :) 而我本人就在一家 SaaS 提供商公司,当然对此表示同情;众所周知,客户更改其网络配置时不会三思而后行通知其合作伙伴。
  • @kkm 问题不止一次被否决。也许是因为我的英语:D。

标签: sockets email resolve smtps


【解决方案1】:

这在技术上不是必须支持的,但根据RFC 6186 Sec. 3.1,您应该在_tcp 子域中查询_submission 主机的SRV 记录,如下所示:

$ nslookup -type=srv _submission._tcp.gmail.com
Server:  google-public-dns-a.google.com
Address:  8.8.8.8

Non-authoritative answer:
_submission._tcp.gmail.com      SRV service location:
          priority       = 5
          weight         = 0
          port           = 587
          svr hostname   = smtp.gmail.com

你应该记住的一些事情:

  1. 从某种意义上说,这是一个“便利功能”。如果您有一个电子邮件地址myname@acme.com,那么 acme.com 可能(也可能不会)公开_tcp.acme.com 中的这些 RFC 6186 记录,以允许其用户的邮件客户端程序自动发现诸如 POP3、IMAP 等服务。
  2. 对连接到域 MSA 可能有特殊限制。例如,服务可能会简单地拒绝来自xyz.com 内部网络之外的机器的连接,或者需要内部信任的证书,等等。 MSA 旨在与邮件客户端 MUA 进行交互,而不是用于一般邮件传递。

【讨论】:

  • 非常感谢!我刚刚为 gmx nslookup -type=srv _submission._tcp.gmx.com 尝试过它,它已经不起作用了。那么这是否意味着他们已经不支持它了?
  • 一般情况下,您可以先查询type=ns获取权威DNS服务器并查询,但在您的情况下,权威答案是一样的:nslookup -type=ns _submission._tcp.gmx.com ns-gmx.ui-dns.com报告没有答案。
  • 所以据我现在所见,这是正确的答案,但是当它已经不适用于 gmx 时,我什至不值得进行 c++ 实现,对吧?你同意吗?
  • 取决于你的数字。如果您有 1000 个客户端,其中 500 个在其 DNS 中公开配置,那么这可以消除您一半的头痛。由于在现实世界中,配置对于 500 人中的另外 250 人来说是不正确的,因此将其降级为四分之一。我们有很多客户交互(SIP 中继、各种 Web API 和其他数据交换方式),他们每天都会因他们认为无害的变化而意外地破坏它们。但正如我所说,这不是必需的,而是为方便地自动配置电子邮件客户端而提供的。最后,你得到了你所拥有的。
  • 非常感谢您!您是否可以允许我稍后在我实现它时向这个答案添加一个 c++ 例程,也许只是检查它是否看起来不错?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-05
  • 2018-03-15
  • 2018-05-23
  • 2011-11-02
  • 1970-01-01
  • 2013-01-09
  • 1970-01-01
相关资源
最近更新 更多