| [ Team LiB ] |
|
24.6 Verifying an Email AddressIt doesn't take much experience with email to discover what happens when it is misaddressed. The email is returned to you. This is called bounced email. Consider for a moment a Web site that allows users to fill out a form that includes an email address and sends a thank-you message. Certainly, many people will either mistakenly mistype their addresses or purposely give a bad address. You can check the form of the address, of course, but a well-formed address can fail to match to a real mail box. When this happens, the mail bounces back to the user who sent the mail. Unfortunately, this is probably the Web server itself. Reading through the bounced email can be interesting. Those running an e-commerce site may be concerned about order confirmations that go undelivered. Yet, the volume of mail can be very large. Add to this that delivery failure is not immediate. To the process that sends the mail, it appears to be successful. It may be worthwhile to verify an email address before sending mail. RFC 821 describes the SMTP protocol, which is used for exchanging email. You can read it at the faqs.org Web site <http://www.faqs.org/rfcs/rfc821.html>. It lives up to its name, Simple Mail Transfer Protocol, in that it's simple enough to use interactively from a telnet session. In order to verify an address, you can connect to the appropriate SMTP server and begin sending a message. If you specify a valid recipient, the server will return a 250 response code, at which point you can abort the process. It sounds easy, but there's a catch. The domain name portion of an address, the part after the @, is not necessarily the same machine that receives email. Domains are associated with one or more mail exchangers—machines that accept STMP connections for delivery of local mail. The getmxrr function returns all DNS records for a given domain. Now consider Listing 24.8. The verifyEmail function is based on a similar function written by Jon Stevens. As you can see, the function attempts to fetch a list of mail exchangers. If a domain doesn't have mail exchangers, the script guesses that the domain name itself accepts mail. Listing 24.8 Verifying an email address
<html>
<head>
<title>Listing 24-8</title>
</head>
<body>
<?php
/*
** Function: verifyEmail
** Input: STRING address, REFERENCE error
** Output: BOOLEAN
** Description: Attempts to verify an email address by
** contacting a mail exchanger. Registered mail
** exchangers are requested from the domain controller first,
** then the exact domain itself. The error argument will
** contain relevant text if the address could not be
** verified.
*/
function verifyEmail($address, &$error)
{
$mxhost = array();
$mxweight = array();
list($user, $domain) = split("@", $address, 2);
//make sure the domain has a mail exchanger
if(dns_check_record($domain, "MX"))
{
//get mail exchanger records
if(!dns_get_mx($domain, $mxhost, $mxweight))
{
$error =
"Could not retrieve mail exchangers!<br>\n";
return(FALSE);
}
}
else
{
//if no mail exchanger, maybe the host itself
//will accept mail
$mxhost[] = $domain;
$mxweight[] = 1;
}
//create sorted array of hosts
$weighted_host = array();
for($i = 0; $i < count($mxhost); $i++)
{
$weighted_host[($mxweight[$i])] = $mxhost[$i];
}
ksort($weighted_host);
//loop over each host
foreach($weighted_host as $host)
{
//connect to host on SMTP port
if(!($fp = fsockopen($host, 25)))
{
//couldn't connect to this host, but
//the next might work
continue;
}
/*
** skip over "220" messages
** give up if no response for 10 seconds
*/
stream_set_blocking($fp, FALSE);
$stopTime = time() + 10;
$gotResponse = FALSE;
while(TRUE)
{
//try to get a line from mail server
$line = fgets($fp, 1024);
if(substr($line, 0, 3) == "220")
{
//reset timer
$stopTime = time() + 10;
$gotResponse = TRUE;
}
elseif(($line == "") AND ($gotResponse))
{
break;
}
elseif(time() > $stopTime)
{
break;
}
}
if(!$gotResponse)
{
//this host was unresponsive, but
//maybe the next will be better
continue;
}
stream_set_blocking($fp, TRUE);
//sign in
fputs($fp, "HELO {$_SERVER['SERVER_NAME']}\r\n");
fgets($fp, 1024);
//set from
fputs($fp, "MAIL FROM: " .
"<httpd@{$_SERVER['SERVER_NAME']}>\r\n");
fgets($fp, 1024);
//try address
fputs($fp, "RCPT TO: <$address>\r\n");
$line = fgets($fp, 1024);
//close connection
fputs($fp, "QUIT\r\n");
fclose($fp);
if(substr($line, 0, 3) != "250")
{
//mail server doesn't recognize
//this address, so it must be bad
$error = $line;
return(FALSE);
}
else
{
//address recognized
return(TRUE);
}
}
$error = "Unable to reach a mail exchanger!";
return(FALSE);
}
if(verifyEmail("leon@clearink.com", $error))
{
print("Verified!<br>\n");
}
else
{
print("Could not verify!<br>\n");
print("Error: $error<br>\n");
}
?>
</body>
</html>
SMTP servers precede each message with a numerical code, such as the 250 code mentioned above. When first connecting with a server, it may send any number of 220 messages. These contain comments, such as the AOL servers' reminders not to use them for spam. No special code marks the end of the comments; the server simply stops sending lines. Recall that by default the fgets function returns after encountering the maximum number of characters specified or an end-of-line marker. This will not work in the case of an indeterminate number of lines. The script will wait forever after the last comment. Socket blocking must be turned off to handle this situation. When set_socket_blocking turns off blocking, fgets return immediately with whatever data is available in the buffer. The strategy is to loop continually, checking the buffer each time through the loop. There will likely be some lag time between establishing a connection and receiving the first message from the server. Then, as 220 messages appear, the script must begin watching for the data to stop flowing, which means the server is likely waiting for a command. To avoid the situation where a server is very unresponsive, a further check must be made against a clock. If 10 seconds pass, the server will be considered unavailable. Of course, this may reject addresses on slow servers. |
| [ Team LiB ] |
|