Form mail with captcha

21 May 2008 - 12:51

Recently, the contact forms on my websites have begun attracting the attention of idiots who use them to send me spam. Why they bother, I do not know. The emails consist of HTML text containing links to sites with gobbledygook names. Whether these sites exist I have no idea. I have no intention of trying to visit them even if the names looked real. It is a complete waste of their time and mine. So to eliminate the need to look at, and then delete these emails, I have implemented a captcha check on all of my contact forms.

Web form to email scripts are easy to write using the PHP mailer. However, they are not quite as easy as they seem. Badly written form mail scripts can be used by spammers to send spam from your server. So there are a number of checks you need to do to try to prevent abuse of your email form.

  • You must prevent the script from being used except by a POST action from a form
  • You must prevent it from being called from anywhere other than your server.
  • You must check any information that is going to form part of the email header, to prevent spammers injecting extra Cc: or Bcc: headers (this is how they send spam using your script - it is called header injection.)

You should validate the sender's email address, to avoid receiving emails from idiots that just type nonsense into all the fields. You should avoid allowing the To: address to be specified using the form field, or at least prevent mail from being sent anywhere other than an address at your domain, to prevent use by spammers. You may wish to avoid having the To: address appear anywhere on the form, so that spammers cannot harvest it and use it to send you spam by other means.

Finally, you need to generate a captcha image and verify that the answer has been entered correctly.

My existing form mail pages used a Perl script, and I don't know Perl, so modifying it to add a captcha check was beyond me. I found several quite powerful and flexible PHP scripts, but understanding how they worked and how to fit them into my existing pages would probably have taken longer than writing my own from scratch. I wasn't sure if the simpler ones met all my requirements. In the end, I put together my own, using ideas gleaned from various forum postings discovered using Google.

First of all, the main form mail script formmail.php:

<?
$domain = "mydomain.com";              // domain name of this site
$to_email = "webmaster@mydomain.com";  // default destination email (override with hidden value 'to')
$subj_prefix = "[Form Mail]";          // optional subject prefix to show where the mail is from
// validate email address function
function validemail($email) {
// Check that there is only one @ symbol and that the lengths are right
if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email)) {
return false;
}
// Split it into sections
$email_array = explode("@", $email);
$local_array = explode(".", $email_array[0]);
for ($i = 0; $i < sizeof($local_array); $i++) {
if (!ereg("^(([A-Za-z0-9!#$%&#038;'*+/=?^_`{|}~-][A-Za-z0-9!#$%&#038;'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i])) {
return false;
}
}
if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) { // Check if domain is IP or valid domain name
$domain_array = explode(".", $email_array[1]);
if (sizeof($domain_array) < 2) {
return false; // Not enough parts to domain
}
for ($i = 0; $i < sizeof($domain_array); $i++) {
if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) {
return false;
}
}
}
return true;
}
// header injection check function
function hicheck($field) {
if (eregi("\r", $field) || eregi("\n", $field) || eregi("\t", $field) || eregi("%08", $field)
|| eregi("%09", $field) || eregi("%0a", $field) || eregi("%0d", $field)) die("Access denied (0x0004)");
}
// ensure script is only used with action="POST"
if(!$_SERVER['REQUEST_METHOD'] == "POST") die("Access denied (0x0001)");
// ensure script is only called from this domain
if (stripos($_SERVER['HTTP_REFERER'],$domain)===FALSE) die("Access denied (0x0002)");
// load the form fields
$from_name = trim(stripslashes($_POST["from-name"]));
$from_email = trim(stripslashes($_POST["from-email"]));
$to = trim(stripslashes($_POST["to"]));
$subject = trim(stripslashes($_POST["subject"]));
$message = trim(stripslashes($_POST["message"]));
$verification = $_POST["verification"];
$success = $_POST["success"];
$failure = $_POST["failure"];
// hicheck all fields that will go into the email headers
hicheck($from_name);
hicheck($from_email);
hicheck($subject);
if($to != "") {
hicheck($to);
$to_email = $to."@".$domain;
}
// validate form fields
$response = "";
if(!validemail($from_email)) $response = "Email address is invalid. ";
if($subject == "") $response .= "Subject line is blank. ";
if($message == "") $response .= "Message is blank. ";
if(md5($verification) != $_COOKIE['tpverify']) $response .= "Verification code is incorrect. ";
// if no errors, send the message
if($response == "") {
if($from_name=="") {
$from = $from_email;
} else {
$from = '"'.$from_name.'" <'.$from_email.'>';
}
mail($to_email, trim($subj_prefix." ".$subject), $message, "From: $from");
setcookie('tpverify','');  // delete the cookie
if(!empty($success)) {
header("Location: ".$success);
} else {
echo "Message sent.";
}
} else {
$response .= "<br/>Click the Back button, correct your error and try again.";
if(!empty($failure)) {
header("Location: ".$failure."?err=".urlencode($response));
} else {
echo $response;
}
}
?>

You will need to change the values of the three variables at the top of the script, to suit the site you are working on.

Next, the captcha image generation script captcha.php:

<?php
header('Content-type: image/jpeg');
$width = 50;
$height = 22;
$my_image = imagecreatetruecolor($width, $height);
imagefill($my_image, 0, 0, 0xA0A0A0);
// add noise
for ($c = 0; $c < 40; $c++){
  $x = rand(0,$width-1);
  $y = rand(0,$height-1);
  imagesetpixel($my_image, $x, $y, 0x404040);
}
$x = rand(1,8);
$y = rand(1,8);
$rand_string = rand(1000,9999);
imagestring($my_image, 5, $x, $y, $rand_string, 0x000000);
setcookie('tpverify',(md5($rand_string)));
imagejpeg($my_image);
imagedestroy($my_image);
?>

This does not require any changes.

Finally, this is the form code which you paste into your HTML page:

<form action="formmail.php" method="POST">
<input type="hidden" name="success" value="email_ok.html">
<input type="hidden" name="failure" value="email_err.html">
<input type="hidden" name="to" value="sales">
<table border="0" cellpadding="2">
<tr>
<td>From (name):</td>
<td><input type="text" size="32" name="from-name"></td>
</tr>
<tr>
<td>Email address:</td>
<td><input type="text" size="32" name="from-email"></td>
</tr>
<tr>
<td>Subject:</td>
<td><input type="text" size="62" name="subject"></td>
</tr>
<tr>
<td valign="top">Message:</td>
<td valign="top"><textarea name="message" rows="15"
            cols="48"></textarea></td>
</tr>
<tr>
<td>Verification code:</td>
<td>
<input type="text" size="8" name="verification">
<img src="captcha.php" alt="Verification code, please enter it" width="50" height="24" align="absbottom" />
</td>
</tr>
</table>
<p><input type="submit" value="Send"></p>
</form>

So there you have it. Possibly the simplest PHP form mail with captcha there is. Just copy the two PHP files to your server, edit three variables, paste the form code into an HTML page and you're ready to go!

Used tags: , , , , ,

« Windows replaces Linu… | Home | The perfect CMS »


five comments

Same idiots sends me their name and some garbage text from last few weeks. I don’t know the motive behind it. Neither it is spam or useful e-mail. A complete waste of time
Manoj Shinde () (URL) - 29 May 2008 - 07:51

I really like this script and the flexibility of it. However, I keep getting a
“Fatal error: Call to undefined function: stripos() in formmail.php on line 40”

Could I possibly be doing something wrong?
Dawn () - 4 June 2008 - 02:45

“Call to undefined function: stripos()” probably means that you are using an old version of PHP. If you can’t use PHP 5 on your server then look at the php.net pages for stripos (http://php.net/stripos), and in the comments you will find some suggestions for a stripos function that you can use in PHP 4.
Tech-Pro - 9 June 2008 - 10:46

I want to include the Submitter’s IP address on the message. What should I do?
Neil () - 23 June 2008 - 19:10

You just need to append $_SERVER[‘REMOTE_ADDR’] to the text of the message.
Tech-Pro - 1 July 2008 - 17:42

Trackback link:

Please enable javascript to generate a trackback url


Leave a comment
  
Remember personal info?

Emoticons / Textile
  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.

Today's Bargain Offers