There is no email validation for outgoing invitations (I think--maybe there is and it's just not working for me). This may not seem like a big deal for the core functionality of Invitations, but it becomes a problem when it interfaces with Userpoints. Users can send invitations to hundreds of non-existent email addresses at a time and receive thousands of Userpoints for doing so. This is especially problematic if Userpoints is used with eCommerce (not the case in my site, but a big issue nonetheless).

I think the best solution would be to call functions from a module like Email Verify (if the user has installed that module) since the validation ability already exists and would not have to be rewritten.

Comments

wotstheguts’s picture

I've had a go at modifying invite.module as follows by taking code from Email Verify as suggested by IceCreamYou. (Have included functions to handle checkdnsrr and getmxrr functions on a Windows platform).

Disclaimer - I am but a newbie, and I don't know if this is the best way to do it. It works, but is a bit slow if users are permitted to enter a list of email addresses. Let me know what you think.

First, modify the invite_form_validate() function in invite.module:

    // Check that there is at least one valid e-mail remaining after filtering
    // out dupes
    if (count($emails) == 0) {
      form_set_error('email');
      return;
    } else {
      $errors = 0;
      foreach ($emails as $check) {
        if ($result = email_verify_check($check)) {
          drupal_set_message($result, 'error');
          $errors++;
        }
      }
      if ($errors) {
        form_set_error('email');
        return;
      }
    }

Then borrow this code from Email Verify module and add to the end of invite.module:

/**
   Verifies whether the given mail address exists.
   @param mail the email address to verify
   @return NULL if the address exists, as far as we could check.
           an error message if we found a problem with the address
*/
function email_verify_check($mail) {
  $host = substr(strchr($mail, '@'), 1);

  // Let's see if we can find anything about this host in the DNS
  if (!checkdnsrr($host, 'ANY')) {
    return t('Cannot send email to %host because the domain is not valid', array('%host' => "$host"));
  }

  // What SMTP servers should we contact?
  $mxHosts = array();
  if (! getmxrr($host, $mxHosts)) {
    // When there is no MX record, the host itself should be used
    $mxHosts[] = $host;
  }

  // Try to connect to one SMTP server
  foreach ($mxHosts as $smtpServer) {
    $connect = @fsockopen($smtpServer, 25, $errno, $errstr, 15);
    if (!$connect) continue;

    if (ereg("^220", $out = fgets($connect, 1024))) {
      // OK, we have a SMTP connection
      break;
    } else {
      // The SMTP server probably does not like us
      // (dynamic/residential IP for aol.com for instance)
      // Be on the safe side and accept the address, at least it has a valid
      // domain part...
      watchdog('email_verify', "Could not verify email address at host $host: $out");
      return;
    }
  }

  if (! $connect)
    return t('Email host %host does not seem valid, it does not answer', array('%host' => "$host"));

  $from = variable_get('site_mail', ini_get('sendmail_from'));

  // Extract the <...> part if there is one
  if (preg_match('/\<(.*)\>/', $from, $match) > 0) {
    $from = $match[1];
  }

  $localhost = $_SERVER["HTTP_HOST"];
  if (!$localhost) // Happens with HTTP/1.0
    //should be good enough for RFC compliant SMTP servers
    $localhost = 'localhost';

  fputs($connect, "HELO $localhost\r\n");
  $out  = fgets($connect, 1024);
  fputs($connect, "MAIL FROM: <$from>\r\n");
  $from = fgets($connect, 1024);
  fputs($connect, "RCPT TO: <{$mail}>\r\n");
  $to   = fgets($connect, 1024);
  fputs($connect, "QUIT\r\n");
  fclose($connect);

  if (!ereg ("^250", $from )&& !ereg ("^421", $from)) {
    // Again, something went wrong before we could really test the address,
    // be on the safe side and accept it.
    watchdog('email_verify', "Could not verify email address at host $host: $from");
    return;
  }

  if (ereg("(Client host|Helo command) rejected", $to)) {
    // This server does not like us, accept the email address
    // (noos.fr behaves like this for instance)
    watchdog('email_verify', "Could not verify email address at host $host: $to");
    return;
  }

  if (!ereg ("^250", $to )&& !ereg ("^421", $to)) {
    watchdog('email_verify', "Rejected email address: $mail. Reason: $to");
    return t('Email address %mail does not seem valid', array('%mail' => "$mail"));
  }

  return;
}


if(!function_exists('checkdnsrr')) {
  function checkdnsrr($hostName, $recType = '') {
   if(!empty($hostName)) {
     if( $recType == '' ) $recType = "MX";
     exec("nslookup -type=$recType $hostName", $result);
     // check each line to find the one that starts with the host
     // name. If it exists then the function succeeded.
     foreach ($result as $line) {
       if(eregi("^$hostName",$line)) {
         return true;
       }
     }
     // otherwise there was no mail handler for the domain
     return false;
   }
   return false;
  }
}

if(!function_exists('getmxrr')) {
  function getmxrr($hostname, &$mxhosts) {
    $mxhosts = array();
    exec('nslookup -type=mx '.$hostname, $result_arr);
    foreach($result_arr as $line) {
      if (preg_match("/.*mail exchanger = (.*)/", $line, $matches)) $mxhosts[] = $matches[1];
    }
    return( count($mxhosts) > 0 );
  }
}
icecreamyou’s picture

That won't work if the Email Verify module is installed. This error will be thrown:

<strong>Fatal error</strong>: Cannot redeclare email_verify_check() (previously declared in /home/[root]/domains/[domain]/public_html/[subdomain]/modules/email_verify/email_verify.module:49) in <strong>/home/sukinisa/domains/babelup.com/public_html/music/modules/invite/invite.module</strong> on line <strong>1475</strong>

I haven't tried it, but everything *should* work if email_verify_check() is renamed to invite_email_verify_check().

Thanks for looking into this.

wotstheguts’s picture

Hiya, yes I don't have the Email Verify module installed, so that's why I copied the function to invite.module.

If it's already installed, you don't need to duplicate the email_verify_check function in invite.module as you can just call it from invite.module - then only the first block of code needs to be changed.

icecreamyou’s picture

Alright, I tried it. Renaming email_verify_check works (obviously you have to do it in both places) but the rules set allow things like asdfjaokwejl@aol.com to get through--anything at a valid domain/host.

EDIT: oh, didn't see your last post. Okay.

wotstheguts’s picture

You're right. It doesn't allow random Hotmail addresses, but when I tried a random AOL it allowed it through.

I saw that it logged this message in the watchdog:

Type email_verify
Date Sunday, 20 April, 2008 - 11:01
Message Could not verify email address at host aol.com: 220- authorize the use of its proprietary computers and computer

Looking at the code, if it gets any response other than a 250 or 421 it just logs it and exits the function. But AOL returned a 220 response.

If you telnet to port 25 on one of the AOL MX records you get this:

220-rly-ya06.mx.aol.com ESMTP mail_relay_in-ya6.3; Fri, 20 Jan 2006 22:15:42 -05 00
220-America Online (AOL) and its affiliated companies do not
220- authorize the use of its proprietary computers and computer
220- networks to accept, transmit, or distribute unsolicited bulk
220- e-mail sent from the internet. Effective immediately: AOL
220- may no longer accept connections from IP addresses which
220 have no reverse-DNS (PTR record) assigned.

So it looks like the problem might be that AOL sends a whole bunch of 220 messages instead of just one, so the code is putting the wrong info into the $to and $from variables. I'll dig into it a bit more and look at how it could be fixed.

Cheers,
Pete.

wotstheguts’s picture

Hey IceCreamYou,

On another website I found some excellent code to check email address validity. It may be worth using this instead of the Email Verify code, but will need to do some testing first:

https://svn.ampache.org/trunk/modules/validatemail/validateEmail.php

In the meantime, I borrowed a loop from it to check for multiple 220 instances, so it now works for AOL addresses. It still seems to allow yahoo.com addresses through though - for some reason the yahoo mail servers return a 250 "OK" message when you enter any old email address, not sure why. Will check it out.

Updated email_verify_check() function below.

/**
   Verifies whether the given mail address exists.
   @param mail the email address to verify
   @return NULL if the address exists, as far as we could check.
           an error message if we found a problem with the address
*/
function email_verify_check($mail) {
  $host = substr(strchr($mail, '@'), 1);

  // Let's see if we can find anything about this host in the DNS
  if (!checkdnsrr($host, 'ANY')) {
    return t('Cannot send email to %host because the domain is not valid', array('%host' => "$host"));
  }

  // What SMTP servers should we contact?
  $mxHosts = array();
  if (! getmxrr($host, $mxHosts)) {
    // When there is no MX record, the host itself should be used
    $mxHosts[] = $host;
  }

  // Try to connect to one SMTP server
  foreach ($mxHosts as $smtpServer) {
    $connect = @fsockopen($smtpServer, 25, $errno, $errstr, 15);
    if (!$connect) continue;

    if (ereg("^220", $out = fgets($connect, 1024))) {
      // OK, we have a SMTP connection
      break;
    } else {
      // The SMTP server probably does not like us
      // (dynamic/residential IP for aol.com for instance)
      // Be on the safe side and accept the address, at least it has a valid
      // domain part...
      watchdog('email_verify', "Could not verify email address at host $host: $out");
      return;
    }
  }

  if (! $connect)
    return t('Email host %host does not seem valid, it does not answer', array('%host' => "$host"));

  // Check for multi-line output (###-)
  if ( preg_match ( "/^2..-/", $out ) ) {
    $end = false;
    while ( !$end ) {
      // keep listening
      $line = fgets ( $connect, 1024 );
      $out .= $line;
      if ( preg_match ( "/^2.. /", $line ) ) {
        // the last line of output shouldn't
        // have a dash after the response code
        $end = true;
      }
    }
  }

  $from = variable_get('site_mail', ini_get('sendmail_from'));

  // Extract the <...> part if there is one
  if (preg_match('/\<(.*)\>/', $from, $match) > 0) {
    $from = $match[1];
  }

  $localhost = $_SERVER["HTTP_HOST"];
  if (!$localhost) // Happens with HTTP/1.0
    //should be good enough for RFC compliant SMTP servers
    $localhost = 'localhost';

  fputs($connect, "HELO $localhost\r\n");
  $out  = fgets($connect, 1024);
  fputs($connect, "MAIL FROM: <$from>\r\n");
  $from = fgets($connect, 1024);
  fputs($connect, "RCPT TO: <{$mail}>\r\n");
  $to   = fgets($connect, 1024);
  fputs($connect, "QUIT\r\n");
  fclose($connect);

  if (!ereg ("^250", $from )&& !ereg ("^421", $from)) {
    // Again, something went wrong before we could really test the address,
    // be on the safe side and accept it.
    watchdog('email_verify', "Could not verify email address at host $host: $from");
    return;
  }

  if (ereg("(Client host|Helo command) rejected", $to)) {
    // This server does not like us, accept the email address
    // (noos.fr behaves like this for instance)
    watchdog('email_verify', "Could not verify email address at host $host: $to");
    return;
  }

  if (!ereg ("^250", $to )&& !ereg ("^421", $to)) {
    watchdog('email_verify', "Rejected email address: $mail. Reason: $to");
    return t('Email address %mail does not seem valid', array('%mail' => "$mail"));
  }

  return;
}
icecreamyou’s picture

That's excellent. Thanks for looking into this.

I kind of feel like this should be a separate Email Verify issue and not a continuation of this Invite issue though. Both are equally important and valid, but they're not the same thing and it's just as important that this code get into Email Verify.

wotstheguts’s picture

Yes I agree, the code needs to be looked at in detail and added to the Email Verify issue.

For now, I've noticed that the code is rejecting email addresses that are "greylisted" by some email servers - this just means that the server hasn't seen the sender before, and is expecting the email to be resent after a short delay. Need to change the if statement in the code to allow 450 responses:

  if (!ereg ("^250", $to )&& !ereg ("^421", $to) && !ereg ("^450", $to)) {
    watchdog('email_verify', "Rejected email address: $mail. Reason: $to");
    return t('Email address %mail does not seem valid', array('%mail' => "$mail"));
  }
ckng’s picture

Issue summary: View changes
Status: Active » Closed (won't fix)

Cleaning out old issues.