Get SPF records from a Domain

Viewed 10235

What are the ways to Check SPF records on a domain?

There is a website where i can do it manually using - http://www.mxtoolbox.com/SuperTool.aspx

How can i do it via ASP.NET and C#? Basically i want to verify/check SPF records on a domain if its supporting out own mail web server.

9 Answers

We tried to use @martin-liversage's answer, but after some time running on hundrets of domains it failed on some memory problem. (Maybe there was some invalid/another type DNS record?) So i studied this exact WINAPI functions and structures used in this case and edited the solution acordingly.

Links to WINAPI documentation are included in code.

So here's our improved code, that's working 100% even in our case:

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;
}

Based on DnsDig project I created a DLL which can be used on any .net (vb , c#, forms, web. etc..) project

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

Download the DLL , unzip, and Add as reference to your project (if website place on root/bin folder):

DnsDig.dll

nunit.framework.dll

(126KB in total)

Then use this example as for an ASP.Ne website (vb.net code)

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

RESULTS: https://yourwebsiteusingabovedlls.com/anyplacewhereabovecode/?d=goodyes.com

will write

goodyes.com. 3535 IN TXT "google-site-verification=IMw-tL0VWgMJbtcRgt_bu5UaVwpbNb94dvcOSObooa4" goodyes.com. 3535 IN TXT "v=spf1 include:_spf.buzondecorreo.com ~all"

One option is to use the package DnsClient.NET (Github-Repo).

This library is able to do high performant DNS lookups as well as lookups for SPF-entries.

Example code for SPF-lookup:

var domainName = "example.domain";
var lookup = new LookupClient();
var result = await lookup.QueryAsync(domainName, QueryType.TXT).ConfigureAwait(false);

var records = result.Answers.OfType<TxtRecord>().ToList();
Related