#!/usr/bin/perl -I/usr/lib/perl5
use locale;
#  Хосты, с которых разрешен прием формы
@OurHosts = ('xray\\.sai\\.msu\\.su','pereplet\\.sai\\.msu\\.su',
             'xray\\.sai\\.msu\\.ru','pereplet\\.sai\\.msu\\.ru',
             '195\\.208\\.220\\.238',
	     'www\\.pereplet\\.ru','www\\.pereplet\\.ru:90');
#  Директория для базы данных
$GuestbookPath="./guestbook";
#  Директория для настроечных файлов
$ConfigPath="./guestbook";
#
#  Директория для log-файла
$LogPath="./guestbook";
# Разделитель полей в базе данных
$Delim="\001";
# Директория для временных файлов 
$TmpDir="/tmp";
# Количество показываемых сообщений
$TailSize=40;


require "cgi-lib.pl";
&ReadParse(*input);
print "Content-type: text/html\n";

foreach(keys(%input)) { 
   $input{$_}=~s/</&lt;/;
   $input{$_}=~s/\n/\002/g;
   $input{$_}=~s/[\001\003-\032]/ /g;
   $input{$_}=~s/%//g; 
   $input{$_}=~s/>/&gt;/g; 
   $input{$_}=~s/</&lt;/g;
   $input{$_}=~s/\"/&quot;/g;
}


#print "\n";
#foreach $var (sort keys %ENV) {
#    print "<b>$var</b> <i>$ENV{$var}</i><br>\n";
#}


# Существование  log-файла
$LogFile=$LogPath."/log";
unless(-w $LogFile) { 
  PrintError(" Error: Script not properly configured <br>"); 
  exit; 
}


# Проверка HTTP-Referer : Принимать форму только со своих хостов  или из Линкса
$Referer=$ENV{HTTP_REFERER};

wlog("Call from $Referer by $ENV{REMOTE_ADDR} : $ENV{HTTP_USER_AGENT}");
$HostOk=0;
foreach(@OurHosts) {  $HostOk ||= ($Referer =~ /^http:\/\/$_/) ; } 
unless($ENV{HTTP_USER_AGENT}=~/^\s*Lynx/) { 
unless($HostOk) { PrintError( "Error: this script can be used only from trusted hosts<br>\n");
                  wlog("Скрипт вызван с плохого хоста ($Referer)");
                  exit;
                }
}
# 


# Проверка названия книги: только маленькие анг. буквы (для безопасности)

$GbookName=$input{guestbook}; # Название гостевой книги 

if($GbookName eq "") { PrintError( "Error: No guestbook name<br> ".
                          "В вашем html-коде должен определяться параметр <b>guestbook</b><br>\n");
                       wlog("No guestbook name");
                       exit;
                     }
if($GbookName=~/[^a-z]/) { PrintError( "Error: Bad guestbook name: don't hack us <br>\n");
                           wlog("Bad guestbook name ($GbookName)");
                           exit;
                         }

# Проверка существования гостевой книги с заданным именем
# Работа с несуществующими книгами невозможна, поэтому хакер не 
# может создать новую книгу или писать не в тот файл.

$File=$GuestbookPath."/$GbookName.book";
unless(-f $File) { PrintError( "Guestbook $GbookName does not exist<br>\n");
                   wlog("Хочу файл $File, а его нет");
                   exit;
                 }

# Чтение конфигурационного файла 

$HeaderFile=$ConfigPath."/$GbookName.head.html";
$ConfigFile=$ConfigPath."/$GbookName.config";
unless(-f $ConfigFile) { PrintError( "Guestbook $GbookName is not configured<br>\n");
                   wlog("Хочу конфигурационный файл $ConfigFile, а его нет");
                   exit;
                 }
unless(open(CONF,$ConfigFile) ) { PrintError( "Cannot read configuration file for this guestbook<br>\n");
                            wlog("Не могу открыть конфигурационный файл ($ConfigFile): $!");
                            exit;
                 }
while(<CONF>) { 
 chomp;
 next if /^\s*#/ or !/\S/;    # Пропускаем строки - коментарии и пустые 
 if(/\s*(\w+)\s*=\s*(.*)\s*$/) {  # Строки вида PARAM = something
      if(defined ($Config{$1})) { $Config{$1}.=" ".$2;}
              # Если параметр встретится второй раз, новое значение 
              # припишется к старому
                   else         { $Config{$1}=$2; }
 } else {
      wlog("Не понимаю в конф. файле $ConfigFile строчку $.: $_");
 }
}
close(CONF);

# Проверка списка запрещенных хостов

$RemoteHost=$ENV{REMOTE_ADDR};
@RejectedHosts=eval("qw($Config{BadHosts})");
foreach(@RejectedHosts) { 
  if($RemoteHost eq $_) { 
    PrintError( "You are not allowed to work with the guestbook<br>\n");
    wlog("Попытка обращения с запрещенного адреса $RemoteHost");
    exit;
  }
}

# Что за команда : запись, подготовка формы  или чтение

$Command=$input{command};
if($Command eq "") { PrintError( "Скрипт вызван без команды<br>".
                  "В вашей форме должен определяться параметр <b>command</b>=add или view<br>");
                     wlog("Скрипт вызван без параметра 'command'");
                    exit;
}

if($Command eq "save") {  # Добавить запись в guestbook
  if($input{Correct}) { &Prepare; }
  elsif($input{Cancel}) { &View($TailSize); } 
  else {   &Add; }
} elsif ($Command eq "add") { # Показать запись и приготовиться к команде Add.
  &PreView;
} elsif ($Command eq "view") { # Показать guestbook
  &View($TailSize);
} elsif ($Command =~ /^view(\d+)/) { # Показать guestbook начиная с номера
  &View($TailSize,$1);
} elsif ($Command eq "viewall") { # Показать guestbook весь 
  &View();
} elsif ($Command eq "prepare") {
  &Prepare; 
} else { 
 PrintError("Неправильный вызов скрипта - Don't hack us<br>\n");
 wlog("Плохая команда ".$command);
}                     

     

sub PrintError { 
 my $msg=shift;
 print "\n<font color=red>".$msg."</font>";

}

sub wlog { 
 my $msg=shift;
 if(open(LOG,">>".$LogFile)) { 
       print LOG $msg,"\n";
       close(LOG);
 }
 else { print "Log: $msg  <br>\n"; }
}

sub Add { 
  #  Сформируем запись вида IP.Время.Параметры
  $Record=$RemoteHost.$Delim.localtime();
  foreach $param (keys(%input)) { 
     next if($param eq "command" or $param eq "guestbook"); # Пропускаем служебные параметры
     if($param=~/[^\w_]/) { PrintError("Плохой параметр $param в форме<br>\n");
                            wlog("Плохой параметр $param=".$input{$param});
                            next;
     } 
     $value=$input{$param};
     &wlog("$param=".$value); 
     $value=~s/[\001-\014\016-\032]//g;
     $value=~s/\015/\002/g;
     $value=~s/%//g; 
     $value=~s/>/&gt;/g; 
     $value=~s/</&lt;/g;
     $value=~s/\"/&quot;/g;

     $Record.=$Delim."$param=".$value;
  } 
  #Теперь припишем эту запись к базе 
  unless(open(BASE,">>".$File)) { PrintError("Не могу дописывать в гостевую книгу<br>\n");
                                wlog("Не могу (я ".getpwuid($<).") дописывать в гостевую книгу $File: $!");
                                exit;
  }
  print BASE $Record,"\n";
  close(BASE); 
  # Теперь выдадим благодарственную страницу
  if($Config{OkPage} eq "view") { 
     print "Location: http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}?guestbook=$GbookName&command=view\n\n";
     exit;
   } 
  if($Config{OkPage} eq "") { 
     PrintError("Спасибо за участие в дискуссии<br>\n");
     wlog("В конфиг. файле $ConfigFile не указана страница с благодарностью (OkPage)");
  } else { 
     print "Location: $Config{OkPage}\n\n";      
#      unless(open(OKPAGE,$Config{OkPage})) { 
#         PrintError("Спасибо за участие в дискуссии<br>\n");    
#         wlog("Не могу (я ".getpwuid($<).") открыть файл $Config{OkPage}");
#      }
#      print "\n";
#      while(<OKPAGE>) { 
#          s/\\ViewBook/gbook.cgi?guestbook=$GbookName&command=view/g;
#          print; 
#      }
#      close(OKPAGE);  
  }

}
sub PreView { 
 my $param;
 $Line=ReadTemplate($ConfigPath."/$GbookName.html"); 
 print "Pragma: no-cache\n\n<title>Предварительный просмотр сообщения</title>".
       `grep -i "<body" $HeaderFile `.
       "<form action=http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME} method=post>";
 foreach $param (keys(%input)) { 
     next if($param eq "command" or $param eq "guestbook"); # Пропускаем служебные параметры
     if($param=~/[^\w_]/) { PrintError("Плохой параметр $param в форме<br>\n");
                            wlog("Плохой параметр $param=".$input{$param});
                            next;
     } 
     &wlog("$param=".$value); 
     $input{$param}=~s/[\001-\011\013\014\016-\032]//g;
     $input{$param}=~s/\015/\002/g;
     $input{$param}=~s/\012/\002/g;
     $input{$param}=~s/%//g; 
     $input{$param}=~s/>/&gt;/g; 
     $input{$param}=~s/</&lt;/g;
     $input{$param}=~s/\"/&quot;/g;

     $value=$input{$param};
     $Line=~s/\\$param/$value/g; 
#     $value=~s/\"/\\\"/g;
     print "<input type=hidden name=$param value=\"".$value."\">";      
 } 
 $time=localtime();
 $Line =~s/\\time/$time/;

 print "<input type=hidden name=command value=save><input type=hidden name=guestbook value=$GbookName>".
       "<center>".
       "<input type=submit name=Cancel  value=\"ОТМЕНИТЬ\" >".
       "<input type=submit name=Correct value=\"ИСПРАВИТЬ\" >".
       "<input type=submit name=Ok value=\"ПОМЕСТИТЬ\">".
       "<p>Ваше сообщение на экране будет выглядеть ТАК:</center></form>\n";
 
 AllowHTML();
 $Line =~s/\<input[^\>]*\>//g;
 print $Line;
}

sub View { 
 my $Tail=shift;
 my $StartFrom=shift;
 # Прочитаем файл шаблона 
 $Template=ReadTemplate($ConfigPath."/$GbookName.html"); 


 print "\n"; 
if( open(HEAD,$HeaderFile)) { 
   while(<HEAD>) { print; }
   close(HEAD);
 }

 $FileSize=`wc -l $File|awk '{print \$1;}'`; 
 $NavLine="<font size=-1><br>";
   for($i=$FileSize-$Tail;$i+$Tail>0;$i-=$Tail) { 
     local $from=$i+1;
     local $to=$i+$Tail; 
    $from=1 if $from<=0;
    $to=".." if ($to==$FileSize);
  
    if(   ($from==$StartFrom && $to==$StartFrom+$Tail-1)
        || (!defined($StartFrom) && $to==$FileSize    )    ) { 
        $NavLine.= "<b>$from..$to</b>";
   } else { 
        $NavLine.= "<a href=gbook.cgi?guestbook=$GbookName&command=view$from>$from..$to</a> ";
   }
  
  }
 $NavLine.= "<br><a href=gbook.cgi?guestbook=$GbookName&command=viewall>Все&nbsp;$FileSize&nbsp;сообщений</a><br>";
 $NavLine.= "</font>";
 

 print $NavLine,"\n";

 if($StartFrom) { 
   $LastShown=$StartFrom + $Tail -1;
   $File="head -$LastShown $File | tail -$Tail |" if($Tail);
 } else { 
   $File="tail -$Tail $File |" if($Tail);
 }
# print $File,"<br>";
 unless(open(BASE,$File)) { PrintError("Не могу читать гостевую книгу!\n<br>");
                     wlog("Не могу (я ".getpwuid($<).") читать гостевую книгу $File: $!");
                     exit;
 }
 while(<BASE>) { 
   chomp;
   push @Lines, ($_);
 }
 close(BASE);
 

 for($j=$#Lines;$j>=0;$j--) { # Читаем базу, разбираем строчки
   @Fields=split($Delim,$Lines[$j]); 
   $time=$Fields[1]; $ip=$Fields[0];
   $Line=$Template;
   for($i=2;$i<=$#Fields;$i++) { 
     ($name,$val)=split(/=/,$Fields[$i],2);
     $val=~s/\002/<br>/g;
     $Line=~s/\\$name/$val/g;
#           print "<b>$name</b> $val<br>";
   }
   $Line =~ s/\\time/$time/g;
   $Line =~ s/\\\w+//g;
   AllowHTML();
   print $Line;
#   print "time=$Fields[1] ip=$Fields[0]<br>\n"; 
     
 }
 print $NavLine;
}

sub Prepare { 
 # Подготовить форму для записи в книгу. Прочитать файл $Config{Form}, 
 #  добавить в него имеющиеся параметры как hidden-поля
 $FormFile=$Config{Form}; 
 if($FormFile eq "") {
     PrintError("Нет формы для участия в дискуссии <br>\n");
     wlog("В конфиг. файле $ConfigFile не указана страница с формой (Form)");
     exit;
 }
 
 unless(open(FORM,$FormFile)) { PrintError("Не могу читать промежуточную форму!\n<br>");
                     wlog("Не могу (я ".getpwuid($<).") читать гостевую книгу $FormFile: $!");
                     exit;
 }
 # Готовим хидден-параметры
 $Params="<input type=hidden name=command value=add>\n";
 foreach $param (keys(%input)) { 
   unless($param eq "command" || $param eq "Ok" || $param eq "Send" || $param eq "Correct"
         || $param eq "name" || $param eq "message" || $param eq "www" || $param eq "email") { 
     $Params.="<input type=hidden name=$param value=\"".$input{$param}."\">\n";
   }
 }
print "\n";
  $input{www}=~s/\"/&quot;/g;
  $input{name}=~s/\"/&quot;/g;
  $input{email}=~s/\"/&quot;/g;
  $input{message}=~s/\"/&quot;/g;
 # Читаем файл, приписываем к нему готовые параметры
 while(<FORM>) { 
   chomp;
   s/\\Parameters/$Params/;
   s/\\message/$input{message}/;
   s/\\name/$input{name}/;
   s/\\email/$input{email}/;
   s/\\www/$input{www}/;

   print;
 }
 close(FORM);


}

sub ReadTemplate { 
 my $TemplateFile=shift;
 my $Template="";
 unless(open(TMPL,$TemplateFile)) { PrintError("Не могу прочитать шаблон!\n<br>");
                     wlog("Не могу (я ".getpwuid($<).") прочитать шаблон $TemplateFile: $!");
                     exit;
 }
 while(<TMPL>) { $Template.=$_;  }
 close(TMPL);
 return $Template;
}

sub AllowHTML {
# Разрешенные HTML-тэги: 
#   <a ...>, </a>, <b>, </b>, <i>, </i>, <br>, <img ...>, <center>, </center>, <p> и </p>
      $Line=~s/&lt;img src=&quot;(.*?)&quot;&gt;/<img src=\"\1\">/ig;
      $Line=~s/&lt;br&gt;/<br>/ig;
  $ab=$Line=~s/&lt;A href=&quot;(.*?)&quot;&gt;/<A href=\"\1\">/ig;
  $cb=$Line=~s/&lt;center&gt;/<center>/ig;
  $bb=$Line=~s/&lt;B&gt;/<B>/ig;
  $ib=$Line=~s/&lt;I&gt;/<I>/ig;
  $pb=$Line=~s/&lt;P&gt;/<P>/ig;
  $ae=$Line=~s'&lt;/A&gt;'</A>'ig;
  $be=$Line=~s'&lt;/B&gt;'</B>'ig;
  $ie=$Line=~s'&lt;/I&gt;'</I>'ig;
  $pe=$Line=~s'&lt;/P&gt;'</P>'ig;
  $ce=$Line=~s'&lt;/center&gt;'</center>'ig;
  
  for($i=1; $i<=$ab-$ae; $i++){ $Line.="</a>";}
  for($i=1; $i<=$bb-$be; $i++){ $Line.="</b>";}
  for($i=1; $i<=$ib-$ie; $i++){ $Line.="</i>";}
  for($i=1; $i<=$pb-$pe; $i++){ $Line.="</p>";}
  for($i=1; $i<=$cb-$ce; $i++){ $Line.="</center>";}
}