]> bicyclesonthemoon.info Git - ott/bsta/commitdiff
Initial state as of 31.08.2023 v0.0.2
authorb <rowerynaksiezycu@gmail.com>
Thu, 31 Aug 2023 22:01:25 +0000 (22:01 +0000)
committerb <rowerynaksiezycu@gmail.com>
Thu, 31 Aug 2023 22:01:25 +0000 (22:01 +0000)
34 files changed:
2words.1.c [new file with mode: 0644]
2words.1.pl [new file with mode: 0644]
attach.1.c [new file with mode: 0644]
attach.1.pl [new file with mode: 0644]
bbcode.1.c [new file with mode: 0644]
bbcode.1.pl [new file with mode: 0644]
bsta.css [new file with mode: 0644]
bsta_lib.1.pm [new file with mode: 0644]
chat.1.c [new file with mode: 0644]
chat.1.pl [new file with mode: 0644]
config.1.txt [new file with mode: 0644]
config.txt [new file with mode: 0644]
configure.pl [new file with mode: 0644]
frame.1.c [new file with mode: 0644]
frame.1.pl [new file with mode: 0644]
goto.1.c [new file with mode: 0644]
goto.1.pl [new file with mode: 0644]
info.1.c [new file with mode: 0644]
info.1.pl [new file with mode: 0644]
make.sh [new file with mode: 0755]
makefile [new file with mode: 0644]
makefile.1.mak [new file with mode: 0644]
oldlogs.1.pl [new file with mode: 0644]
ong.1.pl [new file with mode: 0644]
ongtimes.pl [new file with mode: 0755]
reset.1.pl [new file with mode: 0644]
settings [new file with mode: 0644]
settings-again [new file with mode: 0644]
settings-test [new file with mode: 0644]
testhtml.pl [new file with mode: 0644]
timer.js [new file with mode: 0644]
updlist.1.pl [new file with mode: 0644]
viewer.1.c [new file with mode: 0644]
viewer.1.pl [new file with mode: 0644]

diff --git a/2words.1.c b/2words.1.c
new file mode 100644 (file)
index 0000000..5fd947a
--- /dev/null
@@ -0,0 +1,34 @@
+// 2words.c is generated from 2words.1.c
+// 28.09.2016
+// 
+// This is the wrapper for 2words.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2016  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###TWOWORDS_PL;
+###TWOWORDS_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(TWOWORDS_PL_ERRLOG,"at",stderr);
+       return execve(TWOWORDS_PL,argv,envp);
+}
diff --git a/2words.1.pl b/2words.1.pl
new file mode 100644 (file)
index 0000000..590d88b
--- /dev/null
@@ -0,0 +1,610 @@
+###PERL;
+#
+# /bsta/2words
+# 2words is generated from 2words.1.pl.
+# 09.01.2023
+#
+# The wordgame interface
+#
+#    Copyright (C) 2016 - 2017, 2023  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode bb2ht);
+use  File::Copy;
+
+###STORY_PATH;
+###LOGO_PATH;
+###FAVICON_PATH;
+###WEBSITE;
+###WEBSITE_NAME;
+###CSS_PATH;
+###TWOWORDS_PATH;
+###CGI_PATH;
+###SETTINGS_PATH;
+###INDEX_PATH;
+###INTF_DATE;
+###DATA_PATH;
+###VIEWER_PATH;
+###DEFAULT_PATH;
+###STATE_PATH;
+###WWW_PATH;
+###STORY_LENGTH;
+###PAGE_LENGTH;
+###FIRSTPAGE_LENGTH;
+###COIN_PATH;
+###COIN_DATE;
+###LIST_PATH;
+
+my %http;
+my %cgi;
+my %story;
+my %newstory;
+my %settings;
+my %state;
+my %gotolist;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $IP;
+my $words;
+my $color2;
+my $lastip;
+my $storyid;
+my $turn;
+my $message;
+my $firstletter;
+my $secondletter;
+my $intfstate;
+my $intfpass;
+my $intfpause;
+my $intfmode;
+my $storypath;
+my $storyfile;
+my $storylock;
+my @storylines;
+my $ongstate;
+my $page;
+
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $page=int($1);
+}
+else {
+       $page=0;
+}
+
+if ($ENV{'HTTP_X_FORWARDED_FOR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+       $IP=$1;
+}
+elsif ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+       $IP=$1;
+}
+else {
+       $IP='0.0.0.0';
+}
+
+if ($cgi{'words'} ne '') {
+       $words=$cgi{'words'};
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%state=readdatafile(STATE_PATH);
+$ongstate=int($state{'state'});
+
+$storylock=0;
+if (open ($storyfile,"+<",STORY_PATH)){
+       $storylock=1;
+       if (flock($storyfile,2)) {
+               $storylock=2;
+       }
+       %story=readdatafile($storyfile);
+       
+       if ($story{'lastip'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+               $lastip=$1;
+       }
+       else {
+               $lastip='0.0.0.0';
+       }
+       
+       $storyid = int($story{'id'});
+       $intfpass = int($story{'pass'});
+       $intfstate = int($story{'state'});
+       $intfmode = $intfstate;
+       $intfpause = $intfstate & 1;
+       if ($intfpause) {
+               $intfmode -= 1;
+       }
+       
+       if ($IP ne $lastip) {
+               $turn = 1;
+       }
+       else {
+               $turn = 0       ;
+       }
+       
+       if($words =~ /^bstaaaclear(all)?$/ || $intfstate < 0) {
+               if($words eq 'bstaaaclearall' || $intfstate < -1) {
+                       $story{'id'}=0;
+               }
+               $story{'content'}='';
+               $story{'lastip'}='0.0.0.0';
+               $story{'letter'}='';
+               $story{'pass'}='0';
+               $story{'state'}='0';
+               $turn=0;
+               if($ongstate == 0) {
+                       writeindex(INDEX_PATH,0,0,0);
+               }
+               writedatafile($storyfile,%story);
+       }
+       
+       if ($words ne '') {
+               if (!$turn) {
+                       $message = "It's not your turn.";
+               }
+               else {
+                       if ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
+                               
+                               $firstletter = lc(substr($2,0,1));
+                               $secondletter = lc(substr($4,0,1));
+                               if (($firstletter ne $story{'letter'})&&($story{'letter'} ne '')) {
+                                       $message = 'The first word must start with '.uc($story{'letter'}).'.';
+                               }
+                               elsif ($firstletter eq $secondletter) {
+                                       $message = 'The second word can\'t also start with '.uc($firstletter).'.';
+                               }
+                               else {
+                                       $story{'content'} = $story{'content'} . $words."\n";
+                                       $turn = 0;
+                                       $story{'lastip'} = $IP;
+                                       $story{'letter'} = $secondletter;
+                                       
+                                       if ($cgi{'next'} ne '') {
+                                               if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
+                                                       $storypath = STORY_PATH.$storyid;
+                                                       writedatafile($storypath,%story);
+                                                       $newstory{'id'} = $storyid + 1;
+                                                       $newstory{'letter'}='';
+                                                       $newstory{'lastip'}=$IP;
+                                                       $newstory{'content'}='';
+                                                       $newstory{'pass'}='0';
+                                                       $newstory{'state'}='0';
+                                                       $intfstate=0;
+                                                       $intfpass=0;
+                                                       $intfmode=0;
+                                                       $intfpause=0;
+                                                       if($ongstate == 0) {
+                                                               writeindex(INDEX_PATH,0,0,0);
+                                                       }
+                                                       writedatafile($storyfile,%newstory);
+                                               }
+                                               else {
+                                                       $message = 'To early to finish this wordgame.';
+                                                       writedatafile($storyfile,%story);
+                                               }
+                                       }
+                                       else {
+                                               if ($intfpass == 1){
+                                                       $intfpass = 2;
+                                                       $story{'pass'} = '2';
+                                                       if($ongstate == 0) {
+                                                               writeindex(INDEX_PATH,2,0,0);
+                                                       }
+                                               }
+                                               elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
+                                                       if ($intfpass == 0) {
+                                                               if($ongstate == 0) {
+                                                                       my %framedata = readdatafile(DATA_PATH.0);
+                                                                       my %default = readdatafile(DEFAULT_PATH);
+                                                                       my $inpath;
+                                                                       my $outpath;
+                                                                       
+                                                                       $framedata{'ongtime'} = $time;
+                                                                       $gotolist{'title-0'} = $framedata{'title'};
+                                                                       $gotolist{'ongtime-0'} = $framedata{'ongtime'};
+                                                                       writedatafile(DATA_PATH.0,%framedata);
+                                                                       writedatafile(LIST_PATH,%gotolist);
+                                                                       
+                                                                       foreach my $ind (keys %default) {
+                                                                               unless(defined($framedata{$ind})){
+                                                                                       $framedata{$ind}=$default{$ind};
+                                                                               }
+                                                                       }
+                                                                       
+                                                                       $inpath = DATA_PATH.sprintf($settings{'frame'},0,$framedata{'ext'});
+                                                                       $outpath = WWW_PATH.sprintf($settings{'frame'},0,$framedata{'ext'});
+                                                                       
+                                                                       if(copy ($inpath, $outpath)) {
+                                                                               $intfpass = 1;
+                                                                               $intfstate = 0;
+                                                                               $intfmode=0;
+                                                                               $intfpause=0;
+                                                                               $story{'pass'} = '1';
+                                                                               $story{'state'} = '0';
+                                                                               writeindex(INDEX_PATH,1,0,0);
+                                                                       }
+                                                               }
+                                                               else {
+                                                                       $message = "???";
+                                                               }
+                                                       }
+                                                       else {
+                                                               $message = 'The password has already been used in this story.';
+                                                       }
+                                               }
+                                               writedatafile($storyfile,%story);
+                                       }
+                               }
+                       }
+                       else {
+                               $message = 'Please, two words, not more, not less (some punctuation is allowed).';
+                       }
+               }
+       }
+       elsif (($cgi{'s'} ne '') && ($intfpass==1) && ($ongstate == 0)) {
+               $intfstate = int($cgi{'s'});
+               if($intfstate > 63 || $intfstate <0) {
+                       $intfstate = 0;
+               }
+               $intfmode = $intfstate;
+               $intfpause = $intfstate & 1;
+               if ($intfpause) {
+                       $intfmode -= 1;
+               }
+               $story{'state'} = $intfstate;
+               writeindex(INDEX_PATH,1,$intfmode,$intfpause);
+               writedatafile($storyfile,%story);
+       }
+       @storylines = split(/\r?\n/,$story{'content'});
+       if(@storylines & 1) {
+               $turn = !$turn;
+       }
+       
+       close($storyfile);
+}
+
+print "Content-type: text/html\n\n";
+if($method eq 'HEAD') {
+       exit;
+}
+
+print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+print '<html lang="en"><head>'."\n";
+print '<title>Two words &bull; '.WEBSITE_NAME.'</title>'."\n";
+print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+print '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+print '</head><body>'."\n";
+print '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+print '<div id="all">'."\n";
+
+print '<div id="inst" class="ins">'."\n";
+
+print '<div id="title">'."\n";
+print '<H1 id="titletext">Two words</H1>'."\n";
+print '</div>'."\n";
+
+if ($page == 0) {
+       print '<div id="storypuzzle">'."\n";
+       for (my $i = 0; $i < @storylines; ++$i){
+               print '<span class="'.($turn?'ni':'br').'">'.entityencode($storylines[$i]).'</span>'."\n";
+               $turn = !$turn;
+       }
+       print '</div>'."\n";
+
+       print '<div id="command">'."\n";
+       if ($message ne '') {
+               print '<span class="br">'.entityencode($message).'</span>'."\n";
+       }
+       
+       if ($turn) {
+               print '<form method="post" action="'.TWOWORDS_PATH.'">'."\n";
+               if ($message eq '') {
+                       if ($story{"content"} eq '') {
+                               print 'Two words, please:<br>'."\n";
+                       }
+                       else {
+                               print 'Please continue, two words:<br>'."\n";
+                       }
+               }
+               print '<input class="intx" type="text" name="words">'."\n";
+               print '<input class="inbt" type="submit" value="enter">'."\n";
+               if(@storylines >= (STORY_LENGTH-1)) {
+                       print '<input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
+               }
+               print '</form>'."\n";
+       }
+       else {
+               if ($message eq '') {
+                       print 'Wait for it.'."\n";
+               }
+       }
+       print '</div>'."\n";
+}
+elsif ($message ne '') {
+       print '<div id="command">'."\n";
+       print '<span class="br">'.entityencode($message).'</span>'."\n";
+       print '</div>'."\n";
+}
+
+if(($intfpass == 1) && ($ongstate == 0)) {
+       print '</div><div id="framespace">'."\n";
+       print '<table id="intftable" cellspacing="0" cellpadding="0"><tr class="intf">'."\n";
+       
+       print '<td colspan="6" class="intf"><img src="'.CGI_PATH.'intf-00';
+       if ($intfmode == 4) {
+               print '_04';
+       }
+       elsif ($intfmode == 8) {
+               print '_08';
+       }
+       elsif ($intfstate == 16) {
+               print '_10';
+       }       
+       print'.gif" alt="" class="intf"></td>'."\n";
+       
+       print '</tr><tr class="intf">'."\n";
+       print '<td class="intf"><img src="'.CGI_PATH.'intf-20.gif" alt="o" class="intf"></td>'."\n";
+       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?17:16).'"><img src="'.CGI_PATH.'intf-10'.(($intfmode==16)?'_':'').'.gif" class="intf" alt="&gt"></td>'."\n";
+       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?9:8).'"><img src="'.CGI_PATH.'intf-08'.(($intfmode==8)?'_':'').'.gif" class="intf" alt="&lt;&lt;"></td>'."\n";
+       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?5:4).'"><img src="'.CGI_PATH.'intf-04'.(($intfmode==4)?'_':'').'.gif" class="intf" alt="&gt;&gt;"></td>'."\n";
+       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?0:0).'"><img src="'.CGI_PATH.'intf-02.gif" class="intf" alt="^"></td>'."\n";
+       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?$intfmode:$intfmode+1).'"><img src="'.CGI_PATH.'intf-01'.($intfpause?'_':'').'.gif" class="intf" alt="||"></td>'."\n";
+       print '</tr></table>'."\n";
+}
+
+print '</div><div id="insb" class="ins">'."\n";
+print '<div id="undertext">'."\n";
+for (my $i = $storyid-1-(($page!=0)?((($page-1)*PAGE_LENGTH)+FIRSTPAGE_LENGTH):0); $i > ($storyid-1-($page*PAGE_LENGTH)- FIRSTPAGE_LENGTH) && $i >= 0; --$i) {
+       $storypath = STORY_PATH.$i;
+       %newstory = readdatafile($storypath);
+       print '<p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.entityencode($newstory{'content'}).'</p>'."\n";
+}
+
+print '</div>'."\n";
+print '<div id="underlinks">'."\n";
+print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.TWOWORDS_PATH.'">Once again</a>';
+if(($storyid - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
+       print ' | <a href="'.TWOWORDS_PATH.'/'.($page+1).'">Before</a>';
+}
+if($page > 0) {
+       print ' | <a href="'.TWOWORDS_PATH.'/'.($page-1).'">Unbefore</a>';
+}
+if(($storyid - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
+       print ' | <a href="'.TWOWORDS_PATH.'/'.(int(($storyid - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1).'">Initially</a>';
+}
+if($turn) {
+       print ' | (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
+}
+print "\n";
+print '</div>'."\n";
+
+print '</div>'."\n";
+print '</div>'."\n";
+print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+print '</body></html>'."\n";
+
+sub writeindex {
+       (my $indexpath, my $pass, my $mode, my $pause) = @_;
+       my $indexfile;
+       my $indexof;
+               
+       if(ref($indexpath)) {
+               $indexfile=$indexpath;
+               unless (seek($indexfile, 0, 0)) {
+                       return 0;
+               }
+       }
+       else {
+               unless (open ($indexfile, ">", $indexpath)) {
+                       return 0;
+               }
+       }
+       
+       $indexof = CGI_PATH;
+       $indexof =~ s/\/$//g;
+       
+       my %coin = readdatafile(COIN_PATH);
+       
+       if ($pass != 1) {
+               print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'."\n";
+               print $indexfile '<html>'."\n";
+               print $indexfile ' <head>'."\n";
+               print $indexfile '  <title>Index of '.$indexof.'</title>'."\n";
+               print $indexfile ' </head>'."\n";
+               print $indexfile ' <body>'."\n";
+               print $indexfile '<h1>Index of '.$indexof.'</h1>'."\n";
+               print $indexfile '<table><tr><th><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr><tr><th colspan="5"><hr></th></tr>'."\n";
+               print $indexfile '<tr><td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td><td><a href="/">Parent Directory</a></td><td>&nbsp;</td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
+               print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="2words/">2words/</a></td><td align="right">'.INTF_DATE.'  </td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
+               print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="coin/">coin/</a></td><td align="right">'.COIN_DATE.'  </td><td align="right">  - </td><td> Coincidence </td></tr>'."\n";
+               print $indexfile '<tr><th colspan="5"><hr></th></tr>'."\n";
+               print $indexfile '</table>'."\n";
+               print $indexfile '<address>Apache/2.2.22 (Debian) Server at '.WEBSITE.' Port 80</address>'."\n";
+               print $indexfile '</body></html>'."\n";
+       }
+       else {
+               my %framedata;
+               my %nextframedata;
+               my %default;
+               
+               %framedata = readdatafile(DATA_PATH.0);
+               %nextframedata = readdatafile(DATA_PATH.1);
+               %default=readdatafile(DEFAULT_PATH);
+               
+               # if($mode == 16 && $pause) {
+                       # $framedata{'ongtime'} = $time;
+                       # writedatafile(DATA_PATH.0,%framedata);
+               # }
+               
+               foreach my $ind (keys %default) {
+                       unless(defined($framedata{$ind})){
+                               $framedata{$ind}=$default{$ind};
+                       }
+                       unless(defined($nextframedata{$ind})){
+                               $nextframedata{$ind}=$default{$ind};
+                       }
+               }
+               
+               
+               print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+               print $indexfile '<html lang="en"><head>'."\n";
+               print $indexfile '<title>';
+               if($mode == 8 || $mode == 4) {
+                       print $indexfile 'Index of';
+               }
+               elsif($mode == 16) {
+                       print $indexfile entityencode($settings{'story'});
+                       if($pause) {
+                               print $indexfile ' &bull; '.WEBSITE_NAME;
+                       }
+               }
+               else {
+                       print $indexfile 'Index of '.$indexof;
+               }
+               print $indexfile '</title>'."\n";
+               print $indexfile '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+               print $indexfile '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+               print $indexfile '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+               print $indexfile '</head><body>'."\n";
+               print $indexfile '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+               print $indexfile '<div id="all">'."\n";
+               
+               print $indexfile '<div id="inst" class="ins">'."\n";
+               
+               print $indexfile '<div id="title">'."\n";
+               print $indexfile '<H1 id="titletext">';
+               if($mode == 8 || $mode == 4) {
+                       print $indexfile 'Index of';
+               }
+               elsif($mode == 16) {
+                       print $indexfile entityencode($settings{'story'});
+               }
+               else {
+                       print $indexfile 'Index of '.$indexof;
+               }
+               print $indexfile '</H1>'."\n";
+               print $indexfile '</div>'."\n";
+               
+               print $indexfile '</div><div id="framespace">'."\n";
+               print $indexfile '<img src="'.CGI_PATH;
+               if($mode == 8) {
+                       print $indexfile 'intf-ll.gif'
+               }
+               elsif($mode == 4) {
+                       print $indexfile 'intf-pp.gif'
+               }
+               elsif($mode == 16) {
+                       if($pause) {
+                               print $indexfile sprintf($settings{'frame'},0,$framedata{'ext'}).'" title="'.entityencode($framedata{'title'});
+                       }
+                       else {
+                               print $indexfile 'intf-tr.gif'
+                       }
+               }
+               else {
+                       print $indexfile 'intf-kw.gif'
+               }
+               print $indexfile '" alt="0" id="frame">'."\n";
+               
+               print $indexfile '</div><div id="insb" class="ins">'."\n";
+               print $indexfile '<div id="undertext">'."\n";
+               if($mode == 8 || $mode == 4) {
+                       print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br><img src="/icons/folder.gif" alt="[DIR]"> <a href="#">yyyyb/</a>'."\n";
+               }
+               elsif ($mode == 16) {
+                       if($pause) {
+                               print $indexfile bb2ht($framedata{'content'})."\n";
+                       }
+                       else {
+                               print $indexfile "...\n";
+                       }
+               }
+               else {
+                       print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br>'."\n";
+                       print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="2words/">2words/</a> '.INTF_DATE.' - <br>'."\n";
+                       print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="coin/">coin/</a> '.COIN_DATE.' - '.entityencode($coin{'server'})."\n";
+               }
+               print $indexfile '</div>'."\n";
+               
+               print $indexfile '<div id="command">'."\n";
+               if ($mode == 8) {
+                       print $indexfile '[<span class="br">EE</span>:<span class="br">EE</span>:<span class="br">EE</span>]'."\n";
+               }
+               elsif ($mode == 4) {
+                       print $indexfile '[<span class="ni">EE</span>:<span class="ni">EE</span>:<span class="ni">EE</span>]'."\n";
+               }
+               elsif ($mode == 16) {
+                       if($pause) {
+                               print $indexfile '[<span class="br">00</span>:<span class="br">00</span>:<span class="br">00</span>]<br>'."\n";
+                               print $indexfile '&gt;<a href="'.VIEWER_PATH.'/1">'.entityencode($nextframedata{'title'}).'</a>'."\n";
+                       }
+                       else {
+                               print $indexfile '[<span class="ni">--</span>:<span class="ni">--</span>:<span class="ni">--</span>]<br>'."\n";
+                               print $indexfile '&gt;...<span class="inp">_</span>'."\n";
+                       }
+               }
+               else {
+               }
+               print $indexfile '</div>'."\n";
+               
+               print $indexfile '</div>'."\n";
+               
+               print $indexfile '</div>'."\n";
+               print $indexfile '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+               
+               print $indexfile '</body></html>'."\n";
+       }
+       
+       unless (ref($indexpath)) {
+               close ($indexfile);
+       }
+       else {
+               truncate ($indexfile , tell($indexfile));
+       }
+       
+       return 1;
+}
diff --git a/attach.1.c b/attach.1.c
new file mode 100644 (file)
index 0000000..af32854
--- /dev/null
@@ -0,0 +1,34 @@
+// attach.c is generated from attach.1.c
+// 19.10.2016
+// 
+// This is the wrapper for frame.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2016  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###ATTACH_PL;
+###ATTACH_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(ATTACH_PL_ERRLOG,"at",stderr);
+       return execve(ATTACH_PL,argv,envp);
+}
diff --git a/attach.1.pl b/attach.1.pl
new file mode 100644 (file)
index 0000000..da38af8
--- /dev/null
@@ -0,0 +1,154 @@
+###PERL;
+#
+# /bsta/a
+# attach.pl is generated from attach.1.pl.
+# 19.10.2016
+#
+# The attachment interface
+#
+#    Copyright (C) 2016  Balthasar Szczepañski
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+
+use bsta_lib qw(failpage gethttpheader getcgi readdatafile);
+
+###SETTINGS_PATH;
+###DATA_PATH;
+###STATE_PATH;
+
+my %http;
+my %cgi;
+my %settings;
+my %state;
+my %filedata;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $ID;
+my $frame;
+my $password;
+my $passwordOK;
+my $IP;
+my $buffer;
+my @fileinfo;
+my $file;
+my $filepath;
+my $direct;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+# print "content-type: text/plain\n\n";
+
+if ($cgi{'i'} =~ /^(.+)$/) {
+       $ID=int($1);
+}
+elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $ID=int($1);
+}
+else {
+       $ID = 0;
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%state=readdatafile(STATE_PATH);
+%filedata=readdatafile(DATA_PATH.'a'.$ID);
+if ($filedata{'frame'} ne '') {
+       $frame=int($filedata{'frame'});
+}
+else {
+       $frame = -1;
+}
+
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+if ($filedata{'filename'} ne '' && ($passwordOK || (int($state{'state'}) >= 1 && $frame <= int($state{'last'}) && $frame >=0))) {
+}
+else {
+       exit failpage("Status: 404 Not Found\n","404 Not Found"," Attachment ".$ID." not found.");
+}
+
+if ($filedata{'content'} ne '') {
+       $direct=1;
+}
+else {
+       $direct = 0;
+       $filepath=DATA_PATH.$filedata{'filename'};
+       open($file,'<',$filepath) or exit failpage("Status: 404 Not Found\n","404 Not Found"," Attachment ".$ID." not found.");
+       unless(binmode($file)) {
+               close($file);
+               return failpage("Status: 500 Internal Server Error\n","500 Internal Server Error"," Can't switch to binary mode.");
+       }
+       if (my @fileinfo = stat($filepath)){
+               print 'Content-length: '.$fileinfo[7]."\n";
+       }
+}
+print 'Content-type: '.$filedata{'content-type'}."\n";
+print 'Content-disposition: attachment; filename="'.$filedata{'filename'}.'"'."\n";
+print "\n";
+if($method ne 'HEAD'){
+       if($direct) {
+               print $filedata{'content'};
+       }
+       else {
+               while (read ($file,$buffer,1024)) {
+                       print (STDOUT $buffer);
+               }
+       }
+}
+if (!$direct) {
+       close($file);
+}
+
diff --git a/bbcode.1.c b/bbcode.1.c
new file mode 100644 (file)
index 0000000..deee30b
--- /dev/null
@@ -0,0 +1,34 @@
+// bbcode.c is generated from bbcode.1.c
+// 05.06.2017
+// 
+// This is the wrapper for bbcode.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2017  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###BBCODE_PL;
+###BBCODE_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(BBCODE_PL_ERRLOG,"at",stderr);
+       return execve(BBCODE_PL,argv,envp);
+}
diff --git a/bbcode.1.pl b/bbcode.1.pl
new file mode 100644 (file)
index 0000000..34503b8
--- /dev/null
@@ -0,0 +1,181 @@
+###PERL;
+#
+# /bsta/b
+# bbcode.pl is generated from bbcode.1.pl.
+# 05.06.2017
+#
+# The bbcode interface
+#
+#    Copyright (C) 2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi readdatafile bb2bb linehtml);
+use File::Copy;
+
+###DATA_PATH;
+###DEFAULT_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###NOACCESS_PATH;
+###WEBSITE;
+###VIEWER_PATH;
+###ATTACH_PATH;
+###FRAME_PATH;
+###CGI_PATH;
+
+my %http;
+my %cgi;
+my %framedata;
+my %default;
+my %settings;
+my %state;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $frame;
+my $password;
+my $passwordOK;
+my $access;
+my $showcommand;
+my $ongtime;
+my $seconds;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($cgi{'f'} =~ /^(.+)$/) {
+       $frame=int($1);
+}
+elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $frame=int($1);
+}
+else {
+       $frame = 0;
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%default=readdatafile(DEFAULT_PATH);
+%framedata=readdatafile(DATA_PATH.$frame);
+%state=readdatafile(STATE_PATH);
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+if($frame<0) {
+       $frame = int($state{'last'}) + $frame +1;
+       %framedata=readdatafile(DATA_PATH.$frame);
+}
+
+foreach my $ind (keys %default) {
+       unless(defined($framedata{$ind})){
+               $framedata{$ind}=$default{$ind};
+       }
+}
+
+if ($passwordOK || (int($state{'state'}) >= 1 && $frame <= int($state{'last'}) && $frame >= 0)) {
+       $access=1;
+}
+else {
+       $access=0;
+       %framedata = readdatafile(NOACCESS_PATH);
+       foreach my $ind (keys %default) {
+               unless(defined($framedata{$ind})){
+                       $framedata{$ind}=$default{$ind};
+               }
+       }
+}
+
+print "Content-type: text/plain\n";
+if(!$access) {
+       print "Status: 403 Forbidden\n";
+}
+print "\n";
+if($method eq 'HEAD') {
+       exit;
+}
+
+print '[quote][center][size=200]'.$framedata{'title'}.'[/size]'."\n";
+print '[url=http://'.WEBSITE.VIEWER_PATH.'/'.$frame.'][img]http://'.WEBSITE.CGI_PATH.($access?sprintf($settings{'frame'},$frame,$framedata{'ext'}):$framedata{'frame'}).'[/img][/url][/center]'."\n";
+print bb2bbf($framedata{'content'}).'[/quote]'."\n";
+
+sub bb2bbf {
+       (my $bb) = @_;
+       my $tag;
+       my $tagvalue;
+       my $pretext;
+       my $posttext;
+       
+       while($bb =~ m/(###([^#;]*);)/g) {
+               $tag = $1;
+               $tagvalue = $2;
+               $pretext = substr($bb,0,pos ($bb)-length($tag));
+               $posttext = substr ($bb,pos ($bb));
+               
+               if ($tagvalue =~ /^att&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.ATTACH_PATH.'/'.int($1);
+               }
+               elsif ($tagvalue =~ /^vw&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.VIEWER_PATH.'/'.int($1);
+               }
+               elsif ($tagvalue =~ /^fr&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.FRAME_PATH.'/'.int($1);
+               }
+               else {
+                       $tagvalue = '';
+               }
+               
+               $bb = $pretext.$tagvalue.$posttext;
+       }
+       
+       return bb2bb ($bb);
+}
diff --git a/bsta.css b/bsta.css
new file mode 100644 (file)
index 0000000..652f9fc
--- /dev/null
+++ b/bsta.css
@@ -0,0 +1,346 @@
+html
+{
+       background-color: #46a3ff;
+       /* background-color: #d9ecff; */
+       border-color: #000000;
+       color: #000000;
+       text-align: center;
+}
+a
+{
+       border-color: #0057af;
+       color: #0057af;
+       text-decoration:underline;
+}
+a:visited
+{
+       border-color: #bb6622;
+       color: #bb6622;
+}
+a:hover
+{
+       border-color: #bb6622;
+       color: #bb6622;
+}
+a:hover:visited
+{
+       border-color: #0057af;
+       color: #0057af;
+}
+::selection
+{
+       color: #ffffff;
+       background-color: #bb6622;
+}
+
+div#all
+{
+       background-color: #d9ecff;
+       /* background-color: #ffffff; */
+       margin-left: auto;
+       margin-right: auto;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       width: 656px;
+       padding-top: 27px;
+       padding-bottom: 27px;
+       text-align: center;
+}
+div.all
+{
+       background-color: #d9ecff;
+       /* background-color: #ffffff; */
+       margin-left: auto;
+       margin-right: auto;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       width: 656px;
+       padding-top: 27px;
+       padding-bottom: 27px;
+       text-align: center;
+}
+
+div.ins
+{
+       background-color: #ffffff;
+       margin-left: auto;
+       margin-right: auto;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding: 0px;
+       border: 0px;
+       width: 580px;
+}
+div#inst
+{
+       /* margin-top: 27px; */
+       /* margin-bottom: 0px; */
+}
+div#insb
+{
+       /* margin-top: 0px; */
+       /* margin-bottom: 27px; */
+}
+
+div#title
+{
+       text-align: center;
+       padding-top: 21px;
+       padding-bottom: 21px;
+       padding-left: 0px;
+       padding-right: 0px;
+       border: 0px;
+       margin: 0px;
+}
+
+h1#titletext
+{
+       margin: 0px;
+       border: 0px;
+       padding: 0px;
+}
+
+div#storypuzzle
+{
+       text-align: left;
+       padding: 8px;
+       margin: 0px;
+       border: 0px;
+       font-weight: bold;
+}
+
+div#framespace
+{
+       background-color: #ffffff;
+       margin-left: auto;
+       margin-right: auto;
+       margin-top: 0px;
+       margin-bottom: 0px;
+       padding: 0px;
+       border: 0px;
+       width: 656px;
+}
+
+img#frame
+{
+       border: solid #0057af;
+       border-width:27px 38px;
+       padding: 0px;
+       margin: 0px;
+}
+
+img.intf
+{
+       border-width: 0px;
+       padding: 0px;
+       margin: 0px;
+}
+
+tr.intf
+{
+       border-width: 0px;
+       padding: 0px;
+       margin: 0px;
+}
+
+td.intf
+{
+       border-width: 0px;
+       padding: 0px;
+       margin: 0px;
+}
+
+table#intftable
+{
+       border: solid #0057af;
+       border-width:27px 38px;
+       padding: 0px;
+       margin: 0px;
+       background-color: #00ff00;
+       border-collapse: collapse;
+       border-spacing: 0px;
+}
+
+img#frame:hover
+{
+       border-color: #bb6622;
+}
+
+div#undertext
+{
+       text-align: left;
+       padding: 8px;
+       margin: 0px;
+       border: 0px;
+}
+
+div#chat
+{
+       text-align: left;
+       padding: 8px;
+       margin: 0px;
+       border: 0px;
+       font-family: monospace;
+}
+
+div.fq
+{
+       text-align: left;
+       border: solid #0057af 4px;
+       font-family: monospace;
+}
+
+div.fq:hover
+{
+       border-color: #bb6622;
+}
+
+div.tq
+{
+       text-align: left;
+       border: solid #0057af 4px;
+}
+
+div.tq:hover
+{
+       border-color: #bb6622;
+}
+
+div#command
+{
+       text-align: left;
+       margin: 0px;
+       border: 0px;
+       padding: 8px;
+       font-family: monospace;
+       font-size: 150%;
+       /* font-weight: bold; */
+}
+
+div#underlinks
+{
+       text-align: left;
+       padding: 8px;
+       margin: 0px;
+       border: 0px;
+       font-family: monospace;
+}
+
+span.inp
+{
+       animation: inp 2380ms step-start infinite;
+}
+
+@keyframes inp
+{
+       50% { opacity: 0.0;}
+}
+
+input.intx
+{
+       border-color: #0057af;
+       color: #000000;
+       background-color: #ffffff;
+       border-width: 2px;
+       border-style: solid;
+       margin: 2px;
+       font-family: monospace;
+       /* font-size: 150%; */
+}
+
+input.intx:focus
+{
+       border-color: #bb6622;
+}
+
+input.intxc
+{
+       border-color: #0057af;
+       color: #000000;
+       background-color: #ffffff;
+       border-width: 2px;
+       border-style: solid;
+       margin: 2px;
+       font-family: monospace;
+       width: 100%;
+       /* font-size: 150%; */
+}
+
+input.intxc:focus
+{
+       border-color: #bb6622;
+}
+
+/* table.intxc
+{
+       width=100%;
+} */
+
+textarea.inta
+{
+       border-color: #0057af;
+       color: #000000;
+       background-color: #ffffff;
+       border-width: 2px;
+       border-style: solid;
+       width: 100%;
+       margin: 2px;
+       resize: none;
+}
+
+textarea.inta:focus
+{
+       border-color: #bb6622;
+}
+
+input.inbt
+{
+       border-color: #0057af;
+       color: #000000;
+       background-color: #ffffff;
+       border-width: 2px;
+       border-style: solid;
+       margin: 2px;
+       font-family: monospace;
+       /* font-size: 150%; */
+}
+
+input.inbt:focus
+{
+       border-color: #bb6622;
+}
+
+.br
+{
+       border-color: #bb6622!important;
+       color: #bb6622!important;
+}
+.po
+{
+       border-color: #ff8800!important;
+       color: #ff8800!important;
+}
+.ni
+{
+       border-color: #0057af!important;
+       color: #0057af!important;
+}
+.bi
+{
+       border-color: #ffffff!important;
+       color: #ffffff!important;
+}
+.cz
+{
+       border-color: #000000!important;
+       color: #000000!important;
+}
+
+div.le
+{
+       float: left;
+}
+div.pr
+{
+       float: right;
+}
\ No newline at end of file
diff --git a/bsta_lib.1.pm b/bsta_lib.1.pm
new file mode 100644 (file)
index 0000000..1105a4b
--- /dev/null
@@ -0,0 +1,1293 @@
+# bsta_lib.pm is generated from bsta_lib.1.pm
+# 22.09.2022
+# 
+# Library of functions
+#
+#    Copyright (C) 2016-2017, 2019-2020, 2022  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package bsta_lib;
+
+use strict;
+#use warnings
+use Exporter;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+use constant entitycode => {
+       'amp' => '&',
+       'gt' => '>',
+       'lt' => '<',
+       'quot' => '"',
+       'acute' => '´',
+       'cedil' => '¸',
+       'circ' => 'ˆ',
+       'macr' => '¯',
+       'middot' => '·',
+       'tilde' => '˜',
+       'uml' => '¨',
+       'Aacute' => 'Á',
+       'aacute' => 'á',
+       'Acirc' => 'Â',
+       'acirc' => 'â',
+       'AElig' => 'Æ',
+       'aelig' => 'æ',
+       'Agrave' => 'À',
+       'agrave' => 'à',
+       'Aring' => 'Å',
+       'aring' => 'å',
+       'Atilde' => 'Ã',
+       'atilde' => 'ã',
+       'Auml' => 'Ä',
+       'auml' => 'ä',
+       'Ccedil' => 'Ç',
+       'ccedil' => 'ç',
+       'Eacute' => 'É',
+       'eacute' => 'é',
+       'Ecirc' => 'Ê',
+       'ecirc' => 'ê',
+       'Egrave' => 'È',
+       'egrave' => 'è',
+       'ETH' => 'Ð',
+       'eth' => 'ð',
+       'Euml' => 'Ë',
+       'euml' => 'ë',
+       'Iacute' => 'Í',
+       'iacute' => 'í',
+       'Icirc' => 'Î',
+       'icirc' => 'î',
+       'Igrave' => 'Ì',
+       'igrave' => 'ì',
+       'Iuml' => 'Ï',
+       'iuml' => 'ï',
+       'Ntilde' => 'Ñ',
+       'ntilde' => 'ñ',
+       'Oacute' => 'Ó',
+       'oacute' => 'ó',
+       'Ocirc' => 'Ô',
+       'ocirc' => 'ô',
+       'OElig' => 'Œ',
+       'oelig' => 'œ',
+       'Ograve' => 'Ò',
+       'ograve' => 'ò',
+       'Oslash' => 'Ø',
+       'oslash' => 'ø',
+       'Otilde' => 'Õ',
+       'otilde' => 'õ',
+       'Ouml' => 'Ö',
+       'ouml' => 'ö',
+       'Scaron' => 'Š',
+       'scaron' => 'š',
+       'szlig' => 'ß',
+       'THORN' => 'Þ',
+       'thorn' => 'þ',
+       'Uacute' => 'Ú',
+       'uacute' => 'ú',
+       'Ucirc' => 'Û',
+       'ucirc' => 'û',
+       'Ugrave' => 'Ù',
+       'ugrave' => 'ù',
+       'Uuml' => 'Ü',
+       'uuml' => 'ü',
+       'Yacute' => 'Ý',
+       'yacute' => 'ý',
+       'yuml' => 'ÿ',
+       'Yuml' => 'Ÿ',
+       'cent' => '¢',
+       'curren' => '¤',
+       'euro' => '€',
+       'pound' => '£',
+       'yen' => '¥',
+       'brvbar' => '¦',
+       'bull' => '•',
+       'copy' => '©',
+       'dagger' => '†',
+       'Dagger' => '‡',
+       'frasl' => '⁄',
+       'hellip' => '…',
+       'iexcl' => '¡',
+       'image' => 'ℑ',
+       'iquest' => '¿',
+       'lrm' => '‎',
+       'mdash' => '—',
+       'ndash' => '–',
+       'not' => '¬',
+       'oline' => '‾',
+       'ordf' => 'ª',
+       'ordm' => 'º',
+       'para' => '¶',
+       'permil' => '‰',
+       'prime' => '′',
+       'Prime' => '″',
+       'real' => 'ℜ',
+       'reg' => '®',
+       'rlm' => '‏',
+       'sect' => '§',
+       'shy' => '­',
+       'sup1' => '¹',
+       'trade' => '™',
+       'weierp' => '℘',
+       'bdquo' => '„',
+       'laquo' => '«',
+       'ldquo' => '“',
+       'lsaquo' => '‹',
+       'lsquo' => '‘',
+       'raquo' => '»',
+       'rdquo' => '”',
+       'rsaquo' => '›',
+       'rsquo' => '’',
+       'sbquo' => '‚',
+       'emsp' => ' ',
+       'ensp' => ' ',
+       'nbsp' => ' ',
+       'thinsp' => ' ',
+       'zwj' => '‍',
+       'zwnj' => '‌',
+       'deg' => '°',
+       'divide' => '÷',
+       'frac12' => '½',
+       'frac14' => '¼',
+       'frac34' => '¾',
+       'ge' => '≥',
+       'le' => '≤',
+       'minus' => '−',
+       'sup2' => '²',
+       'sup3' => '³',
+       'times' => '×',
+       'alefsym' => 'ℵ',
+       'and' => '∧',
+       'ang' => '∠',
+       'asymp' => '≈',
+       'cap' => '∩',
+       'cong' => '≅',
+       'cup' => '∪',
+       'empty' => '∅',
+       'equiv' => '≡',
+       'exist' => '∃',
+       'fnof' => 'ƒ',
+       'forall' => '∀',
+       'infin' => '∞',
+       'int' => '∫',
+       'isin' => '∈',
+       'lang' => '⟨',
+       'lceil' => '⌈',
+       'lfloor' => '⌊',
+       'lowast' => '∗',
+       'micro' => 'µ',
+       'nabla' => '∇',
+       'ne' => '≠',
+       'ni' => '∋',
+       'notin' => '∉',
+       'nsub' => '⊄',
+       'oplus' => '⊕',
+       'or' => '∨',
+       'otimes' => '⊗',
+       'part' => '∂',
+       'perp' => '⊥',
+       'plusmn' => '±',
+       'prod' => '∏',
+       'prop' => '∝',
+       'radic' => '√',
+       'rang' => '⟩',
+       'rceil' => '⌉',
+       'rfloor' => '⌋',
+       'sdot' => '⋅',
+       'sim' => '∼',
+       'sub' => '⊂',
+       'sube' => '⊆',
+       'sum' => '∑',
+       'sup' => '⊃',
+       'supe' => '⊇',
+       'there4' => '∴',
+       'Alpha' => 'Α',
+       'alpha' => 'α',
+       'Beta' => 'Β',
+       'beta' => 'β',
+       'Chi' => 'Χ',
+       'chi' => 'χ',
+       'Delta' => 'Δ',
+       'delta' => 'δ',
+       'Epsilon' => 'Ε',
+       'epsilon' => 'ε',
+       'Eta' => 'Η',
+       'eta' => 'η',
+       'Gamma' => 'Γ',
+       'gamma' => 'γ',
+       'Iota' => 'Ι',
+       'iota' => 'ι',
+       'Kappa' => 'Κ',
+       'kappa' => 'κ',
+       'Lambda' => 'Λ',
+       'lambda' => 'λ',
+       'Mu' => 'Μ',
+       'mu' => 'μ',
+       'Nu' => 'Ν',
+       'nu' => 'ν',
+       'Omega' => 'Ω',
+       'omega' => 'ω',
+       'Omicron' => 'Ο',
+       'omicron' => 'ο',
+       'Phi' => 'Φ',
+       'phi' => 'φ',
+       'Pi' => 'Π',
+       'pi' => 'π',
+       'piv' => 'ϖ',
+       'Psi' => 'Ψ',
+       'psi' => 'ψ',
+       'Rho' => 'Ρ',
+       'rho' => 'ρ',
+       'Sigma' => 'Σ',
+       'sigma' => 'σ',
+       'sigmaf' => 'ς',
+       'Tau' => 'Τ',
+       'tau' => 'τ',
+       'Theta' => 'Θ',
+       'theta' => 'θ',
+       'thetasym' => 'ϑ',
+       'upsih' => 'ϒ',
+       'Upsilon' => 'Υ',
+       'upsilon' => 'υ',
+       'Xi' => 'Ξ',
+       'xi' => 'ξ',
+       'Zeta' => 'Ζ',
+       'zeta' => 'ζ',
+       'crarr' => '↵',
+       'darr' => '↓',
+       'dArr' => '⇓',
+       'harr' => '↔',
+       'hArr' => '⇔',
+       'larr' => '←',
+       'lArr' => '⇐',
+       'rarr' => '→',
+       'rArr' => '⇒',
+       'uarr' => '↑',
+       'uArr' => '⇑',
+       'clubs' => '♣',
+       'diams' => '♦',
+       'hearts' => '♥',
+       'spades' => '♠',
+       'loz' => '◊',
+};
+
+use constant tagsbb => {
+       'ht'    => '',
+       '/ht'   => '',
+       'fq'    => '[quote]',
+       '/fq'   => '[/quote]',
+       'tq'    => '[quote]',
+       '/tq'   => '[/quote]',
+       'ni'    => '[color=#0057AF]',
+       '/ni'   => '[/color]',
+       'br'    => '[color=#BB6622]',
+       '/br'   => '[/color]',
+       'po'    => '[color=#FF8800]',
+       '/po'   => '[/color]',
+       'url'   => '[url]',
+       'url='  => '[url=',
+       'url/=' => ']',
+       '/url'  => '[/url]',
+       'i'     => '[i]',
+       '/i'    => '[/i]',
+       'list'  => '[list]',
+       'list=' => '[list=',
+       'list/='=> ']',
+       '/list' => '[/list]',
+       '*'     => '[*]',
+       '/*'    => '[/*]',
+       '?'     => '[unknown!]',
+       '/?'    => '[/unknown!]',
+};
+use constant tagsht => {
+       'ht'     => '',
+       '/ht'    => '',
+       'fq'     => '<div class="fq">',
+       '/fq'    => '</div>',
+       'tq'     => '<div class="tq">',
+       '/tq'    => '</div>',
+       'ni'     => '<span class="ni">',
+       '/ni'    => '</span>',
+       'br'     => '<span class="br">',
+       '/br'    => '</span>',
+       'po'     => '<span class="po">',
+       '/po'    => '</span>',
+       'url'    => '<a href="#">',#think: how to add selfincluding?
+       'url='   => '<a href="',
+       'url/='  => '">',
+       '/url'   => '</a>',
+       'i'      => '<i>',
+       '/i'     => '</i>',
+       'list'   => '<ul>',
+       'list='  => '<ol style="list-style-type: ',
+       'list=1' => 'decimal',
+       'list=A' => 'upper-alpha',
+       'list=a' => 'lower-alpha',
+       'list=I' => 'upper-roman',
+       'list=i' => 'lower-roman',
+       'list/=' => '">',
+       '/list'  => '</ul>',
+       '/list=' => '</ol>',
+       '*'      => '<li>',
+       '/*'     => '</li>',
+       '?'      => '[unknown!]',
+       '/?'     => '[/unknown!]',
+};
+
+$VERSION     = 0.000002;
+@ISA         = qw(Exporter);
+@EXPORT      = ();
+@EXPORT_OK   = qw(entityencode failpage gethttpheader getcgi urldecode readdatafile writedatafile printdatafile printdatafileht urlencode linehtml bb2ht bb2bb);
+%EXPORT_TAGS = ();
+
+# Function to show an error page
+# arguments: 1 - header fields, 2 - page title, 3 - error message, 4 method
+sub failpage {
+       (my $header, my $title, my $message, my $method)=@_;
+       if($header ne ''){
+               print $header;
+       }
+       if($method eq 'HEAD') {
+               print "\n";
+               return;
+       }
+       print "Content-type: text/html\n\n";
+       print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+       print '<html lang="en"><head>'."\n";
+       if($title ne ''){
+               print '<title>'.entityencode($title).'</title>'."\n";
+       }
+       print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+       print '</head><body>'."\n";
+       if($title ne ''){
+               print '<h1>'.entityencode($title).'</h1>'."\n";
+       }
+       if($message ne ''){
+               print '<p>'.entityencode($message).'</p>'."\n";
+       }
+       print '</body></html>'."\n";
+}
+
+# function to encode entities, decimal, 
+sub entityencode {
+       (my $t, my $all) = @_;
+       if ($all) {
+               $t =~ s/(.)/sprintf('\&#%02hu;',ord($1))/eg;
+       }
+       else {
+               $t =~ s/([\"=><\&])/sprintf('&#%02hu;',ord($1))/eg;
+       }
+       return $t;
+}
+
+# function to get values of http header fields. Returns a hash. names of header
+# fields are lowercase
+sub gethttpheader {
+       (my $env) = @_;
+       
+       my %http;
+       
+       foreach my $ind (keys %$env) {
+               my $name = '';
+               my $value= '';
+               
+               if ($ind =~ /^HTTP_([A-Z0-9_]+)$/) {
+                       $name=$1;
+               }
+               elsif ($ind =~ /^(CONTENT_[A-Z0-9_]+)$/) {
+                       $name=$1;
+               }
+               else{
+                       next;
+               }
+               $name =~ s/_/-/g;
+               $name = lc($name);
+               if ($$env{$ind} =~ /^([\x20-\x7e]*)$/) {
+                       $value=$1;
+               }
+               else {
+                       next;
+               }
+               $http{$name}=$value;
+       }
+       return %http;
+}
+
+# The function to get CGI parameters from string.
+# Format is: name=url_encoded_value&name=url_encoded_value& ... &name=url_encoded_value
+sub getcgi {
+       my $arg;
+       my $val;
+       my %cgi;
+       my $i = $_[0];
+       $i =~ s/[\r\n]//g;
+       my @s = split('&',$i);
+       foreach my $l ( @s) {
+               ($arg,$val)=split('=',$l);
+               $cgi{$arg}=urldecode($val);
+       }
+       return %cgi;
+}
+
+# Function for decoding URL-encoded text
+sub urldecode {
+       my $t = $_[0];
+       $t =~ s/\+/ /g;
+       $t =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/eg;
+       return $t;
+}
+
+# Function to read data from datafiles.
+# Very similar to http header file reading. (function readheaderfile() in proxy
+# library)
+# 
+# Differences:
+#
+# 1. After field name and colon there must be exactly one whitespace (space or
+# tab). Any other leading or trailing whitespace (but not the newline character
+# at the end of the line) is treated as part of the field value.
+#
+# 2. Instead of colon an equal sign can be used. The number of whitespaces after
+# it is then zero and not one.
+#
+# 3. When header field is split into multiple lines the next lines must start
+# with exactly one whitespace (tab or space) Any other leading or trailing
+# whitespace (but not the newline character at the end of the line) is treated
+# as part of the field value. the lines will be joined with a newline between
+# them.
+#
+# 4. When the same field name appears it replaces the previous one.
+# 
+# 5. Line separator is LF and not CR LF. The CR character is treated as part of
+# the field value.
+#
+# 6. After the end of header (double newline) all next lines are treated as the
+# value of the "content" field.
+#
+# Returns a hash containing the values.
+# Names are case sensitive and are converted to lowercase
+#
+# Argument can be a path or a file handle. In case of a file handle it will just
+# read the file. In case of path it opens the file before reading and closes
+# after. On failure (file not open) returns empty hash.
+# 
+sub readdatafile {
+       (my $datapath) = @_;
+       my $datafile;
+       my %data;
+       my $eoh=0;
+       
+       # check if $datapath is actually a path or maybe a filehandle
+       # filehandles are references.
+       if(ref($datapath)) {
+               $datafile=$datapath;
+               unless (seek($datafile, 0, 0)) {
+                       return %data;
+               }
+       }
+       else {
+               unless (open ($datafile, "<", $datapath)) {
+                       return %data;
+               }
+       }
+
+       # The name of header field in previous line. Required for header fields that
+       # occupy multiple lines.
+       my $lastname='';
+       
+       while (defined(my $line = <$datafile>)) {
+               my $name='';
+               my $value='';
+               
+               if ($eoh){
+                       unless($line eq'') {
+                               $data{'content'} = $data{'content'}.$line;
+                       }
+                       next;
+               }
+               
+               $line =~ s/[\n]$//g;
+               
+               # Empty line - end of header.
+               if ($line eq ''){
+                       $eoh=1;
+               }
+               # Line starts with whitespace. It's a continuation of the previous line.
+               # Concatenate the field value, separated by newline.
+               elsif($line =~ /^[ \t](.*)$/){
+                       if($lastname ne '') {
+                               $data{$lastname}.="\n".$1;
+                       }
+               }
+               # Line starts with a name followed by colon/equal sign. Save the value
+               elsif ($line =~ /^([^:=]+)((:[ \t])|=)(.*)$/) {
+                       $name = lc($1);
+                       $value = $4;
+                       
+                       $data{$name}=$value;
+                       
+                       $lastname = $name;
+               }
+       }
+       
+       # If argument was a path the file must be closed. 
+       unless (ref($datapath)) {
+               close ($datafile);
+       }
+       
+       return %data;
+}
+
+# the function to write data to datafiles (see readdatafile() description)
+#
+# First argument can be a path or a file handle. In case of a file handle it
+# will just write the file. In case of path it opens the file before writing and
+# closes after.
+#
+# On failure (file not open) returns 0.
+# On success returns 1.
+#
+sub writedatafile {
+       (my $headerpath, my %header) = @_;
+       my $headerfile;
+       
+       if(ref($headerpath)) {
+               $headerfile=$headerpath;
+               unless (seek($headerfile, 0, 0)) {
+                       return 0;
+               }
+       }
+       else {
+               unless (open ($headerfile, ">", $headerpath)) {
+                       return 0;
+               }
+       }
+       
+       foreach my $ind (keys %header) {
+               unless($ind eq 'content') {
+                       my $headname = $ind;
+                       my $headval = $header{$ind};
+                       $headval =~ s/\r//g;
+                       $headval =~ s/\n/\n /g;
+                       print $headerfile "$headname: $headval\n";
+               }
+       }
+       print $headerfile "\n".$header{'content'};
+       
+       unless (ref($headerpath)) {
+               close ($headerfile);
+       }
+       else {
+               truncate ($headerfile , tell($headerfile));
+       }
+       
+       return 1;
+}
+
+# the function to print data to stdout (see readdatafile() description)
+#
+# On success returns 1.
+#
+sub printdatafile {
+       (my %header) = @_;
+       
+       foreach my $ind (keys %header) {
+               unless($ind eq 'content') {
+                       my $headname = $ind;
+                       my $headval = $header{$ind};
+                       $headval =~ s/\r//g;
+                       $headval =~ s/\n/\n /g;
+                       print "$headname: $headval\n";
+               }
+       }
+       print "\n".$header{'content'};
+       
+       return 1;
+}
+
+# the function to print data to stdout as html (see readdatafile() description)
+#
+# On success returns 1.
+#
+sub printdatafileht {
+       (my %header) = @_;
+       
+       foreach my $ind (keys %header) {
+               unless($ind eq 'content') {
+                       my $headname = $ind;
+                       my $headval = $header{$ind};
+                       $headval =~ s/\r//g;
+                       $headval =~ s/\n/\n /g;
+                       print linehtml("$headname: $headval\n");
+               }
+       }
+       print linehtml("\n".$header{'content'});
+       
+       return 1;
+}
+
+
+sub urlencode {
+       (my $t, my $all) = @_;
+       if ($all) {
+               $t =~ s/(.)/sprintf('%%%02hX',ord($1))/eg;
+       }
+       else {
+               $t =~ s/([^0-9A-Za-z.~\-_])/sprintf('%%%02hX',ord($1))/eg;
+       }
+       return $t;
+}
+
+#analyse bbcode text to build tag tree #TODO make [/*] optional!
+sub bbtree {
+       (my $bb, my $printdebug) = @_;
+       my %bbtree;
+       my $ind;
+       my $tag;
+       my $tagname;
+       my $tagvalue;
+       my $tagend;
+       my $level=0;
+       my $pretext;
+       my $debug;
+       
+       $ind="_";
+       $level=0;
+       $bbtree{"_.n"}="ht";
+       $bbtree{"_.v"}='';
+       $bbtree{"_.t"}="tg";
+       $bbtree{"_.e"}=0;
+       $bbtree{"_.c"}='';
+       $debug .= debug($printdebug, "\n<!--GENERATING BBCODE TREE:\n".'[_]automatic tag: [ht]'."\n");
+       
+       while ($bb ne '') {
+               if($bb =~ m/(\[(\/?)([a-z]+|\*)(=([^\[\]]*))?\])/g) {
+                       $tag = $1;
+                       $tagend = $2;
+                       $tagname = $3;
+                       $tagvalue = $5;
+                       $pretext = substr($bb,0,pos ($bb)-length($tag));
+                       $bb = substr ($bb,pos ($bb));
+                       
+                       if ($pretext ne '') {
+                               $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']text: '.$pretext."\n");
+                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tx';
+                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$pretext;
+                               $bbtree{$ind.'.e'} += 1;
+                       }
+                       
+                       if($tagname =~ /^(fq|tq|br|ni|po|url|i|list|\*)$/) {
+                               if ($tagend ne '') {
+                                       if(($tagname ne $bbtree{$ind.'.n'}) || ($level <= 0)) {
+                                               $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']text: '.$tag."\n");
+                                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tx';
+                                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$tag;
+                                               $bbtree{$ind.'.e'} += 1;
+                                       }
+                                       else {
+                                               $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']tag: '.$tag."\n");
+                                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tg';
+                                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.n'}='/'.$tagname;
+                                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$tagvalue;
+                                               $bbtree{$ind.'.e'} += 1;
+                                               $bbtree{$ind.'.c'}=1;
+                                               $level -= 1;
+                                               $ind =~ s/\.[0-9]+$//;
+                                       }
+                               }
+                               else
+                               {
+                                       $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']tag: '.$tag."\n");
+                                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tg';
+                                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.n'}=$tagname;
+                                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$tagvalue;
+                                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.e'}=0;
+                                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.c'}='';
+                                       $bbtree{$ind.'.e'} += 1;
+                                       $level += 1;
+                                       $ind = $ind.'.'.($bbtree{$ind.'.e'}-1);
+                               }
+                       }
+                       else {
+                               $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']text: '.$tag."\n");
+                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tx';
+                               $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$tag;
+                               $bbtree{$ind.'.e'} += 1;
+                       }
+               }
+               else {
+                       $debug .= debug($printdebug, '['.$ind.'.'.$bbtree{$ind.'.e'}.']text: '.$bb."\n");
+                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.t'}='tx';
+                       $bbtree{$ind.'.'.$bbtree{$ind.'.e'}.'.v'}=$bb;
+                       $bbtree{$ind.'.e'} += 1;
+                       $bb='';
+               }
+       }
+       $debug .= debug($printdebug, '[_.'.$bbtree{'_.e'}.']automatic tag: [/ht]'."\n -->\n");
+       $bbtree{'_.'.$bbtree{"_.e"}.'.t'}="tg";
+       $bbtree{'_.'.$bbtree{"_.e"}.'.n'}='/ht';
+       $bbtree{"_.e"}+=1;
+       $bbtree{"_.c"}=1;
+       
+       return ($debug, %bbtree);
+}
+
+#convert tag tree to final text
+sub convtree {
+       (my $printdebug, my $debug, my $lang, my %bbtree) = @_;
+       my $ht;
+       my $ind;
+       my $indd;
+       my $level=0;
+       my $tagsr = ($lang eq 'html') ? tagsht : tagsbb;
+       my %tags = %$tagsr;
+       my $escape = ($lang eq 'html');
+       
+       # $debug .= debug($printdebug, "\n****\n");
+       # foreach my $iiii (keys %tags) {
+               # $debug .= debug($printdebug, $iiii.'='.$tags{$iiii}."\n");
+       # }
+       # $debug .= debug($printdebug, "****\n");
+       
+       $level=0;
+       $ind='_';
+       $ht='';
+       $debug .= debug($printdebug, "\n<!--PROCESSING BBCODE TREE:\n");
+       
+       while ($level >=0) {
+               $debug .= debug($printdebug, "[$level:$ind:".int($bbtree{$ind.'.e'})."]");
+               #normal text
+               if($bbtree{$ind.'.t'} eq 'tx') {
+                       $debug .= debug($printdebug, "text: ".$bbtree{$ind.'.v'});
+                       $ht = $ht.($escape?(linehtml($bbtree{$ind.'.v'})):($bbtree{$ind.'.v'}));
+                       
+                       {do{
+                               $ind =~ s/\.([0-9]+)$//;
+                               $indd = int($1)+1;
+                               if ($indd < $bbtree{$ind.'.e'}){
+                                       $ind = $ind.'.'.$indd;
+                                       last;
+                               }
+                               else {
+                                       #should not occur with a correct bbtree
+                                       $debug .= debug($printdebug, "[<tx]");
+                                       $level -= 1;
+                               }
+                       } while ($level>=0);}
+               }
+               #tag
+               elsif($bbtree{$ind.'.t'} eq 'tg') {
+                       #endtag
+                       if($bbtree{$ind.'.n'} =~ /^\//) {
+                               $debug .= debug($printdebug, "tag: [".$bbtree{$ind.'.n'}."]");
+                               $indd = $ind;
+                               $indd =~ s/\.([0-9]+)$//;
+                               if (exists($tags{$bbtree{$ind.'.n'}.'='}) && ($bbtree{$indd.'.v'} ne '')) {
+                                       $ht = $ht.$tags{$bbtree{$ind.'.n'}.'='};
+                               }
+                               elsif (exists($tags{$bbtree{$ind.'.n'}})) {
+                                       $ht = $ht.$tags{$bbtree{$ind.'.n'}};
+                               }
+                               else {
+                                       $ht = $ht.$tags{'/?'};
+                                       $debug .= debug($printdebug, "[unknown!]");
+                               }
+                               
+                               $ind =~ s/\.([0-9]+)$//;
+                               $level -= 1;
+                               $debug .= debug($printdebug, "[<]");
+                               if($level>=0) {
+                                       {do{
+                                               $ind =~ s/\.([0-9]+)$//;
+                                               $indd = int($1)+1;
+                                               if ($indd < $bbtree{$ind.'.e'}){
+                                                       $ind = $ind.'.'.$indd;
+                                                       last;
+                                               }
+                                               else {
+                                                       #should not occur with a correct bbtree
+                                                       $debug .= debug($printdebug, "[<nd]");
+                                                       $level -= 1;
+                                               }
+                                       } while ($level>=0);}
+                               }
+                               else {
+                                       # time to end this
+                                       $level = -1;
+                               }
+                       }
+                       #starttag
+                       else {
+                               if($bbtree{$ind.'.c'} ne '') {
+                                       $debug .= debug($printdebug, "tag: [".$bbtree{$ind.'.n'}."]");
+                                       
+                                       if (exists($tags{$bbtree{$ind.'.n'}.'='}) && ($bbtree{$ind.'.v'} ne '')) {
+                                               if (exists($tags{$bbtree{$ind.'.n'}.'='.$bbtree{$ind.'.v'}})) {
+                                                       $ht = $ht.$tags{$bbtree{$ind.'.n'}.'='}.$tags{$bbtree{$ind.'.n'}.'='.$bbtree{$ind.'.v'}}.$tags{$bbtree{$ind.'.n'}.'/='};
+                                               }
+                                               else {
+                                                       $ht = $ht.$tags{$bbtree{$ind.'.n'}.'='}.($escape?entityencode($bbtree{$ind.'.v'}):$bbtree{$ind.'.v'}).$tags{$bbtree{$ind.'.n'}.'/='};
+                                               }
+                                       }
+                                       elsif (exists($tags{$bbtree{$ind.'.n'}})) {
+                                               $ht = $ht.$tags{$bbtree{$ind.'.n'}};
+                                       }
+                                       else {
+                                               $ht = $ht.$tags{'?'};
+                                               $debug .= debug($printdebug, "[unknown!]");
+                                       }
+                               }
+                               else {
+                                       $debug .= debug($printdebug, "unclosed tag: [".$bbtree{$ind.'.n'}."]");
+                                       $ht = $ht.'['.($escape?linehtml($bbtree{$ind.'.n'}):$bbtree{$ind.'.n'}).']';
+                               }
+                               if($bbtree{$ind.'.e'}>0) {
+                                       $ind = $ind.'.0';
+                                       $level += 1;
+                                       $debug .= debug($printdebug, "[>]");
+                               }
+                               else {
+                                       {do{
+                                               $ind =~ s/\.([0-9]+)$//;
+                                               $indd = int($1)+1;
+                                               if ($indd < $bbtree{$ind.'.e'}){
+                                                       $ind = $ind.'.'.$indd;
+                                                       last;
+                                               }
+                                               else {
+                                                       #should not occur with a correct bbtree
+                                                       $debug .= debug($printdebug, "[<st]");
+                                                       $level -= 1;
+                                               }
+                                       } while ($level>=0);}
+                               }
+                       }
+               }
+               #what is this
+               else {
+                       $debug .= debug($printdebug, "unknown thing: ".$bbtree{$ind.'.t'});
+                       #should not occur with a correct bbtree
+                       #unless unimplemented
+                       $ind =~ s/\.([0-9]+)$//;
+                       $level -= 1;
+                       $debug .= debug($printdebug, "[<ui]");
+                       if($level>0) {
+                               {do{
+                                       $ind =~ s/\.([0-9]+)$//;
+                                       $indd = int($1)+1;
+                                       if ($indd < $bbtree{$ind.'.e'}){
+                                               $ind = $ind.'.'.$indd;
+                                               last;
+                                       }
+                                       else {
+                                               #should not occur with a correct bbtree
+                                               $debug .= debug($printdebug, "[<un]");
+                                               $level -= 1;
+                                       }
+                               } while ($level>=0);}
+                       }
+                       else {
+                               # time to end this
+                               $level = -1;
+                       }
+               }
+               $debug .= debug($printdebug, "[>$level:$ind]\n");
+       }
+       
+       $debug .= debug($printdebug, "-->\n");
+       return ($debug, $ht);
+}
+
+#bbcode to html, TBD
+sub bb2ht {
+       (my $bb, my $printdebug) = @_;
+       my $ht;
+       my %bbtree;
+       my $debug;
+       
+       ($debug, %bbtree) = bbtree($bb,$printdebug);
+       ($debug, $ht) = convtree ($printdebug, $debug, 'html', %bbtree);
+       
+       return $ht;
+       
+       # $level=0;
+       # $ind='_';
+       # $ht='';
+       # $debug .= debug($printdebug, "\n<!--PROCESSING BBCODE TREE:\n");
+       
+       # while ($level >=0) {
+               # $debug .= debug($printdebug, "[$level:$ind:".int($bbtree{$ind.'.e'})."]");
+               # if($bbtree{$ind.'.t'} eq 'tx') {
+                       # $debug .= debug($printdebug, "text: ".$bbtree{$ind.'.v'});
+                       # $ht = $ht.linehtml($bbtree{$ind.'.v'});
+                       
+                       # {do{
+                               # $ind =~ s/\.([0-9]+)$//;
+                               # $indd = int($1)+1;
+                               # if ($indd < $bbtree{$ind.'.e'}){
+                                       # $ind = $ind.'.'.$indd;
+                                       # last;
+                               # }
+                               # else {
+                                       # #should not occur with a correct bbtree
+                                       # $debug .= debug($printdebug, "[<tx]");
+                                       # $level -= 1;
+                               # }
+                       # } while ($level>=0);}
+               # }
+               # elsif($bbtree{$ind.'.t'} eq 'tg') {
+                       # if($bbtree{$ind.'.n'} =~ /^\//) {
+                               # $debug .= debug($printdebug, "tag: [".$bbtree{$ind.'.n'}."]");
+                               # if($bbtree{$ind.'.n'} eq '/ht') {
+                                       # #
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} =~ /^\/(fq|tq)$/) {
+                                       # $ht = $ht.'</div>';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} =~ /^\/(br|ni|po)$/) {
+                                       # $ht = $ht.'</span>';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} eq '/url') {
+                                       # $ht = $ht.'</a>';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} eq '/i') {
+                                       # $ht = $ht.'</i>';
+                               # }
+                               # else { #unimpl.
+                                       # $ht = $ht.'['.linehtml($bbtree{$ind.'.n'}).']';
+                                       # $debug .= debug($printdebug, "[unknown!]");
+                               # }
+                               # $ind =~ s/\.([0-9]+)$//;
+                               # $level -= 1;
+                               # $debug .= debug($printdebug, "[<]");
+                               # if($level>=0) {
+                                       # {do{
+                                               # $ind =~ s/\.([0-9]+)$//;
+                                               # $indd = int($1)+1;
+                                               # if ($indd < $bbtree{$ind.'.e'}){
+                                                       # $ind = $ind.'.'.$indd;
+                                                       # last;
+                                               # }
+                                               # else {
+                                                       # #should not occur with a correct bbtree
+                                                       # $debug .= debug($printdebug, "[<nd]");
+                                                       # $level -= 1;
+                                               # }
+                                       # } while ($level>=0);}
+                               # }
+                               # else {
+                                       # # time to end this
+                                       # $level = -1;
+                               # }
+                       # }
+                       # else {
+                               # if($bbtree{$ind.'.c'} ne '') {
+                                       # if($bbtree{$ind.'.n'} eq 'ht') {
+                                               # #
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'fq') {
+                                               # $ht = $ht.'<div class="fq">';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'tq') {
+                                               # $ht = $ht.'<div class="tq">';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'br') {
+                                               # $ht = $ht.'<span class="br">';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'ni') {
+                                               # $ht = $ht.'<span class="ni">';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'po') {
+                                               # $ht = $ht.'<span class="po">';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'i') {
+                                               # $ht = $ht.'<i>';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'url') {
+                                               # $ht = $ht.'<a href="'.entityencode($bbtree{$ind.'.v'}).'">';
+                                       # }
+                                       # else { #unimpl.
+                                               # $ht = $ht.'['.linehtml($bbtree{$ind.'.n'}).(($bbtree{$ind.'.v'} ne '' )?entityencode($bbtree{$ind.'.v'}):'').']';
+                                               # $debug .= debug($printdebug, "[unknown!]");
+                                       # }
+                               # }
+                               # else {
+                                       # $debug .= debug($printdebug, "unclosed tag: [".$bbtree{$ind.'.n'}."]");
+                                       # $ht = $ht.'['.linehtml($bbtree{$ind.'.n'}).']';
+                               # }
+                               # if($bbtree{$ind.'.e'}>0) {
+                                       # $ind = $ind.'.0';
+                                       # $level += 1;
+                                       # $debug .= debug($printdebug, "[>]");
+                               # }
+                               # else {
+                                       # {do{
+                                               # $ind =~ s/\.([0-9]+)$//;
+                                               # $indd = int($1)+1;
+                                               # if ($indd < $bbtree{$ind.'.e'}){
+                                                       # $ind = $ind.'.'.$indd;
+                                                       # last;
+                                               # }
+                                               # else {
+                                                       # #should not occur with a correct bbtree
+                                                       # $debug .= debug($printdebug, "[<st]");
+                                                       # $level -= 1;
+                                               # }
+                                       # } while ($level>=0);}
+                               # }
+                       # }
+               # }
+               # else {
+                       # $debug .= debug($printdebug, "unknown thing: ".$bbtree{$ind.'.t'});
+                       # #should not occur with a correct bbtree
+                       # #unless unimplemented
+                       # $ind =~ s/\.([0-9]+)$//;
+                       # $level -= 1;
+                       # $debug .= debug($printdebug, "[<ui]");
+                       # if($level>0) {
+                               # {do{
+                                       # $ind =~ s/\.([0-9]+)$//;
+                                       # $indd = int($1)+1;
+                                       # if ($indd < $bbtree{$ind.'.e'}){
+                                               # $ind = $ind.'.'.$indd;
+                                               # last;
+                                       # }
+                                       # else {
+                                               # #should not occur with a correct bbtree
+                                               # $debug .= debug($printdebug, "[<un]");
+                                               # $level -= 1;
+                                       # }
+                               # } while ($level>=0);}
+                       # }
+                       # else {
+                               # # time to end this
+                               # $level = -1;
+                       # }
+               # }
+               # $debug .= debug($printdebug, "[>$level:$ind]\n");
+       # }
+       
+       # $debug .= debug($printdebug, "-->\n");
+       # # print $debug;
+       
+       
+       
+}
+
+#bbcode to bb, TBD
+sub bb2bb {
+       (my $bb, my $printdebug) = @_;
+       my $ht;
+       my %bbtree;
+       my $debug;
+       
+       ($debug, %bbtree) = bbtree($bb,$printdebug);
+       ($debug, $ht) = convtree ($printdebug, $debug, 'bb', %bbtree);
+       
+       return $ht;
+       
+       # $level=0;
+       # $ind='_';
+       # $ht='';
+       # $debug .= debug($printdebug, "\n<!--PROCESSING BBCODE TREE:\n");
+       
+       # while ($level >=0) {
+               # $debug .= debug($printdebug, "[$level:$ind:".int($bbtree{$ind.'.e'})."]");
+               # if($bbtree{$ind.'.t'} eq 'tx') {
+                       # $debug .= debug($printdebug, "text: ".$bbtree{$ind.'.v'});
+                       # $ht = $ht.$bbtree{$ind.'.v'};
+                       
+                       # {do{
+                               # $ind =~ s/\.([0-9]+)$//;
+                               # $indd = int($1)+1;
+                               # if ($indd < $bbtree{$ind.'.e'}){
+                                       # $ind = $ind.'.'.$indd;
+                                       # last;
+                               # }
+                               # else {
+                                       # #should not occur with a correct bbtree
+                                       # $debug .= debug($printdebug, "[<tx]");
+                                       # $level -= 1;
+                               # }
+                       # } while ($level>=0);}
+               # }
+               # elsif($bbtree{$ind.'.t'} eq 'tg') {
+                       # if($bbtree{$ind.'.n'} =~ /^\//) {
+                               # $debug .= debug($printdebug, "tag: [".$bbtree{$ind.'.n'}."]");
+                               # if($bbtree{$ind.'.n'} eq '/ht') {
+                                       # #
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} =~ /^\/(fq|tq)$/) {
+                                       # $ht = $ht.'[/quote]';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} =~ /^\/(br|ni|po)$/) {
+                                       # $ht = $ht.'[/color]';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} eq '/url') {
+                                       # $ht = $ht.'[/url]';
+                               # }
+                               # elsif ($bbtree{$ind.'.n'} eq '/i') {
+                                       # $ht = $ht.'[/i]';
+                               # }
+                               # else { #unimpl.
+                                       # $ht = $ht.'['.$bbtree{$ind.'.n'}.']';
+                                       # $debug .= debug($printdebug, "[unknown!]");
+                               # }
+                               # $ind =~ s/\.([0-9]+)$//;
+                               # $level -= 1;
+                               # $debug .= debug($printdebug, "[<]");
+                               # if($level>0) {
+                                       # {do{
+                                               # $ind =~ s/\.([0-9]+)$//;
+                                               # $indd = int($1)+1;
+                                               # if ($indd < $bbtree{$ind.'.e'}){
+                                                       # $ind = $ind.'.'.$indd;
+                                                       # last;
+                                               # }
+                                               # else {
+                                                       # #should not occur with a correct bbtree
+                                                       # $debug .= debug($printdebug, "[<nd]");
+                                                       # $level -= 1;
+                                               # }
+                                       # } while ($level>=0);}
+                               # }
+                               # else {
+                                       # # time to end this
+                                       # $level = -1;
+                               # }
+                       # }
+                       # else {
+                               # if($bbtree{$ind.'.c'} ne '') {
+                                       # $debug .= debug($printdebug, "tag: [".$bbtree{$ind.'.n'}."]");
+                                       # if($bbtree{$ind.'.n'} eq 'ht') {
+                                               # #
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} =~ /^(fq|tq)$/) {
+                                               # $ht = $ht.'[quote]';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'br') {
+                                               # $ht = $ht.'[color=#BB6622]';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'po') {
+                                               # $ht = $ht.'[color=#FF8800]';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'ni') {
+                                               # $ht = $ht.'[color=#0057AF]';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'url') {
+                                               # $ht = $ht.'[url='.$bbtree{$ind.'.v'}.']';
+                                       # }
+                                       # elsif($bbtree{$ind.'.n'} eq 'i') {
+                                               # $ht = $ht.'[i]';
+                                       # }
+                                       # else { #unimpl.
+                                               # $ht = $ht.'['.$bbtree{$ind.'.n'}.(($bbtree{$ind.'.v'} ne '' )?($bbtree{$ind.'.v'}):'').']';
+                                               # $debug .= debug($printdebug, "[unknown!]");
+                                       # }
+                               # }
+                               # else {
+                                       # $debug .= debug($printdebug, "unclosed tag: [".$bbtree{$ind.'.n'}."]");
+                                       # $ht = $ht.'['.$bbtree{$ind.'.n'}.']';
+                               # }
+                               # if($bbtree{$ind.'.e'}>0) {
+                                       # $ind = $ind.'.0';
+                                       # $level += 1;
+                                       # $debug .= debug($printdebug, "[>]");
+                               # }
+                               # else {
+                                       # {do{
+                                               # $ind =~ s/\.([0-9]+)$//;
+                                               # $indd = int($1)+1;
+                                               # if ($indd < $bbtree{$ind.'.e'}){
+                                                       # $ind = $ind.'.'.$indd;
+                                                       # last;
+                                               # }
+                                               # else {
+                                                       # #should not occur with a correct bbtree
+                                                       # $debug .= debug($printdebug, "[<st]");
+                                                       # $level -= 1;
+                                               # }
+                                       # } while ($level>=0);}
+                               # }
+                       # }
+               # }
+               # else {
+                       # $debug .= debug($printdebug, "unknown thing: ".$bbtree{$ind.'.t'});
+                       # #should not occur with a correct bbtree
+                       # #unless unimplemented
+                       # $ind =~ s/\.([0-9]+)$//;
+                       # $level -= 1;
+                       # $debug .= debug($printdebug, "[<ui]");
+                       # if($level>0) {
+                               # {do{
+                                       # $ind =~ s/\.([0-9]+)$//;
+                                       # $indd = int($1)+1;
+                                       # if ($indd < $bbtree{$ind.'.e'}){
+                                               # $ind = $ind.'.'.$indd;
+                                               # last;
+                                       # }
+                                       # else {
+                                               # #should not occur with a correct bbtree
+                                               # $debug .= debug($printdebug, "[<un]");
+                                               # $level -= 1;
+                                       # }
+                               # } while ($level>=0);}
+                       # }
+                       # else {
+                               # # time to end this
+                               # $level = -1;
+                       # }
+               # }
+               # $debug .= debug($printdebug, "[>$level:$ind]\n");
+       # }
+       
+       # $debug .= debug($printdebug, "-->\n");
+       # # print $debug;
+       
+       
+       
+}
+
+sub linehtml {
+       (my $ht) = @_;
+       my $esc;
+       my $ind;
+       
+       $ht =~ s/\r\n/\n/g;
+       $ht =~ s/\r/\n/g;
+       
+       while ($ht ne '') {
+               $ind = index($ht,"\n");
+               if($ind>=0){
+                       $esc = $esc.entityencode(substr($ht,0,$ind))."<br>\n";
+                       $ht=substr($ht,$ind+1);
+               }
+               else
+               {
+                       $esc = $esc.entityencode($ht);
+                       $ht = '';
+               }
+       }
+       return $esc;
+}
+
+sub debug {
+       (my $print, my $text) = @_;
+       
+       if ($print) {
+               print $text;
+       }
+       
+       return $text;
+}
+
+1
diff --git a/chat.1.c b/chat.1.c
new file mode 100644 (file)
index 0000000..2acd182
--- /dev/null
+++ b/chat.1.c
@@ -0,0 +1,34 @@
+// chat.c is generated from chat.1.c
+// 13.11.2016
+// 
+// This is the wrapper for chat.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2016  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###CHAT_PL;
+###CHAT_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(CHAT_PL_ERRLOG,"at",stderr);
+       return execve(CHAT_PL,argv,envp);
+}
diff --git a/chat.1.pl b/chat.1.pl
new file mode 100644 (file)
index 0000000..ac274ba
--- /dev/null
+++ b/chat.1.pl
@@ -0,0 +1,439 @@
+###PERL;
+#
+# /bsta/coin
+# chat.pl is generated from chat.1.pl.
+# 11.01.2017
+#
+# The coincidence interface
+#
+#    Copyright (C) 2016-2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode);
+use  File::Copy;
+
+###LOGO_PATH;
+###FAVICON_PATH;
+###COIN_PATH;
+###CHAT_PATH;
+###WEBSITE_NAME;
+###FAVICON_PATH;
+###CSS_PATH;
+###LOGO_PATH;
+###WEBSITE;
+###COINCIDENCE_PATH;
+###CGI_PATH;
+
+my %http;
+my %cgi;
+my %coin;
+my %chat;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $IP;
+my $page;
+my $words;
+my $username;
+my $action;
+my $password;
+my $chatfile;
+my $state;
+my $passwordOK;
+my @chatlines;
+my $chatstate;
+my $message;
+my $chatid;
+my $lastid;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $page=int($1);
+}
+else {
+       $page=-1;
+}
+
+if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+       $IP=$1;
+}
+else {
+       $IP='0.0.0.0';
+}
+
+if ($cgi{'words'} ne '') {
+       $words=$cgi{'words'};
+}
+if ($cgi{'username'} ne '') {
+       $username=$cgi{'username'};
+}
+if ($cgi{'join'} ne '') {
+       $action=1;
+}
+elsif ($cgi{'leave'} ne '') {
+       $action=2;
+}
+elsif ($cgi{'nopost'} ne '') {
+       $action=3;
+}
+elsif ($cgi{'file'} ne '') {
+       $action=4;
+}
+else {
+       $action=0;
+}
+if ($cgi{'p'} ne '') {
+       $password=$cgi{'p'};
+}
+
+%coin=readdatafile(COIN_PATH);
+
+if($password eq $coin{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+       $username = '';
+}
+
+if($page < 0) {
+       if (open ($chatfile,"+<",CHAT_PATH)){
+               if (flock($chatfile,2)) {
+                       %chat=readdatafile($chatfile);
+                       
+                       $chatstate=int($chat{'state'});
+                       $chatid=int($chat{'id'});
+                       $lastid=$chatid;
+                       
+                       if($action==0 && $cgi{'words'} ne '') {
+                               if($chatstate < 1 && !$passwordOK) {
+                                       $message = 'Not connected.';
+                               }
+                               else {
+                                       if ($cgi{'words'} !~ /[\r\n]/) {
+                                               if($username =~ /^[A-Za-z]*$/) {
+                                                       $chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n";
+                                                       if($chatstate < 2) {
+                                                               $chat{'state'} = 2;
+                                                               $chatstate = 2;
+                                                       }
+                                                       writedatafile($chatfile,%chat);
+                                               }
+                                               else {
+                                                       $message='Invalid username.';
+                                               }
+                                       }
+                                       else {
+                                               $message='Invalid text.';
+                                       }
+                               }
+                       }
+                       elsif ($action==1) {
+                               if($chatstate > 0 && !$passwordOK) {
+                                       $message = 'Already connected.';
+                               }
+                               else {
+                                       if($username =~ /^[A-Za-z]*$/) {
+                                               if ($passwordOK || $cgi{'words'} eq $coin{'server'}) {
+                                                       $chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n";
+                                                       if($chatstate < 1) {
+                                                               $chat{'state'} = 1;
+                                                               $chatstate = 1;
+                                                       }
+                                                       writedatafile($chatfile,%chat);
+                                               }
+                                               elsif ($cgi{'words'} eq '') {
+                                                       $message='Server ID missing.';
+                                               }
+                                               elsif ($cgi{'words'} !~ /^[0-9]+$/) {
+                                                       $message='Invalid server ID.';
+                                               }
+                                               else {
+                                                       $message='No active Coincidence server with this ID.';
+                                               }
+                                       }
+                                       else {
+                                               $message = 'Invalid username.';
+                                       }
+                               }
+                       }
+                       elsif ($action==2) {
+                               if($chatstate < 1 && !$passwordOK) {
+                                       $message = 'Already disconnected.';
+                               }
+                               else {
+                                       if($username =~ /^[A-Za-z]*$/) {
+                                               $chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n";
+                                               if($username ne '') {
+                                                       writedatafile($chatfile,%chat);
+                                               }
+                                               elsif ($chatstate > 1) {
+                                                       writedatafile(CHAT_PATH.$chatid,%chat);
+                                                       my %newchat;
+                                                       $newchat{'id'}=$chatid+1;
+                                                       $newchat{'state'}=0;
+                                                       $newchat{'content'}='';
+                                                       writedatafile($chatfile,%newchat);
+                                               }
+                                               else {
+                                                       my %newchat;
+                                                       $newchat{'id'}=$chatid;
+                                                       $newchat{'state'}=0;
+                                                       $newchat{'content'}='';
+                                                       writedatafile($chatfile,%newchat);
+                                               }
+                                       }
+                                       else {
+                                               $message = 'Invalid username.';
+                                       }
+                               }
+                       }
+                       elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' &&  $passwordOK) {
+                               if ($cgi{'words'} !~ /[\r\n]/) {
+                                       if($username =~ /^[A-Za-z]*$/) {
+                                               $chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n";
+                                               if($chatstate < 2) {
+                                                       $chat{'state'} = 2;
+                                                       $chatstate = 2;
+                                               }
+                                               writedatafile($chatfile,%chat);
+                                       }
+                                       else {
+                                               $message='Invalid username.';
+                                       }
+                               }
+                               else {
+                                       $message='Invalid text.';
+                               }
+                       }
+                       
+                       @chatlines = split(/\r?\n/,$chat{'content'});
+               }
+               else{
+                       $chatstate=0;
+                       $message='Can\'t lock data file!';
+               }
+               
+               
+               close($chatfile);
+       }
+       else {
+               $chatstate=0;
+               $message='Can\'t open data file!';
+       }
+}
+else {
+       # %chat=readdatafile(CHAT_PATH);
+       # $chatid=int($chat{'id'})-$page;
+       %chat=readdatafile(CHAT_PATH);
+       $lastid=int($chat{'id'});
+       %chat=readdatafile(CHAT_PATH.$page);
+       $chatid=int($chat{'id'});
+       $chatstate=int($chat{'state'});
+       @chatlines = split(/\r?\n/,$chat{'content'});
+}
+
+
+# print "Content-type: text/plain\n\n";
+# print CHAT_PATH."\n";
+# print 'state: '.$chat{'state'}."\n";
+# print 'id: '.$chat{'id'}."\n\n";
+# print $chat{'content'};
+
+print "Content-type: text/html\n\n";
+if($method eq 'HEAD') {
+       exit;
+}
+
+print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+print '<html lang="en"><head>'."\n";
+print '<title>Coincidence &bull; '.WEBSITE_NAME.'</title>'."\n";
+print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+print '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+print '</head><body>'."\n";
+print '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+print '<div id="all">'."\n";
+
+print '<div id="inst" class="ins">'."\n";
+
+print '<div id="title">'."\n";
+print '<H1 id="titletext">Coincidence</H1>'."\n";
+print '</div>'."\n";
+
+print '<div id="storypuzzle">'."\n";
+if ($page >= 0) {
+       print 'Before: '.$chatid."\n";
+}
+elsif ($chatstate>0){
+       print 'Connected to server <span class="br">'.entityencode($coin{'server'}).'</span> as user <span class="ni">'.entityencode(($username ne '')?$username:$coin{'name'}).'</span> (<span class="ni">'.entityencode(abbrname(($username ne '')?$username:$coin{'name'})).'</span>), public key <span class="br">'.entityencode($coin{'key'}).'</span>.'."\n";
+}
+else{
+       print 'Not connected.';
+}
+print '</div>'."\n";
+print '<div id="command">'."\n";
+if ($message ne '') {
+       print '<span class="br">'.entityencode($message).'</span>'."\n";
+}
+if ($page < 0) {
+       print '<form method="post" action="'.COINCIDENCE_PATH.'">'."\n";
+       if ($passwordOK) {
+               print '<input class="intxc" type="text" name="words">'."\n";
+               print '<input class="inbt" type="submit" value="Send">'."\n";
+               print "|\n";
+               print '<input class="intx" type="text" name="username" value="'.entityencode($username).'">'."\n";
+               print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
+               print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
+               print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
+               print '<input class="inbt" type="submit" name="file" value="Send file">'."\n";
+               print '<input type="hidden" name="p" value="'.entityencode($coin{'password'}).'">'."\n";
+       }
+       elsif ($chatstate>0) {
+               print '<input class="intxc" type="text" name="words">'."\n";
+               print '<input class="inbt" type="submit" value="Send">'."\n";
+               print "|\n";
+               print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
+               print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
+       }
+       else {
+               print '<input class="intx" type="text" name="words">'."\n";
+               print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
+       }
+       print '</form>'."\n";
+}
+print '</div>'."\n";
+
+print '</div><div id="insb" class="ins">'."\n";
+
+print '<div id="chat">'."\n";
+if ($page < 0) {
+       for (my $i = @chatlines-1; $i>=0; --$i) {
+               print chatline($chatlines[$i])."<br>\n";
+       }
+}
+else {
+       for (my $i = 0; $i<@chatlines; ++$i) {
+               print chatline($chatlines[$i])."<br>\n";
+       }
+}
+print '</div>'."\n";
+
+print '<div id="underlinks">'."\n";
+print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.COINCIDENCE_PATH.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Once again</a>';
+if ($chatid > 0) {
+       print ' | <a href="'.COINCIDENCE_PATH.'/'.($chatid-1).($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Before</a>';
+}
+if ($chatid < $lastid) {
+       print ' | <a href="'.COINCIDENCE_PATH.'/'.(($chatid < $lastid-1)?($chatid +1):'').($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Unbefore</a>';
+}
+if ($chatid > 0) {
+       print ' | <a href="'.COINCIDENCE_PATH.'/0'.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Initially</a>';
+}
+print ' | (This interface is only a demo, a proof of concept. It is very limited. No autorefresh, no private chat, etc. For full functionality use the actual Coincidence client.)'."\n";
+print "\n";
+print '</div>'."\n";
+
+print '</div>'."\n";
+print '</div>'."\n";
+print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+print '</body></html>'."\n";
+
+
+
+# print CHAT_PATH."\n";
+# print 'state: '.$chat{'state'}."\n";
+# print 'id: '.$chat{'id'}."\n\n";
+# print $chat{'content'};
+
+sub abbrname {
+       (my $name) = @_;
+       my $abbr;
+       
+       if($name !~ /^[A-Za-z]+$/) {
+               return '?';
+       }
+       
+       $abbr = uc(substr($name,0,1));
+       $name = substr($name,1);
+       while($name =~ m/([A-Z])/g) {
+               $abbr = $abbr.$1;
+       }
+       return $abbr;
+}
+
+sub chatline {
+       (my $line) = @_;
+       my $action;
+       my $name;
+       my $text;
+       
+       if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) {
+               $action=$1;
+               $name=$2;
+               $text=$3;
+               
+               if($action ne ''){
+                       if ($action eq 'join@') {
+                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.';
+                       }
+                       elsif ($action eq 'leave@') {
+                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.';
+                       }
+                       elsif ($action eq 'file@') {
+                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.';
+                       }
+                       else {
+                               return 'E:E:E';
+                       }
+               }
+               else {
+                       return '<span class="'.(($name ne '')?'br':'ni').'">'.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).'</span>';
+               }
+       }
+       else {
+               return 'E:E:E';
+       }
+}
diff --git a/config.1.txt b/config.1.txt
new file mode 100644 (file)
index 0000000..be78fb5
--- /dev/null
@@ -0,0 +1,29 @@
+# config.txt is generated from config.1.txt
+# 20.08.2016
+#
+# The file with the autogenerated configurations for Apache2 and crontab
+# 
+#    Copyright (C) 2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ! the license is for config.1.txt, not config.txt.
+
+################################################################################
+#copy this to your Apache2 configuration,
+
+###VIEWER_ALIAS;
+###FRAME_ALIAS;
+
+################################################################################
diff --git a/config.txt b/config.txt
new file mode 100644 (file)
index 0000000..9f32353
--- /dev/null
@@ -0,0 +1,29 @@
+# config.txt is generated from config.1.txt
+# 20.08.2016
+#
+# The file with the autogenerated configurations for Apache2 and crontab
+# 
+#    Copyright (C) 2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ! the license is for config.1.txt, not config.txt.
+
+################################################################################
+#copy this to your Apache2 configuration,
+
+ScriptAlias /bsta/v /eizm/bin/bsta/viewer
+ScriptAlias /bsta/v /eizm/bin/bsta/frame
+
+################################################################################
diff --git a/configure.pl b/configure.pl
new file mode 100644 (file)
index 0000000..b397ac5
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+
+# configure.pl
+# 02.07.2017
+#
+# This script is called from the makefile. It reads the settings file and
+# inserts the information in the source files.
+#
+#    Copyright (C) 2015-2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+unless ($ARGV[0]) {
+       print STDERR "Configfile missing.\n";
+       exit 1;
+}
+
+unless (open $configfile, "<", $ARGV[0]) {
+       print STDERR "Cannot open configfile\n";
+       exit 2;
+}
+
+# Read the config file, line format:
+# some_name = some value # some comment
+while (defined(my $line = <$configfile>)) {
+       $line =~ s/[\r\n]//g;
+       $line =~ s/#.*$//; #comment
+       if ($line =~ /^[ \t]*([a-zA-Z0-9_\-\.]+)[ \t]*=[ \t]*([^ \t](.*[^ \t])?)[ \t]*$/){
+               my $name=$1;
+               my $value=$2;
+               $set{$name}=$value;
+       }
+}
+close ($configfile);
+
+# Now generate things to be inserted.
+
+$def{'GZIP_PATH'}           = "use constant GZIP_PATH          => '".$set{'gzip'}."';";
+$def{'LOG_PATH'}            = "use constant LOG_PATH           => '".$set{'log_path'}."';";
+
+$def{'DATA_PATH'}           = "use constant DATA_PATH          => '".$set{'data_path'}."';";
+$def{'DEFAULT_PATH'}        = "use constant DEFAULT_PATH       => '".$set{'data_path'}."default';";
+$def{'SETTINGS_PATH'}       = "use constant SETTINGS_PATH      => '".$set{'data_path'}."settings';";
+$def{'NOACCESS_PATH'}       = "use constant NOACCESS_PATH      => '".$set{'data_path'}."noaccess';";
+$def{'STATE_PATH'}          = "use constant STATE_PATH         => '".$set{'data_path'}."state';";
+$def{'STORY_PATH'}          = "use constant STORY_PATH         => '".$set{'data_path'}."story';";
+$def{'CHAT_PATH'}           = "use constant CHAT_PATH          => '".$set{'data_path'}."chat';";
+$def{'COIN_PATH'}           = "use constant COIN_PATH          => '".$set{'data_path'}."coincidence';";
+$def{'LIST_PATH'}           = "use constant LIST_PATH          => '".$set{'data_path'}."list';";
+$def{'WWW_PATH'}            = "use constant WWW_PATH           => '".$set{'www_path'}."';";
+$def{'INDEX_PATH'}          = "use constant INDEX_PATH         => '".$set{'www_path'}."index.htm';";
+$def{'CGI_PATH'}            = "use constant CGI_PATH           => '".$set{'cgi_path'}."';";
+$def{'CSS_PATH'}            = "use constant CSS_PATH           => '".$set{'cgi_path'}."bsta.css';";
+$def{'LOGO_PATH'}           = "use constant LOGO_PATH          => '".$set{'cgi_path'}."botmlogo.png';";
+$def{'FRAME_PATH'}          = "use constant FRAME_PATH         => '".$set{'cgi_path'}."f';";
+$def{'VIEWER_PATH'}         = "use constant VIEWER_PATH        => '".$set{'cgi_path'}."v';";
+$def{'ATTACH_PATH'}         = "use constant ATTACH_PATH        => '".$set{'cgi_path'}."a';";
+$def{'INFO_PATH'}           = "use constant INFO_PATH          => '".$set{'cgi_path'}."i';";
+$def{'BBCODE_PATH'}         = "use constant BBCODE_PATH        => '".$set{'cgi_path'}."b';";
+$def{'GOTO_PATH'}           = "use constant GOTO_PATH          => '".$set{'cgi_path'}."g';";
+$def{'TWOWORDS_PATH'}       = "use constant TWOWORDS_PATH      => '".$set{'cgi_path'}."2words';";
+$def{'COINCIDENCE_PATH'}    = "use constant COINCIDENCE_PATH   => '".$set{'cgi_path'}."coin';";
+$def{'TIMER_PATH'}          = "use constant TIMER_PATH         => '".$set{'cgi_path'}."timer.js';";
+$def{'FAVICON_PATH'}        = "use constant FAVICON_PATH       => '".$set{'favicon_path'}."';";
+$def{'WEBSITE'}             = "use constant WEBSITE            => '".$set{'website'}."';";
+$def{'WEBSITE_NAME'}        = "use constant WEBSITE_NAME       => '".$set{'website_name'}."';";
+$def{'INTF_DATE'}           = "use constant INTF_DATE          => '".$set{'intf_date'}."';";
+$def{'COIN_DATE'}           = "use constant COIN_DATE          => '".$set{'coin_date'}."';";
+$def{'LOG_SIZE_LIMIT'}      = "use constant LOG_SIZE_LIMIT     => ".$set{'log_size_limit'}.";";
+$def{'LOGS_UNCOMPRESSED'}   = "use constant LOGS_UNCOMPRESSED  => ".$set{'logs_uncompressed'}.";";
+$def{'LOGS_TOTAL'}          = "use constant LOGS_TOTAL         => ".$set{'logs_total'}.";";
+$def{'STORY_LENGTH'}        = "use constant STORY_LENGTH       => ".int($set{'story_length'}).";";
+$def{'PAGE_LENGTH'}         = "use constant PAGE_LENGTH        => ".int($set{'page_length'}).";";
+$def{'FIRSTPAGE_LENGTH'}    = "use constant FIRSTPAGE_LENGTH   => ".int($set{'firstpage_length'}).";";
+
+$def{'VIEWER_ALIAS'}  = 'ScriptAlias '.$set{'cgi_path'}.'v '.$set{'bin_path'}.'viewer';
+$def{'FRAME_ALIAS' }  = 'ScriptAlias '.$set{'cgi_path'}.'v '.$set{'bin_path'}.'frame';
+
+$def{'LIB'} = "use lib '".$set{'lib_path'}."';";
+
+$def{'PATH'} = "\$ENV{'PATH'} = '".$set{'path'}."';";
+
+$def{'PERL'} = "#!".$set{'perl'};
+
+$def{'FRAME_PL'}            = '#define FRAME_PL           "'.$set{'bin_path'}.'frame.pl"';
+$def{'VIEWER_PL'}           = '#define VIEWER_PL          "'.$set{'bin_path'}.'viewer.pl"';
+$def{'ATTACH_PL'}           = '#define ATTACH_PL          "'.$set{'bin_path'}.'attach.pl"';
+$def{'TWOWORDS_PL'}         = '#define TWOWORDS_PL        "'.$set{'bin_path'}.'2words.pl"';
+$def{'CHAT_PL'}             = '#define CHAT_PL            "'.$set{'bin_path'}.'chat.pl"';
+$def{'INFO_PL'}             = '#define INFO_PL            "'.$set{'bin_path'}.'info.pl"';
+$def{'GOTO_PL'}             = '#define GOTO_PL            "'.$set{'bin_path'}.'goto.pl"';
+$def{'BBCODE_PL'}           = '#define BBCODE_PL          "'.$set{'bin_path'}.'bbcode.pl"';
+$def{'FRAME_PL_ERRLOG'}     = '#define FRAME_PL_ERRLOG    "'.$set{'log_path'}.'frame-stderr.log"';
+$def{'VIEWER_PL_ERRLOG'}    = '#define VIEWER_PL_ERRLOG   "'.$set{'log_path'}.'viewer-stderr.log"';
+$def{'ATTACH_PL_ERRLOG'}    = '#define ATTACH_PL_ERRLOG   "'.$set{'log_path'}.'attach-stderr.log"';
+$def{'TWOWORDS_PL_ERRLOG'}  = '#define TWOWORDS_PL_ERRLOG "'.$set{'log_path'}.'2words-stderr.log"';
+$def{'CHAT_PL_ERRLOG'}      = '#define CHAT_PL_ERRLOG     "'.$set{'log_path'}.'chat-stderr.log"';
+$def{'INFO_PL_ERRLOG'}      = '#define INFO_PL_ERRLOG     "'.$set{'log_path'}.'info-stderr.log"';
+$def{'GOTO_PL_ERRLOG'}      = '#define GOTO_PL_ERRLOG     "'.$set{'log_path'}.'goto-stderr.log"';
+$def{'BBCODE_PL_ERRLOG'}    = '#define BBCODE_PL_ERRLOG   "'.$set{'log_path'}.'bbcode-stderr.log"';
+
+$def{'CC'} = 'CC='.$set{'gcc'};
+$def{'CF'} = 'CF='.$set{'c_flags'};
+$def{'PL'} = 'PL='.$set{'perl'};
+$def{'MV'} = 'MV='.$set{'mv'};
+$def{'CP'} = 'CP='.$set{'cp'};
+$def{'RM'} = 'RM='.$set{'rm'};
+$def{'OD'} = 'OD='.$set{'bin_path'};
+$def{'LD'} = 'LD='.$set{'lib_path'};
+$def{'WD'} = 'WD='.$set{'www_path'};
+$def{'CM'} = 'CM='.$set{'chmod'};
+
+
+# Now go through input file, find lines to be replaced. Format:
+# ###SOME_NAME;
+# If found - replace.
+
+while (defined($line = <STDIN>)) {
+       $line =~ s/[\r\n]//g;
+       if ($line =~ /###([a-zA-Z0-9_]+);/) {
+               print "$def{$1}\n";
+       }
+       else {
+               print "$line\n";
+       }
+}
diff --git a/frame.1.c b/frame.1.c
new file mode 100644 (file)
index 0000000..0f74d7e
--- /dev/null
+++ b/frame.1.c
@@ -0,0 +1,34 @@
+// frame.c is generated from frame.1.c
+// 21.08.2016
+// 
+// This is the wrapper for frame.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2016  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###FRAME_PL;
+###FRAME_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(FRAME_PL_ERRLOG,"at",stderr);
+       return execve(FRAME_PL,argv,envp);
+}
diff --git a/frame.1.pl b/frame.1.pl
new file mode 100644 (file)
index 0000000..ad58411
--- /dev/null
@@ -0,0 +1,154 @@
+###PERL;
+#
+# /bsta/f
+# viewer.pl is generated from viewer.1.pl.
+# 19.10.2016
+#
+# The frame interface
+#
+#    Copyright (C) 2016  Balthasar Szczepañski
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+
+use bsta_lib qw(failpage gethttpheader getcgi readdatafile);
+
+###SETTINGS_PATH;
+###DEFAULT_PATH;
+###DATA_PATH;
+###STATE_PATH;
+###NOACCESS_PATH;
+
+my %http;
+my %cgi;
+my %framedata;
+my %default;
+my %settings;
+my %state;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $frame;
+my $password;
+my $passwordOK;
+my $IP;
+my $access;
+my $framepath;
+my $framefile;
+my $buffer;
+my @fileinfo;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($cgi{'f'} =~ /^(.+)$/) {
+       $frame=int($1);
+}
+elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $frame=int($1);
+}
+else {
+       $frame = 0;
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%default=readdatafile(DEFAULT_PATH);
+%state=readdatafile(STATE_PATH);
+if($frame<0) {
+       $frame = $state{'last'} + $frame +1;
+}
+%framedata=readdatafile(DATA_PATH.$frame);
+foreach my $ind (keys %default) {
+       unless(defined($framedata{$ind})){
+               $framedata{$ind}=$default{$ind};
+       }
+}
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+if ($passwordOK || (int($state{'state'}) >= 1 && $frame <= int($state{'last'}) && $frame > 0)) {
+       $access=1;
+}
+else {
+       $access=0;
+       %framedata = readdatafile(NOACCESS_PATH);
+       foreach my $ind (keys %default) {
+               unless(defined($framedata{$ind})){
+                       $framedata{$ind}=$default{$ind};
+               }
+       }
+}
+
+if($access){
+       $framepath=DATA_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+}
+else {
+       $framepath=DATA_PATH.$framedata{'frame'};
+}
+
+open($framefile,'<',$framepath) or exit failpage("Status: 404 Not Found\n","404 Not Found"," Can't open image file.");
+unless(binmode($framefile)) {
+       close($framefile);
+       exit failpage("Status: 500 Internal Server Error\n","500 Internal Server Error"," Can't switch to binary mode.");
+}
+if (my @fileinfo = stat($framepath)){
+       print 'Content-length: '.$fileinfo[7]."\n";
+}
+print 'Content-type: '.$framedata{'content-type'}."\n";
+print "\n";
+if($method ne 'HEAD'){
+       while (read ($framefile,$buffer,1024)) {
+               print (STDOUT $buffer);
+       }
+}
+close($framefile);
diff --git a/goto.1.c b/goto.1.c
new file mode 100644 (file)
index 0000000..110e0b7
--- /dev/null
+++ b/goto.1.c
@@ -0,0 +1,34 @@
+// info.c is generated from info.1.c
+// 29.06.2017
+// 
+// This is the wrapper for info.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2017  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###GOTO_PL;
+###GOTO_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(GOTO_PL_ERRLOG,"at",stderr);
+       return execve(GOTO_PL,argv,envp);
+}
diff --git a/goto.1.pl b/goto.1.pl
new file mode 100644 (file)
index 0000000..dcc2072
--- /dev/null
+++ b/goto.1.pl
@@ -0,0 +1,172 @@
+###PERL;
+#
+# /bsta/g
+# goto.pl is generated from goto.1.pl.
+# 02.07.2017
+#
+# The frame list
+#
+#    Copyright (C) 2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi readdatafile printdatafile entityencode urlencode);
+use File::Copy;
+
+###DATA_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###LIST_PATH;
+###WEBSITE_NAME;
+###FAVICON_PATH;
+###CSS_PATH;
+###LOGO_PATH;
+###WEBSITE;
+###CGI_PATH;
+###VIEWER_PATH;
+
+
+my %http;
+my %cgi;
+my %settings;
+my %state;
+my %gotolist;
+
+my @timetab;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $password;
+my $passwordOK;
+
+my $frame;
+my $last;
+my $title;
+my $ongtime;
+my $ongstate;
+my $line;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%state=readdatafile(STATE_PATH);
+%gotolist=readdatafile(LIST_PATH);
+
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+print "Content-type: text/html\n";
+print "\n";
+if($method eq 'HEAD') {
+       exit;
+}
+
+print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+print '<html lang="en"><head>'."\n";
+print '<title>GOTO &bull; '.entityencode($settings{'story'}).' &bull; '.WEBSITE_NAME.'</title>'."\n";
+print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+print '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+print '</head><body>'."\n";
+print '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+print '<div id="all">'."\n";
+
+print '<div id="inst" class="ins">'."\n";
+
+print '<div id="title">'."\n";
+print '<H1 id="titletext">'.entityencode($settings{'story'}).'</H1>'."\n";
+print '</div>'."\n";
+
+print '</div><div id="insb" class="ins">'."\n";
+
+print '<div id="chat">'."\n";
+
+$last=int($state{'last'});
+$ongstate=int($state{'state'});
+for ($frame=0; ; ++$frame) {
+       if((($frame > $last) || $ongstate<1) && !$passwordOK) {
+               last;
+       }
+       
+       $ongtime=$gotolist{'ongtime-'.$frame};
+       $title=$gotolist{'title-'.$frame};
+       if ($ongtime eq '') {
+               last;
+       }
+       @timetab=gmtime($ongtime);
+       
+       print '<span class="'.(($frame==$last && int($state{'state'}<2))?'ni':'br').'">'.sprintf('%03d',$frame).'</span> '.sprintf('%02d.%02d.%02d %02d:%02d',$timetab[3],$timetab[4]+1,$timetab[5]-100,$timetab[2],$timetab[1]).' <a href="'.VIEWER_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'').'">'.entityencode($title).'</a><br>'."\n";
+}
+
+print '</div>'."\n";
+
+print '<div id="underlinks">'."\n";
+
+print '<a href="'.CGI_PATH.'">BSTA</a>'."\n";
+
+print '</div>'."\n";
+
+print '</div>'."\n";
+
+print '</div>'."\n";
+print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+
+
+print '</body></html>'."\n";
+
+
+
+
+
+
diff --git a/info.1.c b/info.1.c
new file mode 100644 (file)
index 0000000..7f55f1e
--- /dev/null
+++ b/info.1.c
@@ -0,0 +1,34 @@
+// info.c is generated from info.1.c
+// 02.02.2017
+// 
+// This is the wrapper for info.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2017  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###INFO_PL;
+###INFO_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(INFO_PL_ERRLOG,"at",stderr);
+       return execve(INFO_PL,argv,envp);
+}
diff --git a/info.1.pl b/info.1.pl
new file mode 100644 (file)
index 0000000..8e16798
--- /dev/null
+++ b/info.1.pl
@@ -0,0 +1,181 @@
+###PERL;
+#
+# /bsta/i
+# info.pl is generated from info.1.pl.
+# 05.06.2017
+#
+# The frame/story info interface
+#
+#    Copyright (C) 2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi readdatafile printdatafile);
+use File::Copy;
+
+###DATA_PATH;
+###DEFAULT_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###NOACCESS_PATH;
+
+my %http;
+my %cgi;
+my %framedata;
+my %nextframedata;
+my %default;
+my %settings;
+my %state;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $frame;
+my $password;
+my $passwordOK;
+my $access;
+my $showcommand;
+my $ongtime;
+my $seconds;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($cgi{'f'} =~ /^(.+)$/) {
+       $frame=int($1);
+}
+elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $frame=int($1);
+}
+else {
+       $frame = '';
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+
+%settings=readdatafile(SETTINGS_PATH);
+%default=readdatafile(DEFAULT_PATH);
+%framedata=readdatafile(DATA_PATH.$frame);
+%state=readdatafile(STATE_PATH);
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+if ($frame eq '') {
+       unless($passwordOK) {
+               if ($state{'ip1'} ne '') {
+                       $state{'ip1'}=1;
+               }
+               if ($state{'ip2'} ne '') {
+                       $state{'ip2'}=1;
+               }
+               if ($state{'ip3'} ne '') {
+                       $state{'ip3'}=1;
+               }
+       }
+       print "Content-type: text/plain\n\n";
+       if($method eq 'HEAD') {
+               exit;
+       }
+       printdatafile(%state);
+}
+
+else {
+       if($frame<0) {
+               $frame = int($state{'last'}) + $frame +1;
+               %framedata=readdatafile(DATA_PATH.$frame);
+       }
+       
+       %nextframedata=readdatafile(DATA_PATH.($frame+1));
+       
+       foreach my $ind (keys %default) {
+               unless(defined($framedata{$ind})){
+                       $framedata{$ind}=$default{$ind};
+               }
+               unless(defined($nextframedata{$ind})){
+                       $nextframedata{$ind}=$default{$ind};
+               }
+       }
+       $seconds=int($state{'nextong'})-$time;
+       $ongtime=int($state{'ongtime'});
+       if($ongtime == 0) {
+               $ongtime=int($settings{'ongtime'})
+       }
+       $showcommand = ($seconds < ($ongtime*3600/3));
+       
+       if ($passwordOK || (int($state{'state'}) >= 1 && $frame <= int($state{'last'}) && $frame >= 0)) {
+               $access=1;
+               
+               if ($passwordOK || $frame<int($state{'last'}) || (int($state{'state'}) >= 2 && $showcommand)) {
+                       $framedata{'command'}=$nextframedata{'title'};
+               }
+               $framedata{'frame'}=sprintf($settings{'frame'},$frame,$framedata{'ext'});
+       }
+       else {
+               $access=0;
+               %framedata = readdatafile(NOACCESS_PATH);
+               foreach my $ind (keys %default) {
+                       unless(defined($framedata{$ind})){
+                               $framedata{$ind}=$default{$ind};
+                       }
+               }
+       }
+       
+       # $framedata{'frame'}=sprintf($settings{'frame'},$frame,$framedata{'ext'});
+       
+       print "Content-type: text/plain\n";
+       if(!$access) {
+               print "Status: 403 Forbidden\n";
+       }
+       print "\n";
+       if($method eq 'HEAD') {
+               exit;
+       }
+       printdatafile(%framedata);
+}
diff --git a/make.sh b/make.sh
new file mode 100755 (executable)
index 0000000..38d663e
--- /dev/null
+++ b/make.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# make.sh
+# 13.01.2016
+#
+# This is the script for making the software. Normally, the makefile is used for
+# this purpose. But the makefile has to be generated first.
+# This script generates the makefile and then uses it and finally removes it.
+#
+#    Copyright (C) 2015-2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+set -x
+perl configure.pl settings <makefile.1.mak >makefile
+make
+# rm makefile
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..ad0f7d2
--- /dev/null
+++ b/makefile
@@ -0,0 +1,151 @@
+# makefile is generated from makefile.1.mak.
+#  2.07.2017
+#
+# This is the makefile
+#
+#    Copyright (C) 2016 - 2017  Balthasar Szczepañski
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+CC=/usr/bin/gcc
+CF=-g -Wall
+PL=/usr/bin/perl
+CP=/bin/cp
+MV=/bin/mv
+RM=/bin/rm
+CM=/bin/chmod
+OD=/eizm/bin/bsta/
+LD=/eizm/lib/bsta/
+WD=/eizm/www/time/bsta/
+
+all: moveout copyoutwww moveoutlib remove config.txt
+
+moveout: viewer viewer.pl frame frame.pl 2words 2words.pl chat chat.pl ong.pl updlist.pl oldlogs.pl attach attach.pl info info.pl goto goto.pl bbcode bbcode.pl   setuid exec
+       $(MV) viewer viewer.pl frame frame.pl 2words 2words.pl chat chat.pl ong.pl updlist.pl oldlogs.pl attach attach.pl info info.pl goto goto.pl bbcode bbcode.pl $(OD)
+
+copyout:   setuid exec
+#      $(CP) access.pl $(OD)
+
+copyoutwww: timer.js bsta.css   setuid exec
+       $(CP) timer.js bsta.css $(WD)
+
+
+moveoutlib: bsta_lib.pm   setuid exec
+       $(MV) bsta_lib.pm $(LD)
+
+setuid: viewer frame 2words attach chat info goto bbcode
+       $(CM) u+s viewer frame 2words attach chat info goto bbcode
+
+exec: viewer.pl frame.pl 2words.pl ong.pl updlist.pl oldlogs.pl attach.pl chat.pl info.pl goto.pl bbcode.pl
+       $(CM) +x viewer.pl frame.pl 2words.pl ong.pl updlist.pl oldlogs.pl attach.pl chat.pl info.pl goto.pl bbcode.pl
+
+remove: viewer.c frame.c 2words.c chat.c attach.c info.c goto.c bbcode.c   copyout moveout setuid exec 
+       $(RM) viewer.c frame.c 2words.c chat.c attach.c info.c goto.c bbcode.c
+
+
+bsta_lib.pm: bsta_lib.1.pm configure.pl settings
+       $(PL) configure.pl settings <bsta_lib.1.pm >bsta_lib.pm
+
+
+viewer: viewer.c
+       $(CC) $(CF) -o viewer viewer.c
+
+viewer.c: viewer.1.c configure.pl settings
+       $(PL) configure.pl settings <viewer.1.c >viewer.c
+
+viewer.pl: viewer.1.pl configure.pl settings
+       $(PL) configure.pl settings <viewer.1.pl >viewer.pl
+
+
+frame: frame.c
+       $(CC) $(CF) -o frame frame.c
+
+frame.c: frame.1.c configure.pl settings
+       $(PL) configure.pl settings <frame.1.c >frame.c
+
+frame.pl: frame.1.pl configure.pl settings
+       $(PL) configure.pl settings <frame.1.pl >frame.pl
+
+
+2words: 2words.c
+       $(CC) $(CF) -o 2words 2words.c
+
+2words.c: 2words.1.c configure.pl settings
+       $(PL) configure.pl settings <2words.1.c >2words.c
+
+2words.pl: 2words.1.pl configure.pl settings
+       $(PL) configure.pl settings <2words.1.pl >2words.pl
+
+
+attach: attach.c
+       $(CC) $(CF) -o attach attach.c
+
+attach.c: attach.1.c configure.pl settings
+       $(PL) configure.pl settings <attach.1.c >attach.c
+
+attach.pl: attach.1.pl configure.pl settings
+       $(PL) configure.pl settings <attach.1.pl >attach.pl
+
+
+chat: chat.c
+       $(CC) $(CF) -o chat chat.c
+
+chat.c: chat.1.c configure.pl settings
+       $(PL) configure.pl settings <chat.1.c >chat.c
+
+chat.pl: chat.1.pl configure.pl settings
+       $(PL) configure.pl settings <chat.1.pl >chat.pl
+
+
+info: info.c
+       $(CC) $(CF) -o info info.c
+
+info.c: info.1.c configure.pl settings
+       $(PL) configure.pl settings <info.1.c >info.c
+
+info.pl: info.1.pl configure.pl settings
+       $(PL) configure.pl settings <info.1.pl >info.pl
+       
+goto: goto.c
+       $(CC) $(CF) -o goto goto.c
+
+goto.c: goto.1.c configure.pl settings
+       $(PL) configure.pl settings <goto.1.c >goto.c
+
+goto.pl: goto.1.pl configure.pl settings
+       $(PL) configure.pl settings <goto.1.pl >goto.pl
+
+
+bbcode: bbcode.c
+       $(CC) $(CF) -o bbcode bbcode.c
+
+bbcode.c: bbcode.1.c configure.pl settings
+       $(PL) configure.pl settings <bbcode.1.c >bbcode.c
+
+bbcode.pl: bbcode.1.pl configure.pl settings
+       $(PL) configure.pl settings <bbcode.1.pl >bbcode.pl
+
+
+ong.pl: ong.1.pl configure.pl settings
+       $(PL) configure.pl settings <ong.1.pl >ong.pl
+
+updlist.pl: updlist.1.pl configure.pl settings
+       $(PL) configure.pl settings <updlist.1.pl >updlist.pl
+
+oldlogs.pl: oldlogs.1.pl configure.pl settings
+       $(PL) configure.pl settings <oldlogs.1.pl >oldlogs.pl
+
+
+config.txt: config.1.txt configure.pl settings
+       $(PL) configure.pl settings <config.1.txt >config.txt
diff --git a/makefile.1.mak b/makefile.1.mak
new file mode 100644 (file)
index 0000000..cdeb46e
--- /dev/null
@@ -0,0 +1,156 @@
+# makefile is generated from makefile.1.mak.
+#  2.07.2017
+#
+# This is the makefile
+#
+#    Copyright (C) 2016 - 2017  Balthasar Szczepañski
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+###CC;
+###CF;
+###PL;
+###CP;
+###MV;
+###RM;
+###CM;
+###OD;
+###LD;
+###WD;
+
+all: moveout copyoutwww moveoutlib remove config.txt
+
+moveout: reset.pl viewer viewer.pl frame frame.pl 2words 2words.pl chat chat.pl ong.pl updlist.pl oldlogs.pl attach attach.pl info info.pl goto goto.pl bbcode bbcode.pl   setuid exec
+       $(MV) reset.pl viewer viewer.pl frame frame.pl 2words 2words.pl chat chat.pl ong.pl updlist.pl oldlogs.pl attach attach.pl info info.pl goto goto.pl bbcode bbcode.pl $(OD)
+
+copyout:   setuid exec
+#      $(CP) access.pl $(OD)
+
+copyoutwww: timer.js bsta.css   setuid exec
+       $(CP) timer.js bsta.css $(WD)
+
+
+moveoutlib: bsta_lib.pm   setuid exec
+       $(MV) bsta_lib.pm $(LD)
+
+setuid: viewer frame 2words attach chat info goto bbcode
+       $(CM) u+s viewer frame 2words attach chat info goto bbcode
+
+exec: reset.pl viewer.pl frame.pl 2words.pl ong.pl updlist.pl oldlogs.pl attach.pl chat.pl info.pl goto.pl bbcode.pl
+       $(CM) +x reset.pl viewer.pl frame.pl 2words.pl ong.pl updlist.pl oldlogs.pl attach.pl chat.pl info.pl goto.pl bbcode.pl
+
+remove: viewer.c frame.c 2words.c chat.c attach.c info.c goto.c bbcode.c   copyout moveout setuid exec 
+       $(RM) viewer.c frame.c 2words.c chat.c attach.c info.c goto.c bbcode.c
+
+
+bsta_lib.pm: bsta_lib.1.pm configure.pl settings
+       $(PL) configure.pl settings <bsta_lib.1.pm >bsta_lib.pm
+
+
+viewer: viewer.c
+       $(CC) $(CF) -o viewer viewer.c
+
+viewer.c: viewer.1.c configure.pl settings
+       $(PL) configure.pl settings <viewer.1.c >viewer.c
+
+viewer.pl: viewer.1.pl configure.pl settings
+       $(PL) configure.pl settings <viewer.1.pl >viewer.pl
+
+
+frame: frame.c
+       $(CC) $(CF) -o frame frame.c
+
+frame.c: frame.1.c configure.pl settings
+       $(PL) configure.pl settings <frame.1.c >frame.c
+
+frame.pl: frame.1.pl configure.pl settings
+       $(PL) configure.pl settings <frame.1.pl >frame.pl
+
+
+2words: 2words.c
+       $(CC) $(CF) -o 2words 2words.c
+
+2words.c: 2words.1.c configure.pl settings
+       $(PL) configure.pl settings <2words.1.c >2words.c
+
+2words.pl: 2words.1.pl configure.pl settings
+       $(PL) configure.pl settings <2words.1.pl >2words.pl
+
+
+attach: attach.c
+       $(CC) $(CF) -o attach attach.c
+
+attach.c: attach.1.c configure.pl settings
+       $(PL) configure.pl settings <attach.1.c >attach.c
+
+attach.pl: attach.1.pl configure.pl settings
+       $(PL) configure.pl settings <attach.1.pl >attach.pl
+
+
+chat: chat.c
+       $(CC) $(CF) -o chat chat.c
+
+chat.c: chat.1.c configure.pl settings
+       $(PL) configure.pl settings <chat.1.c >chat.c
+
+chat.pl: chat.1.pl configure.pl settings
+       $(PL) configure.pl settings <chat.1.pl >chat.pl
+
+
+info: info.c
+       $(CC) $(CF) -o info info.c
+
+info.c: info.1.c configure.pl settings
+       $(PL) configure.pl settings <info.1.c >info.c
+
+info.pl: info.1.pl configure.pl settings
+       $(PL) configure.pl settings <info.1.pl >info.pl
+
+
+goto: goto.c
+       $(CC) $(CF) -o goto goto.c
+
+goto.c: goto.1.c configure.pl settings
+       $(PL) configure.pl settings <goto.1.c >goto.c
+
+goto.pl: goto.1.pl configure.pl settings
+       $(PL) configure.pl settings <goto.1.pl >goto.pl
+
+
+bbcode: bbcode.c
+       $(CC) $(CF) -o bbcode bbcode.c
+
+bbcode.c: bbcode.1.c configure.pl settings
+       $(PL) configure.pl settings <bbcode.1.c >bbcode.c
+
+bbcode.pl: bbcode.1.pl configure.pl settings
+       $(PL) configure.pl settings <bbcode.1.pl >bbcode.pl
+
+
+ong.pl: ong.1.pl configure.pl settings
+       $(PL) configure.pl settings <ong.1.pl >ong.pl
+
+updlist.pl: updlist.1.pl configure.pl settings
+       $(PL) configure.pl settings <updlist.1.pl >updlist.pl
+
+oldlogs.pl: oldlogs.1.pl configure.pl settings
+       $(PL) configure.pl settings <oldlogs.1.pl >oldlogs.pl
+
+reset.pl: reset.1.pl configure.pl settings
+       $(PL) configure.pl settings <reset.1.pl >reset.pl
+
+
+
+config.txt: config.1.txt configure.pl settings
+       $(PL) configure.pl settings <config.1.txt >config.txt
diff --git a/oldlogs.1.pl b/oldlogs.1.pl
new file mode 100644 (file)
index 0000000..67fca65
--- /dev/null
@@ -0,0 +1,119 @@
+###PERL;
+
+# oldlogs is generated from oldlogs.1.pl.
+# 02.01.2016
+#
+# This script renames log files if they are big enough.
+# Compresses or removes older log files.
+#
+#    Copyright (C) 2015-2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+###GZIP_PATH;
+###LOG_PATH;
+###LOG_SIZE_LIMIT;
+###LOGS_TOTAL;
+###LOGS_UNCOMPRESSED;
+
+if ($ARGV[0] ne '') {
+       $log_path = $ARGV[0];
+}
+else {
+       $log_path = LOG_PATH;
+}
+if ($ARGV[1] =~ /^([0-9]+)$/) {
+       $log_size_limit = $1;
+}
+else {
+       $log_size_limit = LOG_SIZE_LIMIT;
+}
+if ($ARGV[2] =~ /^([0-9]+)$/) {
+       $logs_total = $1;
+}
+else {
+       $logs_total = LOGS_TOTAL;
+}
+if ($ARGV[3] =~ /^([0-9]+)$/) {
+       $logs_uncompressed = $1;
+}
+else {
+       $logs_uncompressed = LOGS_UNCOMPRESSED;
+}
+
+if ( opendir ($dir, $log_path)) {
+       while ($subpath = readdir $dir) {
+               if ($subpath !~ /\.log$/) {
+                       next;
+               }
+               $fullpath=$log_path.$subpath;
+               unless (-f $fullpath) {
+                       next;
+               }
+               unless (@stat = stat($fullpath)) {
+                       next;
+               }
+               if ($stat[7] > $log_size_limit) {
+                       movelog($fullpath,0,0);
+               }
+               
+       }
+       closedir($dir);
+}
+
+sub movelog {
+       (my $path, my $number, my $gz) = @_;
+       my $nextgz = 0;
+       my $thispath;
+       my $nextpath;
+       my $nextnumber=$number+1;
+       my @gzip_arg = (GZIP_PATH, '-q', '-9','-f');
+       
+       $thispath = $path.(($number != 0)?'.'.$number.($gz?'.gz':''):'');
+       if ($number == $logs_total) {
+               if (unlink $thispath) {
+                       return 1;
+               }
+               else {
+                       return 0;
+               }
+       }
+       if ($number == $logs_uncompressed) {
+               $nextgz=1;
+               $nextpath = $path.'.'.$nextnumber.'.gz';
+       }
+       else {
+               $nextpath = $path.'.'.$nextnumber.($gz?'.gz':'');
+       }
+       
+       if (-e $nextpath) {
+               unless (movelog($path,$nextnumber,($nextgz or $gz)?1:0)) {
+                       return 0;
+               }
+       }
+       
+       if ($nextgz) {
+               push @gzip_arg, $thispath;
+               unless (! system (@gzip_arg)) {
+                       return 0;
+               }
+               $thispath .= '.gz';
+       }
+       
+       unless (rename ($thispath, $nextpath)) {
+               return 0;
+       }
+       return 1;
+}
diff --git a/ong.1.pl b/ong.1.pl
new file mode 100644 (file)
index 0000000..8da5522
--- /dev/null
+++ b/ong.1.pl
@@ -0,0 +1,155 @@
+###PERL;
+#
+# ong.pl is generated from ong.1.pl.
+# 29.06.2017
+#
+# The ONG bot
+#
+#    Copyright (C) 2016-2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(entityencode readdatafile writedatafile urlencode);
+use File::Copy;
+
+###DATA_PATH;
+###WWW_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###DEFAULT_PATH;
+###LIST_PATH;
+
+my %framedata;
+my %nextframedata;
+my %default;
+my %settings;
+my %state;
+my %gotolist;
+
+my $statefile;
+my $ongstate;
+my $frame;
+my $nextong;
+my $inpath;
+my $outpath;
+my $ongtime;
+my $static;
+my $dynamic;
+my $last;
+
+my $time = time();
+srand ($time-$$);
+$ongtime = int($time / 3600) * 3600;
+
+print $time.' - '.$ongtime."\n";
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+
+if (open ($statefile,"+<",STATE_PATH)){
+       if (flock($statefile,2)) {
+               %state=readdatafile($statefile);
+               $ongstate=int($state{'state'});
+               print 'state: '.$ongstate."\n";
+               if($ongstate > 0) {
+                       $nextong = int($state{'nextong'});
+                       print 'ongtime: '.$nextong."\n";
+                       
+                       if($ongtime >= $nextong) {
+                               %settings=readdatafile(SETTINGS_PATH);
+                               $static=int($settings{'ongtime'});
+                               $dynamic=int($settings{'dynamicongtime'});
+                               $last=int($settings{'last'});
+                               $frame=int($state{'last'})+1;
+                               
+                               if($dynamic > 0 && $frame < $last) {
+                                       $dynamic = int($dynamic/($last-$frame));
+                               }
+                               else {
+                                       $dynamic=0;
+                               }
+                               
+                               if($static>$dynamic){
+                                       $dynamic=$static;
+                               }
+                               $nextong=$ongtime+($dynamic*3600);
+                               $state{'nextong'}=$nextong;
+                               print 'next ongtime: '.$nextong.' (+'.$dynamic.")\n";
+                               $state{'ongtime'}=$dynamic;
+                               
+                               if($ongstate == 2) {
+                                       print 'next frame: '.$frame."\n";
+                                       %framedata=readdatafile(DATA_PATH.$frame);
+                                       %default=readdatafile(DEFAULT_PATH);
+                                       %gotolist=readdatafile(LIST_PATH);
+                                       
+                                       $framedata{'ongtime'}=$time;
+                                       $framedata{'timer'}=$dynamic;
+                                       $gotolist{'title-'.$frame}=$framedata{'title'};
+                                       $gotolist{'ongtime-'.$frame}=$framedata{'ongtime'};
+                                       writedatafile(DATA_PATH.$frame,%framedata);
+                                       writedatafile(LIST_PATH,%gotolist);
+                                       
+                                       foreach my $ind (keys %default) {
+                                               unless(defined($framedata{$ind})){
+                                                       $framedata{$ind}=$default{$ind};
+                                               }
+                                               unless(defined($nextframedata{$ind})){
+                                                       $nextframedata{$ind}=$default{$ind};
+                                               }
+                                       }
+                                       
+                                       $inpath = DATA_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+                                       $outpath = WWW_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+                                       
+                                       print $inpath.' -> '.$outpath."\n";
+                                       
+                                       # ALSO-ATTACHMENTS!!! (here)
+                                       
+                                       if(copy ($inpath, $outpath)) {
+                                               $state{'last'}=$frame;
+                                               $state{'state'}=1;
+                                               $state{'ip1'}='';
+                                               $state{'ip2'}='';
+                                               $state{'ip3'}='';
+                                               $state{'ongtime'}=$dynamic;
+                                               print "ONG\n";
+                                       }
+                                       else {
+                                               print "NO ONG\n";
+                                       }
+                               }
+                               writedatafile($statefile,%state);
+                       }
+                       else {
+                               print "WAIT\n";
+                       }
+               }
+               else {
+                       print "INACTIVE\n";
+               }
+       }
+       else {
+               print "NO STATELOCK\n";
+       }
+       close ($statefile);
+}
+else {
+       print "NO STATEFILE\n";
+}
+print "\n";
\ No newline at end of file
diff --git a/ongtimes.pl b/ongtimes.pl
new file mode 100755 (executable)
index 0000000..e44ff45
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+# 01.02.2017
+
+use strict;
+#use warnings;
+use lib '/eizm/lib/bstatest/';
+use bsta_lib qw(readdatafile writedatafile);
+
+use constant DATA_PATH          => '/eizm/data/bsta/';
+use constant SETTINGS_PATH      => '/eizm/data/bsta/settings';
+use constant STATE_PATH         => '/eizm/data/bsta/state';
+
+my %framedata;
+my %settings;
+my %state;
+
+my $last;
+my $ongtime;
+my @timetab;
+my $timer;
+my $prevongtime;
+my $cleantimer;
+my $nicetime;
+
+%state=readdatafile(STATE_PATH);
+$last=int($state{'last'});
+
+for (my $f=$last; $f>=0; --$f) {
+       %framedata=readdatafile(DATA_PATH.$f);\r $ongtime=int($framedata{'ongtime'});
+       @timetab=gmtime($ongtime);
+       $nicetime=sprintf('UTC %04d.%02d.%02d %02d:%02d:%02d',$timetab[5]+1900,$timetab[4]+1,$timetab[3],$timetab[2],$timetab[1],$timetab[0]);
+       
+       if($framedata{'timer'}ne'') {
+               $timer=int($framedata{'timer'});
+               $cleantimer=$timer;
+       }
+       # elsif($f<$last) {
+               # $timer=($prevongtime-$ongtime)/3600;
+               # $cleantimer=0-int($timer+0.5);
+               # $framedata{'timer'}=0-$cleantimer;
+               # writedatafile(DATA_PATH.$f,%framedata);
+       # }
+       
+       print "$f\t$nicetime\t$ongtime\t\t$timer\n";
+       $prevongtime=$ongtime;
+}
+for (my $f=0; $f<=$last; ++$f) {
+       
+}
\ No newline at end of file
diff --git a/reset.1.pl b/reset.1.pl
new file mode 100644 (file)
index 0000000..353814b
--- /dev/null
@@ -0,0 +1,116 @@
+###PERL;
+#
+# /bsta/2words
+# reset is generated from reset.1.pl.
+# 26.03.2018
+#
+# Reset BSTA state
+#
+#    Copyright (C) 2016 - 2018  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(writedatafile);
+
+###STORY_PATH;
+###WEBSITE;
+###CGI_PATH;
+###INDEX_PATH;
+###INTF_DATE;
+###STATE_PATH;
+###COIN_DATE;
+###CHAT_PATH;
+
+my %story = (
+       'id'      => '0',
+       'letter'  => '',
+       'lastip'  => '0.0.0.0',
+       'content' => '',
+       'pass'    => '0',
+       'state'   => '0'
+);
+my %state = (
+       'state' => '0',
+       'last'  => '0',
+       'ongtime' => '',
+       'nextong' => '',
+       'ip1' => '',
+       'ip2' => '',
+       'ip3' => ''
+);
+my %chat = (
+       'id'      => '0',
+       'state'   => '0',
+       'content' => ''
+);
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+writedatafile(STATE_PATH,%state);
+writedatafile(STORY_PATH,%story);
+writedatafile(CHAT_PATH,%chat);
+
+writeindex(INDEX_PATH);
+
+#function borrowed from 2words.pl - keep consistent!
+sub writeindex {
+       (my $indexpath) = @_;
+       my $indexfile;
+       my $indexof;
+               
+       if(ref($indexpath)) {
+               $indexfile=$indexpath;
+               unless (seek($indexfile, 0, 0)) {
+                       return 0;
+               }
+       }
+       else {
+               unless (open ($indexfile, ">", $indexpath)) {
+                       return 0;
+               }
+       }
+       
+       $indexof = CGI_PATH;
+       $indexof =~ s/\/$//g;
+       
+       print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'."\n";
+       print $indexfile '<html>'."\n";
+       print $indexfile ' <head>'."\n";
+       print $indexfile '  <title>Index of '.$indexof.'</title>'."\n";
+       print $indexfile ' </head>'."\n";
+       print $indexfile ' <body>'."\n";
+       print $indexfile '<h1>Index of '.$indexof.'</h1>'."\n";
+       print $indexfile '<table><tr><th><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr><tr><th colspan="5"><hr></th></tr>'."\n";
+       print $indexfile '<tr><td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td><td><a href="/">Parent Directory</a></td><td>&nbsp;</td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
+       print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="2words/">2words/</a></td><td align="right">'.INTF_DATE.'  </td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
+       print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="coin/">coin/</a></td><td align="right">'.COIN_DATE.'  </td><td align="right">  - </td><td> Coincidence </td></tr>'."\n";
+       print $indexfile '<tr><th colspan="5"><hr></th></tr>'."\n";
+       print $indexfile '</table>'."\n";
+       print $indexfile '<address>Apache/2.2.22 (Debian) Server at '.WEBSITE.' Port 80</address>'."\n";
+       print $indexfile '</body></html>'."\n";
+       
+       
+       unless (ref($indexpath)) {
+               close ($indexfile);
+       }
+       else {
+               truncate ($indexfile , tell($indexfile));
+       }
+       
+       return 1;
+}
diff --git a/settings b/settings
new file mode 100644 (file)
index 0000000..65579ae
--- /dev/null
+++ b/settings
@@ -0,0 +1,64 @@
+# settings
+# 19.11.2016
+#
+# In this file are defined values specific for the user's system
+# 
+#    Copyright (C) 2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#all directory paths must end with '/' and must already exist.
+
+bin_path  = /eizm/bin/bsta/  #Where the software will be located
+data_path = /eizm/data/bsta/ #where the software  will remember data; subdir:
+                                  #group, groupsettings
+log_path  = /eizm/log/bsta/  #where the software will remember data
+tmp_path  = /eizm/tmp/bsta/  #for temporary fies
+www_path  = /eizm/www/time/bsta/  #for the www server
+lib_path  = /eizm/lib/bsta/
+cgi_path  = /bsta/
+
+path    = /usr/local/bin:/usr/bin:/bin #The path environment variable. Must be
+                                       #overwritten if SETUID. Otherwise
+                                       #launching programs may fail. (Perl
+                                       #security...)
+
+
+#paths to software
+perl    = /usr/bin/perl
+chmod   = /bin/chmod
+cp      = /bin/cp
+mv      = /bin/mv
+rm      = /bin/rm
+gcc     = /usr/bin/gcc
+gzip    = /bin/gzip
+c_flags = -g -Wall
+
+log_size_limit    = 65536 # How big can a log file be
+logs_uncompressed =     2 # How many uncompressed old logs to keep
+logs_total        =    10 # How many old logs to keep 
+
+rm_access_crontab = 10 2 * * * # How often to remove leftover unlock info.
+oldlogs_crontab   =  0 2 * * * # How often to deal with old logs
+bot_crontab       = 20 2 * * * # How often to run the bot
+
+website = 1190.bicyclesonthemoon.info
+website_name = Bicycles on the Moon
+favicon_path = /img/favicon.png
+intf_date = 28-Sep-2016 20:34
+coin_date = 13-Nov-2016 22:15
+
+story_length = 16
+firstpage_length = 4
+page_length = 16
diff --git a/settings-again b/settings-again
new file mode 100644 (file)
index 0000000..ee732f9
--- /dev/null
@@ -0,0 +1,64 @@
+# settings
+# 19.11.2016
+#
+# In this file are defined values specific for the user's system
+# 
+#    Copyright (C) 2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#all directory paths must end with '/' and must already exist.
+
+bin_path  = /eizm/bin/bstagain/  #Where the software will be located
+data_path = /eizm/data/bstagain/ #where the software  will remember data; subdir:
+                                  #group, groupsettings
+log_path  = /eizm/log/bstagain/  #where the software will remember data
+tmp_path  = /eizm/tmp/bstagain/  #for temporary fies
+www_path  = /eizm/www/time/bstagain/  #for the www server
+lib_path  = /eizm/lib/bstagain/
+cgi_path  = /bstagain/
+
+path    = /usr/local/bin:/usr/bin:/bin #The path environment variable. Must be
+                                       #overwritten if SETUID. Otherwise
+                                       #launching programs may fail. (Perl
+                                       #security...)
+
+
+#paths to software
+perl    = /usr/bin/perl
+chmod   = /bin/chmod
+cp      = /bin/cp
+mv      = /bin/mv
+rm      = /bin/rm
+gcc     = /usr/bin/gcc
+gzip    = /bin/gzip
+c_flags = -g -Wall
+
+log_size_limit    = 65536 # How big can a log file be
+logs_uncompressed =     2 # How many uncompressed old logs to keep
+logs_total        =    10 # How many old logs to keep 
+
+rm_access_crontab = 10 2 * * * # How often to remove leftover unlock info.
+oldlogs_crontab   =  0 2 * * * # How often to deal with old logs
+bot_crontab       = 20 2 * * * # How often to run the bot
+
+website = 1190.bicyclesonthemoon.info
+website_name = Bicycles on the Moon
+favicon_path = /img/favicon.png
+intf_date = 28-Sep-2016 20:34
+coin_date = 13-Nov-2016 22:15
+
+story_length = 16
+firstpage_length = 4
+page_length = 16
diff --git a/settings-test b/settings-test
new file mode 100644 (file)
index 0000000..93bb2de
--- /dev/null
@@ -0,0 +1,64 @@
+# settings
+# 19.11.2016
+#
+# In this file are defined values specific for the user's system
+# 
+#    Copyright (C) 2016  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#all directory paths must end with '/' and must already exist.
+
+bin_path  = /eizm/bin/bstatest/  #Where the software will be located
+data_path = /eizm/data/bstatest/ #where the software  will remember data; subdir:
+                                  #group, groupsettings
+log_path  = /eizm/log/bstatest/  #where the software will remember data
+tmp_path  = /eizm/tmp/bstatest/  #for temporary fies
+www_path  = /eizm/www/time/tbst/  #for the www server
+lib_path  = /eizm/lib/bstatest/
+cgi_path  = /tbst/
+
+path    = /usr/local/bin:/usr/bin:/bin #The path environment variable. Must be
+                                       #overwritten if SETUID. Otherwise
+                                       #launching programs may fail. (Perl
+                                       #security...)
+
+
+#paths to software
+perl    = /usr/bin/perl
+chmod   = /bin/chmod
+cp      = /bin/cp
+mv      = /bin/mv
+rm      = /bin/rm
+gcc     = /usr/bin/gcc
+gzip    = /bin/gzip
+c_flags = -g -Wall
+
+log_size_limit    = 65536 # How big can a log file be
+logs_uncompressed =     2 # How many uncompressed old logs to keep
+logs_total        =    10 # How many old logs to keep 
+
+rm_access_crontab = 10 2 * * * # How often to remove leftover unlock info.
+oldlogs_crontab   =  0 2 * * * # How often to deal with old logs
+bot_crontab       = 20 2 * * * # How often to run the bot
+
+website = 1190.bicyclesonthemoon.info
+website_name = Bicycles on the Moon
+favicon_path = /img/favicon.png
+intf_date = 28-Sep-2016 20:34
+coin_date = 13-Nov-2016 22:15
+
+story_length = 16
+firstpage_length = 4
+page_length = 16
diff --git a/testhtml.pl b/testhtml.pl
new file mode 100644 (file)
index 0000000..97f72fb
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+use lib '/eizm/lib/bstatest/';
+use strict;
+use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile printdatafileht urlencode bb2ht bb2bb linehtml);
+
+my $bb;
+my $ht;
+
+$bb="[fq]<fq>[/fq][tq]<tq>[/tq][ni]<ni>[/ni][br]<br>[/br][po]<po>[/po][url=<url>]<url>[/url][i]<i>[/i]<teqt>\n";
+$ht=bb2bb($bb,1);
+print $ht."\n";
+$ht=bb2ht($bb,1);
+print $ht."\n";
+
+# print ht;
diff --git a/timer.js b/timer.js
new file mode 100644 (file)
index 0000000..7ca7807
--- /dev/null
+++ b/timer.js
@@ -0,0 +1,85 @@
+// timer.js
+//  3.07.2017
+// 
+// The countdown script.
+//
+//    Copyright (C) 2017  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+window.onload = function () {
+       var ongh;
+       var ongm;
+       var ongs;
+       var ongvh;
+       var ongvm;
+       var ongvs;
+       var timer;
+       
+       ongh = document.getElementById("ongh");
+       ongm = document.getElementById("ongm");
+       ongs = document.getElementById("ongs");
+       
+       if(ongh == null || ongm == null || ongs == null) {
+               // window.alert("NUL");
+       }
+       else {
+               timer = setInterval (function() {
+                       ongvh = +(ongh.innerHTML);
+                       ongvm = +(ongm.innerHTML);
+                       ongvs = +(ongs.innerHTML);
+                       
+                       if(isNaN(ongvh) || isNaN(ongvm) || isNaN(ongvs)) {
+                               // window.alert("NAN");
+                               clearInterval(timer);
+                               return;
+                       }
+                       
+                       if(ongvs == 0) {
+                               ongvs = 59;
+                               if(ongvm == 0) {
+                                       ongvm == 59;
+                                       if(ongvh ==0) {
+                                               ongvm = 0;
+                                               ongvs = 0;
+                                       }
+                                       else {
+                                               ongvh -= 1;
+                                       }
+                               }
+                               else {
+                                       ongvm -= 1;
+                               }
+                       }
+                       else {
+                               ongvs -= 1;
+                       }
+                       
+                       if(ongvh == 0 && ongvm == 0 && ongvs == 0) {
+                               ongh.innerHTML = "00";
+                               ongm.innerHTML = "00";
+                               ongs.innerHTML = "NG";
+                               // window.alert("ONG");
+                               clearInterval(timer);
+                               return;
+                       }
+                       else {
+                               ongh.innerHTML = ((ongvh<10)?"0":"")+ongvh;
+                               ongm.innerHTML = ((ongvm<10)?"0":"")+ongvm;
+                               ongs.innerHTML = ((ongvs<10)?"0":"")+ongvs;
+                       }
+                       
+               }, 1000);
+       }
+};
diff --git a/updlist.1.pl b/updlist.1.pl
new file mode 100644 (file)
index 0000000..8e8e6a2
--- /dev/null
@@ -0,0 +1,55 @@
+###PERL;
+#
+# updlist.pl is generated from updlist.1.pl.
+# 29.06.2017
+#
+# The framelist update bot
+#
+#    Copyright (C) 2016-2017  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(readdatafile writedatafile);
+use File::Copy;
+
+###DATA_PATH;
+###WWW_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###DEFAULT_PATH;
+###LIST_PATH;
+
+my %framedata;
+my %gotolist;
+
+my $frame;
+
+%gotolist=readdatafile(LIST_PATH);
+
+for($frame=0; ; ++$frame) {
+       %framedata=readdatafile(DATA_PATH.$frame);
+       if($framedata{'ongtime'} eq '') {
+               last;
+       }
+       
+       print $frame.' '.$framedata{'ongtime'}.' '.$framedata{'title'}."\n";
+       
+       $gotolist{'title-'.$frame}=$framedata{'title'};
+       $gotolist{'ongtime-'.$frame}=$framedata{'ongtime'};
+}
+
+writedatafile (LIST_PATH,%gotolist);
diff --git a/viewer.1.c b/viewer.1.c
new file mode 100644 (file)
index 0000000..2a3f1a6
--- /dev/null
@@ -0,0 +1,34 @@
+// viewer.c is generated from viewer.1.c
+// 20.08.2016
+// 
+// This is the wrapper for viewer.pl.
+// It's run with SETUID to have accesss to some files where the www server
+// should not. That's why it has a C wrapper. In modern systems running scripts
+// directly with SETUID is considered unsafe and not allowed.
+//
+//    Copyright (C) 2016  Balthasar Szczepański
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU Affero General Public License as
+//    published by the Free Software Foundation, either version 3 of the
+//    License, or (at your option) any later version.
+//
+//    This 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 Affero General Public License for more details.
+//
+//    You should have received a copy of the GNU Affero General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <unistd.h>
+#include <stdio.h>
+
+###VIEWER_PL;
+###VIEWER_PL_ERRLOG;
+
+int main(int argc, char *argv[], char *envp[])
+{
+       freopen(VIEWER_PL_ERRLOG,"at",stderr);
+       return execve(VIEWER_PL,argv,envp);
+}
diff --git a/viewer.1.pl b/viewer.1.pl
new file mode 100644 (file)
index 0000000..7afe8a1
--- /dev/null
@@ -0,0 +1,627 @@
+###PERL;
+#
+# /bsta/v
+# viewer is generated from viewer.1.pl.
+# 09.01.2023
+#
+# The viewer interface
+#
+#    Copyright (C) 2016-2017, 2019-2020, 2023  Balthasar Szczepański
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This 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 Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+#use warnings;
+###LIB;
+use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile printdatafileht urlencode bb2ht bb2bb linehtml);
+use File::Copy;
+
+###DATA_PATH;
+###DEFAULT_PATH;
+###SETTINGS_PATH;
+###STATE_PATH;
+###LOGO_PATH;
+###FAVICON_PATH;
+###WEBSITE;
+###WEBSITE_NAME;
+###CSS_PATH;
+###CGI_PATH;
+###FRAME_PATH;
+###VIEWER_PATH;
+###NOACCESS_PATH;
+###STORY_PATH;
+###WWW_PATH;
+###INDEX_PATH;
+###ATTACH_PATH;
+###INFO_PATH;
+###BBCODE_PATH;
+###GOTO_PATH;
+###LIST_PATH;
+###TIMER_PATH;
+
+my %http;
+my %cgi;
+my %framedata;
+my %nextframedata;
+my %default;
+my %settings;
+my %state;
+my %newstate;
+my %gotolist;
+
+my $time = time();
+srand ($time-$$);
+
+my $method;
+my $frame;
+my $password;
+my $passwordOK;
+my $IP;
+my $access;
+my $seconds;
+my $minutes;
+my $hours;
+my $statefile;
+my $showcommand;
+my $ongtime;
+my $textmode;
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+###PATH;
+
+if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
+       $method=$1;
+}
+else{
+       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+}
+
+%http = gethttpheader (\%ENV);
+%cgi = getcgi($ENV{'QUERY_STRING'});
+
+if ($method eq 'POST') {
+       if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
+               my %cgipost=getcgi( <STDIN> );
+               foreach my $ind (keys %cgipost) {
+                       $cgi{$ind}=$cgipost{$ind};
+               }
+       }
+       # multipart not supported
+       else{
+               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+       }
+}
+
+if ($ENV{'HTTP_X_FORWARDED_FOR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+       $IP=$1;
+}
+elsif ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
+       $IP=$1;
+}
+else {
+       $IP='0.0.0.0';
+}
+
+if ($cgi{'f'} =~ /^(.+)$/) {
+       $frame=int($1);
+}
+elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
+       $frame=int($1);
+}
+else {
+       $frame = 0;
+}
+
+if ($cgi{'p'} =~ /^(.+)$/) {
+       $password=$1;
+}
+else {
+       $password='';
+}
+# print "Content-type: text/plain\n\n";
+
+%settings=readdatafile(SETTINGS_PATH);
+%default=readdatafile(DEFAULT_PATH);
+%framedata=readdatafile(DATA_PATH.$frame);
+if($password eq $settings{'password'}){
+       $passwordOK = 1;
+}
+else{
+       $passwordOK = 0;
+}
+
+if (open ($statefile,"+<",STATE_PATH)){
+       if (flock($statefile,2)) {
+               
+               %state=readdatafile($statefile);
+               
+               if($frame<0) {
+                       $frame = int($state{'last'}) + $frame +1;
+                       %framedata=readdatafile(DATA_PATH.$frame);
+               }
+               
+               if(int($state{'state'})==1 && $frame == int($state{'last'}) && $method ne 'HEAD' && !$passwordOK){
+                       my %newstate=%state;
+                       if($state{'ip1'} ne $IP) {
+                               if ($state{'ip1'} eq '') {
+                                       $newstate{'ip1'} = $IP;
+                                       writedatafile($statefile,%newstate);
+                               }
+                               elsif($state{'ip2'} ne $IP) {
+                                       if ($state{'ip2'} eq '') {
+                                               $newstate{'ip2'} = $IP;
+                                               writedatafile($statefile,%newstate);
+                                       }
+                                       else {
+                                               $newstate{'state'}=2;
+                                               $newstate{'ip3'} = $IP;
+                                               writedatafile($statefile,%newstate);
+                                       }
+                               }
+                       }
+               }
+               elsif(int($state{'state'})==0 && $frame == 1) {
+                       my %story;
+                       my $inpath;
+                       my $outpath;
+                       
+                       %story = readdatafile(STORY_PATH);
+                       %gotolist=readdatafile(LIST_PATH);
+                       if(int($story{'state'}) == 17 && int($story{'pass'}) == 1) {
+                               #ACTIVATE!
+                               
+                               $framedata{'ongtime'} = $time;
+                               writedatafile(DATA_PATH.$frame,%framedata);
+                               $state{'state'} = 1;
+                               $state{'last'} = 1;
+                               $state {'ip1'} = '0.0.0.0';
+                               $state {'ip2'} = '0.0.0.0';
+                               $state {'ip3'} = '';
+                               $state {'nextong'} = (int($time / 3600) + int($settings{'firstongtime'})) * 3600 ;
+                               $state{'ongtime'} = int($settings{'firstongtime'});
+                               
+                               unless(defined($framedata{'ext'})){
+                                       $framedata{'ext'}=$default{'ext'};
+                               }
+                               
+                               $inpath = DATA_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+                               $outpath = WWW_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+                               
+                               $gotolist{'title-1'}=$framedata{'title'};
+                               $gotolist{'ongtime-1'}=$framedata{'ongtime'};
+                               
+                               if(copy ($inpath, $outpath)) {
+                                       writeindex(INDEX_PATH);
+                                       writedatafile($statefile,%state);
+                                       writedatafile(LIST_PATH,%gotolist);
+                               }
+                               else {
+                                       $state{'state'} = 0;
+                               }
+                       }
+               }
+       }
+       else {
+               $state{'state'} = 0;
+       }
+       close ($statefile);
+}
+else {
+       $state{'state'} = 0;
+}
+
+%nextframedata=readdatafile(DATA_PATH.($frame+1));
+foreach my $ind (keys %default) {
+       unless(defined($framedata{$ind})){
+               $framedata{$ind}=$default{$ind};
+       }
+       unless(defined($nextframedata{$ind})){
+               $nextframedata{$ind}=$default{$ind};
+       }
+}
+
+$hours=int($state{'nextong'})-$time;
+$ongtime=int($state{'ongtime'});
+if($ongtime == 0) {
+       $ongtime=int($settings{'ongtime'})
+}
+
+$showcommand = ($hours < ($ongtime*3600/3));
+if($hours>0){
+       $seconds = sprintf('%02d',$hours % 60);
+       $hours = int($hours/60);
+       $minutes = sprintf('%02d',$hours % 60);
+       $hours = sprintf('%02d',int($hours/60));
+}
+elsif(($hours>=-15) && (int($state{'state'}) == 2)){
+       $seconds='NG';
+       $minutes='00';
+       $hours='00';
+}
+else{
+       $seconds='EE';
+       $minutes='EE';
+       $hours='EE';
+}
+
+
+if ($passwordOK || (int($state{'state'}) >= 1 && $frame <= int($state{'last'}) && $frame >= 0)) {
+       $access=1;
+}
+else {
+       $access=0;
+       %framedata = readdatafile(NOACCESS_PATH);
+       foreach my $ind (keys %default) {
+               unless(defined($framedata{$ind})){
+                       $framedata{$ind}=$default{$ind};
+               }
+       }
+}
+
+$textmode = int($cgi{'b'});
+if($textmode > 2) {
+       $textmode = 0;
+}
+
+# print "Content-type: text/plain\n\n";
+# print 'frame='.$frame."\n";
+# print 'password='.$password."\n";
+# print 'passwordOK='.$passwordOK."\n";
+# print 'access='.$access."\n";
+# print "\n>>>ENV<<<\n\n";
+# foreach my $ind (keys %ENV) {
+       # print $ind.'='.$ENV{$ind}."\n";
+# }
+# print "\n>>>HTTP<<<\n\n";
+# foreach my $ind (keys %http) {
+       # print $ind.': '.$http{$ind}."\n";
+# }
+# print "\n>>>CGI<<<\n\n";
+# foreach my $ind (keys %cgi) {
+       # print $ind.'='.$cgi{$ind}."\n";
+# }
+# print "\n>>>FRAMEDATA<<<\n\n";
+# foreach my $ind (keys %framedata) {
+       # print $ind.': '.$framedata{$ind}."\n";
+# }
+# print "\n>>>NEXTFRAMEDATA<<<\n\n";
+# foreach my $ind (keys %nextframedata) {
+       # print $ind.': '.$nextframedata{$ind}."\n";
+# }
+# print "\n>>>SETTINGS<<<\n\n";
+# foreach my $ind (keys %settings) {
+       # print $ind.': '.$settings{$ind}."\n";
+# }
+# print "\n>>>STATE<<<\n\n";
+# foreach my $ind (keys %state) {
+       # print $ind.': '.$state{$ind}."\n";
+# }
+
+print "Content-type: text/html\n";
+if(!$access) {
+       print "Status: 403 Forbidden\n";
+}
+print "\n";
+if($method eq 'HEAD') {
+       exit;
+}
+
+print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+print '<html lang="en"><head>'."\n";
+print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+print '<title>';
+unless($framedata{'title'} eq '' || $framedata{'title'} eq $settings{'story'}) {
+       print entityencode($framedata{'title'}).' &bull; ';
+}
+print entityencode($settings{'story'}).' &bull; '.WEBSITE_NAME.'</title>'."\n";
+print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+print '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+print '<link rel="index" href="'.GOTO_PATH.($passwordOK?('?p='.urlencode($password)):'').'">'."\n";
+print '<link rel="start" href="'.VIEWER_PATH.'/0'.($passwordOK?('?p='.urlencode($password)):'').'">'."\n";
+if($frame>0 && $access) {
+       print '<link rel="prev" href="'.VIEWER_PATH.'/'.($frame - 1).($passwordOK?('?p='.urlencode($password)):'').'">'."\n";
+}
+if ($passwordOK || $frame<int($state{'last'})) {
+       print '<link rel="next" href="'.VIEWER_PATH.'/'.($frame + 1).($passwordOK?('?p='.urlencode($password)):'').'">'."\n";
+       print '<link rel="prefetch" href="'.VIEWER_PATH.'/'.($frame + 1).($passwordOK?('?p='.urlencode($password)):'').'">'."\n";
+}
+if($frame==int($state{'last'}) && int($state{'state'}) >= 1 && int($state{'state'}) < 3) {
+       print '<!-- <script src="'.TIMER_PATH.'"></script> -->'."\n";
+}
+print '</head><body>'."\n";
+print '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+print '<div id="all">'."\n";
+
+print '<div id="inst" class="ins">'."\n";
+
+print '<div id="title">'."\n";
+print '<H1 id="titletext">'.entityencode($framedata{'title'}).'</H1>'."\n";
+print '</div>'."\n";
+
+print '</div><div id="framespace">'."\n";
+if(!$access) {
+       print '<img src="'.CGI_PATH.$framedata{'frame'}.'" id ="frame" alt="'.$frame.'" title="'.entityencode($framedata{'title'}).'">'."\n";
+}
+else {
+       print'<img src="';
+       if($frame<=int($state{'last'}) && int($state{'state'}) >= 1) {
+               print CGI_PATH.sprintf($settings{'frame'},$frame,$framedata{'ext'});
+       }
+       else {
+               print FRAME_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'');
+       }
+       print '" id="frame" alt="'.$frame.'" title="'.entityencode($framedata{'title'}).'">'."\n";
+}
+print '</div><div id="insb" class="ins">'."\n";
+
+if($textmode==2){
+       print '<div id="chat">'."\n";
+       
+       if ($passwordOK || $frame<int($state{'last'}) || (int($state{'state'}) >= 2 && $showcommand)) {
+               $framedata{'command'}=$nextframedata{'title'};
+       }
+       if ($access) {
+               $framedata{'frame'}=sprintf($settings{'frame'},$frame,$framedata{'ext'});
+       }
+       
+       printdatafileht(%framedata);
+       print '</div>'."\n";
+}
+elsif($textmode==1){
+       print '<div id="chat">'."\n";
+       print '[quote][center][size=200]'.entityencode($framedata{'title'}).'[/size]<br>'."\n";
+       print '[url=http://'.WEBSITE.VIEWER_PATH.'/'.$frame.'][img]http://'.WEBSITE.CGI_PATH.($access?sprintf($settings{'frame'},$frame,$framedata{'ext'}):$framedata{'frame'}).'[/img][/url][/center]<br>'."\n";
+       print bb2bbf($framedata{'content'}).'[/quote]</div>'."\n";
+}
+elsif($framedata{'content'} ne ''){
+       print '<div id="undertext">'."\n";
+       print bb2htf($framedata{'content'})."\n";
+       print '</div>'."\n";
+}
+
+print '<div id="command">'."\n";
+if($frame==int($state{'last'}) && int($state{'state'}) >= 1 && int($state{'state'}) < 3) {
+       print '[<span id="ongh" class="';
+       if(int($state{'state'}) >= 2 || $state{'ip1'} ne '') {
+               print 'br';
+       }
+       else {
+               print 'ni';
+       }
+       print '">'.$hours.'</span>:<span id="ongm" class="';
+       if(int($state{'state'}) >= 2 || $state{'ip2'} ne '') {
+               print 'br';
+       }
+       else {
+               print 'ni';
+       }
+       print '">'.$minutes.'</span>:<span id="ongs" class="';
+       if(int($state{'state'}) >= 2 || $state{'ip3'} ne '') {
+               print 'br';
+       }
+       else {
+               print 'ni';
+       }
+       print '">'.$seconds.'</span>]<br>';
+}
+print '&gt;';
+if (!$access){
+       print '<a href="'.VIEWER_PATH.'/-1">'.entityencode($framedata{'command'}).'</a><br>'."\n";
+}
+else {
+       if ($passwordOK || $frame<int($state{'last'}) || (int($state{'state'}) >= 2 && $showcommand)) {
+               if ($passwordOK || $frame<int($state{'last'})) {
+                       print '<a href="'.VIEWER_PATH.'/'.($frame + 1).($passwordOK?('?p='.urlencode($password)):'').'">';
+               }
+               if($nextframedata{'title'} ne '') {
+                       print entityencode($nextframedata{'title'});
+               }
+               elsif ($passwordOK || $frame<int($state{'last'})) {
+                       print'<span class="inp">_</span>';
+               }
+               if ($passwordOK || $frame<int($state{'last'})) {
+                       print '</a>';
+               }
+               else {
+                       print'<span class="inp">_</span>';
+               }
+       }
+       else {
+               print'<span class="inp">_</span>';
+       }
+       print '<br>'."\n";
+}
+print '</div>'."\n";
+
+print '<div id="underlinks">'."\n";
+print '<a href="'.CGI_PATH.'">Once again</a>';
+if($frame>0 && $access) {
+       print ' | <a href="'.VIEWER_PATH.'/'.($frame-1).($passwordOK?('?p='.urlencode($password)):'').'">Before</a>';
+}
+if($frame != int($state{'last'})) {
+       print ' | <a href="'.VIEWER_PATH.'/'.int($state{'last'}).($passwordOK?('?p='.urlencode($password)):'').'">Now</a>';
+}
+print ' | <a href="'.GOTO_PATH.($passwordOK?('?p='.urlencode($password)):'').'">GOTO</a>'."\n";
+print '<span style="float: right;">'."\n";
+
+if ($textmode!=0) {
+       print '<a href="'.VIEWER_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'').'">Without</a> | ';
+}
+
+print '<a href="'.(($textmode==2)?(INFO_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'')):(VIEWER_PATH.'/'.$frame.'?b=2'.($passwordOK?('&amp;p='.urlencode($password)):''))).'">Info</a>';
+print ' | <a href="'.(($textmode==1)?(BBCODE_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'')):(VIEWER_PATH.'/'.$frame.'?b=1'.($passwordOK?('&amp;p='.urlencode($password)):''))).'">BB</a>';
+print "\n</span>\n";
+
+print '</div>'."\n";
+
+print '</div>'."\n";
+
+print '</div>'."\n";
+print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+
+print '</body></html>'."\n";
+
+sub writeindex {
+       (my $indexpath) = @_;
+       my $indexfile;
+       my %framedata;
+       my %nextframedata;
+       my %default;
+       
+       if(ref($indexpath)) {
+               $indexfile=$indexpath;
+               unless (seek($indexfile, 0, 0)) {
+                       return 0;
+               }
+       }
+       else {
+               unless (open ($indexfile, ">", $indexpath)) {
+                       return 0;
+               }
+       }
+       
+       %framedata = readdatafile(DATA_PATH.0);
+       %nextframedata = readdatafile(DATA_PATH.1);
+       %default=readdatafile(DEFAULT_PATH);
+       
+       foreach my $ind (keys %default) {
+               unless(defined($framedata{$ind})){
+                       $framedata{$ind}=$default{$ind};
+               }
+               unless(defined($nextframedata{$ind})){
+                       $nextframedata{$ind}=$default{$ind};
+               }
+       }
+       
+       print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
+       print $indexfile '<html lang="en"><head>'."\n";
+       print $indexfile '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
+       print $indexfile '<title>'.entityencode($settings{'story'}).' &bull; '.WEBSITE_NAME.'</title>'."\n";
+       print $indexfile '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
+       print $indexfile '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
+       print $indexfile '<link rel="index" href="'.GOTO_PATH.'">'."\n";
+       print $indexfile '<link rel="start" href="'.VIEWER_PATH.'/0">'."\n";
+       print $indexfile '<link rel="next" href="'.VIEWER_PATH.'/1">'."\n";
+       print $indexfile '<link rel="prefetch" href="'.VIEWER_PATH.'/1">'."\n";
+       print $indexfile '</head><body>'."\n";
+       print $indexfile '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
+       print $indexfile '<div id="all">'."\n";
+       
+       print $indexfile '<div id="inst" class="ins">'."\n";
+       
+       print $indexfile '<div id="title">'."\n";
+       print $indexfile '<H1 id="titletext">'.entityencode($settings{'story'}).'</H1>'."\n";
+       print $indexfile '</div>'."\n";
+       
+       print $indexfile '</div><div id="framespace">'."\n";
+       print $indexfile '<img src="'.CGI_PATH.sprintf($settings{'frame'},0,$framedata{'ext'}).'" title="'.entityencode($framedata{'title'}).'" alt="0" id="frame">'."\n";
+       
+       print $indexfile '</div><div id="insb" class="ins">'."\n";
+       print $indexfile '<div id="undertext">'."\n";
+       print $indexfile bb2htf($framedata{'content'})."\n";
+       print $indexfile '</div>'."\n";
+       
+       print $indexfile '<div id="command">'."\n";
+       print $indexfile '&gt;<a href="'.VIEWER_PATH.'/1">'.entityencode($nextframedata{'title'}).'</a>'."\n";
+       print $indexfile '</div>'."\n";
+       
+       print $indexfile '<div id="underlinks">'."\n";
+
+       
+       # <span style="float: right;"><a href="/bsta/v/0?b=2">Info</a> | <a href="/bsta/v/0?b=1">BB</a></span>
+       
+       print $indexfile '<a href="'.VIEWER_PATH.'/-1">Now</a> | <a href="'.GOTO_PATH.'">GOTO</a>';
+       print $indexfile '<span style="float: right;"><a href="'.INFO_PATH.'/0?b=2">Info</a> | <a href="'.VIEWER_PATH.'/0?b=1">BB</a></span>'."\n";
+       print $indexfile '</div>'."\n";
+       
+       print $indexfile '</div>'."\n";
+       
+       print $indexfile '</div>'."\n";
+       print $indexfile '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
+       
+       print $indexfile '</body></html>'."\n";
+       
+       unless (ref($indexpath)) {
+               close ($indexfile);
+       }
+       else {
+               truncate ($indexfile , tell($indexfile));
+       }
+       
+       return 1;
+}
+
+sub bb2htf {
+       (my $bb) = @_;
+       my $tag;
+       my $tagvalue;
+       my $pretext;
+       my $posttext;
+       
+       while($bb =~ m/(###([^#;]*);)/g) {
+               $tag = $1;
+               $tagvalue = $2;
+               $pretext = substr($bb,0,pos ($bb)-length($tag));
+               $posttext = substr ($bb,pos ($bb));
+               
+               if ($tagvalue =~ /^att&([0-9]+)$/) {
+                       $tagvalue = ATTACH_PATH.'/'.int($1).($passwordOK?('?p='.urlencode($password)):'');
+               }
+               elsif ($tagvalue =~ /^vw&([0-9]+)$/) {
+                       $tagvalue = VIEWER_PATH.'/'.int($1).($passwordOK?('?p='.urlencode($password)):'');
+               }
+               elsif ($tagvalue =~ /^fr&([0-9]+)$/) {
+                       $tagvalue = FRAME_PATH.'/'.int($1).($passwordOK?('?p='.urlencode($password)):'');
+               }
+               else {
+                       $tagvalue = '';
+               }
+               
+               $bb = $pretext.$tagvalue.$posttext;
+       }
+       
+       return bb2ht ($bb);
+}
+
+sub bb2bbf {
+       (my $bb) = @_;
+       my $tag;
+       my $tagvalue;
+       my $pretext;
+       my $posttext;
+       
+       while($bb =~ m/(###([^#;]*);)/g) {
+               $tag = $1;
+               $tagvalue = $2;
+               $pretext = substr($bb,0,pos ($bb)-length($tag));
+               $posttext = substr ($bb,pos ($bb));
+               
+               if ($tagvalue =~ /^att&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.ATTACH_PATH.'/'.int($1);
+               }
+               elsif ($tagvalue =~ /^vw&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.VIEWER_PATH.'/'.int($1);
+               }
+               elsif ($tagvalue =~ /^fr&([0-9]+)$/) {
+                       $tagvalue = 'http://'.WEBSITE.FRAME_PATH.'/'.int($1);
+               }
+               else {
+                       $tagvalue = '';
+               }
+               
+               $bb = $pretext.$tagvalue.$posttext;
+       }
+       
+       return linehtml(bb2bb ($bb));
+}