12

检查域上的 SPF 记录的方法有哪些?

有一个网站,我可以手动使用 - http://www.mxtoolbox.com/SuperTool.aspx

我如何通过 ASP.NET 和 C# 来实现?基本上,如果域支持自己的邮件 Web 服务器,我想验证/检查域上的 SPF 记录。

4

8 回答 8

15

我有同样的问题,并设法找到两个三个解决方案:

解决nslookup方案

您可以通过在命令行中键入以下命令来获取 SPF:

nslookup -type=TXT <hostname>

您可以在 C# 中使用 自动执行此操作System.Diagonstics.Process,如本博文中所述。

DNS.NET 解析器项目

我找到了这篇关于 DNS 解析的CodeProject 文章。它带有演示项目。我运行该项目并得到以下结果stackexchange.com

DNS 挖掘截图

注意:确保QType字段设置为TXT,然后再按发送按钮

以黄色突出显示的部分代表 SPF 记录。我还没有深入研究代码以了解它是如何完成的,但这似乎是上述解决方案的一个很好的替代nslookup方案。

[更新] ARSoft.Tools.Net 项目

如果您只需要检查域是否支持邮件服务器,则可以使用ARSoft.Tools.Net库(也可作为NuGet 包提供)。

安装软件包后,我设法使用以下代码执行 SPF 检查:

var spfValidator = new ARSoft.Tools.Net.Spf.SpfValidator();

var mailIpAddress = IPAddress.Parse("X.X.X.X");
var domain = "example.com";
var senderAddress = "sender@example.com";

ARSoft.Tools.Net.Spf.SpfQualifier result = 
    spfValidator.CheckHost(mailIpAddress, domain, senderAddress);
于 2012-08-09T13:03:26.157 回答
6

尽管 .NET 对网络有很多支持,包括进行主机名到地址映射,但它缺乏查询 DNS 的通用方法。

但是,您可以使用 P/Invoke 直接调用DnsQuery 函数。API 有点麻烦,但根据您的要求创建正确的 P/Invoke 签名并非不可能。

SPF 记录在 DNS 中存储为 TXT 记录。您必须使用的相应结构是DNS_TXT_DATA 结构。如果您可以找到查询 MX 记录的示例,您可以重用代码并DNS_TYPE_TEXT用于查询类型并将数据解组到DNS_TXT_DATA结构中。

或者您可以只使用以下代码:

using System.ComponentModel;
using System.Runtime.InteropServices;

public String DnsGetTxtRecord(String name) {
  const Int16 DNS_TYPE_TEXT = 0x0010;
  const Int32 DNS_QUERY_STANDARD = 0x00000000;
  const Int32 DNS_ERROR_RCODE_NAME_ERROR = 9003;
  const Int32 DNS_INFO_NO_RECORDS = 9501;
  var queryResultsSet = IntPtr.Zero;
  try {
    var dnsStatus = DnsQuery(
      name,
      DNS_TYPE_TEXT,
      DNS_QUERY_STANDARD,
      IntPtr.Zero,
      ref queryResultsSet,
      IntPtr.Zero
    );
    if (dnsStatus == DNS_ERROR_RCODE_NAME_ERROR || dnsStatus == DNS_INFO_NO_RECORDS)
      return null;
    if (dnsStatus != 0)
      throw new Win32Exception(dnsStatus);
    DnsRecordTxt dnsRecord;
    for (var pointer = queryResultsSet; pointer != IntPtr.Zero; pointer = dnsRecord.pNext) {
      dnsRecord = (DnsRecordTxt) Marshal.PtrToStructure(pointer, typeof(DnsRecordTxt));
      if (dnsRecord.wType == DNS_TYPE_TEXT) {
        var lines = new List<String>();
        var stringArrayPointer = pointer
          + Marshal.OffsetOf(typeof(DnsRecordTxt), "pStringArray").ToInt32();
        for (var i = 0; i < dnsRecord.dwStringCount; ++i) {
          var stringPointer = (IntPtr) Marshal.PtrToStructure(stringArrayPointer, typeof(IntPtr));
          lines.Add(Marshal.PtrToStringUni(stringPointer));
          stringArrayPointer += IntPtr.Size;
        }
        return String.Join(Environment.NewLine, lines);
      }
    }
    return null;
  }
  finally {
    const Int32 DnsFreeRecordList = 1;
    if (queryResultsSet != IntPtr.Zero)
      DnsRecordListFree(queryResultsSet, DnsFreeRecordList);
  }
}

[DllImport("Dnsapi.dll", EntryPoint = "DnsQuery_W", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
static extern Int32 DnsQuery(String lpstrName, Int16 wType, Int32 options, IntPtr pExtra, ref IntPtr ppQueryResultsSet, IntPtr pReserved);

[DllImport("Dnsapi.dll")]
static extern void DnsRecordListFree(IntPtr pRecordList, Int32 freeType);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct DnsRecordTxt {
  public IntPtr pNext;
  public String pName;
  public Int16 wType;
  public Int16 wDataLength;
  public Int32 flags;
  public Int32 dwTtl;
  public Int32 dwReserved;
  public Int32 dwStringCount;
  public String pStringArray;
}
于 2012-08-09T13:08:21.847 回答
1

基于Martin Liversage 的回答,我添加了一些注释来解释发生了什么,并调整为返回多个记录(如果存在)。

我的示例还连接 TXT 记录中的多个字符串,而不是用换行符分隔。

if (dnsRecord.wType == DNS_TYPE_TEXT)考虑到约束在函数的参数中,我不知道该行是否真的有必要DnsQuery,但无论如何我还是从马丁的回答中保留了它。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

namespace Util
{
    /// <summary>
    /// Based on https://stackoverflow.com/a/11884174 (Martin Liversage)
    /// </summary>
    class DnsInterop
    {
        private const short DNS_TYPE_TEXT = 0x0010;
        private const int DNS_QUERY_STANDARD = 0x00000000;
        private const int DNS_ERROR_RCODE_NAME_ERROR = 9003;
        private const int DNS_INFO_NO_RECORDS = 9501;


        public static IEnumerable<string> GetTxtRecords(string domain)
        {
            var results = new List<string>();
            var queryResultsSet = IntPtr.Zero;
            DnsRecordTxt dnsRecord;

            try
            {
                // get all text records
                // pointer to results is returned in queryResultsSet
                var dnsStatus = DnsQuery(
                  domain,
                  DNS_TYPE_TEXT,
                  DNS_QUERY_STANDARD,
                  IntPtr.Zero,
                  ref queryResultsSet,
                  IntPtr.Zero
                );

                // return null if no records or DNS lookup failed
                if (dnsStatus == DNS_ERROR_RCODE_NAME_ERROR
                    || dnsStatus == DNS_INFO_NO_RECORDS)
                {
                    return null;
                }

                // throw an exception if other non success code
                if (dnsStatus != 0)
                    throw new Win32Exception(dnsStatus);

                // step through each result
                for (
                    var pointer = queryResultsSet; 
                    pointer != IntPtr.Zero; 
                    pointer = dnsRecord.pNext)
                {
                    dnsRecord = (DnsRecordTxt)
                        Marshal.PtrToStructure(pointer, typeof(DnsRecordTxt));

                    if (dnsRecord.wType == DNS_TYPE_TEXT)
                    {
                        var builder = new StringBuilder();

                        // pointer to array of pointers
                        // to each string that makes up the record
                        var stringArrayPointer = pointer + Marshal.OffsetOf(
                            typeof(DnsRecordTxt), "pStringArray").ToInt32();

                        // concatenate multiple strings in the case of long records
                        for (var i = 0; i < dnsRecord.dwStringCount; ++i)
                        {
                            var stringPointer = (IntPtr)Marshal.PtrToStructure(
                                stringArrayPointer, typeof(IntPtr));

                            builder.Append(Marshal.PtrToStringUni(stringPointer));
                            stringArrayPointer += IntPtr.Size;
                        }

                        results.Add(builder.ToString());
                    }
                }
            }
            finally
            {
                if (queryResultsSet != IntPtr.Zero)
                {
                    DnsRecordListFree(queryResultsSet, 
                        (int)DNS_FREE_TYPE.DnsFreeRecordList);
                }
            }

            return results;
        }


        [DllImport("Dnsapi.dll", EntryPoint = "DnsQuery_W", 
            ExactSpelling = true, CharSet = CharSet.Unicode, 
            SetLastError = true)]
        static extern int DnsQuery(string lpstrName, short wType, int options, 
            IntPtr pExtra, ref IntPtr ppQueryResultsSet, IntPtr pReserved);


        [DllImport("Dnsapi.dll")]
        static extern void DnsRecordListFree(IntPtr pRecordList, int freeType);


        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct DnsRecordTxt
        {
            public IntPtr pNext;
            public string pName;
            public short wType;
            public short wDataLength;
            public int flags;
            public int dwTtl;
            public int dwReserved;
            public int dwStringCount;
            public string pStringArray;
        }


        enum DNS_FREE_TYPE
        {
            DnsFreeFlat = 0,
            DnsFreeRecordList = 1,
            DnsFreeParsedMessageFields = 2
        }
    }
}
于 2016-07-04T09:23:43.643 回答
0

你基本上需要做一个 DNS 请求,询问域的 MX/SPF 记录。有一些在 C# 中执行此操作的示例。http://mailsystem.codeplex.com/上有一个库,其中有一个ValidatorGetMxRecords可以执行此操作,您可能会发现它很有用

于 2012-08-07T17:01:24.980 回答
0

值得一提的是——MailBee 的 .NET 对象也支持这一点。我之所以这么说,是因为我们已经拥有了这个组件,当我发现这个好东西已经融入我们所拥有的东西时,我正要实现其他东西。

http://www.afterlogic.com/mailbee-net/docs/filter_spam_with_dns.html

于 2014-02-25T01:25:20.850 回答
0

基于 DnsDig 项目,我创建了一个 DLL,可用于任何 .net(vb、c#、forms、web.等)项目

https://devselz.com/software/devselz_dnsdig_dns-txt-etc-query-domain-register.zip

下载 DLL ,解压缩并添加为项目的引用(如果网站位于根/bin 文件夹中):

DnsDig.dll

nunit.framework.dll

(共 126KB)

然后将此示例用作 ASP.Ne 网站(vb.net 代码)

Imports DnsDig
Imports Heijden.DNS
Partial Class lib_u_l_Default
    Inherits System.Web.UI.Page
    Public Resolver As Resolver
    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        Resolver = New Resolver
        Dim SW As New System.Diagnostics.Stopwatch
        SW.Start()
        Dim DNSResponse As Heijden.DNS.Response = Resolver.Query(Request.QueryString("d"), QType.SPF, QClass.ANY)
        SW.Stop()
        If DNSResponse.header.ANCOUNT > 0 Then
            For Each answerRR As AnswerRR In DNSResponse.Answers
                Response.Write("<br/>" & answerRR.ToString)
            Next
        End If
    End Sub

End Class

结果: https ://yourwebsiteusingabovedlls.com/anyplacewhereabovecode/?d=goodyes.com

将会写

goodyes.com。3535 IN TXT“google-site-verification=IMw-tL0VWgMJbtcRgt_bu5UaVwpbNb94dvcOSObooa4”goodyes.com。第3535章

于 2021-07-11T05:22:56.627 回答
0

我们尝试使用@martin-liversage 的答案,但在数百个域上运行一段时间后,它在一些内存问题上失败了。(也许有一些无效/其他类型的 DNS 记录?)所以我研究了这个案例中使用的确切的 WINAPI 函数和结构,并相应地编辑了解决方案。

代码中包含指向 WINAPI 文档的链接。

所以这是我们改进的代码,即使在我们的例子中也可以 100% 工作:

public String GetSpfRecord(String domain)
{
    // Definition of DNS params
    const Int16 DNS_TYPE_TXT = 0x0010;
    const Int32 DNS_QUERY_STANDARD = 0x00000001;
    const Int32 DNS_ERROR_RCODE_NAME_ERROR = 9003;
    const Int32 DNS_INFO_NO_RECORDS = 9501;

    DnsRecordA dnsRecord;
    var queryResultsSet = IntPtr.Zero;

    try
    {
        var dnsStatus = DnsQuery(
          domain,
          DNS_TYPE_TXT,
          DNS_QUERY_STANDARD,
          IntPtr.Zero,
          ref queryResultsSet,
          IntPtr.Zero
        );

        if (dnsStatus == DNS_ERROR_RCODE_NAME_ERROR || dnsStatus == DNS_INFO_NO_RECORDS)
            return null;

        if (dnsStatus != 0)
            throw new Win32Exception(dnsStatus);

        for (IntPtr pointer = queryResultsSet; pointer != IntPtr.Zero; pointer = dnsRecord.pNext)
        {
            // Copies data from memory (size of DnsRecordA) from adress pointer to new alocated memory and creates instance of pointer to this place.
            dnsRecord = (DnsRecordA)Marshal.PtrToStructure(pointer, typeof(DnsRecordA));

            // pokud se jedná o typ TXT
            if (dnsRecord.wType == DNS_TYPE_TXT)
            {
                // get pointer to informations in "Data" property (https://docs.microsoft.com/en-us/windows/win32/api/windns/ns-windns-dns_recorda)
                var dataPointer = pointer + Marshal.SizeOf(typeof(DnsRecordA));

                // Get the txtData
                var txtData = (DNS_TXT_DATAA)Marshal.PtrToStructure(dataPointer, typeof(DNS_TXT_DATAA));
                if (txtData.dwStringCount >= 1)
                {
                    string line = Marshal.PtrToStringUni(txtData.pStringArray[0]);

                    // only if record starts with "v=spf" (Getting only SPF records)
                    // Getting only first (here is always maximum of 1 record) and returning whole line
                    if (line.StartsWith("v=spf") && string.IsNullOrEmpty(result))
                    {
                        return line;
                    }
                }
            }
        }

        // no SPF record - returning null
        return null;
    }
    finally
    {
        const Int32 DnsFreeRecordList = 1;

        // always release the memory alocated for list of dns records
        if (queryResultsSet != IntPtr.Zero)
            DnsRecordListFree(queryResultsSet, DnsFreeRecordList);
    }
}

// https://docs.microsoft.com/en-us/windows/win32/api/windns/nf-windns-dnsquery_a
[DllImport("Dnsapi.dll", EntryPoint = "DnsQuery_W", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
static extern Int32 DnsQuery(String lpstrName, Int16 wType, Int32 options, IntPtr pExtra, ref IntPtr ppQueryResultsSet, IntPtr pReserved);

// https://docs.microsoft.com/en-us/windows/win32/api/windns/nf-windns-dnsrecordlistfree
[DllImport("Dnsapi.dll")]
static extern void DnsRecordListFree(IntPtr pRecordList, Int32 freeType);

// https://docs.microsoft.com/en-us/windows/win32/api/windns/ns-windns-dns_recorda
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct DnsRecordA
{
    public IntPtr pNext;
    public String pName;
    public Int16 wType;
    public Int16 wDataLength;
    public Int32 flags;
    public Int32 dwTtl;
    public Int32 dwReserved;

    // Commented, because i'm getting this value dynamicaly (it can also be another structure type which might cause some problems)
    //public DNS_TXT_DATA Data;
}

// https://docs.microsoft.com/en-us/windows/win32/api/windns/ns-windns-dns_txt_dataa
[StructLayout(LayoutKind.Sequential)]
struct DNS_TXT_DATAA
{
    /// DWORD->unsigned int
    public uint dwStringCount;

    /// PSTR[1]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.SysUInt)]
    internal IntPtr[] pStringArray;
}
于 2020-09-02T07:50:51.927 回答
-1

有趣的是所有网站都有这个错误

SPF 不是 TXT

您可以有没有 SPF 的 txt 记录和没有 TXT 的 SPF,因此 TXT 查找不会显示 SPF

于 2014-07-09T10:55:59.293 回答