Form mail with captcha
21 May 2008 - 12:51Recently, 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!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{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!

Trackback link:Please enable javascript to generate a trackback url
Leave a comment