1package Authen::Captcha; 2 3# $Source: /usr/local/cvs/Captcha/pm/Captcha.pm,v $ 4# $Revision: 1.23 $ 5# $Date: 2003/12/18 04:44:34 $ 6# $Author: jmiller $ 7# License: GNU General Public License Version 2 (see license.txt) 8 9use 5.00503; 10use strict; 11use GD; 12use String::Random qw(random_regex); 13use Carp; 14# these are used to find default images dir 15use File::Basename; 16use File::Spec; 17 18use vars qw($VERSION); 19 20$VERSION = '1.024'; 21 22# get our file name, used to find the default images 23my $default_images_folder; 24{ 25 my $this_file = __FILE__; 26 my $this_dir = dirname($this_file); 27 my @this_dirs = File::Spec->splitdir( $this_dir ); 28 $default_images_folder = File::Spec->catdir(@this_dirs,'Captcha','images'); 29} 30 31my $num_of_soundfile_versions = 10; 32 33# Preloaded methods go here. 34 35sub new 36{ 37 my ($this) = shift; 38 my $class = ref($this) || $this; 39 my $self = {}; 40 bless( $self, $class ); 41 42 my %opts = @_; 43 44 # default character source images 45 my $type = defined($opts{type}) ? $opts{type} : 'image'; 46 $self->type($type); 47 my $src_images = (defined($opts{images_folder}) && (-d $opts{images_folder})) 48 ? $opts{images_folder} : $default_images_folder; 49 $self->images_folder($src_images); 50 51 my $debug = (defined($opts{debug}) && ($opts{debug} =~ /^\d+$/)) 52 ? $opts{debug} : 0; 53 $self->debug($debug); 54 $self->data_folder($opts{data_folder}) if($opts{data_folder}); 55 $self->output_folder($opts{output_folder}) if($opts{output_folder}); 56 my $expire = (defined($opts{expire}) && ($opts{expire} =~ /^\d+$/)) 57 ? $opts{expire} : 300; 58 $self->expire($expire); 59 my $width = (defined($opts{width}) && ($opts{width} =~ /^\d+$/)) 60 ? $opts{width} : 25; 61 $self->width($width); 62 my $height = (defined($opts{height}) && ($opts{height} =~ /^\d+$/)) 63 ? $opts{height} : 35; 64 $self->height($height); 65 my $keep_failures = (defined($opts{keep_failures}) && $opts{keep_failures}) 66 ? 1 : 0; 67 $self->keep_failures($keep_failures); 68 69 # create a random seed if perl version less than 5.004 70 if ($] < 5.005) 71 { # have to seed rand. using a fairly good seed 72 srand( time() ^ ($$ + ($$ << 15)) ); 73 } # else, we're just going to let perl do it's thing 74 75 return $self; 76} 77 78sub type 79{ 80 ref(my $self = shift) or croak "instance variable needed"; 81 if (@_) 82 { 83 if ($_[0] =~ /^(jpg|png|gif|image|picture)$/i) 84 { 85 $self->{_type} = 'image'; 86 } elsif ($_[0] =~ /^(sound|snd|wav|mp3)$/i) { 87 $self->{_type} = 'sound'; 88 } 89 return $self->{_type}; 90 } else { 91 return $self->{_type}; 92 } 93} 94 95sub debug 96{ 97 ref(my $self = shift) or croak "instance variable needed"; 98 if (@_) 99 { 100 $self->{_debug} = $_[0]; 101 return $self->{_debug}; 102 } else { 103 return $self->{_debug}; 104 } 105} 106 107sub keep_failures 108{ 109 ref(my $self = shift) or croak "instance variable needed"; 110 if (@_) 111 { 112 croak "keep_failures must be a zero or one" unless ($_[0] =~ /^[01]$/); 113 $self->{_keep_failures} = $_[0]; 114 return $self->{_keep_failures}; 115 } else { 116 return $self->{_keep_failures}; 117 } 118} 119 120sub expire 121{ 122 ref(my $self = shift) or croak "instance variable needed"; 123 if (@_) 124 { 125 croak "expire must be a possitive integer" unless ($_[0] =~ /^\d+$/); 126 $self->{_expire} = $_[0]; 127 return $self->{_expire}; 128 } else { 129 return $self->{_expire}; 130 } 131} 132 133sub width 134{ 135 ref(my $self = shift) or croak "instance variable needed"; 136 if (@_) 137 { 138 croak "width must be a possitive integer" unless ($_[0] =~ /^\d+$/); 139 $self->{_width} = $_[0]; 140 return $self->{_width}; 141 } else { 142 return $self->{_width}; 143 } 144} 145 146sub height 147{ 148 ref(my $self = shift) or croak "instance variable needed"; 149 if (@_) 150 { 151 croak "height must be a possitive integer" unless ($_[0] =~ /^\d+$/); 152 $self->{_height} = $_[0]; 153 return $self->{_height}; 154 } else { 155 return $self->{_height}; 156 } 157} 158 159sub output_folder 160{ 161 162 ref(my $self = shift) or croak "instance variable needed"; 163 if (@_) 164 { # it's a setter 165 $self->{_output_folder} = $_[0]; 166 return $self->{_output_folder}; 167 } else { 168 return $self->{_output_folder}; 169 } 170} 171 172sub images_folder 173{ 174 ref(my $self = shift) or croak "instance variable needed"; 175 if (@_) 176 { # it's a setter 177 $self->{_images_folder} = $_[0]; 178 return $self->{_images_folder}; 179 } else { 180 return $self->{_images_folder}; 181 } 182} 183 184sub data_folder 185{ 186 ref(my $self = shift) or croak "instance variable needed"; 187 if (@_) 188 { # it's a setter 189 $self->{_data_folder} = $_[0]; 190 return $self->{_data_folder}; 191 } else { 192 return $self->{_data_folder}; 193 } 194} 195 196 197sub check_code 198{ 199 ref(my $self = shift) or croak "instance variable needed"; 200 my ($code, $token) = @_; 201 202 $code = lc($code); 203 204 warn "$code $token\n" if($self->debug() >= 2); 205 206 my $current_time = time; 207 # solution was not found in database (well, yet :) 208 my $return_value = -2; 209 my $database_file = File::Spec->catfile($self->data_folder(),"codes.txt"); 210 211 # create database file if it doesn't already exist 212 $self->_touch_file($database_file); 213 214 # zeros (0) and ones (1) are not part of the code 215 # they could be confused with (o) and (l), so we swap them in 216 $code =~ tr/01/ol/; 217 218 # pull in current database 219 warn "Open File: $database_file\n" if($self->debug() >= 2); 220 $self->_get_exclusive_lock(); 221 open (DATA, "<$database_file") or die "Can't open File: $database_file\n"; 222 my @data=<DATA>; 223 close(DATA); 224 warn "Close File: $database_file\n" if($self->debug() >= 2); 225 226 my $passed=0; 227 # $new_data will hold the part of the database we want to keep and 228 # write back out 229 my $new_data = ""; 230 foreach my $line (@data) 231 { 232 $line =~ s/\n//; 233 my ($data_time,$data_token,$data_code) = $line =~ m/(^\d+)::([a-f0-9]{32})::(.*)$/ 234 or next; 235 236 my $png_file = File::Spec->catfile($self->output_folder(),$data_token . ".png"); 237 if ($data_token eq $token) 238 { 239 240 # the token was found in the database 241 if (($current_time - $data_time) > $self->expire()) 242 { 243 warn "Crypt Found But Expired\n" if($self->debug() >= 2); 244 # the token was found but has expired 245 $return_value = -1; 246 } else { 247 warn "Match Crypt in File Crypt: $token\n" if($self->debug() >= 2); 248 } 249 250 if ( ($data_code eq $code) && ($return_value != -1) ) 251 { 252 warn "Match: " . $data_token . " And " . $token . "\n" if($self->debug() >= 2); 253 # solution was correct and was found in database - passed 254 $return_value = 1; 255 } 256 257 if ( $return_value < ($self->keep_failures() ? -1 : -2) ) 258 { 259 warn "No Match: " . $data_token . " And " . $token . "\n" if($self->debug() >= 2); 260 # solution was wrong, not expired, and we're keeping failures 261 $new_data .= $line."\n"; 262 } else { 263 # remove the found token so it can't be used again 264 warn "Unlink File: " . $png_file . "\n" if($self->debug() >= 2); 265 unlink($png_file) or carp("Can't remove png file [$png_file]\n"); 266 } 267 268 if ( $return_value == -2 ) { 269 # incorrect solution 270 $return_value = -3; 271 } 272 273 } elsif (($current_time - $data_time) > $self->expire()) { 274 # removed expired token 275 warn "Removing Expired Crypt File: " . $png_file ."\n" if($self->debug() >= 2); 276 unlink($png_file) or carp("Can't remove png file [$png_file]\n"); 277 } else { 278 # token not found or expired, keep it 279 $new_data .= $line."\n"; 280 } 281 } 282 283 # update database 284 open(DATA,">$database_file") or die "Can't open File: $database_file\n"; 285 print DATA $new_data; 286 close(DATA); 287 $self->_release_lock(); 288 289 return $return_value; 290} 291 292 293sub _open_lock_file { 294 my $self = shift; 295 my $file_name = shift; 296 open(LOCK, ">>$file_name") or die "Error opening lockfile $file_name: $!\n"; 297} 298 299sub _get_shared_lock { 300 my $self = shift; 301 my $lock_file_name = File::Spec->catfile($self->data_folder(),"codes.lock"); 302 $self->_open_lock_file($lock_file_name); 303 304 # shared lock 305 flock(LOCK, 1) or die "Error locking lockfile in shared mode: $!\n"; 306} 307 308sub _get_exclusive_lock { 309 my $self = shift; 310 my $lock_file_name = File::Spec->catfile($self->data_folder(),"codes.lock"); 311 $self->_open_lock_file($lock_file_name); 312 313 # exclusive lock 314 flock(LOCK, 2) or die "Error locking lockfile exclusively: $!\n"; 315} 316 317sub _release_lock { 318 my $self = shift; 319 flock(LOCK, 8) or die "Error unlocking lockfile: $!\n"; 320 close(LOCK); 321} 322 323sub _touch_file 324{ 325 ref(my $self = shift) or croak "instance variable needed"; 326 my $file = shift; 327 # create database file if it doesn't already exist 328 if (! -e $file) 329 { 330 open (DATA, ">>$file") or die "Can't create File: $file\n"; 331 close(DATA); 332 } 333} 334 335sub generate_random_string 336{ 337 ref(my $self = shift) or croak "instance variable needed"; 338 my $length = shift; 339 340 # generate a new code 341 my $code = ""; 342 for(my $i=0; $i < $length; $i++) 343 { 344 my $char; 345 my $list = int(rand 4) +1; 346 if ($list == 1) 347 { # choose a number 1/4 of the time 348 $char = int(rand 7)+50; 349 } else { # choose a letter 3/4 of the time 350 $char = int(rand 25)+97; 351 } 352 $char = chr($char); 353 $code .= $char; 354 } 355 return $code; 356} 357 358sub _save_code 359{ 360 ref(my $self = shift) or croak "instance variable needed"; 361 my $code = shift; 362 my $token = shift; 363 364 my $database_file = File::Spec->catfile($self->data_folder(),'codes.txt'); 365 366 # set a variable with the current time 367 my $current_time = time; 368 369 # create database file if it doesn't already exist 370 $self->_touch_file($database_file); 371 372 # clean expired codes and images 373 $self->_get_exclusive_lock(); 374 375 open (DATA, "<$database_file") or die "Can't open File: $database_file\n"; 376 my @data=<DATA>; 377 close(DATA); 378 379 my $new_data = ""; 380 foreach my $line (@data) 381 { 382 $line =~ s/\n//; 383 my ($data_time,$data_token,$data_code) = $line =~ m/(^\d+)::([a-f0-9]{32})::(.*)$/ 384 or next; 385 if ( (($current_time - $data_time) > ($self->expire())) || 386 ($data_token eq $token) ) 387 { # remove expired captcha, or a dup 388 my $png_file = File::Spec->catfile($self->output_folder(),$data_token . ".png"); 389 unlink($png_file) or carp("Can't remove png file [$png_file]\n"); 390 } else { 391 $new_data .= $line."\n"; 392 } 393 } 394 395 # save the code to database 396 warn "open File: $database_file\n" if($self->debug() >= 2); 397 open(DATA,">$database_file") or die "Can't open File: $database_file\n"; 398 warn "-->>" . $new_data . "\n" if($self->debug() >= 2); 399 warn "-->>" . $current_time . "::" . $token."::".$code."\n" if($self->debug() >= 2); 400 print DATA $new_data; 401 print DATA $current_time."::".$token."::".$code."\n"; 402 close(DATA); 403 $self->_release_lock(); 404 warn "Close File: $database_file\n" if($self->debug() >= 2); 405} 406 407sub create_image_file 408{ 409 ref(my $self = shift) or croak "instance variable needed"; 410 my $code = shift; 411 412 my $length = length($code); 413 my $im_width = $self->width(); 414 # create a new image and color 415 my $im = new GD::Image(($im_width * $length),$self->height()); 416 my $black = $im->colorAllocate(0,0,0); 417 418 # copy the character images into the code graphic 419 for(my $i=0; $i < $length; $i++) 420 { 421 my $letter = substr($code,$i,1); 422 my $letter_png = File::Spec->catfile($self->images_folder(),$letter . ".png"); 423 my $source = new GD::Image($letter_png); 424 $im->copy($source,($i*($self->width()),0,0,0,$self->width(),$self->height())); 425 my $a = int(rand (int(($self->width())/14)))+0; 426 my $b = int(rand (int(($self->height())/12)))+0; 427 my $c = int(rand (int(($self->width())/3)))-(int(($self->width())/5)); 428 my $d = int(rand (int(($self->height())/3)))-(int(($self->height())/5)); 429 $im->copyResized($source,($i*($self->width()))+$a,$b,0,0,($self->width())+$c,($self->height())+$d,$self->width(),$self->height()); 430 } 431 432 # distort the code graphic 433 for(my $i=0; $i<($length*($self->width())*($self->height())/14+200); $i++) 434 { 435 my $a = (int(rand ($length*($self->width())))+0); 436 my $b = (int(rand $self->height())+0); 437 my $c = (int(rand ($length*($self->width())))+0); 438 my $d = (int(rand $self->height())+0); 439 my $index = $im->getPixel($a,$b); 440 if ($i < (($length*($self->width())*($self->height())/14+200)/100)) 441 { 442 $im->line($a,$b,$c,$d,$index); 443 } elsif ($i < (($length*($self->width())*($self->height())/14+200)/2)) { 444 $im->setPixel($c,$d,$index); 445 } else { 446 $im->setPixel($c,$d,$black); 447 } 448 } 449 450 # generate a background 451 my $a = int(rand 5)+1; 452 my $background_img = File::Spec->catfile($self->images_folder(),"background" . $a . ".png"); 453 my $source = new GD::Image($background_img); 454 my ($background_width,$background_height) = $source->getBounds(); 455 my $b = int(rand (int($background_width/13)))+0; 456 my $c = int(rand (int($background_height/7)))+0; 457 my $d = int(rand (int($background_width/13)))+0; 458 my $e = int(rand (int($background_height/7)))+0; 459 my $source2 = new GD::Image(($length*($self->width())),$self->height()); 460 $source2->copyResized($source,0,0,$b,$c,($length*($self->width())),$self->height(),$background_width-$b-$d,$background_height-$c-$e); 461 462 # merge the background onto the image 463 $im->copyMerge($source2,0,0,0,0,($length*($self->width())),$self->height(),40); 464 465 # add a border 466 $im->rectangle(0,0,((($length)*($self->width()))-1),(($self->height())-1),$black); 467 468 # save the image to file 469 my $png_data = $im->png; 470 471 return \$png_data; 472} 473 474sub create_sound_file 475{ 476 ref(my $self = shift) or croak "instance variable needed"; 477 my $code = shift; 478 my $length = length($code); 479 480 my @chars = split('',$code); 481 my $snd_file; 482 local $/; # input record separator. So we can slurp the data. 483 # get a random voice speaking the code 484 foreach my $char (@chars) 485 { 486 my $voice = int(rand $num_of_soundfile_versions) + 1; 487 my $src_name = File::Spec->catfile($self->images_folder(),$voice, $char . ".wav"); 488 warn "Open File: $src_name\n" if($self->debug() >= 2); 489 open (FILE,"< $src_name") or die "Can't open File: $src_name\n"; 490 flock FILE, 1; # read lock 491 binmode FILE; 492 $snd_file .= <FILE>; 493 close FILE; 494 warn "Close File: $src_name\n" if($self->debug() >= 2); 495 } 496 return \$snd_file; 497} 498 499sub _save_file 500{ 501 ref(my $self = shift) or croak "instance variable needed"; 502 my $file_ref = shift; 503 my $file_name = shift; 504 505 warn "Open File: $file_name\n" if($self->debug() >= 2); 506 open (FILE,">$file_name") or die "Can't open File: $file_name \n"; 507 flock FILE, 2; # write lock 508 binmode FILE; 509 print FILE $$file_ref; 510 close FILE; 511 warn "Close File: $file_name\n" if($self->debug() >= 2); 512} 513 514sub generate_code 515{ 516 ref(my $self = shift) or croak "instance variable needed"; 517 my $length = shift; 518 519 my $code = $self->generate_random_string($length); 520 my $token = random_regex('[a-f0-9]{32}'); 521 522 my ($captcha_data_ref,$output_filename); 523 if ($self->type() eq 'image') 524 { 525 $captcha_data_ref = $self->create_image_file($code); 526 $output_filename = File::Spec->catfile($self->output_folder(),$token . ".png"); 527 } elsif ($self->type() eq 'sound') { 528 $captcha_data_ref = $self->create_sound_file($code); 529 $output_filename = File::Spec->catfile($self->output_folder(),$token . ".wav"); 530 } else { 531 croak "invalid captcha type [" . $self->type() . "]"; 532 } 533 534 $self->_save_file($captcha_data_ref,$output_filename); 535 $self->_save_code($code,$token); 536 537 # return token (token)... or, if they want it, the code as well. 538 return wantarray ? ($token,$code) : $token; 539} 540 541sub version 542{ 543 return $VERSION; 544} 545 5461; 547__END__ 548# Below is stub documentation for your module. You'd better edit it! 549 550=head1 NAME 551 552Authen::Captcha - Perl extension for creating captcha's to verify the human element in transactions. 553 554=head1 SYNOPSIS 555 556 use Authen::Captcha; 557 558 # create a new object 559 my $captcha = Authen::Captcha->new(); 560 561 # set the data_folder. contains flatfile db to maintain state 562 $captcha->data_folder('/some/folder'); 563 564 # set directory to hold publicly accessible images 565 $captcha->output_folder('/some/http/folder'); 566 567 # Alternitively, any of the methods to set variables may also be 568 # used directly in the constructor 569 570 my $captcha = Authen::Captcha->new( 571 data_folder => '/some/folder', 572 output_folder => '/some/http/folder', 573 ); 574 575 # create a captcha. Image filename is "$token.png" 576 my $token = $captcha->generate_code($number_of_characters); 577 578 # check for a valid submitted captcha 579 # $code is the submitted letter combination guess from the user 580 # $token is the submitted token from the user (that we gave them) 581 my $results = $captcha->check_code($code,$token); 582 # $results will be one of: 583 # 1 : Passed 584 # 0 : Code not checked (file error) 585 # -1 : Failed: code expired 586 # -2 : Failed: invalid code (not in database) 587 # -3 : Failed: invalid code (code does not match token) 588 ############## 589 590=head1 WARNING 591 592The captcha produced by this module is rather weak compared to other modules available. You are advised to update to L<GD::SecurityImage>, which provides API-compatible interface in L<GD::SecurityImage::AC> module. 593 594=head1 ABSTRACT 595 596Authen::Captcha provides an object oriented interface to captcha file creations. Captcha stands for Completely Automated Public Turing test to tell Computers and Humans Apart. A Captcha is a program that can generate and grade tests that: 597 598 - most humans can pass. 599 - current computer programs can't pass 600 601The most common form is an image file containing distorted text, which humans are adept at reading, and computers (generally) do a poor job. 602This module currently implements that method. We plan to add other methods, 603such as distorted sound files, and plain text riddles. 604 605=head1 REQUIRES 606 607 GD (see http://search.cpan.org/~lds/GD-2.11/) 608 Digest::MD5 (standard perl module) 609 610In most common situations, you'll also want to have: 611 612 A web server (untested on windows, but it should work) 613 cgi-bin or mod-perl access 614 Perl: Perl 5.00503 or later must be installed on the web server. 615 GD.pm (with PNG support) 616 617=head1 INSTALLATION 618 619Download the zipped tar file from: 620 621 http://search.cpan.org/search?dist=Authen-Captcha 622 623Unzip the module as follows or use winzip: 624 625 tar -zxvf Authen-Captcha-1.xxx.tar.gz 626 627The module can be installed using the standard Perl procedure: 628 629 perl Makefile.PL 630 make 631 make test 632 make install # you need to be root 633 634Windows users without a working "make" can get nmake from: 635 636 ftp://ftp.microsoft.com/Softlib/MSLFILES/nmake15.exe 637 638=head1 METHODS 639 640=head2 MAIN METHODS 641 642=over 643 644=item C<$captcha = Authen::Captcha-E<gt>new();> 645 646This creates a new Captcha object. 647Optionally, you can pass in a hash with configuration information. 648See the method descriptions for more detail on what they mean. 649 650=over 2 651 652 data_folder => '/some/folder', # required 653 output_folder => '/some/http/folder', # required 654 expire => 300, # optional. default 300 655 width => 25, # optional. default 25 656 height => 35, # optional. default 35 657 images_folder => '/some/folder', # optional. default to lib dir 658 keep_failures => 0, # optional, defaults to 0(false) 659 debug => 0, # optional. default 0 660 661=back 662 663=item C<$token = $captcha-E<gt>generate_code( $number_of_characters );> 664 665Creates a captcha. Image filename is "$token.png" 666 667It can also be called in array context to retrieve the string of characters used to generate the captcha (the string the user is expected to respond with). This is useful for debugging. 668ex. 669 670C<($token,$chars) = $captcha-E<gt>generate_code( $number_of_characters );> 671 672=item C<$results = $captcha-E<gt>check_code($code,$token);> 673 674check for a valid submitted captcha 675 676$code is the submitted letter combination guess from the user 677 678$token is the submitted token from the user (that we gave them) 679 680If the $code and $token are correct, the image file and database entry will be removed. 681 682If the $token matches one in the database, and "keep_failures" is false (the default), the image file and database entry will be removed to avoid repeated attempts on the same captcha. 683 684$results will be one of: 685 686 1 : Passed 687 0 : Code not checked (file error) 688 -1 : Failed: code expired 689 -2 : Failed: invalid code (not in database) 690 -3 : Failed: invalid code (code does not match token) 691 692=back 693 694=head2 ACCESSOR METHODS 695 696=over 697 698=item C<$captcha-E<gt>data_folder( '/some/folder' );> 699 700Required. Sets the directory to hold the flatfile database that will be used to store the current non-expired valid captcha tokens. 701Must be writable by the process running the script (usually the web server user, which is usually either "apache" or "http"), but should not be accessible to the end user. 702 703=item C<$captcha-E<gt>output_folder( '/some/folder' );> 704 705Required. Sets the directory to hold the generated Captcha image files. This is usually a web accessible directory so that the user can view the images in here, but it doesn't have to be web accessible (you could be attaching the images to an e-mail for some verification, or some other Captcha implementation). 706Must be writable by the process running the script (usually the web server user, which is usually either "apache" or "http"). 707 708=item C<$captcha-E<gt>images_folder( '/some/folder' );> 709 710Optional, and may greatly affect the results... use with caution. Allows you to override the default character graphic png's and backgrounds with your own set of graphics. These are used in the generation of the final captcha image file. The defaults are held in: 711 [lib install dir]/Authen/Captcha/images 712 713=item C<$captcha-E<gt>expire( 300 );> 714 715Optional. Sets the number of seconds this captcha will remain valid. This means that the created captcha's will not remain valid forever, just as long as you want them to be active. Set to an appropriate value for your application. Defaults to 300. 716 717=item C<$captcha-E<gt>width( 25 );> 718 719Optional. Number of pixels high for the character graphics. Defaults to 25. 720 721=item C<$captcha-E<gt>height( 35 );> 722 723Optional. Number of pixels wide for the character graphics. Defaults to 35. 724 725=item C<$captcha-E<gt>keep_failures( [0|1] );> 726 727Optional. Defaults to zero. This option controls whether or not the captcha will remain valid after a failed attempt. By default, we only allow one attempt to solve it. This greatly reduces the possibility that a bot could brute force a correct answer. Change it at your own risk. 728 729=item C<$captcha-E<gt>debug( [0|1|2] );> 730 731Optional. 732Sets the debugging bit. 1 turns it on, 0 turns it off. 2 will print out verbose messages to STDERR. 733 734=back 735 736=head1 TODO 737 738sound file captcha: Incorporating distorted sound file creation. 739 740=head1 SEE ALSO 741 742The Captcha project: 743 http://www.captcha.net/ 744 745The origonal perl script this came from: 746 http://www.firstproductions.com/cgi/ 747 748=head1 AUTHORS 749 750Seth T. Jackson, E<lt>sjackson@purifieddata.netE<gt> 751 752Josh I. Miller, E<lt>jmiller@purifieddata.netE<gt> 753 754First Productions, Inc. created the cgi-script distributed under the GPL which was used as the basis for this module. Much work has gone into making this more robust, and suitable for other applications, but much of the origonal code remains. 755 756Fixes were reported and contributed by various people, see Changes file for a complete list. 757 758=head1 COPYRIGHT AND LICENSE 759 760Copyright 2003, First Productions, Inc. (FIRSTPRODUCTIONS HUMAN TEST 1.0) 761 762Copyright 2003 by Seth Jackson 763 764Copyright 2012 by Paolo Rossi, Lubomir Rintel, Chris Dunlop, Gert Schepens and Ernesto Hernández-Novich 765 766This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. (see license.txt). 767 768This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 769 770You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 771 772=cut 773