Powered By Blogger

Wednesday, May 12, 2010

Live Email Validation

Basically there are 3 steps to validate whether a email is real.
1. Simple regex validation like presence of a '@' character
2. Validate the Dns
3. Actually ping the email server to see if the user is listed

Here's a simple java utility that does all the above 3 steps.

__________________________________________________________________________________________________

import java.io.*;
import java.net.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;

import org.apache.log4j.Logger;

/**
* SMTP look for live email validation
*
* @author nitesh.kumar
*
*/
public class LiveEmailValidator {

private static Logger log = Logger.getLogger( LiveEmailValidator.class );

private static DirContext dirContext = null;

static {
if ( dirContext == null ) {
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
try {
dirContext = new InitialDirContext( env );
} catch (NamingException e) {
log.error( "naming exception while initialising dir context.", e );
}
}
}

/**
*
* @return
*/
static DirContext getDirContext() {
return dirContext;
}

/**
*
* @param address
* @return
*/
public static boolean isAddressValid ( String address ) {
Response res = null;
for ( int i = 0; i < EMAILValidationConstants.MAX_COUNT; i++ ) {
res = validateOnce( address );

if ( res == null ) {
return false;
}
int code = res.code;
if ( code < 400 || code >= 500 ) {
break;
}
try {
Thread.sleep( 60 * EMAILValidationConstants.TIME_TO_SLEEP_IN_MIN * 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println( "code :" + res.code + " . message : " + res.message );
return ( res.code == 250 ) ? true : false;
}

/**
*
* @author nitesh.kumar
*
*/
private static class Response {
int code;
String message;

public Response(int code, String message) {
this.code = code;
this.message = message;
}
}

/**
*
* @param code
* @param message
* @return
*/
private static Response getResponse( int code ) {
return new Response( code, SMTPReplyCodes.getMessage( code ) ) ;
}

/**
*
* @param code
* @param message
* @return
*/
private static Response getResponse( int code, String message ) {
return new Response( code, message ) ;
}

/**
*
* @param address
* @return
*/
public static Response validateOnce( String address ) {
int code = -1;
int pos = address.indexOf( '@' );
if ( pos == -1 ) {
return getResponse( -1 );
}

String domain = address.substring( ++pos );
ArrayList mxList = null;
try {
mxList = DNSValidator.getMX( domain );
}
catch (NamingException ex) {
return getResponse( SMTPReplyCodes.DNS_VALIDATION_FAILED, ex.getMessage() );
}

if ( mxList.size() == 0 ) {
return getResponse( SMTPReplyCodes.DNS_VALIDATION_FAILED );
}

for ( int mx = 0 ; mx < mxList.size() ; mx++ ) {
boolean valid = false;
Socket skt = null;
BufferedReader rdr = null;
BufferedWriter wtr = null;
Response res = null;
try {

skt = new Socket( mxList.get( mx ), 25 );
rdr = new BufferedReader( new InputStreamReader( skt.getInputStream() ) );
wtr = new BufferedWriter( new OutputStreamWriter( skt.getOutputStream() ) );

code = doValidationSteps( rdr, wtr, address );
resetAndQuit( rdr, wtr );

if ( code != 250 ) {
throw new Exception( "Address " + address + " is not valid!" );
}
valid = true;
res = getResponse( code );
}
catch (Exception ex) {
log.error( "Exception while validating email :" + address, ex );
}
finally {

if ( log.isDebugEnabled() ) {
StringBuilder sb = new StringBuilder();
sb.append ( "validating email : ")
.append( address )
.append( valid ? " .Address is VALID" : " . Address is not VALID" );
log.debug( sb.toString() );
}
close( rdr, wtr, skt);
if ( valid ) {
return res != null ? res : getResponse( code );
}
}
}
return getResponse( code );
}

/**
*
* @param rdr
* @param wtr
* @param skt
* @throws IOException
*/
private static void close( BufferedReader rdr, BufferedWriter wtr, Socket skt ) {
try {
if ( rdr != null ) rdr.close();
if ( wtr != null ) wtr.close();
if ( skt != null ) skt.close();
} catch ( IOException io ) {
log.error( "error while closing streams", io );
}
}

/**
*
* @param rdr
* @param wtr
* @return
* @throws EMAILValidationException
* @throws IOException
*/
private static int doValidationSteps( BufferedReader rdr, BufferedWriter wtr, String addressToValidate ) throws EMAILValidationException, IOException {
int res = SMTPValidator.validateHeader( rdr );

res = SMTPValidator.requestMailAction( rdr, wtr, EMAILValidationConstants.SENDER_HOST );

res = SMTPValidator.validateSender( rdr, wtr, EMAILValidationConstants.SENDER_ADDRESS );

res = SMTPValidator.validateActualAddress( rdr, wtr, addressToValidate );

return res;
}

/**
* reset and quit smtp validation
* @param rdr
* @param wtr
* @return
* @throws EMAILValidationException
* @throws IOException
*/
private static int resetAndQuit( BufferedReader rdr, BufferedWriter wtr ) throws EMAILValidationException, IOException {
int res = SMTPValidator.reset( rdr, wtr );

res = SMTPValidator.quit( rdr, wtr );
return res;
}

public static void main( String args[] ) {
String testData[] = {
"nitesh.kumar@gmail.com"
};

for ( int ctr = 0 ; ctr < testData.length ; ctr++ ) {
System.out.println( testData[ ctr ] + " is valid? " +
LiveEmailValidator.isAddressValid( testData[ ctr ] ) );
}
return;
}
}

/**
*
* @author nitesh.kumar
*
*/
class SMTPReplyCodes {

public static final int DNS_VALIDATION_FAILED = 100;

public static final int SYSTEM_STATUS = 211;
public static final int HELP_MESSAGE = 214;
public static final int DOMAIN_SERVICE_READY = 220;
public static final int OK_QUERYONG_FOR_NODE_NODE_STARTED = 250;
public static final int DOMAIN_SERVICE_NOT_AVAILABE = 421;
public static final int REQUEST_ACTION_ABORTED = 451;

public static final String UNKNOWN_ERROR = "UNKNOWN ERROR";

private static HashMap map;
static {
if ( map == null ) {
map = new HashMap();

map.put( Integer.valueOf(-1), "ADDRESS DOES NOT SATISFY BASIC REGEX VALIDATION" );
map.put( Integer.valueOf( SYSTEM_STATUS ), "SYSTEM STATUS" );
map.put( Integer.valueOf( HELP_MESSAGE ), "HELP MESSAGE" );
map.put( Integer.valueOf( OK_QUERYONG_FOR_NODE_NODE_STARTED ), "OK QUERYONG FOR NODE NODE STARTED" );
map.put( Integer.valueOf( DOMAIN_SERVICE_NOT_AVAILABE ), "DOMAIN SERVICE NOT AVAILABE" );
map.put( Integer.valueOf( DOMAIN_SERVICE_READY ), "DOMAIN SERVICE READY" );

map.put( Integer.valueOf( REQUEST_ACTION_ABORTED ), "REQUEST ACTION ABORTED" );
map.put( Integer.valueOf( DNS_VALIDATION_FAILED ), "DNS VALIDATION FAILED" );
}
}

public static String getMessage( int code ) {
String msg = map.get( Integer.valueOf( code ) );
if ( msg != null ) {
return msg;
}
if ( code >= 400 && code < 500 ) {
return map.get( Integer.valueOf( REQUEST_ACTION_ABORTED ) );
}
return UNKNOWN_ERROR;
}


}

/**
*
* @author nitesh.kumar@a3logics.in
*
*/
class EMAILValidationConstants {

public static final String SENDER_HOST = "google.com";
public static final String SENDER_ADDRESS = "nitesh.iitr@gmail.com";

public static final int MAX_COUNT = 2;
public static final int TIME_TO_SLEEP_IN_MIN = 2;
}

/**
*
* @author niitesh.kumar@a3logics.in
*
*/
class SMTPValidator {

public SMTPValidator() {
//
}

/**
*
* @param rdr
* @return
* @throws EMAILValidationException
* @throws IOException
*/
public static int validateHeader( BufferedReader rdr ) throws EMAILValidationException, IOException {
int res = hear( rdr );
if ( res != SMTPReplyCodes.DOMAIN_SERVICE_READY ) throw new EMAILValidationException( "Invalid header" );
return res;
}

/**
*
* @param rdr
* @param wtr
* @param toPing
* @return
* @throws EMAILValidationException
* @throws IOException
*/
static int requestMailAction( BufferedReader rdr, BufferedWriter wtr, String toPingHost ) throws EMAILValidationException, IOException {

say( wtr, "EHLO " + toPingHost );

int res = hear( rdr );
if ( res != SMTPReplyCodes.OK_QUERYONG_FOR_NODE_NODE_STARTED ) throw new EMAILValidationException( "Not ESMTP" );
return res;
}

/**
*
* @param rdr
* @param wtr
* @param senderAddress
* @return
* @throws EMAILValidationException
* @throws IOException
*/
static int validateSender( BufferedReader rdr, BufferedWriter wtr, String senderAddress ) throws EMAILValidationException, IOException {

say( wtr, getMAILFROMStr( senderAddress ) );
int res = hear( rdr );
if ( res != SMTPReplyCodes.OK_QUERYONG_FOR_NODE_NODE_STARTED ) throw new EMAILValidationException( "Sender rejected" );
return res;
}

/**
*
* @param rdr
* @param wtr
* @return
* @throws EMAILValidationException
* @throws IOException
*/
static int reset( BufferedReader rdr, BufferedWriter wtr ) throws EMAILValidationException, IOException {

say( wtr, "RSET" );
return hear( rdr );
}

/**
*
* @param rdr
* @param wtr
* @return
* @throws IOException
*/
static int quit( BufferedReader rdr, BufferedWriter wtr ) throws IOException {

say( wtr, "QUIT" );
return hear( rdr );
}

/**
*
* @param actualAddressToPing
* @return
*/
private static String getRCPTOStr( String actualAddressToPing ) {
StringBuilder sb = new StringBuilder();
sb.append( "RCPT TO: <" )
.append( actualAddressToPing )
.append ( ">" );
return sb.toString();
}

/**
*
* @param in
* @return
* @throws IOException
*/
private static int hear( BufferedReader in ) throws IOException {
String line = null;
int res = 0;

while ( (line = in.readLine()) != null ) {
String pfx = line.substring( 0, 3 );
try {
res = Integer.parseInt( pfx );
}
catch (Exception ex) {
res = -1;
}
if ( line.charAt( 3 ) != '-' ) break;
}

return res;
}

/**
*
* @param wr
* @param text
* @throws IOException
*/
private static void say( BufferedWriter wr, String text ) throws IOException {
wr.write( text + "\r\n" );
wr.flush();

return;
}

/**
*
* @param rdr
* @param wtr
* @param actualAddressToPing
* @return
* @throws EMAILValidationException
* @throws IOException
*/
static int validateActualAddress( BufferedReader rdr, BufferedWriter wtr, String actualAddressToPing ) throws EMAILValidationException, IOException {
say( wtr, getRCPTOStr( actualAddressToPing ) );
int res = hear( rdr );
return res;
}

/**
*
* @param senderAddress
* @return
*/
private static String getMAILFROMStr( String senderAddress ) {
StringBuilder sb = new StringBuilder();
sb.append( "MAIL FROM: <" )
.append( senderAddress )
.append ( ">" );
return sb.toString();
}
}

/**
*
* @author nitesh.kumar
*
*/
class DNSValidator {

DNSValidator() {
//
}

/**
*
* @param hostName
* @return
* @throws NamingException
*/
static ArrayList getMX( String hostName ) throws NamingException {

DirContext ictx = LiveEmailValidator.getDirContext();
if ( ictx == null ) {
throw new NamingException( "naming exception while initialising dir context." );
}
Attributes attrs = ictx.getAttributes( hostName, new String[] { "MX" } );
Attribute attr = attrs.get( "MX" );

if (( attr == null ) || ( attr.size() == 0 )) {
attrs = ictx.getAttributes( hostName, new String[] { "A" });
attr = attrs.get( "A" );
if( attr == null ) {
throw new NamingException( "No match for name '" + hostName + "'" );
}
}

ArrayList res = new ArrayList(5);
NamingEnumeration en = attr.getAll();

while ( en.hasMore() ) {
String mailhost;
String x = ( String ) en.next();
String f[] = x.split( " " );

if (f.length == 1)
mailhost = f[0];
else if ( f[1].endsWith( "." ) )
mailhost = f[1].substring( 0, (f[1].length() - 1));
else
mailhost = f[1];

res.add( mailhost );
}
return res;
}

}

/**
*
* @author nitesh.kumar
*
*/
class EMAILValidationException extends Exception {

/**
*
*/
private static final long serialVersionUID = -507597264595246045L;

EMAILValidationException() {
super();
}

public EMAILValidationException( String msg ) {
super ( msg );
}
}