Wednesday, 3 October 2012

Using reCAPTCHA from C++

I wanted to add reCAPTCHA to some old C++ CGI code, but I couldn't find a reCAPTCHA library for C++. Neither could a few other people I saw looking. So I wrote the following code, which I am happy to share:
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <curl/curl.h>

static ostringstream curl_result;

/* curl write callback, to fill tidy's input buffer...  */
uint write_cb(char *in, uint size, uint nmemb, void *out)
{
  int r = size * nmemb;
  in[r] = '\0';
  curl_result << in;
  return(r);
}

static bool recaptcha_check_answer(const char *private_key, const char *remoteip, 
 const char *challenge, const char *response, std::string &error) {
 if (!remoteip || !*remoteip) {
  error = "For security reasons, you must pass the remote ip to reCAPTCHA";
  return false;
 }
 //discard spam submissions
 if (!challenge || !*challenge || !response || !*response) {
  error = "Captcha challenge or response missing";
  return false;
 }

 CURL *curl;
 CURLcode res;
 struct curl_httppost *formpost=NULL;
 struct curl_httppost *lastptr=NULL;

 if(curl_global_init(CURL_GLOBAL_SSL)) {
  error = "Error initialising curl library";
  return false;
 }
 curl = curl_easy_init();
 if(!curl) {
  error = "Error initialising curl library";
  return false;
 }
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "privatekey",
  CURLFORM_COPYCONTENTS, private_key,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "remoteip",
  CURLFORM_COPYCONTENTS, remoteip,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "challenge",
  CURLFORM_COPYCONTENTS, challenge,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "response",
  CURLFORM_COPYCONTENTS, response,
  CURLFORM_END);

 const char *surl = "https://api-verify.recaptcha.net/verify";
 curl_easy_setopt(curl, CURLOPT_URL, surl);
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
 #ifdef SKIP_HOSTNAME_VERFICATION
 /*
  * If the site you're connecting to uses a different host name that what
  * they have mentioned in their server certificate's commonName (or
  * subjectAltName) fields, libcurl will refuse to connect. You can skip
  * this check, but this will make the connection less secure.
  */
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
 #endif
 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);

 res = curl_easy_perform(curl);

 /* always cleanup */
 curl_easy_cleanup(curl);
 if(res && res != CURLE_RECV_ERROR)
  {
  std::ostringstream err;
  err << "Error " << res << " contacting captcha server";
  error = err.str();
  return false;
  }

 std::string resp = curl_result.str();
 std::string::size_type pos = resp.find("\n");
 std::string part1 = pos == std::string::npos ? resp : resp.substr(0, pos);
 if(part1 == "true")
  return true;
 error = pos == std::string::npos ? "Unknown captcha error" : "Captcha error:" + resp.substr(pos);
 return false;
}
This code relies on the curl library to handle the communication with the reCAPTCHA server.