Monthly Archives: April 2008

Tightening Up gdform.php With A Captcha

Disclaimer: I’m no security expert, but it was obvious that gdform.php can be used to annoy the person who receives the email. So this is an attempt to reduce the SPAM that might be sent to that email address.

Like any good geek, I recently put my resume online; not on any of the numerous job hunting sites, but on my own site. At first I was a little concerned about having my personal and professional information crawled by search engines, so I decided to leave my real name and to remove the things like address, phone number, and email address. However, removing the contact information kind of defeats the purpose of posting a resume.

To solve this little problem, I decided to put a contact form on the site.

Contact Form

Problem solved right, not quite. When I went to go write the PHP that would utilize the mail() function to send me an email, I found out that my hosting company (GoDaddy.com) doesn’t support the underlying connection to sendmail that the PHP mail() function needs. This makes perfect sense. Firstly it prevents GoDaddy customers from turning into spammers, but more importantly it prevents GoDaddy customers from writing/using buggy code that can be compromised by a spammer.

GoDaddy.com doesn’t leave you high and dry though. They have a utility installed in the web directory of every account called gdform.php. The first that that you need to do is to set up the email account that you wish to use as a destination of your form emails (GoDaddy howto). Then you need to set up a form that will use it (GoDaddy howto). I used their example and got it working pretty quickly. For testing, I used GET and constructed my own field/value pairs on the URL. I tried this:

http://www.namelessresumesite.com/gdform.php?email=me@namelessresumesite.com&subject=Well%20Preserved%20Meet%20Product&comments=Yummy

This test worked, but the scary part is that anyone can now sent you spam. They could kit that with a cron job using wget about a thousand times a day if they want. Maybe GoDaddy.com has a way of preventing that abuse, but I didn’t want to take a chance.

I decided to protect this PHP script with a captcha. So I visited phpcaptcha.org and tried out their Captcha. I was pretty impressed. It’s really easy to add a refresh button for users that can’t read a particular captcha. And it even has an audio version of the captcha that users can download and listen too.

For my form, I used some simple Javascript to do some client-side validation of the form.

function validateContactForm() {

  if ( (document.getElementById('email').value == '')
       || (document.getElementById('message').value == '')
       || (document.getElementById('captcha_code').value == '') ) {
    return false;
  } else {
    return true;
  }
}

function handleContactForm() {

  if (!validateContactForm()) {
    alert("Please fill out all fields.");
  } else {
    sendMessage();
  }
}

Then I used a little AJAX to submit the form:

function sendMessage() {

  var email = document.getElementById('email').value;
  var message = document.getElementById('message').value;
  var code = document.getElementById('captcha_code').value;

  // Create and send the request
  var oXmlHttp = zXmlHttp.createRequest();
  oXmlHttp.open("get", "contact.php?email=" + email + "&subject=Daveestes.com%20Contact%20Form&message=" + message + "&captcha_code=" + code, true);
  oXmlHttp.onreadystatechange = function () {
    if (oXmlHttp.readyState == 4) {
      if (oXmlHttp.status == 200) {
	if (oXmlHttp.responseText.search(/\|success\|/) != -1) {
	  alert("Message sent successfully.");
	} else if (oXmlHttp.responseText.search(/\|captcha\|/) != -1) {
	  alert("Security Code didn't match. You can hear the code by pressing the Speaker icon.");
	}
	document.getElementById('captcha').src = 'securimage/securimage_show.php?' + Math.random();
	document.getElementById('captcha_code').value = '';
      } else {
	alert("An error occurred: " + oXmlHttp.statusText); //statusText is not always accurate
      }
    }
  };
  oXmlHttp.send(null);
}

You’ll note that I’m using zxml.js, an AJAX library that handles some of the differences between IE and the rest of the world. When I call oXmlHttp.open(), I don’t use gdform.php though. For security reasons, I renamed gdform.php to something obscure and then created contact.php. Here’s my contact.php:

<?php
include_once 'securimage/securimage.php';

$securimage = new Securimage();

print("|".$_GET['captcha_code']."|");

if ($securimage->check($_GET['captcha_code']) == false) {
  print("|captcha|");
 } else {

  $request_method = $_SERVER["REQUEST_METHOD"];
  if($request_method == "GET"){
    $query_vars = $_GET;
  } elseif ($request_method == "POST"){
    $query_vars = $_POST;
  }
  reset($query_vars);
  $t = date("U");
  
  $file = $_SERVER['DOCUMENT_ROOT'] . "/../data/gdform_" . $t;
  $fp = fopen($file,"w");
  while (list ($key, $val) = each ($query_vars)) {
    fputs($fp,"<GDFORM_VARIABLE NAME=$key START>\n");
    fputs($fp,"$val\n");
    fputs($fp,"<GDFORM_VARIABLE NAME=$key END>\n");
  }
  fclose($fp);

  print("|success|");
 }
?>

I used the captcha check to protect the access to the gdform code that I cut and pasted. I also removed the redirect support from that code and opted instead to send by |success| or |captcha| since this is going to be parsed by my Javascript. The last piece to mention is the actual form. It’s pretty straight forward. Even though the form has an action, the onsubmit property returns false and so the actual for action never happens.

<h3>Contact</h3>

<form id="contact_form" class="contactform" action="contact.php" method="get" onsubmit="handleContactForm(); return false" >

<p>
Your Email<br/>
<input id="email" name="id" class="textbox" type="text" /><br/>
Message<br/>
<textarea id="message" name="message" class="textarea" rows="5"></textarea>
Security Code<br/>
<img id="captcha" src="securimage/securimage_show.php" alt="CAPTCHA Image" /><br/>
<input id="captcha_code" name="captcha_code" class="textbox" type="text" size="10" maxlength="6" /><br/>
<a href="#" onclick="updateCaptchaImage(); return false">
<img src="securimage/images/reload.gif" alt="Reload" align="top" border="0" onclick="this.blur()" />
</a>
<a href="securimage/securimage_play.php">
<img src="securimage/images/sound.gif" alt="Listen" onclick="this.blur()" />
</a>
<input class="button" value="Send" type="submit" />
</p>
</form>