Introductuion
A few months ago I was working on an application that would send emails to users that had subscribed to a newsletter. I know that there are numerous mass mail applications but I really wanted to develop an in-house solution. What I present in this article is a set of classes that are used by the system to validate email addresses.
Credit where it's due
As has come to be my
modus operandi in my articles, I have to give thanks to James Oldroyd for helping me find the
::DnsQuery()
function. James is an amazing coder and if you can get him to stop thinking in code for more than five minutes you can even have a conversation with him. Before using
::DNSQuery()
I had examined some C code that connected to the DNS server and did an MX record request � a real nasty mess of code to say the least. So finding and then using the
::DNSQuery()
function was a no-brainer.
Overview
The demo application is a Windows Console Application that accepts email addresses interactively. Entering an email address and pressing the RETURN key starts the email address validation process. Typing exit and pressing the RETURN key will exit the application. The simple demo makes use of two classes that I am describing in this article,
CGetSMTPHostName
which makes use of
::DnsQuery()
and
CVerifySMTPEmailAddress
which does the actual email validation. It should be noted that I decided to split the classes up into two distinct classes because of the way that I intend to use them but they could be combined into a single class. Below I give a brief description of each.
CGetSMTPHostName
The CGetSMTPHostName class provides three member functions:
GetSmtpHostName()
,
ResetHostMap()
and
Dump()
. It also contains a
std::map
that holds the DomainName and SMTPHostName pair.
CGetSMTPHostName::GetSmtpHostName()
This method accepts an email address and a CString& to return the host name for the email address. The host name is found using the
::DNSQuery()
function. The function starts by stripping the Domain part of the email address and looking in our
std::map
to determine if we have previously performed a lookup on this domain. If so, we return the host name and we are done. If not, then we make a call to
::DNSQuery()
to determine the hostname. If the call is successful we insert the domain and hostname pair into the
std::map
and return. Here's the code:
BOOL CGetSMTPHostName::GetSmtpHostName(CString _EmailAddress,
CString& _HostName)
{
BOOL bRV = TRUE;
_HostName.Empty();
int start = _EmailAddress.Find('@')+1;
CString strDomain
= _EmailAddress.Mid(start,_EmailAddress.GetLength()-start);
if( ! strDomain.IsEmpty())
{
m_SMTPHostIterator = m_SMTPHost.find(strDomain);
if(m_SMTPHostIterator != m_SMTPHost.end())
{
_HostName = (*m_SMTPHostIterator).second;
if(_HostName == "UNK")
{
bRV=FALSE;
_HostName.Empty();
}
}
else
{
DNS_RECORD* ppQueryResultsSet = NULL;
DNS_STATUS statusDNS = ::DnsQuery( strDomain, DNS_TYPE_MX,
DNS_QUERY_STANDARD, NULL,
&ppQueryResultsSet, NULL );
if(statusDNS == ERROR_SUCCESS)
{
_HostName = ppQueryResultsSet->Data.MX.pNameExchange;
m_SMTPHost.insert(HostMapValue(strDomain,_HostName));
}
else
{
bRV = FALSE;
DNS_STATUS theError = statusDNS;
m_SMTPHost.insert(HostMapValue(strDomain,"UNK"));
}
}
}
else
{
bRV = FALSE;
}
return(bRV);
}
CGetSMTPHostName::ResetHostMap()
This method is nothing more than a utility function to clear the
std::map
in the event that you need to start fresh.
CGetSMTPHostName::Dump()
Another utility function to print the the console the list of domains and hostnames that are contained within the
std::map
.
CVerifySMTPEmailAddress
This class contains (has a)
CGetSMTPHostName
class as a private member and had a single public method called
VerifyEmailAddress()
shown below:
BOOL CVerifySMTPEmailAddress::VerifyEmailAddress(CString _EmailAddress)
{
CString strHostName;
if(m_GetSmtpHostName.GetSmtpHostName(_EmailAddress, strHostName))
{
return(Connect(strHostName, _EmailAddress));
}
return(FALSE);
}
main() or _tmain()
Here's the main function of the console application:
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
CVerifySMTPEmailAddress vrfyemail;
CString strSMTPAddress;
CString strHostName;
char caSMTPAddress[128];
while(TRUE)
{
std::cout << "Enter SMTP Address : ";
std::cin >> caSMTPAddress;
strSMTPAddress = caSMTPAddress;
strSMTPAddress.MakeLower();
if(strSMTPAddress == "exit")
{
break;
}
if(vrfyemail.VerifyEmailAddress(strSMTPAddress))
{
std::cout << strSMTPAddress.GetBuffer(0) << " is VALID"
<< std::endl;
}
else
{
std::cout << strSMTPAddress.GetBuffer(0) << vrfyemail.GetLastError()
<< std::endl;
}
strSMTPAddress.Empty();
}
}
return nRetCode;
}
Notes
::DNSQuery()[
^] can be used for much more than MX Record lookups. Information can be found on MSDN (click the link).
I prefer to use
#pragma comment
to link in libraries, especially on demonstration applications such as this. You can set up the link libraries to suit your taste.
Conclussion
I wrote this article for one purpose and that was to show that the libraries provided by Microsoft are bountiful. So before you start writing a DNS MX Record lookup function or the like take a look in MSDN or ask a living MSDN index (a.k.a. James) if an existing library contains the functionality that you need.