首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

直連收件箱:繞過 SMTP 轉發直接投遞郵件的 MX 查詢與 SMTP 協定實作

     編輯:關於VC++

Foxmail「特快專遞」功能可直接將郵件送達收件方郵件伺服器,省去 SMTP 轉發延遲、即時掌握投遞狀態、避免免費信箱發送限制。本文完整實作:本機 DNS 列舉 → MX 記錄查詢 → 多 MX 伺服器嘗試 → SMTP 協定對話 → MIME 編碼支援附件。

郵件投遞流程對比

標準路徑:  MUA → SMTP Client → SMTP Relay (發信方) → MX (收信方) → MDA → 收件箱
特快專遞:  MUA → SMTP Client → MX (收信方) → MDA → 收件箱

關鍵差異:直接連線收件方 MX 伺服器,需自行解析 MX 記錄。

步驟 1:列舉本機 DNS 伺服器

使用 IPHLPAPI:GetAdaptersInfo 取得所有網卡 → GetPerAdapterInfo 取得每張網卡的 DNS 伺服器陣列。


std::vector GetLocalDnsServers() {
    std::vector dnsList;
    ULONG bufSize = 0; GetAdaptersInfo(nullptr, &bufSize);
    std::vector buf(bufSize); PIP_ADAPTER_INFO pInfo = (PIP_ADAPTER_INFO)buf.data();
    GetAdaptersInfo(pInfo, &bufSize);
    for (auto p = pInfo; p; p = p->Next) {
        ULONG perSize = 0; GetPerAdapterInfo(p->Index, nullptr, &perSize);
        std::vector perBuf(perSize); PIP_PER_ADAPTER_INFO pPer = (PIP_PER_ADAPTER_INFO)perBuf.data();
        if (GetPerAdapterInfo(p->Index, pPer, &perSize) == ERROR_SUCCESS) {
            for (auto* ip = &pPer->DnsServerList; ip; ip = ip->Next)
                dnsList.push_back(ip->IpAddress.String);
        }
    }
    return dnsList;
}

步驟 2:UDP 埠 53 發送 MX 查詢封包

DNS 訊息結構:Header (12 bytes) + Question (QNAME + QTYPE=15 MX + QCLASS=1 IN) + Answer/Authority/Additional (解析回應用)。


// 簡化版同步查詢 (生產環境建議用非阻塞 + 重試 + 隨機 Transaction ID)
std::vector> QueryMx(const std::string& domain, const std::string& dnsServer) {
    // 1. 建立查詢封包
    // 2. sendto UDP :53
    // 3. recvfrom 解析回應
    // 4. 從 Answer 區解析 MX 記錄:Preference (優先序) + Exchange (主機名)
    // 5. 依 Preference 排序回傳
    // 完整程式碼見附件 (含壓縮指標解析、CNAME 追蹤、Additional 區 A 記錄直接取 IP)
}

查詢 gmail.com 典型回應:

gmail-smtp-in.l.google.com        5
alt1.gmail-smtp-in.l.google.com  10
alt2.gmail-smtp-in.l.google.com  10
alt3.gmail-smtp-in.l.google.com  20
alt4.gmail-smtp-in.l.google.com  20

步驟 3:SMTP 協定對話 (TCP 25 埠)


class SmtpDirect {
    SOCKET sock_;
    bool SendRecv(const std::string& cmd, const std::string& expectedPrefix = "") {
        send(sock_, cmd.c_str(), cmd.size(), 0);
        char buf[1024]; int n = recv(sock_, buf, sizeof(buf)-1, 0);
        if (n <= 0) return false;
        buf[n] = 0;
        if (!expectedPrefix.empty() && strncmp(buf, expectedPrefix.c_str(), expectedPrefix.size()) != 0)
            return false; // 錯誤碼
        return true;
    }
public:
    bool Connect(const char* host, int port = 25) {
        // getaddrinfo → socket → connect → 接收 220 歡迎
    }
    bool MailFrom(const char* from) { return SendRecv("MAIL FROM:<" + std::string(from) + ">\r\n", "250"); }
    bool RcptTo(const char* to)   { return SendRecv("RCPT TO:<" + std::string(to) + ">\r\n", "250"); }
    bool Data(const std::string& mimeMessage) {
        if (!SendRecv("DATA\r\n", "354")) return false;
        send(sock_, mimeMessage.c_str(), mimeMessage.size(), 0);
        send(sock_, "\r\n.\r\n", 5, 0); // 結束標記
        return SendRecv("", "250");
    }
    void Quit() { SendRecv("QUIT\r\n", "221"); closesocket(sock_); }
};

步驟 4:MIME 訊息組裝 (支援 HTML、附件、內嵌圖片)

From: "Sender" 
To: "Recipient" 
Subject: =?utf-8?B?5b6u5L+h6Lev?=
Date: Mon, 01 Jan 2024 12:00:00 +0800
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----BOUNDARY123"

------BOUNDARY123
Content-Type: multipart/alternative; boundary="----ALT456"

------ALT456
Content-Type: text/plain; charset=utf-8
純文字備選內容

------ALT456
Content-Type: text/html; charset=utf-8

HTML 內容

------ALT456-- ------BOUNDARY123 Content-Type: image/png; name="logo.png" Content-Transfer-Encoding: base64 Content-ID: Content-Disposition: inline; filename="logo.png" iVBORw0KGgoAAAANSUhEUg... (Base64 編碼) ------BOUNDARY123--

實戰阻礙與對策

阻礙現象對策
住宅 IP 信譽差Gmail/Outlook 直接拒收 (550 5.7.1)使用具良好信譽的雲端 IP / SMTP Relay / 郵件 API (SendGrid, Mailgun)
無 PTR 記錄多數企業郵箱拒收要求 ISP 設定 rDNS 或改用有 PTR 的伺服器
SPF/DKIM/DMARC 未通過進垃圾信或拒收簽署 DKIM、發布 SPF/DMARC、對齊 From 網域
加密傳輸部分 MX 強制 STARTTLS整合 OpenSSL,支援 STARTTLS 升級
灰名單暫時拒收 450,需重試實作指數退避重試 (10min, 30min, 1h...)

完整流程整合


bool SendDirect(const Email& mail) {
    auto dnsList = GetLocalDnsServers();
    auto mxList = QueryMx(ExtractDomain(mail.to), dnsList[0]); // 取第一組可用 DNS
    for (auto& [host, pref] : mxList) {
        SmtpDirect smtp;
        if (!smtp.Connect(host.c_str())) continue;
        if (!smtp.MailFrom(mail.from)) continue;
        if (!smtp.RcptTo(mail.to)) continue;
        if (!smtp.Data(BuildMime(mail))) continue;
        smtp.Quit();
        return true; // 成功投遞至少一台 MX
    }
    return false; // 全部失敗
}

結語

「直連 MX」技術雖能繞過 SMTP Relay,但現代反垃圾機制 (IP 信譽、SPF/DKIM/DMARC、灰名單、STARTTLS) 使得住宅/伺服器直發成功率極低。實務上建議:自建郵件伺服器請完整部署 SPF/DKIM/DMARC/rDNS/信譽預熱;應用層整合請改用專業郵件 API (SendGrid, Amazon SES, Mailgun, Postmark)。本文完整原始碼 (含 DNS 解析器、SMTP 狀態機、MIME Builder、Base64、OpenSSL STARTTLS、重試邏輯、GUI 測試工具) 請參考文章附件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved