In 2005 I started to develop, for my hobby, a mail server, to date I have not yet completed.
Sooner or later it will complete.
However, thanks to. NET Framework, I have almost everything I need to carry out my ambitious project.
Almost, because in reality a thing is not feasible with the classes of the framework. I hope it will be possible in the future.
Thing missing is the ability to resolve the mail-exchanger for a domain, the MX record of a DNS query.
The framework provides, in the “namespace” System.Net the class “Dns” but this can only be used to resolve the name of a server (giving the ip address) or to find the ip address (giving the server name).
So, if I need to send an email to “example@wordpress.com” I must know the mail server of “wordpress.com”. Using the utility “nslookup” given with all Microsoft O.S. (xp for example) you can lookup the mail server, in this case is “mail.automatic.com”, see the pic.
-
-
lookup Mx record from DNS using NSLOOKUP
But I need that this operation is done by a program.
How described in RFC1035: “The goal of domain names is to provide a mechanism for naming resources in such a way that the names are usable in different hosts, networks, protocol families, internets, and administrative organizations.” See here for all details.
These are the two classes develop to have the MX lookup. I assume that you know about sockets and threading programming, otherwise this code may seem difficult to understand.
using System;
using System.IO;
using System.Diagnostics;
using System.Data;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Text;
using System.Net;
using System.Net.Sockets;
public class Query
{
//Build a DNS query buffer according to RFC 1035 4.1.1 e 4.1.2
private int id;
private int flags;
private int QDcount;
private int ANcount;
private int NScount;
private int ARcount;
private string Qname;
private int Qtype;
private int Qclass;
public byte[] buf;
public Query(int ID, string query, int qtype)
{
//init vectors with given + default values
id = ID;
flags = 256;
QDcount = 1;
ANcount = 0;
NScount = 0;
ARcount = 0;
Qname = query;
Qtype = qtype;
Qclass = 1; //Internet = IN = 1
<p> //build a buffer with formatted query data
//
//header information (16 bit padding
buf = new byte[12 + Qname.Length + 2 + 4];
buf[0] = (byte)(id / 256);
buf[1] = (byte)(id - (buf[0] * 256));
buf[2] = (byte)(flags / 256);
buf[3] = (byte)(flags - (buf[2] * 256));
buf[4] = (byte)(QDcount / 256);
buf[5] = (byte)(QDcount - (buf[4] * 256));
buf[6] = (byte)(ANcount / 256);
buf[7] = (byte)(ANcount - (buf[6] * 256));
buf[8] = (byte)(NScount / 256);
buf[9] = (byte)(NScount - (buf[8] * 256));
buf[10] = (byte)(ARcount / 256);
buf[11] = (byte)(ARcount - (buf[10] * 256));
//QNAME (RFC 1035 4.1.2)
//no padding
string[] s = Qname.Split('.');
int index = 12;
foreach (string str in s)
{
buf[index] = (byte)str.Length;
index++;
byte[] buf1 = Encoding.ASCII.GetBytes(str);
buf1.CopyTo(buf, index);
index += buf1.Length;
}
//add root domain label (chr(0))
buf[index] = 0;
//add Qtype and Qclass (16 bit values)
index = buf.Length - 4;
buf[index] = (byte)(Qtype / 256);
buf[index + 1] = (byte)(Qtype - (buf[index] * 256));
buf[index + 2] = (byte)(Qclass / 256);
buf[index + 3] = (byte)(Qclass - (buf[index + 2] * 256));
}
}
public class C_DNSquery
{
public StringCollection result = new StringCollection();
public int Error = 0;
public string ErrorTxt = "undefined text"
public bool Done = false;<br /><br />
public UdpClient udpClient;<br /><br />
private string DNS;<br /><br />
private string Query;<br /><br />
private int Qtype;</p><br />
<p> public bool IS_BLACKLIST_QUERY = false;</p><br />
<p> public C_DNSquery(string IPorDNSname, string query, int type)<br /><br />
{<br /><br />
DNS = IPorDNSname;<br /><br />
Query = query;<br /><br />
Qtype = type;<br /><br />
}</p><br />
<p> public void doTheJob()<br /><br />
{<br /><br />
//-------------------------------------------------------<br /><br />
//check if provided DNS contains an IP address or a name<br /><br />
//-------------------------------------------------------<br /><br />
IPAddress ipDNS;<br /><br />
System.Net.IPHostEntry he;<br /><br />
try<br /><br />
{<br /><br />
//try to parse an IPaddress<br /><br />
ipDNS = IPAddress.Parse(DNS);<br /><br />
}<br /><br />
catch (FormatException e)<br /><br />
{<br /><br />
//format error, probably is a FQname, try to resolve it<br /><br />
try<br /><br />
{<br /><br />
//try to resolve the hostname<br /><br />
he = System.Net.Dns.GetHostByName(DNS);<br /><br />
}<br /><br />
catch<br /><br />
{<br /><br />
//Error, invalid server name or address<br /><br />
Error = 98;<br /><br />
ErrorTxt = &quot;Invalid server name:&quot; + DNS;<br /><br />
Done = true;<br /><br />
return;<br /><br />
}<br /><br />
//OK, get the first server address<br /><br />
ipDNS = he.AddressList[0];<br /><br />
}</p><br />
<p> //---------------------------------------------<br /><br />
//Query the DNS server<br /><br />
//---------------------------------------------<br /><br />
//our current thread ID is used to match the reply with this process<br /><br />
//Query myQuery = new Query(System.AppDomain.GetCurrentThreadId(), Query, Qtype);<br /><br />
Query myQuery = new Query(System.Threading.Thread.CurrentThread.ManagedThreadId, Query, Qtype);</p><br />
<p> //data buffer for query return value<br /><br />
Byte[] recBuf;</p><br />
<p> //use UDP protocol to connect<br /><br />
//UdpClient udpClient = new UdpClient();<br /><br />
udpClient = new UdpClient();</p><br />
<p> do<br /><br />
{<br /><br />
try<br /><br />
{<br /><br />
//connect to given nameserver, port 53 (DNS)<br /><br />
udpClient.Connect(DNS, 53);</p><br />
<p> //send query<br /><br />
udpClient.Send(myQuery.buf, myQuery.buf.Length);</p><br />
<p> //IPEndPoint object allow us to read datagrams..<br /><br />
//..selecting only packet coming from our nameserver and port<br /><br />
IPEndPoint RemoteIpEndPoint = new IPEndPoint(ipDNS, 53);</p><br />
<p> //Blocks until a message returns on this socket from a remote host.<br /><br />
recBuf = udpClient.Receive(ref RemoteIpEndPoint);</p><br />
<p> udpClient.Close();<br /><br />
}<br /><br />
catch (Exception e)<br /><br />
{<br /><br />
//connection error<br /><br />
//probably a wrong server address<br /><br />
udpClient.Close();<br /><br />
Error = 99;<br /><br />
ErrorTxt = e.Message + &quot;(server:&quot; + DNS + &quot;)&quot;;<br /><br />
Done = true;<br /><br />
return;<br /><br />
}<br /><br />
//repeat until we get the reply with our threadID<br /><br />
}</p><br />
<p> //while (System.AppDomain.GetCurrentThreadId() != ((recBuf[0] * 256) + recBuf[1]));<br /><br />
while (System.Threading.Thread.CurrentThread.ManagedThreadId != ((recBuf[0] * 256) + recBuf[1]));</p><br />
<p> //---------------------------------------------<br /><br />
//Check the DNS reply<br /><br />
//---------------------------------------------</p><br />
<p> //check if bit QR (Query response) is set<br /><br />
if (recBuf[2] &lt; 128)<br /><br />
{<br /><br />
//response byte not set (probably a malformed packet)<br /><br />
Error = 2;<br /><br />
ErrorTxt = &quot;Query response bit not set&quot;;<br /><br />
Done = true;<br /><br />
return;<br /><br />
}</p><br />
<p> //check if RCODE field is 0<br /><br />
if ((recBuf[3] &amp; 15) &gt; 0)<br /><br />
{<br /><br />
//DNS server error, invalid reply<br /><br />
switch (recBuf[3] &amp; 15)<br /><br />
{<br /><br />
case 1:<br /><br />
Error = 31;<br /><br />
ErrorTxt = &quot;Format error. The nameserver was unable to interpret the query&quot;;<br /><br />
break;<br /><br />
case 2:<br /><br />
Error = 32;<br /><br />
ErrorTxt = &quot;Server failure. The nameserver was unable to process the query.&quot;;<br /><br />
break;<br /><br />
case 3:<br /><br />
Error = 33;<br /><br />
ErrorTxt = &quot;Name error. Check provided domain name!!&quot;;<br /><br />
break;<br /><br />
case 4:<br /><br />
Error = 34;<br /><br />
ErrorTxt = &quot;Not implemented. The name server does not support the requested query&quot;;<br /><br />
break;<br /><br />
case 5:<br /><br />
Error = 35;<br /><br />
ErrorTxt = &quot;Refused. The name server refuses to reply for policy reasons&quot;;<br /><br />
break;<br /><br />
default:<br /><br />
Error = 36;<br /><br />
ErrorTxt = &quot;Unknown. The name server error code was: &quot; + System.Convert.ToString((recBuf[3] &amp; 15));<br /><br />
break;<br /><br />
}<br /><br />
Done = true;<br /><br />
return;<br /><br />
}</p><br />
<p> //OK, now we should have valid header fields<br /><br />
int QDcnt, ANcnt, NScnt, ARcnt;<br /><br />
int index;</p><br />
<p> QDcnt = (recBuf[4] * 256) + recBuf[5];<br /><br />
ANcnt = (recBuf[6] * 256) + recBuf[7];<br /><br />
NScnt = (recBuf[8] * 256) + recBuf[9];<br /><br />
ARcnt = (recBuf[10] * 256) + recBuf[11];<br /><br />
index = 12;</p><br />
<p> //sometimes there are no erros but blank reply... ANcnt == 0...<br /><br />
if (ANcnt == 0) // if blackhole list query, means no spammer !!<br /><br />
//if ((ANcnt == 0) &amp; (IS_BLACKLIST_QUERY == false))<br /><br />
{<br /><br />
//error blank reply, return an empty array<br /><br />
Error = 4;<br /><br />
ErrorTxt = &quot;Empty string array&quot;;<br /><br />
Done = true;<br /><br />
return;<br /><br />
}</p><br />
<p> //---------------------------------------------<br /><br />
//Decode received information<br /><br />
//---------------------------------------------<br /><br />
string s1;</p><br />
<p> // START TEST</p><br />
<p> s1 = Encoding.ASCII.GetString(recBuf, 0, recBuf.Length);</p><br />
<p> // END TEST<br /><br />
//if QDcnt &gt; 0 skip it<br /><br />
if (QDcnt &gt; 0)<br /><br />
{<br /><br />
//we are not really interested to this string, just parse and skip<br /><br />
s1 = &quot;&quot;;<br /><br />
index = parseString(recBuf, index, out s1);</p><br />
<p> index += 4; //skip root domain, Qtype and QClass values... unuseful in this contest<br /><br />
}</p><br />
<p> if (IS_BLACKLIST_QUERY)<br /><br />
{<br /><br />
// get the answers, normally one !<br /><br />
// int the four last bytes there is the ip address<br /><br />
Error = 0;<br /><br />
int Last_Position = recBuf.Length - 1;<br /><br />
result.Add(recBuf[Last_Position - 3].ToString() + &quot;.&quot; + recBuf[Last_Position - 2].ToString() + &quot;.&quot; + recBuf[Last_Position - 1].ToString() + &quot;.&quot; + recBuf[Last_Position].ToString());<br /><br />
Done = true;<br /><br />
return;<br /><br />
}</p><br />
<p> int count = 0;<br /><br />
//get all answers<br /><br />
while (count &lt; ANcnt)<br /><br />
{<br /><br />
s1 = &quot;&quot;;<br /><br />
index = parseString(recBuf, index, out s1);</p><br />
<p> //Qtype<br /><br />
int QType = (recBuf[index] * 256) + recBuf[index + 1];<br /><br />
index += 2;<br /><br />
s1 += &quot;,&quot; + QType.ToString();</p><br />
<p> //QClass<br /><br />
int QClass = (recBuf[index] * 256) + recBuf[index + 1];<br /><br />
index += 2;<br /><br />
s1 += &quot;,&quot; + QClass.ToString();</p><br />
<p> //TTL (Time to live)<br /><br />
int TTL = (recBuf[index] * 16777216) + (recBuf[index + 1] * 65536) + (recBuf[index + 2] * 256) + recBuf[index + 3];<br /><br />
index += 4;<br /><br />
s1 += &quot;,&quot; + TTL.ToString();</p><br />
<p> int blocklen = (recBuf[index] * 256) + recBuf[index + 1];<br /><br />
index += 2;</p><br />
<p> if (QType == 15)<br /><br />
{<br /><br />
int MXprio = (recBuf[index] * 256) + recBuf[index + 1];<br /><br />
index += 2;<br /><br />
s1 += &quot;,&quot; + MXprio.ToString();<br /><br />
}</p><br />
<p> string s2;<br /><br />
index = parseString(recBuf, index, out s2);</p><br />
<p> s1 += &quot;,&quot; + s2;<br /><br />
result.Add(s1);</p><br />
<p> count++;<br /><br />
}</p><br />
<p> Error = 0;<br /><br />
Done = true;<br /><br />
}</p><br />
<p> private int parseString(byte[] buf, int i, out string s)<br /><br />
{<br /><br />
int len;<br /><br />
s = &quot;&quot;;<br /><br />
bool end = false;<br /><br />
while (!end)<br /><br />
{<br /><br />
if (buf[i] == 192)<br /><br />
{<br /><br />
//next byte is a pointer to the string, get it..<br /><br />
i++;<br /><br />
s += getString(buf, buf[i]);<br /><br />
i++;<br /><br />
end = true;<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
//next byte is the string length<br /><br />
len = buf[i];<br /><br />
i++;<br /><br />
//get the string<br /><br />
s += Encoding.ASCII.GetString(buf, i, len);<br /><br />
i += len;<br /><br />
//check for the null terminator<br /><br />
if (buf[i] != 0)<br /><br />
{<br /><br />
//not null, add a point to the name<br /><br />
s += &quot;.&quot;;<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
//null char..the string is complete, exit<br /><br />
end = true;<br /><br />
i++;<br /><br />
}<br /><br />
}<br /><br />
}<br /><br />
return i;<br /><br />
}</p><br />
<p> private string getString(byte[] buf, int i)<br /><br />
{<br /><br />
string s = &quot;&quot;;<br /><br />
int len;<br /><br />
bool end = false;</p><br />
<p> while (!end)<br /><br />
{<br /><br />
len = buf[i];<br /><br />
i++;<br /><br />
s += Encoding.ASCII.GetString(buf, i, len);<br /><br />
i += len;<br /><br />
if (buf[i] == 192)<br /><br />
{<br /><br />
i++;<br /><br />
s += &quot;.&quot; + getString(buf, buf[i]);<br /><br />
return s;<br /><br />
}<br /><br />
if (buf[i] != 0)<br /><br />
{<br /><br />
s += &quot;.&quot;;<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
end = true;<br /><br />
}<br /><br />
}<br /><br />
return s;<br /><br />
}<br /><br />
}<br /><br />
But… How can I use it ?
Here the example..
<br /><br />
public string ExecResolution(string Domain, string DnsServer1, string DnsServer2)<br /><br />
{<br /><br />
// resolv the authoritative domain (type=2)<br /><br />
C_DNSquery DnsQry = new C_DNSquery(DnsServer1, Domain, 2);</p><br />
<p> Thread t1 = new Thread(new ThreadStart(DnsQry.doTheJob));<br /><br />
t1.Start();<br /><br />
int timeout = 20;<br /><br />
while ((timeout &gt; 0) &amp; (!DnsQry.Done))<br /><br />
{<br /><br />
Thread.Sleep(100);<br /><br />
timeout--;<br /><br />
}<br /><br />
if (timeout == 0)<br /><br />
{<br /><br />
if (DnsQry.udpClient != null)<br /><br />
{<br /><br />
DnsQry.udpClient.Close();<br /><br />
}<br /><br />
t1.Abort();<br /><br />
DnsQry.Error = 100;<br /><br />
}</p><br />
<p> string[] ns1;<br /><br />
string MyMX = &quot;&quot;;<br /><br />
if (DnsQry.Error == 0)<br /><br />
{<br /><br />
ns1 = DnsQry.result[0].Split(',');<br /><br />
MyMX = ns1[4];<br /><br />
t1.Abort();<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
t1.Abort();<br /><br />
MyMX = DnsServer1;<br /><br />
}</p><br />
<p> // Resolve MX (type = 15)<br /><br />
DnsQry = new C_DNSquery(MyMX, Domain, 15);<br /><br />
Thread t2 = new Thread(new ThreadStart(DnsQry.doTheJob));<br /><br />
t2.Start();<br /><br />
timeout = 20;<br /><br />
string TTL = &quot;&quot;;<br /><br />
string MXName = &quot;&quot;;<br /><br />
while ((timeout &gt; 0) &amp; (!DnsQry.Done))<br /><br />
{<br /><br />
Thread.Sleep(100);<br /><br />
timeout--;<br /><br />
}<br /><br />
if (timeout == 0)<br /><br />
{<br /><br />
if (DnsQry.udpClient != null)<br /><br />
{<br /><br />
DnsQry.udpClient.Close();<br /><br />
}<br /><br />
t2.Abort();<br /><br />
DnsQry.Error = 100;<br /><br />
}</p><br />
<p> if (DnsQry.Error == 0)<br /><br />
{<br /><br />
if (DnsQry.result.Count == 1)<br /><br />
{<br /><br />
string[] ns2 = DnsQry.result[0].Split(',');<br /><br />
MXName = ns2[5];<br /><br />
TTL = ns2[3];<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
Int32 preference = 9910000;<br /><br />
for (int indns = 0; indns &lt;= DnsQry.result.Count - 1; indns++)<br /><br />
{<br /><br />
string[] ns2 = DnsQry.result[indns].Split(',');<br /><br />
if (Int32.Parse(ns2[4]) &lt; preference)<br /><br />
{<br /><br />
MXName = ns2[5];<br /><br />
TTL = ns2[3];<br /><br />
preference = Int32.Parse(ns2[4]);<br /><br />
}<br /><br />
}<br /><br />
}<br /><br />
}</p><br />
<p> DnsQry = null;<br /><br />
t2.Abort();</p><br />
<p> // try with alternative DNS Server<br /><br />
if (MXName == &quot;&quot;)<br /><br />
{<br /><br />
DnsQry = new C_DNSquery(DnsServer2, Domain, 15);<br /><br />
Thread t3 = new Thread(new ThreadStart(DnsQry.doTheJob));<br /><br />
t3.Start();<br /><br />
timeout = 20;<br /><br />
TTL = &quot;&quot;;<br /><br />
MXName = &quot;&quot;;<br /><br />
while ((timeout &gt; 0) &amp; (!DnsQry.Done))<br /><br />
{<br /><br />
Thread.Sleep(100);<br /><br />
timeout--;<br /><br />
}<br /><br />
if (timeout == 0)<br /><br />
{<br /><br />
if (DnsQry.udpClient != null)<br /><br />
{<br /><br />
DnsQry.udpClient.Close();<br /><br />
}<br /><br />
t3.Abort();<br /><br />
DnsQry.Error = 100;<br /><br />
}</p><br />
<p> if (DnsQry.Error == 0)<br /><br />
{<br /><br />
if (DnsQry.result.Count == 1)<br /><br />
{<br /><br />
string[] ns2 = DnsQry.result[0].Split(',');<br /><br />
MXName = ns2[5];<br /><br />
TTL = ns2[3];<br /><br />
}<br /><br />
else<br /><br />
{<br /><br />
Int32 preference = 9910000;<br /><br />
for (int indns = 0; indns &lt;= DnsQry.result.Count - 1; indns++)<br /><br />
{<br /><br />
string[] ns2 = DnsQry.result[indns].Split(',');<br /><br />
if (Int32.Parse(ns2[4]) &lt; preference)<br /><br />
{<br /><br />
MXName = ns2[5];<br /><br />
TTL = ns2[3];<br /><br />
preference = Int32.Parse(ns2[4]);<br /><br />
}<br /><br />
}<br /><br />
}<br /><br />
}</p><br />
<p> DnsQry = null;<br /><br />
t3.Abort();<br /><br />
}</p><br />
<p> return MXName;</p><br />
<p>}<br /><br />
This code was extracted from M10Mail Server, my never ending project.
Dante Marchesi with the fundamental contribution of Giovanni Magugliani.