--- /dev/null
+// 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);
+}
--- /dev/null
+###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 • '.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=">"></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="<<"></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=">>"></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> </td><td align="right"> - </td><td> </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> </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 ' • '.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 '><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 '>...<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;
+}
--- /dev/null
+// 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);
+}
--- /dev/null
+###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);
+}
+
--- /dev/null
+// 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);
+}
--- /dev/null
+###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);
+}
--- /dev/null
+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
--- /dev/null
+# 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
--- /dev/null
+// 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);
+}
--- /dev/null
+###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 • '.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';
+ }
+}
--- /dev/null
+# 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;
+
+################################################################################
--- /dev/null
+# 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
+
+################################################################################
--- /dev/null
+#!/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";
+ }
+}
--- /dev/null
+// 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);
+}
--- /dev/null
+###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);
--- /dev/null
+// 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);
+}
--- /dev/null
+###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 • '.entityencode($settings{'story'}).' • '.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";
+
+
+
+
+
+
--- /dev/null
+// 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);
+}
--- /dev/null
+###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);
+}
--- /dev/null
+#!/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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+###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;
+}
--- /dev/null
+###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
--- /dev/null
+#!/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
--- /dev/null
+###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> </td><td align="right"> - </td><td> </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> </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;
+}
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+#!/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;
--- /dev/null
+// 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);
+ }
+};
--- /dev/null
+###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);
--- /dev/null
+// 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);
+}
--- /dev/null
+###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'}).' • ';
+}
+print entityencode($settings{'story'}).' • '.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 '>';
+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?('&p='.urlencode($password)):''))).'">Info</a>';
+print ' | <a href="'.(($textmode==1)?(BBCODE_PATH.'/'.$frame.($passwordOK?('?p='.urlencode($password)):'')):(VIEWER_PATH.'/'.$frame.'?b=1'.($passwordOK?('&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'}).' • '.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 '><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));
+}