org.talend.dataquality.email.checkerImpl.CallbackMailServerCheckerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.talend.dataquality.email.checkerImpl.CallbackMailServerCheckerImpl.java

Source

// ============================================================================
//
// Copyright (C) 2006-2016 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================
package org.talend.dataquality.email.checkerImpl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.talend.dataquality.email.api.EmailVerifyResult;
import org.talend.dataquality.email.exception.TalendSMTPRuntimeException;

/**
 * created by talend on 20141226 Detailled comment
 *
 */
public class CallbackMailServerCheckerImpl extends AbstractEmailChecker {

    private static final Logger LOG = Logger.getLogger(CallbackMailServerCheckerImpl.class);

    private static String HEADER = "Email Indicator - "; //$NON-NLS-1$

    private String genericEmailRegex = "^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$"; //$NON-NLS-1$

    private Pattern emailPattern = java.util.regex.Pattern.compile(genericEmailRegex);

    private String dns = null;

    private DirContext ictx = null;

    /**
     * The default port for smtp MX(Mail Exchanger) server
     */
    private int port = 25;

    public CallbackMailServerCheckerImpl() {
        init();
    }

    /**
     * 
     * Write the text ot buffer.
     * 
     * @param wr
     * @param text
     * @throws IOException
     */
    private static void write(BufferedWriter wr, String text) throws IOException {
        wr.write(text + "\r\n"); //$NON-NLS-1$
        wr.flush();
    }

    /**
     * 
     * Get response status's code, 250 means OK, queuing for node node started. Requested mail action okay, completed.
     * See more details at http://email.about.com/cs/standards/a/smtp_error_code_2.htm
     * 
     * @param in
     * @return
     * @throws IOException
     */
    private static int getResponse(BufferedReader in) throws IOException, TalendSMTPRuntimeException {
        String line = null;
        int res = 0;
        do {
            try {
                line = in.readLine();
            } catch (IOException e) {
                line = e.getMessage();
                LOG.warn(line, e);
                continue;
            }
            if (LOG.isInfoEnabled()) {
                LOG.info(line);
            }
            // Make sure the input stream is over and not effect next one read
            if (line == null) {
                break;
            }
            if (res != 0 && line.charAt(3) != '-') {
                continue;
            }
            // if in.ready() is true then line will not be null
            String pfx = line.substring(0, 3);
            try {
                res = Integer.parseInt(pfx);
            } catch (NumberFormatException ex) {
                res = -1;
            }
        } while (in.ready());
        // line.contains("authentication is required") judge whether authentication is required(for example 139.com)
        if (res != 250 && res != 221 && res != 220 || line.contains("authentication is required")) { //$NON-NLS-1$
            throw new TalendSMTPRuntimeException(line);
        }
        return res;
    }

    private List<String> getMX(String hostName) throws NamingException {
        // Perform a DNS lookup for MX records in the domain
        Attributes attrs = ictx.getAttributes(hostName, new String[] { "MX" }); //$NON-NLS-1$
        Attribute attr = attrs.get("MX"); //$NON-NLS-1$
        List<String> res = new ArrayList<String>();

        // if we don't have an MX record, try the machine itself
        if ((attr == null) || (attr.size() == 0)) {
            attrs = ictx.getAttributes(hostName, new String[] { "A" }); //$NON-NLS-1$
            attr = attrs.get("A"); //$NON-NLS-1$
            if (attr == null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info(HEADER + "No match for hostname '" + hostName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
                }
                return res;
            }
        }
        // we have machines to try. Return them as an array list
        NamingEnumeration<?> en = attr.getAll();
        Map<Integer, String> map = new TreeMap<Integer, String>();

        while (en.hasMore()) {
            String mailhost;
            String x = (String) en.next();
            String f[] = x.split(" "); //$NON-NLS-1$
            Integer key = 0;
            if (f.length == 1) {
                mailhost = f[0];
            } else if (f[1].endsWith(".")) { //$NON-NLS-1$
                mailhost = f[1].substring(0, f[1].length() - 1);
                key = Integer.valueOf(f[0]);
            } else {
                mailhost = f[1];
                key = Integer.valueOf(f[0]);
            }
            map.put(key, mailhost);
        }
        // NOTE: We SHOULD take the preference into account to be absolutely
        // correct.
        Iterator<Integer> keyInterator = map.keySet().iterator();
        while (keyInterator.hasNext()) {
            res.add(map.get(keyInterator.next()));
        }
        return res;
    }

    public void init() {
        // Prepare naming directory context.
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); //$NON-NLS-1$ //$NON-NLS-2$

        // if the user add the paramter for: java.naming.provider.url, if has then add it to env
        // Added TDQ-6918 Allow user add parameter: java.naming.provider.url
        String dnsUrl = dns;
        if (dnsUrl != null) {
            env.put(Context.PROVIDER_URL, dnsUrl);
        } // ~

        try {
            ictx = new InitialDirContext(env);
        } catch (NamingException e) {
            LOG.error("Invalid DNS: " + e); //$NON-NLS-1$
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.talend.dataquality.email.IEmailChecker#check(java.lang.String)
     */
    @Override
    public boolean check(String email) throws TalendSMTPRuntimeException {
        if (email == null) {
            if (LOG.isInfoEnabled()) {
                LOG.info("mail is empty."); //$NON-NLS-1$
            }
            return false;
        }
        // Find the separator for the domain name
        int pos = email.indexOf('@');
        // If the email does not contain an '@', it's not valid
        if (pos == -1) {
            if (LOG.isInfoEnabled()) {
                LOG.info("no @ charactor in the mail string.");
            }
            return false;
        }

        // check loose email regex
        final Matcher matcher = emailPattern.matcher(email);
        if (!matcher.find()) {
            if (LOG.isInfoEnabled()) {
                LOG.info(HEADER + "Invalid email syntax for " + email); //$NON-NLS-1$
            }
            return false;
        }

        // Isolate the domain/machine name and get a list of mail exchangers
        String domain = email.substring(++pos);
        List<String> mxList = null;
        try {
            mxList = getMX(domain);
        } catch (NamingException ex) {
            if (LOG.isInfoEnabled()) {
                LOG.info(ex.getMessage());
            }
            // talend email on the outside of office room case
            throw new TalendSMTPRuntimeException(ex.getMessage());
        }

        // Just because we can send mail to the domain, doesn't mean that the
        // email is valid, but if we can't, it's a sure sign that it isn't
        if (mxList.isEmpty()) {
            if (LOG.isInfoEnabled()) {
                LOG.info("MX size is 0"); //$NON-NLS-1$
            }
            return false;
        }

        // Now, do the SMTP validation, try each mail exchanger until we get
        // a positive acceptance. It *MAY* be possible for one MX to allow
        // a message [store and forwarder for example] and another [like
        // the actual mail server] to reject it. This is why we REALLY ought
        // to take the preference into account.
        String errorMessage = StringUtils.EMPTY;
        for (int mx = 0; mx < mxList.size(); mx++) {
            try {
                int res;
                Socket skt = new Socket(mxList.get(mx), port);
                BufferedReader rdr = new BufferedReader(new InputStreamReader(skt.getInputStream()));
                BufferedWriter wtr = new BufferedWriter(new OutputStreamWriter(skt.getOutputStream()));

                res = getResponse(rdr);
                if (res != 220) { // SMTP Service ready.
                    skt.close();
                    if (LOG.isInfoEnabled()) {
                        LOG.info(HEADER + "Invalid header:" + mxList.get(mx)); //$NON-NLS-1$
                    }
                    return false;
                }
                write(wtr, "EHLO " + domain); //$NON-NLS-1$  

                res = getResponse(rdr);
                if (res != 250) {
                    skt.close();
                    if (LOG.isInfoEnabled()) {
                        LOG.info(HEADER + "Not ESMTP: " + domain); //$NON-NLS-1$
                    }
                    return false;
                }

                // validate the sender email
                write(wtr, "MAIL FROM: <" + email + ">"); //$NON-NLS-1$//$NON-NLS-2$
                res = getResponse(rdr);
                if (res != 250) {
                    skt.close();
                    if (LOG.isInfoEnabled()) {
                        LOG.info(HEADER + "Sender rejected: " + email); //$NON-NLS-1$
                    }
                    return false;
                }

                write(wtr, "RCPT TO: <" + email + ">"); //$NON-NLS-1$//$NON-NLS-2$
                res = getResponse(rdr);

                // be polite
                write(wtr, "RSET"); //$NON-NLS-1$
                getResponse(rdr);
                write(wtr, "QUIT"); //$NON-NLS-1$
                getResponse(rdr);
                if (res != 250) {
                    skt.close();
                    return false;
                }
                rdr.close();
                wtr.close();
                skt.close();
                return true;
            } catch (IOException e) {
                // Do nothing but try next host
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Connection to " + mxList.get(mx) + " failed.", e); //$NON-NLS-1$ //$NON-NLS-2$
                }
                errorMessage = e.getMessage();
                continue;
            }
        }
        throw new TalendSMTPRuntimeException(errorMessage);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.talend.dataquality.email.checkerImpl.AbstractEmailChecker#checkEmail(java.lang.String)
     */
    @Override
    public EmailVerifyResult checkEmail(String email) throws TalendSMTPRuntimeException {
        return check(email) ? EmailVerifyResult.VERIFIED : EmailVerifyResult.REJECTED;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.talend.dataquality.email.checkerImpl.AbstractEmailChecker#check(java.lang.String, java.lang.String[])
     */
    @Override
    public EmailVerifyResult check(String email, String... strings) {
        EmailVerifyResult result = EmailVerifyResult.REJECTED;
        if (check(email)) {
            result = EmailVerifyResult.VERIFIED;
        } else {
            result = EmailVerifyResult.REJECTED;
        }
        return result;
    }

}