# wakautils.pl v8.5 use strict; use Time::Local; use Socket; my $has_md5=0; eval 'use Digest::MD5 qw(md5)'; $has_md5=1 unless $@; my $has_encode=0; eval 'use Encode qw(decode)'; $has_encode=1 unless $@; use constant MAX_UNICODE => 1114111; # # HTML utilities # my $protocol_re=qr{(?:http://|https://|ftp://|mailto:|news:|irc:)}; my $url_re=qr{(${protocol_re}[^\s<>()"]*?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.||\]|!|\?|,|,|")*(?:[\s<>()"]|$))}; sub protocol_regexp() { return $protocol_re } sub url_regexp() { return $url_re } sub abbreviate_html($$$) { my ($html,$max_lines,$approx_len)=@_; my ($lines,$chars,@stack); return undef unless($max_lines); while($html=~m!(?:([^<]+)|<(/?)(\w+).*?(/?)>)!g) { my ($text,$closing,$tag,$implicit)=($1,$2,lc($3),$4); if($text) { $chars+=length $text; } else { push @stack,$tag if(!$closing and !$implicit); pop @stack if($closing); if(($closing or $implicit) and ($tag eq "p" or $tag eq "blockquote" or $tag eq "pre" or $tag eq "li" or $tag eq "ol" or $tag eq "ul" or $tag eq "br")) { $lines+=int($chars/$approx_len)+1; $lines++ if($tag eq "p" or $tag eq "blockquote"); $chars=0; } if($lines>=$max_lines) { # check if there's anything left other than end-tags return undef if (substr $html,pos $html)=~m!^(?:\s*)*\s*$!s; my $abbrev=substr $html,0,pos $html; while(my $tag=pop @stack) { $abbrev.="" } return $abbrev; } } } return undef; } sub sanitize_html($%) { my ($html,%tags)=@_; my (@stack,$clean); my $entity_re=qr/&(?!\#[0-9]+;|\#x[0-9a-fA-F]+;|amp;|lt;|gt;)/; while($html=~/(?:([^<]+)|<([^<>]*)>|(<))/sg) { my ($text,$tag,$lt)=($1,$2,$3); if($lt) { $clean.="<"; } elsif($text) { $text=~s/$entity_re/&/g; $text=~s/>/>/g; $clean.=$text; } else { if($tag=~m!^\s*(/?)\s*([a-z0-9_:\-\.]+)(?:\s+(.*?)|)\s*(/?)\s*$!si) { my ($closing,$name,$args,$implicit)=($1,lc($2),$3,$4); if($tags{$name}) { if($closing) { if(grep { $_ eq $name } @stack) { my $entry; do { $entry=pop @stack; $clean.=""; } until $entry eq $name; } } else { my %args; $args=~s/\s/ /sg; while($args=~/([a-z0-9_:\-\.]+)(?:\s*=\s*(?:'([^']*?)'|"([^"]*?)"|['"]?([^'" ]*))|)/gi) { my ($arg,$value)=(lc($1),defined($2)?$2:defined($3)?$3:$4); $value=$arg unless defined($value); my $type=$tags{$name}{args}{$arg}; if($type) { my $passes=1; if($type=~/url/i) { $passes=0 unless $value=~/(?:^${protocol_re}|^[^:]+$)/ } if($type=~/number/i) { $passes=0 unless $value=~/^[0-9]+$/ } if($passes) { $value=~s/$entity_re/&/g; $args{$arg}=$value; } } } $args{$_}=$tags{$name}{forced}{$_} for (keys %{$tags{$name}{forced}}); # override forced arguments my $cleanargs=join " ",map { my $value=$args{$_}; $value=~s/'/%27/g; "$_='$value'"; } keys %args; $implicit="/" if($tags{$name}{empty}); push @stack,$name unless $implicit; $clean.="<$name"; $clean.=" $cleanargs" if $cleanargs; $clean.=" $implicit" if $implicit; $clean.=">"; } } } } } my $entry; while($entry=pop @stack) { $clean.="" } return $clean; } sub describe_allowed(%) { my (%tags)=@_; return join ", ",map { $_.($tags{$_}{args}?" (".(join ", ",sort keys %{$tags{$_}{args}}).")":"") } sort keys %tags; } sub do_wakabamark($;$$) { my ($text,$handler,$simplify)=@_; my $res; my @lines=split /(?:\r\n|\n|\r)/,$text; while(defined($_=$lines[0])) { if(/^\s*$/) { shift @lines; } # skip empty lines elsif(/^(1\.|[\*\+\-]) /) # lists { my ($tag,$re,$skip,$html); if($1 eq "1.") { $tag="ol"; $re=qr/[0-9]+\./; $skip=1; } else { $tag="ul"; $re=qr/\Q$1\E/; $skip=0; } while($lines[0]=~/^($re)(?: |\t)(.*)/) { my $spaces=(length $1)+1; my $item="$2\n"; shift @lines; while($lines[0]=~/^(?: {1,$spaces}|\t)(.*)/) { $item.="$1\n"; shift @lines } $html.="
  • ".do_wakabamark($item,$handler,1)."
  • "; if($skip) { while(@lines and $lines[0]=~/^\s*$/) { shift @lines; } } # skip empty lines } $res.="<$tag>$html"; } elsif(/^(?: |\t)/) # code sections { my @code; while($lines[0]=~/^(?: |\t)(.*)/) { push @code,$1; shift @lines; } $res.="
    ".(join "
    ",@code)."
    "; } elsif(/^>/) # quoted sections { my @quote; while($lines[0]=~/^(>.*)/) { push @quote,$1; shift @lines; } $res.="
    ".do_spans($handler,@quote)."
    "; #while($lines[0]=~/^>(.*)/) { push @quote,$1; shift @lines; } #$res.="
    ".do_blocks($handler,@quote)."
    "; } else # normal paragraph { my @text; while($lines[0]!~/^(?:\s*$|1\. |[\*\+\-] |>| |\t)/) { push @text,shift @lines; } if(!defined($lines[0]) and $simplify) { $res.=do_spans($handler,@text) } else { $res.="

    ".do_spans($handler,@text)."

    " } } $simplify=0; } return $res; } sub do_spans($@) { my $handler=shift; return join "
    ",map { my $line=$_; my @hidden; # hide sections $line=~s{ (?]+?) (?$2"; ""}sgex; # make URLs into links and hide them $line=~s{$url_re}{push @hidden,"$1\"; "$2"}sge; # do $line=~s{ (?\s\*_]) ([^<>]+?) (?\s\*_\x80-\x9f\xe0-\xfc]) \1 (?![0-9a-zA-Z\*_]) }{$2}gx; # do $line=~s{ (?\s\*_]) ([^<>]+?) (?\s\*_\x80-\x9f\xe0-\xfc]) \1 (?![0-9a-zA-Z\*_]) }{$2}gx; # do ^H if($]>5.007) { my $regexp; $regexp=qr/(?:&#?[0-9a-zA-Z]+;|.)(?".(substr $1,0,(length $1)/3).""}gex; } $line=$handler->($line) if($handler); # fix up hidden sections $line=~s{}{$hidden[$1]}ge; $line; } @_; } sub compile_template($;$) { my ($str,$nostrip)=@_; my $code; unless($nostrip) { $str=~s/^\s+//; $str=~s/\s+$//; $str=~s/\n\s*/ /sg; } while($str=~m!(.*?)(<(/?)(var|const|if|loop)(?:|\s+(.*?[^\\]))>|$)!sg) { my ($html,$tag,$closing,$name,$args)=($1,$2,$3,$4,$5); $html=~s/(['\\])/\\$1/g; $code.="\$res.='$html';" if(length $html); $args=~s/\\>/>/g; if($tag) { if($closing) { if($name eq 'if') { $code.='}' } elsif($name eq 'loop') { $code.='$$_=$__ov{$_} for(keys %__ov);}}' } } else { if($name eq 'var') { $code.='$res.=eval{'.$args.'};' } elsif($name eq 'const') { my $const=eval $args; $const=~s/(['\\])/\\$1/g; $code.='$res.=\''.$const.'\';' } elsif($name eq 'if') { $code.='if(eval{'.$args.'}){' } elsif($name eq 'loop') { $code.='my $__a=eval{'.$args.'};if($__a){for(@$__a){my %__v=%{$_};my %__ov;for(keys %__v){$__ov{$_}=$$_;$$_=$__v{$_};}' } } } } my $sub=eval 'no strict; sub { '. 'my $port=$ENV{SERVER_PORT}==80?"":":$ENV{SERVER_PORT}";'. 'my $self=$ENV{SCRIPT_NAME};'. 'my $absolute_self="http://$ENV{SERVER_NAME}$port$ENV{SCRIPT_NAME}";'. 'my ($path)=$ENV{SCRIPT_NAME}=~m!^(.*/)[^/]+$!;'. 'my $absolute_path="http://$ENV{SERVER_NAME}$port$path";'. 'my %__v=@_;my %__ov;for(keys %__v){$__ov{$_}=$$_;$$_=$__v{$_};}'. 'my $res;'. $code. '$$_=$__ov{$_} for(keys %__ov);'. 'return $res; }'; die "Template format error" unless $sub; return $sub; } sub template_for($$$) { my ($var,$start,$end)=@_; return [map +{$var=>$_},($start..$end)]; } sub include($) { my ($filename)=@_; open FILE,$filename or return ''; my $file=do { local $/; }; $file=~s/^\s+//; $file=~s/\s+$//; $file=~s/\n\s*/ /sg; return $file; } sub forbidden_unicode($;$) { my ($dec,$hex)=@_; return 1 if length($dec)>7 or length($hex)>7; # too long numbers my $ord=($dec or hex $hex); return 1 if $ord>MAX_UNICODE; # outside unicode range return 1 if $ord<32; # control chars return 1 if $ord>=0xd800 and $ord<=0xdfff; # surrogate code points return 1 if $ord>=0x202a and $ord<=0x202e; # text direction return 0; } sub clean_string($;$) { my ($str,$cleanentities)=@_; if($cleanentities) { $str=~s/&/&/g } # clean up & else { $str=~s/&(#([0-9]+);|#x([0-9a-fA-F]+);|)/ if($1 eq "") { '&' } # change simple ampersands elsif(forbidden_unicode($2,$3)) { "" } # strip forbidden unicode chars else { "&$1" } # and leave the rest as-is. /ge # clean up &, excluding numerical entities } $str=~s/\/>/g; $str=~s/"/"/g; # clean up quotes for HTML attributes $str=~s/'/'/g; $str=~s/,/,/g; # clean up commas for some reason I forgot $str=~s/[\x00-\x08\x0b\x0c\x0e-\x1f]//g; # remove control chars return $str; } sub decode_string($;$$) { my ($str,$charset,$noentities)=@_; my $use_unicode=$has_encode && $charset; $str=decode($charset,$str) if $use_unicode; $str=~s{(&#([0-9]*)([;&])|&#([x&])([0-9a-f]*)([;&]))}{ my $ord=($2 or hex $5); if($3 eq '&' or $4 eq '&' or $5 eq '&') { $1 } # nested entities, leave as-is. elsif(forbidden_unicode($2,$5)) { "" } # strip forbidden unicode chars elsif($ord==35 or $ord==38) { $1 } # don't convert & or # elsif($use_unicode) { chr $ord } # if we have unicode support, convert all entities elsif($ord<128) { chr $ord } # otherwise just convert ASCII-range entities else { $1 } # and leave the rest as-is. }gei unless $noentities; $str=~s/[\x00-\x08\x0b\x0c\x0e-\x1f]//g; # remove control chars return $str; } sub escamp($) { my ($str)=@_; $str=~s/&/&/g; return $str; } sub urlenc($) { my ($str)=@_; $str=~s/([^\w ])/"%".sprintf("%02x",ord $1)/sge; $str=~s/ /+/sg; return $str; } sub clean_path($) { my ($str)=@_; $str=~s!([^\w/._\-])!"%".sprintf("%02x",ord $1)!sge; return $str; } # # Javascript utilities # sub clean_to_js($) { my $str=shift; $str=~s/&/\\x26/g; $str=~s/</\\x3c/g; $str=~s/>/\\x3e/g; $str=~s/"/\\x22/g; #" $str=~s/('|')/\\x27/g; $str=~s/,/,/g; $str=~s/&#[0-9]+;/sprintf "\\u%04x",$1/ge; $str=~s/&#x[0-9a-f]+;/sprintf "\\u%04x",hex($1)/gie; $str=~s/(\r\n|\r|\n)/\\n/g; return "'$str'"; } sub js_string($) { my $str=shift; $str=~s/\\/\\\\/g; $str=~s/'/\\'/g; $str=~s/([\x00-\x1f\x80-\xff<>&])/sprintf "\\x%02x",ord($1)/ge; eval '$str=~s/([\x{100}-\x{ffff}])/sprintf "\\u%04x",ord($1)/ge'; $str=~s/(\r\n|\r|\n)/\\n/g; return "'$str'"; } sub js_array(@) { return "[".(join ",",@_)."]"; } sub js_hash(%) { my %hash=@_; return "{".(join ",",map "'$_':$hash{$_}",keys %hash)."}"; } # # HTTP utilities # # LIGHTWEIGHT HTTP/1.1 CLIENT # by fatalM4/coda, modified by WAHa.06x36 use constant CACHEFILE_PREFIX => 'cache-'; # you can make this a directory (e.g. 'cachedir/cache-' ) if you'd like use constant FORCETIME => '0.04'; # If the cache is less than (FORCETIME) days old, don't even attempt to refresh. # Saves everyone some bandwidth. 0.04 days is ~ 1 hour. 0.0007 days is ~ 1 min. eval 'use IO::Socket::INET'; # Will fail on old Perl versions! sub get_http($;$$$) { my ($url,$maxsize,$referer,$cacheprefix)=@_; my ($host,$port,$doc)=$url=~m!^(?:http://|)([^/]+)(:[0-9]+|)(.*)$!; $port=80 unless($port); my $hash=encode_base64(rc4(null_string(6),"$host:$port$doc",0),""); $hash=~tr!/+!_-!; # remove / and + my $cachefile=($cacheprefix or CACHEFILE_PREFIX).($doc=~m!([^/]{0,15})$!)[0]."-$hash"; # up to 15 chars of filename my ($modified,$cache); if(open CACHE,"<",$cachefile) # get modified date and cache contents { $modified=; $cache=join "",; chomp $modified; close CACHE; return $cache if((-M $cachefile)new("$host:$port") or return $cache; print $sock "GET $doc HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n"; print $sock "If-Modified-Since: $modified\r\n" if $modified; print $sock "Referer: $referer\r\n" if $referer; print $sock "\r\n"; #finished! # header my ($line,$statuscode,$lastmod); do { $line=<$sock>; $statuscode=$1 if($line=~/^HTTP\/1\.1 (\d+)/); $lastmod=$1 if($line=~/^Last-Modified: (.*)/); } until ($line=~/^\r?\n/); # body my ($line,$output); while($line=<$sock>) { $output.=$line; last if $maxsize and $output>=$maxsize; } undef $sock; if($statuscode=="200") { #navbar changed, update cache if(open CACHE,">$cachefile") { print CACHE "$lastmod\n"; print CACHE $output; close CACHE or die "close cache: $!"; } return $output; } else # touch and return cache, or nothing if no cache { utime(time,time,$cachefile); return $cache; } } sub make_http_forward($;$) { my ($location,$alternate_method)=@_; if($alternate_method) { print "Content-Type: text/html\n"; print "\n"; print ""; print ''; print ''; print ''.$location.''; } else { print "Status: 303 Go West\n"; print "Location: $location\n"; print "Content-Type: text/html\n"; print "\n"; print ''.$location.''; } } sub make_cookies(%) { my (%cookies)=@_; my $charset=$cookies{'-charset'}; my $expires=($cookies{'-expires'} or time+14*24*3600); my $autopath=$cookies{'-autopath'}; my $path=$cookies{'-path'}; my $date=make_date($expires,"cookie"); unless($path) { if($autopath eq 'current') { ($path)=$ENV{SCRIPT_NAME}=~m!^(.*/)[^/]+$! } elsif($autopath eq 'parent') { ($path)=$ENV{SCRIPT_NAME}=~m!^(.*?/)(?:[^/]+/)?[^/]+$! } else { $path='/'; } } foreach my $name (keys %cookies) { next if($name=~/^-/); # skip entries that start with a dash my $value=$cookies{$name}; $value="" unless(defined $value); $value=cookie_encode($value,$charset); print "Set-Cookie: $name=$value; path=$path; expires=$date;\n"; } } sub cookie_encode($;$) { my ($str,$charset)=@_; if($]>5.007) # new perl, use Encode.pm { if($charset) { require Encode; $str=Encode::decode($charset,$str); $str=~s/&\#([0-9]+);/chr $1/ge; $str=~s/&\#x([0-9a-f]+);/chr hex $1/gei; } $str=~s/([^0-9a-zA-Z])/ my $c=ord $1; sprintf($c>255?'%%u%04x':'%%%02x',$c); /sge; } else # do the hard work ourselves { if($charset=~/\butf-?8$/i) { $str=~s{([\xe0-\xef][\x80-\xBF][\x80-\xBF]|[\xc0-\xdf][\x80-\xBF]|&#([0-9]+);|&#[xX]([0-9a-fA-F]+);|[^0-9a-zA-Z])}{ # convert UTF-8 to URL encoding - only handles up to U-FFFF my $c; if($2) { $c=$2 } elsif($3) { $c=hex $3 } elsif(length $1==1) { $c=ord $1 } elsif(length $1==2) { my @b=map { ord $_ } split //,$1; $c=(($b[0]-0xc0)<<6)+($b[1]-0x80); } elsif(length $1==3) { my @b=map { ord $_ } split //,$1; $c=(($b[0]-0xe0)<<12)+(($b[1]-0x80)<<6)+($b[2]-0x80); } sprintf($c>255?'%%u%04x':'%%%02x',$c); }sge; } elsif($charset=~/\b(?:shift.*jis|sjis)$/i) # old perl, using shift_jis { require 'sjis.pl'; my $sjis_table=get_sjis_table(); $str=~s{([\x80-\x9f\xe0-\xfc].|&#([0-9]+);|&#[xX]([0-9a-fA-F]+);|[^0-9a-zA-Z])}{ # convert Shift_JIS to URL encoding my $c=($2 or ($3 and hex $3) or $$sjis_table{$1}); sprintf($c>255?'%%u%04x':'%%%02x',$c); }sge; } else { $str=~s/([^0-9a-zA-Z])/sprintf('%%%02x',ord $1)/sge; } } return $str; } sub get_xhtml_content_type(;$$) { my ($charset,$usexhtml)=@_; my $type; if($usexhtml and $ENV{HTTP_ACCEPT}=~/application\/xhtml\+xml/) { $type="application/xhtml+xml"; } else { $type="text/html"; } $type.="; charset=$charset" if($charset); return $type; } sub expand_filename($) { my ($filename)=@_; return $filename if($filename=~m!^/!); return $filename if($filename=~m!^\w+:!); my ($self_path)=$ENV{SCRIPT_NAME}=~m!^(.*/)[^/]+$!; return $self_path.$filename; } # # Network utilities # sub resolve_host($) { my $ip=shift; return (gethostbyaddr inet_aton($ip),AF_INET or $ip); } # # Data utilities # sub process_tripcode($;$$$) { my ($name,$tripkey,$secret,$charset)=@_; $tripkey="!" unless($tripkey); if($name=~/^(.*?)((?$maxlen); # $trip=$tripkey.$tripkey.encode_base64(rc4(null_string(6),"t".$str.$secret),""); $trip=$tripkey.$tripkey.hide_data($1,6,"trip",$secret,1); return ($namepart,$trip) unless($trippart); # return directly if there's no normal tripcode } # 2ch trips are processed as Shift_JIS whenever possible eval 'use Encode qw(decode encode)'; unless($@) { $trippart=decode_string($trippart,$charset); $trippart=encode("Shift_JIS",$trippart,0x0200); } $trippart=clean_string($trippart); my $salt=substr $trippart."H..",1,2; $salt=~s/[^\.-z]/./g; $salt=~tr/:;<=>?@[\\]^_`/ABCDEFGabcdef/; $trip=$tripkey.(substr crypt($trippart,$salt),-10).$trip; return ($namepart,$trip); } return (clean_string(decode_string($name,$charset)),""); } sub make_date($$;@) { my ($time,$style,@locdays)=@_; my @days=qw(Sun Mon Tue Wed Thu Fri Sat); my @months=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); @locdays=@days unless(@locdays); if($style eq "2ch") { my @ltime=localtime($time); return sprintf("%04d-%02d-%02d %02d:%02d", $ltime[5]+1900,$ltime[4]+1,$ltime[3],$ltime[2],$ltime[1]); } elsif($style eq "futaba" or $style eq "0") { my @ltime=localtime($time); return sprintf("%02d/%02d/%02d(%s)%02d:%02d", $ltime[5]-100,$ltime[4]+1,$ltime[3],$locdays[$ltime[6]],$ltime[2],$ltime[1]); } elsif($style eq "localtime") { return scalar(localtime($time)); } elsif($style eq "tiny") { my @ltime=localtime($time); return sprintf("%02d/%02d %02d:%02d", $ltime[4]+1,$ltime[3],$ltime[2],$ltime[1]); } elsif($style eq "http") { my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time); return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", $days[$wday],$mday,$months[$mon],$year+1900,$hour,$min,$sec); } elsif($style eq "cookie") { my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time); return sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT", $days[$wday],$mday,$months[$mon],$year+1900,$hour,$min,$sec); } elsif($style eq "month") { my ($sec,$min,$hour,$mday,$mon,$year,$wday)=gmtime($time); return sprintf("%s %d", $months[$mon],$year+1900); } elsif($style eq "2ch-sep93") { my $sep93=timelocal(0,0,0,1,8,93); return make_date($time,"2ch") if($time<$sep93); my @ltime=localtime($time); return sprintf("%04d-%02d-%02d %02d:%02d", 1993,9,int ($time-$sep93)/86400+1,$ltime[2],$ltime[1]); } } sub parse_http_date($) { my ($date)=@_; my %months=(Jan=>0,Feb=>1,Mar=>2,Apr=>3,May=>4,Jun=>5,Jul=>6,Aug=>7,Sep=>8,Oct=>9,Nov=>10,Dec=>11); if($date=~/^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$/) { return eval { timegm($6,$5,$4,$1,$months{$2},$3-1900) } } return undef; } sub cfg_expand($%) { my ($str,%grammar)=@_; $str=~s/%(\w+)%/ my @expansions=@{$grammar{$1}}; cfg_expand($expansions[rand @expansions],%grammar); /ge; return $str; } sub encode_base64($;$) # stolen from MIME::Base64::Perl { my ($data,$eol)=@_; $eol="\n" unless(defined $eol); my $res=pack "u",$data; $res=~s/^.//mg; # remove length counts $res=~s/\n//g; # remove newlines $res=~tr|` -_|AA-Za-z0-9+/|; # translate to base64 my $padding=(3-length($data)%3)%3; # fix padding at the end $res=~s/.{$padding}$/'='x$padding/e if($padding); $res=~s/(.{1,76})/$1$eol/g if(length $eol); # break encoded string into lines of no more than 76 characters each return $res; } sub decode_base64($) # stolen from MIME::Base64::Perl { my ($str)=@_; $str=~tr|A-Za-z0-9+=/||cd; # remove non-base64 characters $str=~s/=+$//; # remove padding $str=~tr|A-Za-z0-9+/| -_|; # translate to uuencode return "" unless(length $str); return unpack "u",join '',map { chr(32+length($_)*3/4).$_ } $str=~/(.{1,60})/gs; } sub dot_to_dec($) { return unpack('N',pack('C4',split(/\./, $_[0]))); # wow, magic. } sub dec_to_dot($) { return join('.',unpack('C4',pack('N',$_[0]))); } sub mask_ip($$;$) { my ($ip,$key,$algorithm)=@_; $ip=dot_to_dec($ip) if $ip=~/\./; my ($block,$stir)=setup_masking($key,$algorithm); my $mask=0x80000000; for(1..32) { my $bit=$ip&$mask?"1":"0"; $block=$stir->($block); $ip^=$mask if(ord($block)&0x80); $block=$bit.$block; $mask>>=1; } return sprintf "%08x",$ip; } sub unmask_ip($$;$) { my ($id,$key,$algorithm)=@_; $id=hex($id); my ($block,$stir)=setup_masking($key,$algorithm); my $mask=0x80000000; for(1..32) { $block=$stir->($block); $id^=$mask if(ord($block)&0x80); my $bit=$id&$mask?"1":"0"; $block=$bit.$block; $mask>>=1; } return dec_to_dot($id); } sub setup_masking($$) { my ($key,$algorithm)=@_; $algorithm=$has_md5?"md5":"rc6" unless $algorithm; my ($block,$stir); if($algorithm eq "md5") { return (md5($key),sub { md5(shift) }) } else { setup_rc6($key); return (null_string(16),sub { encrypt_rc6(shift) }) } } sub make_random_string($) { my ($num)=@_; my $chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; my $str; $str.=substr $chars,rand length $chars,1 for(1..$num); return $str; } sub null_string($) { "\0"x(shift) } sub make_key($$$) { my ($key,$secret,$length)=@_; return rc4(null_string($length),$key.$secret); } sub hide_data($$$$;$) { my ($data,$bytes,$key,$secret,$base64)=@_; my $crypt=rc4(null_string($bytes),make_key($key,$secret,32).$data); return encode_base64($crypt,"") if $base64; return $crypt; } # # File utilities # sub read_array($) { my ($file)=@_; if(ref $file eq "GLOB") { return map { s/\r?\n?$//; $_ } <$file>; } else { open FILE,$file or return (); my @array=map { s/\r?\n?$//; $_ } ; close FILE; return @array; } } sub write_array($@) { my ($file,@array)=@_; if(ref $file eq "GLOB") { print $file join "\n",@array; } else # super-paranoid atomic write { my $rndname1="__".make_random_string(12).".dat"; my $rndname2="__".make_random_string(12).".dat"; if(open FILE,">$rndname1") { if(print FILE join "\n",@array) { close FILE; rename $file,$rndname2 if -e $file; if(rename $rndname1,$file) { unlink $rndname2 if -e $rndname2; return; } } } close FILE; die "Couldn't write to file \"$file\""; } } # # Spam utilities # sub spam_check($$) # Deprecated function { my ($text,$spamfile)=@_; return compile_spam_checker($spamfile)->($text); } sub compile_spam_checker(@) { my @re=map { s{(\\?\\?&\\?#([0-9]+)\\?;|\\?&\\?#x([0-9a-f]+)\\?;)}{ sprintf("\\x{%x}",($2 or hex $3)); }gei if $has_encode; $_; } map { s/(^|\s+)#.*//; s/^\s+//; s/\s+$//; # strip perl-style comments and whitespace if(!length) { () } # nothing left, skip elsif(m!^/(.*)/$!) { $1 } # a regular expression elsif(m!^/(.*)/([xism]+)$!) { "(?$2)$1" } # a regular expression with xism modifiers else { quotemeta } # a normal string } map read_array($_),@_; return eval 'sub { $_=shift; # study; # causes a strange bug - moved to spam_engine() return '.(join "||",map "/$_/mo",(@re)).'; }'; } sub spam_engine(%) { my %args=@_; my @spam_files=@{$args{spam_files}||[]}; my @trap_fields=@{$args{trap_fields}||[]}; my %excluded_fields=map ($_=>1),@{$args{excluded_fields}||[]}; my $query=$args{query}||new CGI; my $charset=$args{charset}; for(@trap_fields) { spam_screen($query) if $query->param($_) } my $spam_checker=compile_spam_checker(@spam_files); my @fields=$query->param; @fields=grep !$excluded_fields{$_},@fields if %excluded_fields; my $fulltext=join "\n",map decode_string($query->param($_),$charset),@fields; study $fulltext; spam_screen($query) if $spam_checker->($fulltext); } sub spam_screen($) { my $query=shift; print "Content-Type: text/html\n\n"; print ""; print "

    Anti-spam filters triggered.

    "; print "

    If you are not a spammer, you are probably accidentially "; print "trying to use an URL that is listed in the spam file. Try "; print "editing your post to remove it. Sorry for any inconvenience.

    "; print ""; print "$_
    " for(map $query->param($_),$query->param); print "
    "; exit 0; } # # Image utilities # sub analyze_image($$) { my ($file,$name)=@_; my (@res); return ("jpg",@res) if(@res=analyze_jpeg($file)); return ("png",@res) if(@res=analyze_png($file)); return ("gif",@res) if(@res=analyze_gif($file)); # find file extension for unknown files my ($ext)=$name=~/\.([^\.]+)$/; return (lc($ext),0,0); } sub analyze_jpeg($) { my ($file)=@_; my ($buffer); read($file,$buffer,2); if($buffer eq "\xff\xd8") { OUTER: for(;;) { for(;;) { last OUTER unless(read($file,$buffer,1)); last if($buffer eq "\xff"); } last unless(read($file,$buffer,3)==3); my ($mark,$size)=unpack("Cn",$buffer); last if($mark==0xda or $mark==0xd9); # SOS/EOI die "Possible virus in image" if($size<2); # MS GDI+ JPEG exploit uses short chunks if($mark>=0xc0 and $mark<=0xc2) # SOF0..SOF2 - what the hell are the rest? { last unless(read($file,$buffer,5)==5); my ($bits,$height,$width)=unpack("Cnn",$buffer); seek($file,0,0); return($width,$height); } seek($file,$size-2,1); } } seek($file,0,0); return (); } sub analyze_png($) { my ($file)=@_; my ($bytes,$buffer); $bytes=read($file,$buffer,24); seek($file,0,0); return () unless($bytes==24); my ($magic1,$magic2,$length,$ihdr,$width,$height)=unpack("NNNNNN",$buffer); return () unless($magic1==0x89504e47 and $magic2==0x0d0a1a0a and $ihdr==0x49484452); return ($width,$height); } sub analyze_gif($) { my ($file)=@_; my ($bytes,$buffer); $bytes=read($file,$buffer,10); seek($file,0,0); return () unless($bytes==10); my ($magic,$width,$height)=unpack("A6 vv",$buffer); return () unless($magic eq "GIF87a" or $magic eq "GIF89a"); return ($width,$height); } sub make_thumbnail($$$$$;$) { my ($filename,$thumbnail,$width,$height,$quality,$convert)=@_; # first try ImageMagick my $magickname=$filename; $magickname.="[0]" if($magickname=~/\.gif$/); $convert="convert" unless($convert); `$convert -size ${width}x${height} -geometry ${width}x${height}! -quality $quality $magickname $thumbnail`; return 1 unless($?); # if that fails, try pnmtools instead if($filename=~/\.jpg$/) { `djpeg $filename | pnmscale -width $width -height $height | cjpeg -quality $quality > $thumbnail`; # could use -scale 1/n return 1 unless($?); } elsif($filename=~/\.png$/) { `pngtopnm $filename | pnmscale -width $width -height $height | cjpeg -quality $quality > $thumbnail`; return 1 unless($?); } elsif($filename=~/\.gif$/) { `giftopnm $filename | pnmscale -width $width -height $height | cjpeg -quality $quality > $thumbnail`; return 1 unless($?); } # try Mac OS X's sips `sips -z $height $width -s formatOptions normal -s format jpeg $filename --out $thumbnail >/dev/null`; # quality setting doesn't seem to work return 1 unless($?); # try PerlMagick (it sucks) eval 'use Image::Magick'; unless($@) { my ($res,$magick); $magick=Image::Magick->new; $res=$magick->Read($magickname); return 0 if "$res"; $res=$magick->Scale(width=>$width, height=>$height); #return 0 if "$res"; $res=$magick->Write(filename=>$thumbnail, quality=>$quality); #return 0 if "$res"; return 1; } # try GD lib (also sucks, and untested) eval 'use GD'; unless($@) { my $src; if($filename=~/\.jpg$/i) { $src=GD::Image->newFromJpeg($filename) } elsif($filename=~/\.png$/i) { $src=GD::Image->newFromPng($filename) } elsif($filename=~/\.gif$/i) { if(defined &GD::Image->newFromGif) { $src=GD::Image->newFromGif($filename) } else { `gif2png $filename`; # gif2png taken from futallaby $filename=~s/\.gif/\.png/; $src=GD::Image->newFromPng($filename); } } else { return 0 } my ($img_w,$img_h)=$src->getBounds(); my $thumb=GD::Image->new($width,$height); $thumb->copyResized($src,0,0,0,0,$width,$height,$img_w,$img_h); my $jpg=$thumb->jpeg($quality); open THUMBNAIL,">$thumbnail"; binmode THUMBNAIL; print THUMBNAIL $jpg; close THUMBNAIL; return 1 unless($!); } return 0; } # # Crypto code # sub rc4($$;$) { my ($message,$key,$skip)=@_; my @s=0..255; my @k=unpack 'C*',$key; my @message=unpack 'C*',$message; my ($x,$y); $skip=256 unless(defined $skip); $y=0; for $x (0..255) { $y=($y+$s[$x]+$k[$x%@k])%256; @s[$x,$y]=@s[$y,$x]; } $x=0; $y=0; for(1..$skip) { $x=($x+1)%256; $y=($y+$s[$x])%256; @s[$x,$y]=@s[$y,$x]; } for(@message) { $x=($x+1)%256; $y=($y+$s[$x])%256; @s[$x,$y]=@s[$y,$x]; $_^=$s[($s[$x]+$s[$y])%256]; } return pack 'C*',@message; } my @S; sub setup_rc6($) { my ($key)=@_; $key.="\0"x(4-(length $key)&3); # pad key my @L=unpack "V*",$key; $S[0]=0xb7e15163; $S[$_]=add($S[$_-1],0x9e3779b9) for(1..43); my $v=@L>44 ? @L*3 : 132; my ($A,$B,$i,$j)=(0,0,0,0); for(1..$v) { $A=$S[$i]=rol(add($S[$i],$A,$B),3); $B=$L[$j]=rol(add($L[$j]+$A+$B),add($A+$B)); $i=($i+1)%@S; $j=($j+1)%@L; } } sub encrypt_rc6($) { my ($block,)=@_; my ($A,$B,$C,$D)=unpack "V4",$block."\0"x16; $B=add($B,$S[0]); $D=add($D,$S[1]); for(my $i=1;$i<=20;$i++) { my $t=rol(mul($B,rol($B,1)|1),5); my $u=rol(mul($D,rol($D,1)|1),5); $A=add(rol($A^$t,$u),$S[2*$i]); $C=add(rol($C^$u,$t),$S[2*$i+1]); ($A,$B,$C,$D)=($B,$C,$D,$A); } $A=add($A,$S[42]); $C=add($C,$S[43]); return pack "V4",$A,$B,$C,$D; } sub decrypt_rc6($) { my ($block,)=@_; my ($A,$B,$C,$D)=unpack "V4",$block."\0"x16; $C=add($C,-$S[43]); $A=add($A,-$S[42]); for(my $i=20;$i>=1;$i--) { ($A,$B,$C,$D)=($D,$A,$B,$C); my $u=rol(mul($D,add(rol($D,1)|1)),5); my $t=rol(mul($B,add(rol($B,1)|1)),5); $C=ror(add($C,-$S[2*$i+1]),$t)^$u; $A=ror(add($A,-$S[2*$i]),$u)^$t; } $D=add32($D,-$S[1]); $B=add32($B,-$S[0]); return pack "V4",$A,$B,$C,$D; } sub setup_xtea($) { } sub encrypt_xtea($) { } sub decrypt_xtea($) { } sub add(@) { my ($sum,$term); while(defined ($term=shift)) { $sum+=$term } return $sum%4294967296 } sub rol($$) { my ($x,$n); ( $x = shift ) << ( $n = 31 & shift ) | 2**$n - 1 & $x >> 32 - $n; } sub ror($$) { rol(shift,32-(31&shift)); } # rorororor sub mul($$) { my ($a,$b)=@_; return ( (($a>>16)*($b&65535)+($b>>16)*($a&65535))*65536+($a&65535)*($b&65535) )%4294967296 } 1;