Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating emails using checkdnsrr, a good or bad solution?

I am using the below code to validate emails

if (checkdnsrr($domain , "MX")) {
    echo 'mx - pass <br>';
} else {
    echo 'mx - fail <br>';
}

My desire is to check that the domain is valid and has an MX record.

I am already using a regular expression to check the email format, but people enter things like [email protected] which obviously is wrong but pases basic format validation.

I want to validate further but I do not want to go too far and get false negatives.

Does anyone see any issue with my solution or have a better way?

like image 908
Mar Avatar asked Feb 24 '17 11:02

Mar


2 Answers

Your solution is perfectly fine! However, before you even make a DNS call, I'd recommend that you first validate the email address with FILTER_VALIDATE_EMAIL and then pass it for MX DNS check.

While, it may not be needed to check if the MX record exists, but you're looking to avoid bounce emails, go ahead!

like image 61
Areeb Avatar answered Sep 19 '22 11:09

Areeb


Yes, using checkdnsrr() is a good idea. When to use it is what you should focus on. I use a four pass system. Here is the skeleton, not the exact organization of what I do for email.

Four Pass System

Pass 1) My custom filter using filter_input_array() and this rule (where filter occurs in a method of a class/object. Add elements as needed (for other fields).

$customFilterRules = [
    'email' => ['filter'  => FILTER_CALLBACK,
                'flags'   => FILTER_REQUIRE_SCALAR,
                'options' => [$this, 'scrubber']
];

Pass 2) Use filter_input_array() with this filter rule. Add more elements as needed (for other fields).

$phpFilterRules = [
    'email' => ['filter' => FILTER_SANITIZE_EMAIL,
                'flags'  => FILTER_REQUIRE_SCALAR]
];

Pass 3) Use filter_var_array() on the output of filter_input_array() with this validation rule. Add more rules as necessary.

$phpValidationRules = [
    'email' => ['filter' => FILTER_VALIDATE_EMAIL,
                'flags'  => FILTER_REQUIRE_SCALAR]
];

Pass 4) Check the following with an EmailValidator class. Also, if you don't like using filter_input_array(), here are some methods from this class that might be helpful, generally. Alter as necessary.

Oh, I also check the basic, application specific, acceptable length of an email address with:

$length = mb_strlen($email, 'UTF-8') //Make a decision based on this.

Additionally, I have a great, application specific, e-mail regular expression I use with preg_match(). I only accept 128 characters in e-mail addresses.

'/(?>\A[A-Za-z0-9_-][A-Za-z0-9_.-]{0,62}?[A-Za-z0-9_-]{0,1}@{1}?(?:(?:[A-Za-z0-9]{1}?){1}?(?:[A-Za-z0-9.-]{0,61}?[A-Za-z0-9]{1}?){0,1}?){1,127}?\.{1}?[a-z]{2,20}?\z){1}?/u'

Here are some EmailValidator methods.

/**
 * Compares 2 email addresses. If 1, just keep going.
 */
private function sameEmailAddress()
{
    if (count($this->filteredInputArray) === 2) {  //If there are two.
        if ($this->identical($this->filteredInputArray['email1'], $this->filteredInputArray['email2'])) {
            return true;
        }

        $this->testResultsArray['email1'] = false;
        $this->testResultsArray['email2'] = false;
        $this->errorMessagesArray['email1'] = 'Does not match e-mail below.';
        $this->errorMessagesArray['email2'] = 'Does not match e-mail above.';
        return false;
    }

    if (count($this->filteredInputArray) === 1) {  //If there is only 1.
        return true;
    }

    return false;
}

/**
 * Finds problems with e-mail as a whole.
 * There is a regular expression you can do this with.
 */
private function consecutivePeriodTest ($emailAddress, &$errorMessage)
{
    if (!preg_match('/\A(?!..)+?\z/', $emailAddress)) {
        return true;
    }

    $errorMessage = 'Consecutive periods are illegal!';
    return false;
}

Finally, I use checkdnsrr().

/**
 * Given an array of unique domains, check DNS MX records.
 */
private function mxDNSPing(array $uniqueDomains)
{   
    $badDomains = [];

    foreach ($uniqueDomains as $key => $domain) {
        if (!checkdnsrr($domain, 'MX')) {
            $this->testResultsArray[$key] = false;
            $this->errorMessagesArray[$key] = 'No DNS MX records found.';
            $badDomains[$key] = $domain;
        }
    }

    return $badDomains;
}

Determining what is wrong with the email address.

/**
 * Finds problems with local or domain parts.
 * Should break up into two methods, though.
 */
private function emailPartProblemFinder($string, &$errorMessage)
{
    $emailParts = $this->string->getEmailAddressParts($string); //explode() on `@`

    if (count($emailParts) !== 2) {
        $errorMessage = 'Invalid e-mail address!';
    } else {
        list($localPart, $domain) = $emailParts;

        $localLength  = mb_strlen($localPart);
        $domainLength = mb_strlen($domain);

        if ($localLength === 0) {
            $errorMessage = 'Missing local part of address.';
        } elseif ($localLength > 64) {
            $errorMessage = 'Only 64 characters are alloed before the @ symbol ('.$localLength.' given)';
        } elseif (mb_strrpos($string, '.') === ($localLength - 1)) {
            $errorMessage = 'The local part of an email address cannot end with a period (.).';
        } elseif (mb_strpos($string, '..') >= 0) {
            $errorMessage = 'The local part of an email address cannot contain consecutive periods (..).';
        } elseif ($domainLength < 4) { //x.yy, is my minimum domain format.
            $errorMessage = 'Domain part < 4 characters. ('.$domainLength.' given)';
        } elseif ($domainLength > 253) {
            $errorMessage = 'Domain part exceeds 253 characters. ('.$domainLength.' given)';
        }
    }

    return;
}

/**
 * Finds problems with e-mail as a whole.
 */
private function emailAddressProblemFinder($string, $max, &$errorMessage)
{
    $length = mb_strlen($string, 'UTF-8');
    $atSymbolCount = mb_substr_count($string, '@', 'UTF-8');

    if ($length === 0) {
        return false;    //The reason was already assigned to the error message inside of $this->validateInput()
    } elseif ($length > $max) {
        $errorMessage = 'Exceeds max length (' . $max . ' characters)';
    } elseif ((mb_strpos($string, '@') === 0)) {
        $errorMessage = 'Cannot start with a @';
    } elseif ((mb_strrpos($string, '@') === ($length - 1))) {
        $errorMessage = 'Cannot end with a @';
    } elseif ($atSymbolCount > 1) {
        $errorMessage = '@ appears '.$atSymbolCount.' times.';
    } elseif ((mb_strpos($string, '@') === false)) {
        $errorMessage = 'The @ symbol is missing.';
    } elseif (mb_strpos($string, '.') === 0) {
        $errorMessage = 'The local part of an email address cannot start with a period (.).';
    } else {
        $this->emailPartProblemFinder($string, $errorMessage);
    }

    return;
}

The method, emailAddressProblemFinder() is only called to discover what went wrong. It calls emailPartProblemFinder(), if necessary.

My point is that there are tons of tests you could do before using checkdnsrr(). The wisdom of this is something for you and others to debate. Anyway, I always like to see what other people do!

like image 41
Anthony Rutledge Avatar answered Sep 20 '22 11:09

Anthony Rutledge