|
The .NET Framework provides wrappers to native APIs for a very large
number of classes and functions, including the System.Net.Dns classes.
However, it doesn't cover all of them. One useful API is the set of DNS
query methods in the
Dnsapi.dll library, and a particularly useful technique of note is the ability
to query a domain and get back the list of MX (Mail Exchange) records
of the mail servers associated with the domain.
Once you have the code completed to handle this, you will be able to
validate an email address against its domain for the mail exchange ("MX")
type entries. If there aren't any, then you have a pretty good idea that
the email address you are dealing with is BOGUS. Obviously, this can
be helpful in tasks such as verifying user registrations on your web
site and to fight SPAM. The implementation I provide here is a simplified
and very "narrow" one
that is intended solely to deal with the MX record lookup. If you want
a more complete implementation, there are a number of examples in the
Net-o-sphere. One interesting one can
be found here.
For starters, it might be wise to visit the Platform
SDK section on DNS to familiarize ourselves with what we need to
create a managed C# PInvoke call to the underlying method.
As can be seen, there are three DnsQuery calls, with DnsQuery_W (Unicode)
being the most commonly used. There is an
fOptions parameter where the types of queries can be combined,
the wType type of record parameter, and a
ppQueryResultsSet [in, out] pointer parameter.
Finally, we are told that we need to free the returned RR Sets with
the DnsRecordListFree API
call.
Note that in the sample code implementation below,
VBByRefStr is used for the marshaling directive. The
argument is treated as "byref" on the managed side, but stays "byval"
on the unmanaged side. If
the unmanaged side writes into the buffer, it won't mangle the managed string,
which remains immutable. Instead, a different string is propagated
to the managed side, preserving managed string immutability. In VB.Net,
you could code as if the VBByRefStr is actually byval on the managed side, and
the compiler will clean up after you, but in C# you must explicitly add the
"ref" keyword in the right places:
namespace PAB.DnsUtils
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class DnsMx
{
public DnsMx()
{
}
[DllImport("dnsapi", EntryPoint="DnsQuery_W", CharSet=CharSet.Unicode, SetLastError=true, ExactSpelling=true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string pszName, QueryTypes wType, QueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet=CharSet.Auto, SetLastError=true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
public static string[] GetMXRecords(string domain)
{
IntPtr ptr1=IntPtr.Zero ;
IntPtr ptr2=IntPtr.Zero ;
MXRecord recMx;
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new NotSupportedException();
}
ArrayList list1 = new ArrayList();
int num1 = DnsMx.DnsQuery(ref domain, QueryTypes.DNS_TYPE_MX, QueryOptions.DNS_QUERY_BYPASS_CACHE, 0, ref ptr1, 0);
if (num1 != 0)
{
throw new Win32Exception(num1);
}
for (ptr2 = ptr1; !ptr2.Equals(IntPtr.Zero); ptr2 = recMx.pNext)
{
recMx = ( MXRecord) Marshal.PtrToStructure(ptr2, typeof(MXRecord));
if (recMx.wType == 15)
{
string text1 = Marshal.PtrToStringAuto(recMx.pNameExchange);
list1.Add(text1);
}
}
DnsMx.DnsRecordListFree(ptr1, 0);
return (string[]) list1.ToArray(typeof(string));
}
private enum QueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum QueryTypes
{
DNS_TYPE_MX = 15
}
[StructLayout(LayoutKind.Sequential)]
private struct MXRecord
{
public IntPtr pNext;
public string pName;
public short wType;
public short wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public IntPtr pNameExchange;
public short wPreference;
public short Pad;
}
}
}
| The downloadable sample solution
includes a simple Console Tester app that makes the following call:
static void Main(string[] args)
{
string[] s= PAB.DnsUtils.DnsMx.GetMXRecords("microsoft.com");
foreach (string st in s)
Console.WriteLine("Server: {0}",st);
Console.ReadLine();
which will return the following output:
Remember, hamburger@hamburgerhal.com is
a valid email address. But, that doesn't mean that you can actually send mail
to it. This handy class, along with some good REGEX match strings,
should be sufficient to validate most any email address that can be thrown
at you. The downloadable solution has more user-friendly exception handling
built in.
Download
the Visual Studio.NET Solution that accompanies this article
|