检查域名上的SPF记录有哪些方法?
有一个网站,我可以使用 - http://www.mxtoolbox.com/SuperTool.aspx
手动完成我如何通过ASP.NET和C#来实现?基本上我想验证/检查域上的SPF记录,如果它支持自己的邮件Web服务器。
答案 0 :(得分:13)
我遇到同样的问题,并设法找到两个三个解决方案:
nslookup
解决方案您可以通过在命令行中键入以下命令来获取SPF:
nslookup -type=TXT <hostname>
您可以使用System.Diagonstics.Process
在C#中自动执行此操作,如this blog post中所述。
我找到了关于DNS解析的this CodeProject article。它附带演示项目。我运行了该项目并获得了stackexchange.com
的以下结果:
注意: 在按下之前,请确保 QType 字段设置为 TXT 发送按钮
以黄色突出显示的部分代表SPF记录。我还没有深入研究代码,看看它是如何完成的,但这似乎是上述nslookup
解决方案的一个很好的替代方案。
如果您需要做的只是检查域是否支持邮件服务器,您可以使用ARSoft.Tools.Net库(也可以NuGet Package提供)。
安装软件包后,我设法使用以下代码执行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);
答案 1 :(得分:6)
尽管.NET对网络有很多支持,包括对主机名进行地址映射,但它缺乏查询DNS的一般方法。
但是,您可以使用P / Invoke直接调用DnsQuery function。 API有点麻烦但是根据您的要求创建正确的P / Invoke签名并非不可能。
SPF记录在DNS中存储为TXT记录。您必须使用的相应结构是DNS_TXT_DATA structure。如果您可以找到an example of querying a MX record,则可以重复使用代码并使用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;
}
答案 2 :(得分:1)
在the answer by Martin Liversage的基础上,我添加了一些注释来解释发生了什么,并调整为返回多条记录(如果存在)。
我的例子还连接TXT记录中的多个字符串,而不是按换行符分隔。
我不知道if (dnsRecord.wType == DNS_TYPE_TEXT)
行是否真的是必要的,因为约束在DnsQuery
函数的参数中,但我仍然保留了Martin的答案。
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
}
}
}
答案 3 :(得分:0)
您基本上需要执行DNS请求,询问域的MX / SPF记录。在C#中有一些这样做的例子。 http://mailsystem.codeplex.com/上有一个库Validator
,GetMxRecords
类,{{1}}这样做,您可能会觉得有用
答案 4 :(得分:0)
值得的是 - MailBee的.NET Objects也支持这一点。我之所以这么说,是因为我们已经拥有了这个组件,当我发现这个好东西已经融入我们所拥有的东西时,我就要实施其他的东西了。
http://www.afterlogic.com/mailbee-net/docs/filter_spam_with_dns.html
答案 5 :(得分:0)
有趣的是所有网站都有这个错误
SPF不是TXT
你可以有没有SPF和没有TXT的SPF的txt记录,所以TXT查找不会显示SPF
答案 6 :(得分: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;
}
答案 7 :(得分:0)
基于 DnsDig 项目,我创建了一个可用于任何 .net(vb、c#、表单、Web 等)项目的 DLL
https://devselz.com/software/devselz_dnsdig_dns-txt-etc-query-domain-register.zip
下载 DLL 、解压并添加作为对您项目的引用(如果网站位于 root/bin 文件夹中):
(共 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 章 goodyes.com。第 3535 章