#!/usr/bin/perl
# Gbook 2006
# - Anti Spam
# - Includes admin function (delete/edit posts), also to link with AccesSite CMS
# Configuration is in gbook_config.pm:
do "gbook_config.pm";
# Anti SPAM:
# - give each time another name, based on a secret algorythm
# - check allowed/unallowed words
# - check referer
# - check field formats (ie Age fields only accepts numbers)
=pod
# view:
$html_head
(if)$sign_link
(if)$total_numer
$sepperator
@(entries)/@($sepperator)
$sepperator
(if)$displaying
(if)$view_older
(if)$view_newer
$html_foot
# sign
$html_head
Sign()
$html_foot
#preview
$html_head
$preview
$sepperator
Entry()
$sepperator
SubmitButtons()
$html_foot
=cut
# Signpage: ?sign
# Adminpage: ?admin
use CGI; my %CGI = CGI::Vars();
my $logged_in = 0;
my ($total,$AS,@entries);
LoadGuestbook();
#print "\n\n";
if ($use_accessite) {
eval "use lib \"$use_accessite/..\";use AccesSite;";
$AS = new AccesSite(data_path => $accessite_data
#, debug_to => *STDOUT
);
#my $user;
my ($user,$headers) = $AS->Login();
print "$headers";#\n\nUser='$user' - ($user->{name})\n\n";
# = $AS->GetUser();
foreach (@as_groups) {
#print "$user->{name} InGroup($_)?\n\n";
if ($user->InGroup($_)) {
#print "Yes\n\n";
$logged_in = $user->{name};
#print "Logged in as '$logged_in'
";
last;
}
}
}
if (lc($ENV{QUERY_STRING}) eq 'sign') {
OutPutSignPage();
}
elsif ($CGI{action} eq 'preview') {
Preview();
}
elsif ($CGI{action} eq 'sign') {
SignBook();
}
elsif (lc($ENV{QUERY_STRING}) eq 'admin') {
$CGI{admin} = 1;
}
my @deleted = ();
if ($CGI{admin} || $logged_in) {
unless ($logged_in) {
if ($use_accessite) {
print "content-type: text/html\n".
"pragma: no-cache\n".
"expires: 0\n\n".
$html_head;
print <<" FORM";
FORM
print $html_foot;
# AccesSite login page
#$AS->SetInput('view','login');
#print "\n\n".$AS->Auto();
exit;
}
}
$CGI{admin} = "&admin=1";
if ($CGI{delete_entries}) {
if ($logged_in || $CGI{password} eq $password) {
foreach my $key (sort keys(%CGI)) {
if ($key =~ /^delete_(\d+)$/) {
my $i = $1;
my $line = $i . $entries[$i-1];
$line =~ s/\W//g;
if ($CGI{"delete_$i"} eq $line) {
#print "\n\n'".$CGI{"delete_$i"}."' eq '$line'
";
push @deleted, $entries[$i-1];
$entries[$i-1] = "";
}
}
}
if (@deleted) {
# Re-write data
open (OUTPUT, ">$datafile") || die "re-write $datafile: $!";
foreach (@entries) {
next if $_ eq "";
print OUTPUT $_;
}
close OUTPUT;
# Re-load data
LoadGuestbook();
}
}
else {
print "content-type: text/html\n".
"pragma: no-cache\n".
"expires: 0\n\n".
$html_head.
"You do not have permission to modify this guestbook.".
$html_foot;
exit;
}
}
}
else {
$CGI{admin} = "";
}
OutputGuestbook();
sub OutputGuestbook {
local ($from,$to);
$from = $CGI{sm}; # total = 98
$from or $from = $total; # from = 88
$from = $total if $from > $total;
$to = $from - $max_messages + 1; # = 88 - 10 + 1 = 79
$to = 1 if $to < 1;
# = @entries;
#$displaying = "Displaying message ~from~ to ~to~ of ~total~ total";
$displaying =~ s/~from~/$from/gi;
$displaying =~ s/~to~/$to/gi;
$displaying =~ s/~total~/$total/gi;
my $style = "style=\"background-color:#EEEEEE;border:1px solid #999999;".
"padding:2px;font-color:black;font-size:10pt;font-family:sans-serif;\"";
print "content-type: text/html\n".
"pragma: no-cache\n".
"expires: 0\n\n".
$html_head;
if ($sign_link) {
print "$sign_link\n";
}
if ($CGI{admin}) {
print <<" FORM";
FORM
}
if ($displaying) {
print "$displaying\n";
}
if ($view_older) {
if ($to > 1) {
print "$view_older\n";
}
else {
print "$view_older";
}
}
print "$view_sepperator";
if ($view_newer) {
if ($from < $total) {
print "$view_newer\n";
}
else {
print "$view_newer";
}
}
print $html_foot;
exit;
}
sub OutPutSignPage {
my ($error,$name,$age,$city,$email,$website,$message) = @_;
# Make up secret input name=s
my ($code,@names) = GetNames();
my $faker = pop(@names);
#my $quarter = int(time / 1000);
print "content-type: text/html\n".
"pragma: no-cache\n".
"expires: 0\n\n".
$html_head.
"".
$html_foot;
exit;
}
sub Preview {
# Check submission
RefererCheck();
my ($code,@names) = GetNames($CGI{k});
if ($CGI{$names[6]} ne "ok") {
Reject("6th CGI value ($names[6]) doesn't equal 'ok': $CGI{$names[6]}");
}
my %entry;
($entry{name},$entry{age},$entry{city},$entry{email},$entry{website},$entry{message}) =
($CGI{$names[0]},$CGI{$names[1]},$CGI{$names[2]},$CGI{$names[3]},$CGI{$names[4]},$CGI{$names[5]});
my $error;
my $counter = 0;
foreach my $entry (keys(%entry)) {
unless ($entry eq 'message') {
$entry{$entry} =~ s/\n//g;
$entry{$entry} =~ s/\cM//g;
$entry{$entry} =~ s/\cM\n//g;
}
$entry{$entry} =~ s/\|/\//g;
unless ($accept_html) {
$entry{$entry} =~ s/&/&/g;
$entry{$entry} =~ s/</g;
$entry{$entry} =~ s/>/>/g;
$entry{$entry} =~ s/"/"/g;
}
foreach (@forbidden_words) {
my $count = $entry{$entry} =~ /$_/i;
$counter += $count;
last if $counter >= $reject_at;
}
last if $counter >= $reject_at;
}
#$entry{message} .= " $counter";
if ($counter >= $reject_at) {
Reject("Too many forbidden words ($counter) Reject at: $reject_at");
}
$counter = $entry{message} =~ /http:/;
if ($counter > $allow_http) {
Reject("Too many occurances of /http:/ ($counter) Allowed: $allow_http");
}
$error .= "$fill_out "$field_name"\n" if $require_name && $entry{name} eq "";
$error .= "$fill_out "$field_age"\n" if $require_age && $entry{age} eq "";
$error .= "$fill_out "$field_city"\n" if $require_city && $entry{city} eq "";
$error .= "$fill_out "$field_email"\n" if $require_email && $entry{email} eq "";
$error .= "$fill_out "$field_website"\n" if $require_website && $entry{website} eq "";
$error .= "$fill_out "$field_message"\n" if $require_message && $entry{message} eq "";
if ($error) {
OutPutSignPage("",$entry{name},$entry{age},$entry{city},$entry{email},$entry{website},$entry{message});
}
$entry{website} =~ s/^http:\/\///gi;
# Then already create the date
# 0 1 2 3 4 5 6 7 8
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
my $time = sprintf("%02d:%02d",$hour,$min);
if (lc($date_format) eq "english") {
$entry{date} = "$days[$wday] $months[$mon] $mday $year, $at $time";
}
else {
$entry{date} = "$days[$wday] $mday $months[$mon] $year, $at $time";
}
$entry{message} =~ s/\cM\n/\n/g;
$entry{message} =~ s/\n/
/g;
if ($expand_html) {
my @words = split (/ /,$entry{message});
foreach $word (@words) {
if ($word =~ /^http:\/\//) {
$word = "".substr($word,7)."";
}
elsif (!$accept_html && $word =~ /.*\@.*\..*/){
$word = "$word";
}
elsif ($word =~ /^www\./){
$word = "$word";
}
}
$entry{message} = join(" ",@words);
}
=pod
"Name: $names[0]: $CGI{$names[0]}
\n".
"Age: $names[1]: $CGI{$names[1]}
\n".
"City: $names[2]: $CGI{$names[2]}
\n".
"Email: $names[3]: $CGI{$names[3]}
\n".
"Website: $names[4]: $CGI{$names[4]}
\n".
"Message: $names[5]: $CGI{$names[5]}
\n";
=cut
($code,@names) = GetNames();
#my $quarter = int(time / 1000);
print "content-type: text/html\n".
"pragma: no-cache\n".
"expires: 0\n\n".
$html_head.
"".
$html_foot;
exit;
}
sub SignBook {
RefererCheck();
my ($code,@names) = GetNames($CGI{k});
if ($CGI{$names[6]} eq "") {
Reject("6th CGI value ($names[6]) equals '': $CGI{$names[6]}");
}
#my %entry;
#($entry{name},$entry{age},$entry{city},$entry{email},$entry{website},$entry{message},$entry{date}) =
my @input = ($CGI{$names[0]},$CGI{$names[5]},$CGI{$names[1]},$CGI{$names[2]},$CGI{$names[3]},$CGI{$names[4]},$CGI{$names[6]},$ENV{HTTP_USER_AGENT},$ENV{REMOTE_ADDR});
foreach (@input) {
$_ =~ s/\cM\n//g;
$_ =~ s/\n//g;
$_ =~ s/\r//g;
$_ =~ s/\|/\//g;
}
open (OUTPUT, ">>$datafile") || die "write $datafile: $!";
print OUTPUT join("|",@input)."\n";
close OUTPUT;
if ($notify_mail) {
my $email = $CGI{$names[3]};
unless ($email) {
$notify_mail =~ /^([^,]+)/;
$email = $1;
}
if (open(MAIL, "$sendmail")) {# -s -t")) {
print MAIL "To: $notify_mail\n";
print MAIL "From: $email\n";
print MAIL "Subject: New entry in $guestbook_name\n";
print MAIL "Content-type: text/html\n\n";
print MAIL <<" MESSAGE";
$guestbook_name
$sepperator
MESSAGE
print MAIL Entry(($total+1),$CGI{$names[0]},$CGI{$names[1]},$CGI{$names[2]},$CGI{$names[3]},
$CGI{$names[4]},$CGI{$names[5]},$CGI{$names[6]},$ENV{REMOTE_ADDR},
$ENV{HTTP_USER_AGENT});
print MAIL "$sepperator\n\n";
close MAIL;
}
}
print "Location: $ENV{SCRIPT_NAME}?x=".time()."\n".
"Content-type: text/html\n\n".
"OK\n";
exit;
}
sub Reject {
my ($why) = @_;
print "status: 403 Forbidden\n\nYour entry is marked as SPAM. Please go away.\n";
# log:
if ($spamlog) {
if (open (SPAMLOG, ">>$spamlog")) {
my $time = time();
print SPAMLOG "$time\t".localtime($time)."\t$why\t";
foreach (qw(HTTP_REFERER HTTP_USER_AGENT REMOTE_ADDR QUERY_STRING)) {
print SPAMLOG "\t$_=$ENV{$_}";
}
print SPAMLOG "\tCGI:";
foreach (sort keys(%CGI)) {
$CGI{$_} =~ s/\n/\\n/g;
print SPAMLOG "\t$_=$CGI{$_}";
}
print SPAMLOG "\n";
}
}
exit;
}
sub RefererCheck {
unless ($ENV{HTTP_REFERER} =~ /$ENV{SCRIPT_NAME}/ &&
($ENV{HTTP_REFERER} =~ /$ENV{SERVER_NAME}/ ||
$ENV{HTTP_REFERER} =~ /$ENV{HTTP_HOST}/
)) {
Reject("RefererCheck failed");
}
}
sub GetNames {
my ($key) = @_;
$key or $key = int(rand() * 1000);
$key =~ /(\d)$/;
my $adder = $1;
$adder++;
my $quarter = int(time / 1000);
my @names;# = (qw(A B C D E F));
foreach (qw(A B C D E F G)) {
my $sum = ($quarter * $key * $adder);
$sum =~ /(\d\d\d\d\d)$/;
push @names, $_.$1;
$adder += $adder;
}
my $code = <<" SCRIPT";
SCRIPT
return ($code,@names);
}
sub LoadGuestbook {
open (INPUT, "$datafile") || die "read $datafile: $!";
@entries = ();
close INPUT;
$total = @entries;
}