shz.cgi

#!/usr/bin/perl
#
#

$myname = "shz.cgi";
$title = '着せ替え総本山の遺産';
$color_text    = '#000000';
$color_link    = '#0000FF';
$color_vlink   = '#800080';
$color_alink   = '#FF0000';
$color_bg      = '#FFFFE0';
$background    = '';

$basedir = '';
$lha = '/usr/local/bin/lha';
$errorlog = "error-log";
$search_all = 1;

$maxclients = 0;		# Limit of Clients (0:unlimited)
$maxclientreq = 0;		# Limit of request per client (0:unlimited)
$clientsfile = "clients";
$txbps = 0;			# 転送速度 (bytes/sec, 0:unlimited)


$config = "./config.pl";
require "$config" if -e $config;

$remotehost = &getclient;

binmode(STDIN);
binmode(STDOUT);

sub handler
{
    local($sig) = @_;
    local(*F);
    
    $_ = &tmstr(localtime(time)) . " $myname[$$]: SIG{$sig} $remotehost\n  $decode_buffer\n";
    open(F, ">>$errorlog");
    print F $_;
    close(F);
    &end;
    exit 0;
}

foreach ('HUP', 'INT', 'QUIT', 'ABRT', 'FPE', 'KILL', 'PIPE', 'ALRM', 'TERM') {
    $SIG{$_} = 'handler';
}

if ($maxclients) {
    local(*F, @clients);
    
    if (!-e $clientsfile) {
	open(F, ">$clientsfile");
	close(F);
    }
    open(F, "+<$clientsfile");
    flock(F, 2);
    @clients = <F>;
    if (@clients >= $maxclients) {
	close(F);
	print "Content-Type: text/html\n\n";
	print "<html><head><title>Too many clients</title></head>\n";
	print "<body><h1>Too many clients!</h1>\n";
	print "<p>Please connect some minutes later.</p>\n";
	print "</body></html>\n";
	exit 0;
    }
    if ($maxclientreq &&
	grep(/\d*\s+$remotehost\s/, @clients) >= $maxclientreq) {
	close(F);
	print "Content-Type: text/html\n\n";
	print "<html><head><title>Too many requests</title></head>\n";
	print "<body><h1>Too many requests!</h1>\n";
	print "<p>Please connect some minutes later.</p>\n";
	print "</body></html>\n";
	exit 0;
    }
    seek(F, 0, 0);
    push(@clients, "$$\t$remotehost $ENV{'QUERY_STRING'}\n");
    print F @clients;
    close(F);
    $clients = @clients;
}

&read_control;

&decode;

if ( $dl ) {
	&download;
	&end;
	exit 0;
}

print "Content-type: text/html; charset=Shift_JIS\n\n";
print "<html>\n";
print "\n";
print "<head>\n";
print qq(<script type="text/javascript" src="https://emk.name/dhtml/stdio.js"></script>\n);
print qq(<script type="text/javascript" src="https://emk.name/dhtml/unlzh.js"></script>\n);
print qq(<script type="text/javascript" src="https://emk.name/dhtml/kiss.js"></script>\n);

if ( $word ) {
	&search;
}
elsif ( $file ) {
    if ( $arno ) {
	&log_read;
    }
    else {
	&log_list;
    }
}
elsif ( $dump ) {
    &dump_file("$myname");
}
else {
    &top_page;
}

print "</body>\n";
print "</html>\n";
&end;
exit 0;


sub get_ttl
{
	$file = $_[0];
	$file2 = $file;
	$file2 =~ s/&/%26/;

	$tmp = $file;
	$tmp =~ y/a-z/A-Z/;

	print "<tr><td><a href=\"$myname?f=$file2\">$tmp</a></td>";

	open(FI,"$basedir$file.idx");
	binmode(FI);
	if ( read(FI,$buf,256) == 256 ) {
		$id  = substr($buf,0x1a,8);
		$wtr = substr($buf,0x22,16);
		$ttl = substr($buf,0xb0,40);
		$ttl =~ s/\0.*//;
		print "<td>$ttl</td>";
	}
	close(FI);

	print "</tr>\n";
}

sub make_files
{
	@files = (
	'anime',
	'cgdata',
	'cgtalk',
	'comic',
	'free',
	'game',
	'hgame',
	'info',
	'junkdata',
	'junktalk',
	'kissdata',
	'kissinfo',
	'kissq&a',
	'kisstalk',
	'ksug',
	'local',
	'music',
	'needs',
	'prof',
	'proglib',
	'progtalk',
	'q&a',
	'sfx',
	'test',
	'xxxdata',
	'xxxtalk' );
}

sub top_page
{
	&make_files;
	&print_title($title);
	print "<div align=\"center\">\n";
	print "<H2>$title</H2>\n";
	&print_form;
	print "<table border=1>\n";
	foreach $_ ( @files ) {
		&get_ttl( $_ );
	}
	print "</table>\n";
	print "</div>\n";
	print "<p align=right><a href=\"?dump=myself\"><code>$myname</code></a></p>\n";
}

sub log_list_line
{
	$id  = substr($buf,0x1a,8);
	$wtr = substr($buf,0x22,16);
	$wtr =~ s/\0/ /g;
	$_ = substr($buf,0xb0,40);
	s/\0.*//;
	&enc_amp_lt_gt;
	$ttl = $_;
	&date_string( unpack("x172V",$buf) );
	$flags = unpack("x54v", $buf);
	printf( "%4d:", $cnt );
	if ($flags & 1) {
		print " " x 46 . "(削除)\n";
	} else {
		print "$id $wtr $date ";
		($pos,$len) = unpack("x224V2",$buf);
		print '●' if ( $len );
		print "<a href=\"$myname?f=$file2;n=$cnt\">$ttl</a>\n";
	}
}

sub log_list
{
	$file2 = $file;
	$file2 =~ s/&/%26/;

	$tmp = $file;
	$tmp =~ y/a-z/A-Z/;

	open(FI,"$basedir$file.idx");
	binmode(FI);
	if ( read(FI,$buf,256) == 256 ) {
		$_ = substr($buf,0xb0,40);
		s/\0.*//;
		&enc_amp_lt_gt;
		$ttl = $_;
		&print_title("$tmp : $ttl");
		print "<div align=\"right\"><a href=\"$myname\">トップへ</a></div>\n";
		print "<H3>$tmp : $ttl</H3>\n";
	}
	print "●印はバイナリデータがあります<br>\n";
	print "<pre>";
	$cnt = 1;
	while ( read(FI,$buf,256) == 256 ) {
		&log_list_line;
		$cnt++;
	}
	close(FI);
	print "</pre>\n";
}

sub dump
{
	foreach $_ ( unpack( "C1000", $_[0] ) ) {
		printf( "%02x ", $_ );
	}
}

sub print_html5
{
	local($tmpfile) = "/tmp/Tmp$$";

	open(FD,"$basedir$file.dat");
	binmode(FD);
	seek(FD,$pos,0);
	read(FD,$tmp,$len);
	close(FD);

	open FC, "| $lha lgq - *.lzh *.LZH >$tmpfile";
	binmode FC;
	$SIG{PIPE} = 'IGNORE';
	$status = print FC $tmp or $! != EPIPE;
	$SIG{PIPE} = 'handler';
	close FC;
	return unless $status;
	open FC, $tmpfile;
	undef @nest_arcs;
	while (<FC>) {
		chomp;
		@line = split(/ /);
		push @nest_arcs, $line[$#line];
	}
	close FC;

	$cnforg = $cnfname;
	$cnfname = "*.cnf *.CNF";# unless $cnfname;
	open FC, "| $lha lgq - $cnfname >$tmpfile";
	binmode FC;
	print FC $tmp;
	close FC;
	foreach (@nest_arcs) {
		open FC, "| $lha pgq - $_ | $lha lgq - $cnfname >>$tmpfile";
		binmode FC;
		print FC $tmp;
		close FC;
	}
	if (!-e $tmpfile) {
		print "cannot create $tmpfile";
	}

	open FC, "$tmpfile";
	binmode FC;
	$_ = <FC>; # cnf名
	chomp;
	@line = split(/ /);
	$cnfname = $line[$#line];
	if ($cnfname =~ /.*[:\/]([^:\/]+)$/) {
		$cnfname = $1;
	}
	$cnforg = $cnfname unless $cnforg;
	print "<p id=\"cnfnames\">\n";
#	if ($cnfname ne $cnforg) {
#		$anchor = "<a href=\"$myname?f=$file2;n=$arno\">$cnfname</a>";
#	} else {
#		$anchor = $cnfname;
#	}
#	print "$anchor\n";
	while (<FC>) {
		chomp;
		@line = split(/ /);
		$cnfname = $line[$#line];
		if ($cnfname =~ /.*[:\/]([^:\/]+)$/) {
			$cnfname = $1;
		}
#		if ($cnfname ne $cnforg) {
#			$cnfname2 = $cnfname;
#			$cnfname2 =~ s/&/%26/g;
#			$anchor = "<a href=\"$myname?f=$file2;n=$arno;cnfname=$cnfname2\">$cnfname</a>";
#		} else {
#			$anchor = $cnfname;
#		}
#		print "$anchor\n";
	}
	print "</p>\n";
	close FC;
	unlink $tmpfile;

	if ($cnfname) {
		$cnfname = $cnforg;

		undef @exsets;
		push(@exsets, qq("$myname/$name?f=$file2;dl=$arno"));

		$index = 0;
		open FE, "./exsets";
		while (<FE>) {
			chomp;
			local($f, $no, $cnf, $exf, $exno, $exname, $dir) = split(/;/);
			if ($f eq $file && $no eq $arno) {
				if ($cnf eq '' || lc $cnf eq lc $cnfname) {
					if ($dir eq '+') {
						push(@exsets, qq("$myname/$exname?f=$exf;dl=$exno"));
					} else {
						unshift(@exsets, qq("$myname/$exname?f=$exf;dl=$exno"));
						$index++;
					}
				}
			}
		}
		close FE;

		if (@exsets > 1) {
			$arcname = "[" . join(',', @exsets) . "]";
		} else {
			$arcname = $exsets[0];
		}
		print <<EOM;
<script type="text/javascript"><!--
(function(){
	var cnfnames = document.getElementById("cnfnames");
	load_cnf(cnfnames, function(arcnames){
		//cnfnames.appendChild(document.createElement("br"));
		var first = true;
		var filenames = arcnames[$index][1];
		for (var j = 0; j < filenames.length; j++) {
			if (!/\\.cnf\$/i.exec(filenames[j])) continue;
			var filename = filenames[j];
			var leafname = filename[filename.length - 1];
			if (leafname == "$cnfname") {
				cnfnames.appendChild(document.createTextNode(leafname + "\\n"));
			} else {
				var a = document.createElement("a");
				if (first) {
					a.href = "$myname?f=$file2;n=$arno";
				} else {
					a.href = "$myname?f=$file2;n=$arno;cnfname=" + leafname.replace(/[#&]/, function(c){return "%"+c.charCodeAt(0).toString(16);});
				}
				a.appendChild(document.createTextNode(leafname));
				cnfnames.appendChild(a);
				cnfnames.appendChild(document.createTextNode("\\n"));
			}
			first = false;
		}
		return "$cnfname";
	}, $arcname, undefined, true);
})();
--></script>
EOM
	}

}

sub print_navigation
{
	$_ = $arno-1;
	print "<a href=\"$myname?f=$file2;n=$_\">前へ</a> \n" if $arno > 1;
	$_ = $arno+1;
	if ($_*256 < $idxsize) {
		print "<a href=\"$myname?f=$file2;n=$_\">後へ</a> \n";
	} else {
		print "後へ \n";
	}
	print "<a href=\"$myname?f=$file2\">上へ</a>\n";
}

sub log_read
{
	$file2 = $file;
	$file2 =~ s/&/%26/;

	open(FI,"$basedir$file.idx");
	binmode(FI);
	seek(FI,256*$arno,0);
	if ( read(FI,$buf,256) == 256 ) {
		$flags = unpack("x54v", $buf);
		if ($flags & 1) {
			$ttl = "(削除)";
		} else {
			$id  = substr($buf,0x1a,8);
			$wtr = substr($buf,0x22,16);
			$_ = substr($buf,0xb0,40);
			s/\0.*//;
			&enc_amp_lt_gt;
			$ttl = $_;
			&date_string( unpack("x172V",$buf) );
		}
		&print_title($ttl);
		$ref = unpack("x4v", $buf);

		print "<table width=\"100%\"><tr>\n";
		print "<td width=\"5%\">$arno</td>";
		print "<td width=\"10%\">$id</td>";
		print "<td width=\"15%\">$wtr</td>";
		print "<td width=\"50%\">$date</td>";
		print "<td width=\"20%\" align=\"right\">\n";
		$idxsize = -s "$basedir$file.idx";
		&print_navigation();
		print "</td></tr></table>\n";
		print "<h3>$ttl</h3>\n";
		unless ($flags & 1) {
			$_ = $ref;
			print "<a href=\"$myname?f=$file2;n=$_\">$ref</a>へのコメント<br>\n" if $ref > 1;
	
			($pos,$len) = unpack("x216V2",$buf);
			#print "<p>pos=$pos len=$len<br>\n";
			if ( $len ) {
				open(FD,"$basedir$file.dat");
				binmode(FD);
				seek(FD,$pos,0);
				read(FD,$tmp,$len);
				#&dump( $tmp );
				$_ = $tmp;
				s/\0.*//;
				s/\r\n/\n/g;
				s/\r/\n/g;
				&enc_amp_lt_gt;
				$tmp = $_;
				print "<hr><pre>$tmp</pre><hr>\n";
				close(FD);
			}
			($pos,$len) = unpack("x224V2",$buf);
			if ( $len ) {
				&date_string( unpack("x234V",$buf) );
				&date_string2( unpack("x234V",$buf) );
				$name = unpack("x238A12",$buf);
				#&dump( $tmp );
				$name =~ s/\0.*//;
				print qq(<a href="$myname/$name?f=$file2;dl=$arno">$name</a>);
				print " $date $lenバイト\n";
				&print_html5 if ($control{"$file\t$arno"} !~ /^b/);
			}
			print "<table width=\"100%\"><tr>\n";
			print "<td align=\"right\">\n";
			&print_navigation();
			print "</td></tr></table>\n";
			if ($control{"$file\t$arno"}) {
			    &control_print("$file\t$arno");
			}
		}
	}
	close(FI);
}

sub control_print
{
    local($key) = @_;
    split(/\t+/, $control{$key}, 2);
    print $_[1];
}

sub download
{
    $_ = "$file\t$dl";
    &download_inhibit($_) if $control{$_} =~ /^b/;

	open(FI,"$basedir$file.idx");
	binmode(FI);
	seek(FI,256*$dl,0);
	if ( read(FI,$buf,256) == 256 ) {
		$flags = unpack("x54v", $buf);
		unless ($flags & 1) {
			open(FD,"$basedir$file.dat");
			binmode(FD);
			($pos,$len) = unpack("x224V2",$buf);
			if ( $len ) {
				&date_string( unpack("x234V",$buf) );
				&date_string2( unpack("x234V",$buf) );
				$name = unpack("x238A12",$buf);
				$name =~ s/\0.*//;
				print "Content-Type: application/octet-stream\n";
				print "Content-Length: $len\n";
				print "Last-Modified: $date2\n\n";
				seek(FD,$pos,0);
				$| = 1;
				while ($len) {
				    local ($i);
				    
				    $i = $len;
				    $i = $txbps if $txbps && $i > $txbps;
				    $i = read(FD,$tmp,$i);
				    print $tmp;
				    sleep(1) if $txbps; # for Band-width limit
				    $len -= $i;
				}
			}
			close(FD);
		}
	}
	close(FI);
}

sub download_inhibit
{
    local($key) = @_;
    
    print "Content-type: text/html; charset=Shift_JIS\n\n";
    print "<html>\n";
    print "\n";
    print "<head>\n";
    &print_title("Deleted binary");
    &control_print($key);
    print "</body></html>\n";
    &end;
    exit 0;
}


sub decode
{
	if ($ENV{'REQUEST_METHOD'} =~ /POST/i) {
		read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
	} else {
		$buffer = $ENV{'REDIRECT_QUERY_STRING'} || $ENV{'QUERY_STRING'};
	}
	$decode_buffer = $buffer;

	@pairs = split(/[;&]/, $buffer);

	foreach $pair (@pairs) {
		($name, $value) = split(/=/, $pair);
		$value =~ tr/+/ /;
		$value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg;
		$file = $value if ( $name eq 'f' );
		$arno = $value if ( $name eq 'n' );
		$dl = $value if ( $name eq 'dl' );
		$dump = $value if ( $name eq 'dump' );
		$word = $value if ( $name eq 'word' );
		$cnfname = $value if ( $name eq 'cnfname' );
		$cnfname =~ s/[^-a-zA-Z0-9._@~&\$#]//g;
	}
}


sub date_string
{
    # 2038年問題が発生するかもしれません
    if ($_[0] < 0 || $_[0] > 0x7fffffff) {
	$date = '(illegal-time)';
	return;
    }
    local($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime($_[0]);
    $date = sprintf("%d/%02d/%02d %02d:%02d:%02d",
		    $year+1900, $month+1, $mday, $hour, $min, $sec);
}


sub date_string2
{
    $date2 = '(illegal-time)';
    # 2038年問題が発生するかもしれません
    $date2 = &tmstr(gmtime($_[0])) if ($_[0] >= 0 && $_[0] <= 0x7fffffff);
}

sub tmstr
{
    sprintf("%s, %02d %s %d %02d:%02d:%02d GMT",
	    (Sun,Mon,Tue,Wed,Thu,Fri,Sat)[$_[6]], $_[3],
	    (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$_[4]],
	    $_[5] + 1900, $_[2], $_[1], $_[0]);
}


sub print_title
{
	local($title) = @_;

	print "<title>$title</title>\n";
	print "</head>\n";
	print "\n";
	print "<body bgcolor=\"$color_bg\" background=\"$background\" text=\"$color_text\" link=\"$color_link\" vlink=\"$color_vlink\" alink=\"$color_alink\">\n";
	print "\n";
}


sub dump_file
{
    local($file) = @_;
    local(*F);
    
    &print_title($file);
    print "<h1><code>$file</code></h1>\n";
    print "<pre>";
    open(F, $file);
    while (<F>) {
	&enc_amp_lt_gt;
	print;
    }
    close(F);
    print "</pre>\n";
}

sub enc_amp_lt_gt {
    s/&/&amp;/g;
    s/</&lt;/g;
    s/>/&gt;/g;
}

# アクセス制御ファイル "control" を読み込み %control にその内容をセット
# %control は "$file\t$no" をキーとし "$mode\t$comment" という値
# 
sub read_control
{
    local($_, $file, $no, $mode, $comment, *F);

    if (open(F, "control")) {
	while (<F>) {
	    next if /^#/;
	    s/\r?\n$//;
	    next if split(/[ \t]+/, $_, 4) < 2;
	    $file = $_[0];
	    $no = $_[1];
	    $mode = $_[2] if $_[2];
	    $comment = $_[3] if $_[3];
	    $control{"$file\t$no"} = "$mode\t$comment";
	}
	close(F);
    }
}

sub print_form
{
	print "<form action=\"$myname\">";
	print "<input type=\"text\" name=\"word\" value=\"\">";
	print "<input type=\"submit\" value=\"検索\">";
	print "</form>";
}

sub search
{
	&make_files;

	$_ = $word;
	&enc_amp_lt_gt;
	$ttl = "検索結果 : $_";
	&print_title($ttl);
	print "<div align=\"right\"><a href=\"$myname\">トップへ</a></div>\n";
	print "<H3>$ttl</H3>\n";
	print "●印はバイナリデータがあります<br>\n";

	$word =~ s/([\\\^\.\$\|\(\)\[\]\*\+\?\{\}])/\\$1/g; # quotemeta
	@words = split(/ /, $word);

	$total_hit = 0;
	foreach $_ ( @files ) {
	    if ($search_all || !(/data/ || /lib/)) {
		&log_search( $_ );
		$total_hit += $hit;
	    }
	}
	print "<p>$total_hit 件見つかりました。</p>";
}

sub matched
{
	local($word);

	foreach $word (@words) {
		return 0 unless ($tmp =~ /$word/);
	}
	return 1;
}

sub log_search
{
	local($file) = @_;
	local($flags);
	
	$file2 = $file;
	$file2 =~ s/&/%26/;

	$board = $file;
	$board =~ y/a-z/A-Z/;

	open(FI, "$basedir$file.idx");
	binmode(FI);
	open(FD, "$basedir$file.dat");
	binmode(FD);
	
	if (read(FI, $buf, 256) == 256) {
		$_ = substr($buf, 0xb0, 40);
		s/\0.*//;
		&enc_amp_lt_gt;
		$ttl = $_;
	}

	$hit = 0;
	$cnt = 1;
	while (read(FI, $buf, 256) == 256) {
		($pos, $len) = unpack("x216V2", $buf);
		$flags = unpack("x54v", $buf);
		if ($len && !($flags & 1)) {
			seek(FD, $pos, 0);
			read(FD, $tmp, $len);
			if (&matched) {
				print "<h4>$board : $ttl</h4>\n<pre>\n" unless $hit;
				&log_list_line;
				$hit++;
			}
		}
		$cnt++;
	}

	print "</pre>\n" if $hit;
}

sub end
{
    if ($maxclients) {
	local(*F, @clients);
	
	open(F, "+<$clientsfile");
	flock(F, 2);
	@clients = <F>;
	grep(s/^$$\t.*\n$//, @clients);
	truncate(F, 0);
	seek(F, 0, 0);
	print F @clients;
	close(F);
    }
}

sub getclient
{
    local($_, $r);
    
    $_ = $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'};
    $_ = gethostbyaddr(pack('C4',split(/\./,$_)),2) || $_ if /\d+\.\d+\.\d+\.\d+/;
    $r = $_;
    $_ = $ENV{'HTTP_X_FORWARDED_FOR'};
    if ($_) {
	if (/\d+\.\d+\.\d+\.\d+/ && !&isprivateip($_)) {
	    $_ = gethostbyaddr(pack('C4',split(/\./,$_)),2);
	}
	$r = "$r/$_";
    }
    $r;
}

sub isprivateip
{
    local(@ip) = split(/\./, $_[0]);
    
    (($ip[0] == 10) ||
     ($ip[0] == 172 && $ip[1] >= 16 && $ip[1] <= 31) ||
     ($ip[0] == 192 && $ip[1] == 168));
}