shz.cgi

#!/usr/local/bin/perl
#
#

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

$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';
}

sub service_unavailable
{
	my($title, @clients) = @_;

	my($expires) = split(/\s/, shift @clients);
	my($retry_after) = $expires - $now;
	foreach (@clients) {
		($expires, $host) = split(/\s/);
		if ($retry_after > $expires - $now) {
			$retry_after = $expires - $now;
		}
	}
	print "Content-Type: text/html\n\n";
	print "<html><head><title>$title</title></head>\n";
	print "<body><h1>$title!</h1>\n";
	if ($ENV{'HTTP_ACCEPT_LANGUAGE'} =~ /\bja/) {
		print "<p>あと $retry_after 秒お待ちください.</p>\n";
	} else {
		print "<p>Please connect $retry_after seconds later.</p>\n";
	}
	print "</body></html>\n";
}

if ($ENV{'HTTP_IF_MODIFIED_SINCE'}) {
	# always returns 304 since data won't be updated.
	print "Status: 304 Not Modified\n\n";
	exit 0;
}

if ($maxclients) {
    local(*F, @clients, @clientreq);
    
    if (!-e $clientsfile) {
	open(F, ">$clientsfile");
	close(F);
    }
    open(F, "+<$clientsfile");
    flock(F, 2);
    @clients = <F>;
    if (@clients >= $maxclients) {
	close(F);
	&service_unavailable('Too many clients', @clients);
	exit 0;
    }
    if ($maxclientreq &&
	grep(/\d*\s+$remotehost\s/, @clients) >= $maxclientreq) {
	close(F);
	&service_unavailable('Too many requests', @clientreq);
	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";

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 make_anchor
{
	local($link, $title) = @_;
	if ($java) {
		if ($link =~ /\?/) {
			$link = "$link;j=$java";
		} else {
			$link = "$link?j=$java";
		}
	}
	$anchor = "<a href=\"$link\">$title</a>";
}

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

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

	&make_anchor("$myname?f=$file2", $tmp);
	print "<tr><td>$anchor</td>";

	open(FI,"$basedir$file.idx") or print "<td>$!: $basedir$file.idx</td>";
	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_java_select;
	print "<p align=right><a href=\"?dump=myself\"><code>$myname</code></a></p>\n";
}

sub print_java_select
{
	local($c);
	local($query) = $ENV{'QUERY_STRING'};
	$query =~ s/[;&]?j=\d//;
	$c = $query ? ';' : '?';
	$query = '?' . $query if $query;
	print "<p>";
	print "<a href=\"$query\">No Java</a> " if $java;
	print "<a href=\"${query}${c}j=1\">Java</a> " if $java != 1;
	print "<a href=\"${query}${c}j=2\">KissChoco</a> " if $java != 2;
	print "</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 );
		&make_anchor("$myname?f=$file2;n=$cnt", $ttl);
		print  "$anchor\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");
		&make_anchor("$myname", "トップへ");
		print "<div align=\"right\">$anchor</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";
	&print_java_select;
}

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

sub print_file
{
	local($filename) = @_;

	open F, $filename;
	while (<F>) {
		s#\$linkname#$myname/$name?f=$file2;dl=$arno#;
		print;
	}
	close F;
}

sub print_applet
{
	local($width, $height, $fkiss, $trans);
	local($tmpfile) = "/tmp/Tmp$$";

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

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

	open FC, "$tmpfile";
	binmode FC;
#print "<pre>\n";
	$_ = <FC>; # ::::::::
#print;
	$_ = <FC>; # cnf名
#print;
	chomp;
	$cnfname = $_;
	if (/.*[:\/]([^:\/]+)$/) {
		$cnfname = $1;
	}
	$cnforg = $cnfname unless $cnforg;
	$_ = <FC>; # ::::::::
#print;
	print "<p>\n";
	if ($cnfname ne $cnforg) {
		&make_anchor("shz.cgi?f=$file2;n=$arno", $cnfname);
	} else {
		$anchor = $cnfname;
	}
	print "$anchor\n";
	while (<FC>) {
#print;
		if (/^.?::::::::$/) {
			$_ = <FC>;
#print;
			chomp;
			$cnfname = $_;
			if (/.*[:\/]([^:\/]+)$/) {
				$cnfname = $1;
			}
			$_ = <FC>;
			if ($cnfname ne $cnforg) {
				$cnfname2 = $cnfname;
				$cnfname2 =~ s/&/%26/g;
				&make_anchor("shz.cgi?f=$file2;n=$arno;cnfname=$cnfname2", $cnfname);
			} else {
				$anchor = $cnfname;
			}
			print "$anchor\n";
#print;
		}
		if ($cnfname eq $cnforg) {
			if (/^\((\d+)\D+(\d+)/) {
				$width = $1;
				$height = $2;
			}
			$fkiss = 1 if (/^;@\s*EventHandler/);
			$trans = 1 if (/^#[^;]+;%[tT]/);
		}
	}
	print "</p>\n";
	close FC;
	unlink $tmpfile;

	$width = 448 unless $width;
	$height = 320 unless $height;

	$jar = $fkiss ? 'fkiss.jar' : 'kiss.jar';
	$class = $fkiss ? 'Fkiss' : 'Kiss';
#	&print_file("warnfkiss.txt") if $fkiss; # 対応済み
#	&print_file("warntrans.txt") if $trans; # 対応済み

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

		undef @exsets;
		open FE, "./exsets";
		while (<FE>) {
			chomp;
			local($f, $no, $cnf, $exf, $exno, $exname) = split(/;/);
			if ($f eq $file && $no eq $arno) {
				if ($cnf eq '' || $cnf eq $cnfname) {
					push(@exsets, ",shz.cgi/$exname?f=$exf;dl=$exno");
				}
			}
		}
		close FE;

		local($jarname) = $name;
		$jarname =~ s/\.lzh$/.jar/i;
		if ($java == 4) {
			print "<p><object codetype=\"application/java\" codebase=\"./\" classid=\"java:$class\" archive=\"$jar\" width=$width height=$height>\n";
			print "<param name=\"archive\" value=\"$jar\">\n";
			print "<param name=\"lzh_name\" value=\"shz.cgi/$name?f=$file2;dl=$arno";
			foreach (@exsets) {
				print;
			}
			print "\">\n";
			print "<param name=\"cnf_name\" value=\"$cnfname\">\n";
			open F, "noapplet.txt";
			while (<F>) {
				s#\$linkname#shz.cgi/$name?f=$file2;dl=$arno#;
				print;
			}
			close F;
			print "</object></p>\n";
		} elsif ($java == 3) {
			print "<p><object classid=\"clsid:8AD9C840-044E-11D1-B3E9-00805F499D93\"\n";
			print "width=$width height=$height\n";
			print "codebase=\"http://java.sun.com/products/plugin/1.2/jinstall-12-win32.cab#Version=1,2,0,0\">\n";
			print "<param name=\"code\" value=\"$class\">\n";
			print "<param name=\"archive\" value=\"$jar\">\n";
			print "<param name=\"type\" value=\"application/x-java-applet;version=1.2\">\n";
			print "<param name=\"lzh_name\" value=\"shz.cgi/$name?f=$file2;dl=$arno";
			foreach (@exsets) {
				print;
			}
			print "\">\n";
			print "<param name=\"cnf_name\" value=\"$cnfname\">\n";
			print "<embed type=\"application/x-java-applet;version=1.2\" width=$width height=$height\n";
			print "code=\"$class\" archive=\"$jar\" lzh_name=\"shz.cgi/$name?f=$file2;dl=$arno";
			foreach (@exsets) {
				print;
			}
			print "\" cnf_name=\"$cnfname\"\n";
			print "pluginspace=\"http://java.sun.com/products/plugin/1.2/plugin-install.html\">\n";
			print "<noembed>\n";
			open F, "noapplet.txt";
			while (<F>) {
				s#\$linkname#shz.cgi/$name?f=$file2;dl=$arno#;
				print;
			}
			close F;
			print "</noembed>\n";
			print "</embed>\n";
			print "</object></p>\n";
		} elsif ($java == 2) {
			print "<p><applet code=KissChoco archive=\"choco.zip\" width=$width height=".($height+52).">\n";
			print "<param name=\"lzh\" value=\"shz.cgi/$name?f=$file2;dl=$arno;.lzh";
			foreach (@exsets) {
				print;
			}
			print "\">\n";
			print "<param name=\"cnf\" value=\"$cnfname\">\n";
			open F, "noapplet.txt";
			while (<F>) {
				s#\$linkname#shz.cgi/$name?f=$file2;dl=$arno#;
				print;
			}
			close F;
			print "</applet></p>\n";
		} else {
			print "<p><applet code=$class archive=\"$jar\" width=$width height=$height>\n";
			print "<param name=\"lzh_name\" value=\"shz.cgi/$name?f=$file2;dl=$arno";
			foreach (@exsets) {
				print;
			}
			print "\">\n";
			print "<param name=\"cnf_name\" value=\"$cnfname\">\n";
			open F, "noapplet.txt";
			while (<F>) {
				s#\$linkname#shz.cgi/$name?f=$file2;dl=$arno#;
				print;
			}
			close F;
			print "</applet></p>\n";
		}
	}

}

sub print_navigation
{
	$_ = $arno-1;
	print make_anchor("$myname?f=$file2;n=$_", "前へ") . " \n" if $arno > 1;
	$_ = $arno+1;
	print make_anchor("$myname?f=$file2;n=$_", "後へ") . " \n";
	print make_anchor("$myname?f=$file2", "上へ") . "\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, "nofollow");
		$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";
		&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 "<a href=\"$myname/$name?f=$file2;dl=$arno\">$name</a>";
				print " $date $lenバイト\n";
				&print_applet if $java && ($control{"$file\t$arno"} !~ /^b/);
			}
			print "<table width=\"100%\"><tr>\n";
			print "<td>\n";
			&print_java_select unless $control{"$file\t$arno"} =~ /^b/;
			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);
				read(FD,$tmp,$len);
				print $tmp;
			}
			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' );
		$java = $value if ( $name eq 'j' );
		$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, $attr) = @_;

	print "<title>$title</title>\n";
	print "<meta name=\"ROBOTS\" content=\"NOFOLLOW\">\n" if $attr =~ /nofollow/;
	print "</head>\n";
	print "\n";
	print "<body bgcolor=\"$color_bg\" 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 "<input type=\"hidden\" name=\"j\" value=\"$java\">" if $java;
	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));
}