captcha.cc
/*
C++ Arbeitsgemeinschaft AG
Captcha.cc
C++ Implementation eines CAPTCHA unter Verwendung der GD-Library
(c) 2009 Gottfried Rudorfer
rudorfer@a1.net
*/
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
using namespace std;
#include "ReadParse.h"
extern "C" {
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/file.h> // für flock()
#include <errno.h> // für perror()
#include "gd.h" // LIBGB Grafikheader
#include "gdfonts.h"
#include "gdfontl.h"
#include "time.h"
#include "math.h" // M_PI
#include <sys/types.h> //opendir
#include <dirent.h> //opendir
}
#define X 200 // Größe der Grafik in Pixel
#define Y 150
// Web-Page-Strings
static char h_captcha_id[] = "captcha_id";
static char h_captcha_answer[] = "captcha_antwort";
static char h_url[] = "http://rudorfer.homedns.org/cgi-bin/captcha.cgi";
// Verzeichnisse und Dateien
static char captchafile[] = "/tmp/captcha.txt";
static char fontdir[] = "/usr/local/share/ttf"; /* User supplied font dir */
// Erzeuege einen ausgefülltes 2D-Objekt mit p Eckpunkten
void gdImageFilledCircle(gdImagePtr im, int x, int y, int r, int color, int p) {
gdPoint *points;
points = (gdPoint*) malloc(sizeof(gdPoint) * p);
if (!points)
return;
for (int c=0; c < p; c++) {
points[c].x = (int) (x + r * sin(2 * M_PI * c / p));
points[c].y = (int) (y + r * cos(2 * M_PI * c / p));
}
gdImageFilledPolygon(im, points, p, color);
free(points);
}
int min(int a, int b) {
int r;
if (a < b)
r = a;
else
r = b;
return r;
}
int max(int a, int b) {
int r;
if (a > b)
r = a;
else
r = b;
return r;
}
// Erzeuge eine GIF-Datei und gebe sie auf STDOUT aus
void createImage(string secret)
{
gdImagePtr im,il;
FILE *out;
int num=2+rand()%4;
int maxr = min(X,Y)/2;
int numcol = max(num,3);
int col[2];
int p[5][3];
int star=rand()%5;
numcol = min(numcol,5);
// Grafik erzeugen
im=gdImageCreate(X,Y);
col[0]=gdImageColorAllocate(im,255,255,255);
col[1]=gdImageColorAllocate(im,0,0,0);
gdImageColorTransparent(im,col[0]);
// Erzeuge gefüllte Kreise
for(int c=0; c<num; c++) {
int x=1+rand()%X;
int y=1+rand()%Y;
int r=1+rand()%maxr;
int s=3+rand()%8;
int rc = gdImageColorAllocate(im,rand()%255,rand()%255,rand()%255);
gdImageFilledCircle(im,x,y,r,rc,s);
p[c][0]=x;
p[c][1]=y;
p[c][2]=r;
}
// Erzeuge zufällig erstellte Linien
for(int d=0; d<num; d++) {
if ( (p[star][2] < abs(p[star][0]-p[d][0])) && (p[star][2] < abs(p[star][1]-p[d][1])) ) {
int size = 1+rand()%4;
il=gdImageCreate(size,size);
gdImageColorAllocate(il,0,0,0);
gdImageSetBrush(im,il);
gdImageLine(im, p[star][0], p[star][1], p[d][0], p[d][1], gdBrushed);
}
}
// Erzeuge willkürlich verstreute Buchstaben
int bf[8];
int xp = X / secret.length();
double sz = 30.0;
char cZ[3];
char *err;
int x = (int)sz;
int y = (int)sz;
typedef vector<string> VectorOfStrings;
VectorOfStrings vTTF;
DIR *dh;
struct dirent *de;
dh = opendir(fontdir);
while(de = readdir(dh)) {
string file = de->d_name;
int loc = file.rfind(".ttf", file.length());
int locB = file.rfind(".TTF", file.length());
if ((loc != string::npos) || (locB != string::npos)) {
vTTF.push_back(file); // Erzeuge einen Vektor von Truetype-Fontnamen, die sich im Verezeichnis "fontdir" befinden
}
}
closedir(dh);
for (int ci=0; ci<secret.length(); ci++) {
cZ[0] = secret[ci];
cZ[1] = '.';
cZ[2] = '\n';
double an = M_PI * rand()/RAND_MAX;
int indexFont = rand()%vTTF.size();
string pathFont = fontdir;
pathFont += "/" + vTTF[indexFont];
x += (int)sz/1.5;
y += (int)sz/3;
int Fcolor = gdImageColorResolve(im, rand()%255,rand()%255,rand()%255);
err = gdImageStringFT(im,&bf[0],Fcolor,(char*)pathFont.c_str(),sz,an,x,y,cZ); // Erzeuge zufällig plazierten Buchstaben
if (err) {
fprintf(stderr,err);
}
}
out = stdout;
printf("Content-type: image/gif\n\n"); // zuerst HTML-Header für GIF-Grafik ausgeben
gdImageGif(im,out); // Grafik ausgeben
fclose(out);
}
// Ausgabe der Eingabemaske
void print_userform(string id, int len)
{
cout << "<img border=0 src=\"" << h_url << "?mode=gif&" << h_captcha_id << "=" << id << "\"><BR>" << endl
<< "Bitte geben Sie die im obigen Bild gezeigten " << len << " Buchstaben und Ziffern ein! <BR>" << endl
<< "Achtung: Die Buchstaben sind wild durcheinander gewürfelt. Als Hilfe befindet sich hinter jedem Buchstaben ein \".\"<BR>" << endl
<< "Achten Sie auf die Groß- und Kleinschreibung!<BR>" << endl
<< "<FORM METHOD=\"POST\" ACTION=\"" << h_url << "\">" << '\n'
<< "<INPUT type=hidden name=\"" << h_captcha_id << "\" VALUE=\"" << id << "\">" << '\n'
<< "<INPUT type=text name=\"" << h_captcha_answer << "\">" << '\n'
<< "<INPUT type=submit NAME=\"Validierungsantwort\" VALUE=\"Antwort\">" << '\n'
<< "</FORM>" << '\n'
<< "<A HREF=\"" << h_url << "\">Neues Bild erzeugen lassen...</A><BR>" << endl
<< "<A HREF=\"http://rudorfer.homedns.org/cppag\">Zurück zur Homepage der C++ AG</A>" << endl;
}
// Erzeuge Captcha-ID und Captcha-Geheim-ID und lege sie in der Datei captchafile ab.
string gen_captcha_id(int len) {
string letters = "abcdefghjkmnpqrstxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Lasse i1IloO0 wegen vertauschungsgefahr weg
string id = "";
string secret = "";
char letter;
ofstream ofile;
int fd;
int l = letters.length();
for (int i = 0; i<10; i++) {
id += letters[random() %l];
}
int i = 0;
while (i < len) {
letter = letters[random() %l];
int found = 0;
for (int j = 0; j < secret.length(); j++) { // Wenn das Zeichen nochmals zufällig gewhählt wird, dann soll dieses ignoriert werden
if (letter == secret[j]) {
found = 1;
break;
}
}
if (!found) {
secret += letter;
i++;
}
}
// fFile-locking ist bei CGI-Programmen immer notwendig.
// Wir verwenden eine eigene Lock-Datei
string lockfile = (string)captchafile + ".lck";
if (-1 == (fd = open(lockfile.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR))) {
perror(lockfile.c_str());
exit(1);
}
// Beim Lesen schreiben: Exklusiver lock
if ((flock(fd, LOCK_EX)) == -1) {
perror(lockfile.c_str());
exit(1);
}
ofile.open(captchafile, ios::out | ios::app);
if (ofile) {
ofile << id << " " << secret << endl;
}
ofile.close();
if ((flock(fd, LOCK_UN)) == -1) {
perror(lockfile.c_str());
exit(1);
}
close(fd);
return id;
}
int main(int argc, char **argv)
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
ifstream ifile;
string id="", secret="";
int fd;
alarm(10); // maximale Zeit, die dieser Prozess benötigen darf
srand(time(NULL));
ReadParse mycgi;
if (!(mycgi.in.find("mode") == mycgi.in.end()) && (mycgi.in[h_captcha_id] != "") ) { // captcha Bild ausgeben für die angegebene h_captcha_id ausgeben
// Mittels h_captcha_id die Captcha-Secret-ID aus der Datei "captchafile" auslesen
// File-locking ist bei CGI-Programmen immer notwendig.
// Wir verwenden eine eigene Lock-Datei
string lockfile = (string)captchafile + ".lck";
if (-1 == (fd = open(lockfile.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR))) {
perror(lockfile.c_str());
exit(1);
}
// Beim Lesen schreiben: Exklusiver lock
if ((flock(fd, LOCK_SH)) == -1) {
perror(lockfile.c_str());
exit(1);
}
ifile.open(captchafile, ios::in);
while(ifile) {
string s;
ifile >> id;
ifile >> s;
if (id == mycgi.in[h_captcha_id]) {
secret = s;
break;
}
}
ifile.close();
if ((flock(fd, LOCK_UN)) == -1) {
perror(lockfile.c_str());
exit(1);
}
close(fd);
// Erzeugen des GIF-Image
createImage(secret);
} else if ((mycgi.in.find(h_captcha_id) == mycgi.in.end()) || (mycgi.in[h_captcha_id] == "")) { // Startseite ausgeben
mycgi.PrintHeader();
int numChars = 4+rand()%3;
string captcha_id = gen_captcha_id(numChars);
print_userform(captcha_id, numChars); // Zuerst muß die ID ausgegeben werden.
} else if ((mycgi.in[h_captcha_id] != "") && (mycgi.in[h_captcha_answer] != "")) { // Benutzereingabe auswerten
int fd;
mycgi.PrintHeader();
// Mittels h_captcha_id die Captcha-Secret-ID aus der Datei "captchafile" auslesen
// File-locking ist bei CGI-Programmen immer notwendig.
// Wir verwenden eine eigene Lock-Datei
string lockfile = (string)captchafile + ".lck";
if (-1 == (fd = open(lockfile.c_str(), O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR))) {
perror(lockfile.c_str());
exit(1);
}
// Beim Lesen schreiben: Exklusiver lock
if ((flock(fd, LOCK_SH)) == -1) {
perror(lockfile.c_str());
exit(1);
}
ifile.open(captchafile, ios::in);
while(ifile) {
string s;
ifile >> id;
ifile >> s;
if (id == mycgi.in[h_captcha_id]) {
secret = s;
break;
}
}
ifile.close();
if ((flock(fd, LOCK_UN)) == -1) {
perror(lockfile.c_str());
exit(1);
}
close(fd);
// Ergebnis ausgeben
cout << "Entered secret: \"" << mycgi.in[h_captcha_answer] << "\" <p>" << endl;
cout << "Actual secret: \"" << secret << "\" <p>" << endl;
// Vergleich des vom Benutzer eingegebenen String "mycgi.in[h_captcha_answer]" mit
// "secret", wobei wir die Reihenfolge der Buchstaben ignorieren wollen
// Wir ignorieren "."-Eingaben durch den Benutzer
int checkedAnswer = 0;
int answerNotOk = 0;
string g_answer = mycgi.in[h_captcha_answer];
for (int i=0; i<g_answer.length(); i++) {
char answer_char = g_answer[i];
int foundLetter = 0;
for (int j=0; j<secret.length(); j++) {
if (answer_char == secret[j]) {
checkedAnswer++;
foundLetter = 1;
break;
}
}
// wenn keine Übereinstimmung und kein ".", war die Antwort nicht richtig
if (!foundLetter) {
if (answer_char != '.') {
answerNotOk++; // Anzahl der Zeichen, die der Benutzer falsch eingegeben hat. Wir ignorieren den "."
}
}
}
// für eine richtige Antwort muss ebenso die Anzahl der eingegebenen Zeichen der Länge der Captcha-Secret-ID entsprechen
if ((checkedAnswer == secret.length()) && (!answerNotOk)) {
cout << "Okay <p>" << endl;
} else {
cout << "Not okay <p>" << endl;
}
} else {
mycgi.PrintHeader();
cout << "Error" << endl;
}
return 0;
}
Makefile
# defines
CC=g++
CXX=g++
CXXFLAGS=-g -Igd-2.0.33
CFLAGS=-g -Igd-2.0.33
#LDFLAGS=-static -s
LDFLAGS=-s
LIBS=-Lgd-2.0.33/.libs -lm -lgd -lfreetype -lpthread
BIN=captcha.cgi
BINDIR=/var/html/cgi-bin
# Programs
SRC= captcha.cc ReadParse.cc
OBJS= captcha.o ReadParse.o
all: ${BIN}
clean:
-@rm *.o *.dat *~ 2> /dev/null
@echo "The filesystem thanks you for your kindness"
copy:
-mkdir ${HOME}/pub
cp * ${HOME}/pub
echo "done"
# dependencies
# linking
${BIN}: ${OBJS} ${SRC}
${CC} ${LDFLAGS} ${OBJS} -o ${BIN} ${LIBS}
install: ${BIN}
@if [ ! -d ${BINDIR} ]; \
then \
echo "Erzeuge Verzeichnis ${BINDIR}..."; \
mkdir -p ${BINDIR}; \
fi; \
strip ${BIN} && \
cp -p ${BIN} ${BINDIR}/${BIN} && \
chmod 4711 ${BINDIR}/${BIN} && \
echo "Die Programm-Datei wurde in das Verzeichnis ${BINDIR} kopiert.";
ReadParse.h
//******************************************************************************
// ReadParse.h
// Copyright 1999 by Gottfried Rudorfer
//
// E-Mail: Gottfried.Rudorfer@ai.wu-wien.ac.at
//
// All rights reserved.
//******************************************************************************
// Documentation: ***************************************************************
// VARIABLES:
// in .... map of key/value pairs
//******************************************************************************
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <new>
using namespace std;
extern "C"
{
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
}
// characters that we accept (according to CERT)
const char _ok_chars[] = "abcdefghijklmnopqrstuvwxyz\
ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-+.@%=&";
typedef map<string,string> StringStringMap;
void CgiNoSpace();
class ReadParse
{
public:
ReadParse();
~ReadParse();
void PrintHeader();
string EvalString(const string);
string EvalFile(const string);
void Mygetenv(char*);
StringStringMap in;
int strnicmp(const char *s1, const char *s2, const int count);
int HexToChar(char *strptr, char &thechar);
private:
bool header;
void Parse();
char *_html_string;
int _has_parsed;
string Line(const string& text, const string::size_type pos);
char *_env_string;
char *CertFix(char*);
};
ReadParse.cc
/*****************************************************************************
// Simple ReadParse.cc for CGI-Programming.
// Distribute freely for non-commercial use.
//
// E-Mail: Gottfried.Rudorfer@wu-wien.ac.at
//
// Bugfixes:
// 01/98 integrated CERT security fix.
//
// (c) 1999 by Gottfried Rudorfer
// All rights reserved.
//*****************************************************************************
// Documentation: *************************************************************
// 1. Evaluates the strings passed by the GET and POST method. Uses a STL-Map.
// An Example: This code expands Key1=Value1&Key3&Key2=Value2 pairs or singles
// into the map "in".
/*
#include <iostream>
using namespace std;
#include "ReadParse.h"
int main()
{
ReadParse mycgi;
mycgi.PrintHeader();
map<string,string>::iterator pos;
for(pos=mycgi.in.begin(); pos != mycgi.in.end(); ++pos) {
cout << "Name: <B>" << pos->first << "</B>,"
<< "Value: " << pos->second << "<BR>" << endl;
cout << "<HR>\n";
}
}
*/
// 2. The method EvalFile(string) opens the file named in string
// and parses the contents of that file for $vars. If such a string is
// found, it is replaced with the value behind the key stored in the map "in".
// You may have more than one variable substiution i.e.:
// <INPUT type=hidden name="$h_user " value="$$h_user ">
//*****************************************************************************
#include"ReadParse.h"
ReadParse::ReadParse()
{
_env_string = _html_string = NULL;
_has_parsed = 0;
Parse();
header = false;
}
ReadParse::~ReadParse()
{
if (_html_string)
delete _html_string;
if (_env_string)
delete _env_string;
}
void ReadParse::PrintHeader()
{
if (!header) {
header = true;
cout << "Content-type: text/html\n" << endl;
}
}
int ReadParse::strnicmp(const char *s1, const char *s2, const int count) {
char a='\0', b='\0';
for(int n=0; n<count; n++)
{
a = toupper(*s1++);
b = toupper(*s2++);
if ((a=='\0')||(b=='\0')||(a!=b))
break;
}
return(a - b);
}
char *ReadParse::CertFix(char *s)
{
// security fix see ftp.cert.org
for (char *cp = s; *(cp += strspn(cp, _ok_chars)); /* */)
*cp = '_';
return s;
}
void ReadParse::Mygetenv(char *s)
{
char *strptr;
if (_env_string) {
delete _env_string;
_env_string = NULL;
}
if ((strptr=getenv(s)))
{
_env_string = new char [strlen(strptr)+1];
strcpy(_env_string, strptr);
}
}
//*****************************************************************************
void CgiNoSpace()
{
cerr << "Not enough free memory; "
<< "new failed" <<endl;
exit(1);
}
int ReadParse::HexToChar(char *strptr, char &thechar)
{
char value=0, c, w[]={1, 16};
for(int j=1;j>=0;j--)
{
c=*++strptr; //skip %
if ((c >= '0') && (c <= '9'))
{
value+=w[j]*(c-'0');
}
else if ((c >= 'a') && (c <= 'f'))
{
value+=w[j]*(10+(c-'a'));
}
else if ((c >= 'A') && (c <= 'F'))
{
value+=w[j]*(10+c-'A');
}
else
return 0;
}
thechar=value;
return 1;
}
void ReadParse::Parse()
{
int html_length;
set_new_handler(CgiNoSpace);
char *sg = NULL, *sp = NULL;
if(_has_parsed)
{
cerr << "Don't Call ReadParse twice !!!!\n";
return;
}
_has_parsed=1;
Mygetenv("QUERY_STRING");
if (_env_string) {
int l = strlen(_env_string);
sg=new char[l+2];
strcpy(sg,_env_string);
}
Mygetenv("CONTENT_LENGTH");
if (_env_string) {
int l = atoi(_env_string);
if (l > 0) {
sp=new char[l+2];
cin.get(sp,l+1);
}
}
if(!sg && !sp)
{
cerr << "ReadParse: Hey boy ... we understand METHOD GET & POST\n";
cerr << "ReadParse: Define at least the environment variable QUERY_STRING\n";
exit(1);
}
html_length = 0;
if (sg)
html_length += strlen(sg);
if (sp)
html_length += strlen(sp);
if(sg && sp)
html_length++; // '&'
_html_string = new char [html_length+2];
_html_string[0] = '\0';
if (sg) {
strcat(_html_string, sg);
delete sg;
}
if (sp) {
if (_html_string[0] != '\0')
strcat(_html_string, "&");
strcat(_html_string, sp);
delete sp;
}
int rows=html_length+1, cols=2, html_bufcount[] = {0,0}, readparse_flag=0;
char *ptr, *html_arr=new char[rows*cols], c;
html_length=strlen(_html_string);
ptr=_html_string;
while (true)
{
switch(*ptr)
{
case '=':
readparse_flag=1;
break;
case '&':
case '\0':
*(html_arr+html_bufcount[0])='\0';
*(html_arr+rows+html_bufcount[1])='\0';
in[(string)CertFix(html_arr)] = (string)CertFix(html_arr+rows);
if (*ptr == '\0')
return;
readparse_flag=0;
html_bufcount[0] = 0;
html_bufcount[1] = 0;
break;
case '%':
if (strlen(ptr) >=3)
{
if (HexToChar(ptr, c))
{
*(html_arr+readparse_flag*rows+html_bufcount[readparse_flag]++)=c;
ptr+=2;
break;
}
}
case '+':
*ptr=' ';
default:
*(html_arr+readparse_flag*rows+html_bufcount[readparse_flag]++) = *ptr;
break;
}
ptr++;
}
delete html_arr;
}
string ReadParse::Line(const string& text, const string::size_type pos)
{
string::size_type nl_pos, cpos = 0;
unsigned int line=1;
char buf[10];
while((cpos < pos) && (cpos != string::npos)) {
nl_pos = text.find_first_of("\n", cpos);
if (nl_pos == string::npos)
break;
if (nl_pos < pos) {
line++;
cpos = nl_pos + 1;
} else
break;
}
sprintf(buf, "%d", line);
return string(buf);
}
string ReadParse::EvalString(const string text)
{
string::size_type dollar, space;
const string trennzeichen(" \t\n");
string neu="";
unsigned int numsub;
for (string::size_type cpos=0; cpos != string::npos; ) {
dollar = text.find_first_of("$", cpos);
numsub = 1;
if (dollar != string::npos) {
neu += text.substr(cpos, dollar-cpos);
space=text.find_first_of(trennzeichen,dollar);
while(text.at(dollar+numsub) == '$')
numsub++;
string name=text.substr(dollar+numsub,space-dollar-numsub);
if (space != string::npos)
cpos = space + 1;
else
cpos = space;
string subs="";
bool ok = false;
for(unsigned int j = 0; j < numsub; j++) {
subs += name + " -> ";
if ((in.find(name) != in.end()) && (name.length() > 0)) {
name = in[name];
ok = true;
} else {
neu += " Fehler: Variable \'" + name
+ "\' wurde nicht in der Zeile " + Line(text, dollar)
+ " gefunden! Die Ableitung ist: " + subs + ".";
ok = false;
break;
}
}
if (ok)
neu += name;
} else {
neu += text.substr(cpos, dollar-cpos);
cpos = dollar;
}
}
return neu;
}
string ReadParse::EvalFile(const string file)
{
ifstream in;
string r="", s;
in.open(file.c_str(), ios::in);
if (!in) {
cout << "Fehler beim Lesen der Datei \"" << file << "\"" << endl;
} else {
while(getline(in, s))
r += s + '\n';
in.close();
}
return EvalString(r);
}