# Blosxom Plugin: wbcaptcha
# Author(s): Pasi Savolainen
# Version: 2005-08-26
# Documentation: See the bottom of this file or type: perldoc wbcaptcha
# Requirements: Obviously writebacks -plugin (some relatives of it are
# compatible too)
# figlet -ASCII text image -generating program,
# available from . Many unix
# installations have this installed or available as package.
#
# If you need CAPTCHA plugin in more general form, such IS
# available. I just can't warrant having two modules doing this work.
package wbcaptcha;
# --- Configurable variables -----
# -- wbcaptcha part --
# -- blcaptcha part --
# -k : By default don't mash characters together (easier reading)
# -w 80 : Default 4 characters aren't likely to be over 80 characters
# in image width
$figprog = 'figlet -w 80 -k';
# Fonts used when 'random font' is requested.
@fonts = ('standard');
# Please ensure that font you use contains all the characters from
# @random_chars below. Some figlet fonts lack numbers, etc.
# @fonts = ('standard', 'sans', 'big', 'banner', 'contrast');
# how often to run cleanup. It's _average_. 1 will get you cleaning on
# every pageload, 50 once in 50 pageloads.
$cleanup = 100;
# (In minutes) How long will captcha be kept alive.
$lifetime = 60;
# where to keep captcha data
$statedir = $blosxom::plugin_state_dir . '/wbcaptcha';
# this is reasonable default.
# 0(zero) is not in character set to avoid confusion with O
# 1(one) -"- I
@random_chars = ('a'..'z','A'..'Z','2'..'9');
# Location of sendmail -executable. If default doesn't work, try to find the
# right location and replace this variable with it.
# This may help you find something suitable:
# for i in locate sendmail; do if [ -x $i ]; then echo $i; fi; done;
# Define it as "no" if you don't want email to be sent.
my $sendmail = "no";
# my $sendmail = "/usr/lib/sendmail";
my $address = 'your.address@example.com.invalid';
# · Variables below don't need tuning.
# ----------------------------------
$debug = 0;
# 'image', best put into
$wbcaptcha::image
$image = "image not created";
# challenge id. usable when loading with ?wbcaptcha=gen (or post-parameter)
$captcha_id = "no";
# has this round failed?
$captcha_fail = "";
# -- blcaptcha part
# where we store generated captchas
my $time = time();
# --------------------------------
use CGI qw/:standard/;
sub start {
# startup checks
# check existance and permissions
if ( -e $statedir and (!-d $statedir or !-w $statedir )) {
warn "blosxom: wbcaptcha \$statedir lacks correct rights!\n";
return 0;
}
if ( !-e $statedir ) {
my $mkdir_r = mkdir("$statedir", 0777);
warn "blosxom: wbcaptcha > created statedir $statedir\n";
}
# cleanup our state dir.
if (0 == int(rand $cleanup)) {
@files = glob($statedir . "/*");
for $file (@files) {
$fullname = $file;
$_ = $file;
m,/([^/]*)$,;
m,^(\d+),;
$file = $1;
$clifetime = 0+$file + $lifetime*60;
if ($time > $clifetime) {
warn "blosxom: wbcaptcha > removing $fullname $time $clifetime $file\n" if $debug;
unlink $fullname or
warn "blosxom: wbcaptcha > clean-removing $fullname failed: $!\n";
}
}
}
# Only spring into action if POSTing to the writeback plug-in
# BECAUSE this is changing state and we don't want stupid bots
# DDoS:ing our server.
if ( request_method() ne 'POST' ) {
return 1;
}
if (param('wbcaptcha') eq 'gen') {
my ($fname, $output) = &gen_in_file(4, 'random');
$image = $output;
$captcha_id = $fname;
}
if (param('plugin') eq 'writeback') {
$fid = param('captcha_id');
$entered_text = param('captcha_in');
if ( ! &check_from_file($fid, $entered_text) ) {
# mismatch. start cleaning variables.
warn "wbcaptcha > failed match $fid $entered_text\n" if $debug;
$captcha_fail = "You entered text '$entered_text', that didn't" .
"match presented image. Retry by hitting 'back' and " .
"writing bot-test text again. Thank you.";
CGI::delete('plugin');
if ( $sendmail ne 'no') {
send_notification($fid, $entered_text);
}
# don't kill captcha, if it was a typo, client may
# retry.
} else {
# check was ok, so kill this captcha.
warn "wbcaptcha > success, removing $statedir/$fid\n" if $debug;
unlink "$statedir/$fid" or
warn "wbcaptcha > wb-removing $statedir/$fid failed: $!\n";
}
}
1;
}
# Send notification with some information about attempted post.
sub send_notification {
my ($fid, $ctext) = @_;
if (open(MAIL, "| $sendmail -ti")) {
$name = param('name');
$url = param('url');
$title_raw = param('title');
$body = param('comment');
# try to stop some spam?
$title = substr($title_raw, 0, 25);
$title =~ s/[\n\r]//g;
print MAIL <<"_MAIL_";
From: Writeback captcha Notification
To: $address
Subject: Auto: [wbcaptcha] CAPTCHA entering failed
Content-Type: text/plain
X-Mailer: blosxom wbcaptcha plugin
Reply-To: do.not@reply.com.invalid
Auto-Submitted: auto-replied
Your blog was about to receive a comment, but then CAPTCHA entering
failed.
Given file id was $fid and entered text was ($ctext), sans parenthesis.
It all happened in this address:
$blosxom::url/$blosxom::path_info:
More data.
Name: $name
URL: $url
Title: $title
Body:
$body
--
wbcaptcha plugin for blosxom
_MAIL_
close(MAIL);
} else {
warn "blsxom : wbcaptcha > unable to open sendmail\n";
}
}
# check entered text against text saved in file_id.
# return boolean of test
sub check_from_file {
my ($fid, $ctext) = @_;
# check for bad characters, filename has only [a-zA-Z0-9-]
return 0 if ( $fid =~ /[^a-zA-Z0-9-]|^$/ );
open (CFILE, "<$statedir/$fid") or return 0; # doesn't exist, so fail.
my $etext = ;
chomp($etext);
close CFILE;
warn "wbcaptcha > cff 1$fid 2$etext 3$ctext\n" if $debug;
return lc($etext) eq lc($ctext);
}
# save supplied text in a file.
# return filename (which will work as captcha id/file_id)
sub save_in_file {
my ($ctext) = @_;
my $randstr = &generate_random_string(4);
my $fname = "${time}-${randstr}";
open (CFILE, ">$statedir/$fname") or return 0; # failed, so fail
print CFILE "${ctext}\n";
warn "wbcaptcha > Saving $ctext in $fname\n" if $debug;
close CFILE;
return $fname;
}
# generate captcha using supplied length and fontname.
# default to 4 characters and 'standard' fontname. fontname 'random'
# will use supplied list of random fontnames.
# returns string to be entered and same string captcha'd
sub generate {
my ($len, $font) = @_;
$len ||= 4;
$font ||= 'standard';
$font = $fonts[rand @fonts] if $font == 'random';
my $text = &generate_random_string( $len );
$command = $figprog . " -f \"$font\" -- \"$text\"";
# generate the CAPTCHA 'image'
$output = `$command`;
# print $output;
return ($text, $output);
}
# in: same as generate()
# returns file_id and generated captcha
sub gen_in_file {
my ($len, $font) = @_;
my ($text, $output) = &generate($len, $font);
my $fname = &save_in_file($text);
# return generated figlet and filename-id to check against.
return ($fname, $output);
}
###############
# found off the web.
sub generate_random_string {
my $length_of_randomstring=shift; # the length of
# the random string to generate
my @chars = @random_chars;
my $random_string;
foreach (1..$length_of_randomstring)
{
# rand @chars will generate a random
# number between 0 and scalar @chars
$random_string.=$chars[rand @chars];
}
return $random_string;
}
1;
__END__
=head1 NAME
Blosxom Plug-in: wbcaptcha
=head1 SYNOPSIS
Provides CAPTCHA checking for writebacks (and some related) plugins.
CAPTCHA checking is done with ASCII text generated by figlet
.
=head2 QUICK START
Drop this plug-in file into your plug-ins directory
(whatever you set as C<$plugin_dir> in blosxom.cgi).
Name it so that it will be loaded by blosxom before writebacks
plugin. For example if writebacks is called F<1writebacks>, name
F as F<05wbcaptcha>.
Modify your F so that it will load itself via POST method,
with parameter C set to gen. F.ex:
Modify your F so that in writeback form it has variable
C with value C<$wbcaptcha::captcha_id> . F.ex:
Modify your F so that in writeback form it has variable
C, in which user can type in text he sees. Example:
You can show generated image with:
$wbcaptcha::image
C<$wbcaptcha::captcha_id> has value of "no" when no captcha is generated,
you can use it with F to hide forms when input isn't
possible.
=back
=head2 VARIABLES
C<$wbcaptcha::image> Contains ASCII image of generated text.
C<$wbcaptcha::captcha_id> Contains captcha identifier by which user-typed
text will be identified.
C<$wbcaptcha::captcha_fail> Contains verbose description of captcha
operation status. (Mostly says 'Inputted text mismatch' when user
mistypes captcha text)
=head2 SAMPLE story.html
This uses F. It's much simplified for easy
comprehending.
- -
?>
?>
- -
=end text
=back
=head1 INSTALLATION
Drop wbcaptcha into your plug-ins directory (C<$blosxom::plugin_dir>).
Name it so that it will be loaded by blosxom before writebacks
plugin. For example if writebacks is called F<1writebacks>, name
F as F<05wbcaptcha>.
=head1 CONFIGURATION
=head2 (REQUIRED) MODIFYING story.flavour
Do things described in QUICK START. You most likely will want to
operate on F (Or whichever story.flavour that holds you
writebacks)
=head2 (OPTIONAL) TUNE VARIABLES IN wbcaptcha
F contains several variable that can be tuned for your
site. Defaults are sane, so this isn't strictly required.
=head1 AUTHOR
Pasi Savolainen , http://iki.fi/psavo/
=head1 SEE ALSO
Blosxom Plugin Docs: http://www.raelity.org/apps/blosxom/plugin.shtml
=head1 BUGS
Address bug reports and comments to .
=head1 LICENSE
GNU General Public License v2.0,