#!/usr/local/bin/perl -T ## ## COTSE Pseudo-Anonymous Usenet. ## Version 2.02 ## July 11, 2000 ## ## This is copyright 2000 Stephen K. Gielda. ## http://www.cotse.com ## steve@cotse.com ## ## This script is set up to be a pseudo-anonymous gateway to usenet, but it can just ## as easily be used for a regular e-mail interface. When we offered psuedo-anon ## e-mail I used this script. All that was required was the addition of a field on ## the form for To. Then comment out the $to variable setting in the user customizable ## section and you have an e-mail interface. ## ## This is made available to use freely. It may not be rereleased without including ## this header in its entirety. ## ## Cotse maintains no liability over anything that may result from your use of this. ## You are fully responsible for understanding all possible implications of running it. ## ## Setup: ## ## This script requires the following Perl Modules ## ## CGI.pm ## Form.pm ## URI.pm ## ## Create a directory to store ban databases (ie. /home/blockit). This directory must ## be read/write by the user ID of the web server (typically user nobody). ## ## Create a directory to store session keys (ie. /home/anonnewskeys). This must also ## be read/write by the user ID of the web server. ## ## Change appropriate user configuration variables listed below. ## ########################################################################################## ## Set the path for security and taint checking. $ENV{'PATH'} = "/usr/bin:/bin"; use CGI::Carp; use CGI::Form; use strict; ## Variable initializations (for use strict). my ($to,$mheader,$refs,$BLK,$IBLK,$admin,$from,$mult1,$mult2); my ($test1,$from1,$addr,$xtra1,$xtra2,$subject,$body,$sendmail,$query); my ($file_name,$number_to_ban,$max_time,$group,$total_max_time); my ($HEADER, $FOOTER, $blockpath, $scriptpage, $pagename, $xabuse, $org, $randfile); my ($FST,$SND,$TRD,$FTH,$classa,$classb,$classc,$ISBLK,$FBLK, $runline); my ($outfile, $key, $scriptname, $abusepage, $sessdir, $sessexp, $offline); ## Seed random number generator. srand(time() ^ ($$ + ($$<<15)) ); ## Randomize filename for server key. $randfile = randname(); ################### Begin user customizable entries ##################### ## Take service down - Reason set automatically takes service offline with that ## reason. Uncomment to activate. # $offline = "Temporarily down. Please check back later."; ## Name of the service. $pagename = "COTSE Pseudo-Anonymous News"; ## scriptpage is for use when calling cgi from your own html form. ## I do this on cotse with an exec cgi SSI tag. ## If doing that then the scriptpage is set to the html form. ## If not using an html form, set scriptpage to be the same as scriptname. $scriptpage = "http://anon.cotse.com/anonnews.htm"; ## Name of the script. $scriptname = "http://anon.cotse.com/cgi-bin/anonnews.cgi"; ## Path to policies page $abusepage = "http://anon.cotse.com/cgi-bin/blockit.cgi"; ## Please adjust the following to point to your domain and addresses. I do not want ## abuse mail from a service someone else runs, I'll chase you down :) $from = "anonymous\@cotse.com"; $to = "mail2news\@zedz.net"; $xabuse = "abuse\@cotse.com"; $org = "Cotse"; $admin = "steve\@cotse.com"; ## Path to sendmail $sendmail = "/usr/lib/sendmail"; ## Path to files storing blocked newsgroups and IP's. ## This directory must be writable by the user ID that the web ## server operates under (typically nobody). $blockpath="/usr/local/blockit"; ## These files are simple text files of newsgroups or IPs in a column format. ## Unlike other files within that directory, these are manually administered. ## files must be readable by user nobody or the user which web daemon runs under. ## They do not need to be writeable by this user, it is probably better if they aren't. ## ## Functional Files: ## ## blockit.txt - for blocking posts to specified newsgroups ## ipblock.txt - for blocking an IP from using the interface ## classcblock.txt - for blocking a class c range from using the interface. ## classbblock.txt - for blocking a class b range from using the interface. ## classablock.txt - for blocking a class a range from using the interface. ## forgeblock.txt - to force from line of Anon in specified newsgroups. ## Set header for pages. $HEADER = "
This is your header.
\n"; ## Set footer for pages. $FOOTER = "


Cotse Pseudo-Anon Usenet Interface. Copyright 2000 Stephen K. Gielda

Get the source here!
\n"; ## Vars for autoban of user for posting too much too fast. ## num of posts before user is temp banned. ## Note: Each get of the interface is counted also. So 20 ## total hits of the script result in 10 posts. $number_to_ban=20; ## Seconds max time in which they can post the number set above. $max_time=86400; ## when the time count gets reset default 1 hour 60*60=3600 $total_max_time=86400; ## Set autoblock after x posts for a specific group. $group=""; ## example $group="comp.unix.programming"; ## ## NOTE: The above example will only check the number of posts for that one group. ## If you wish to have X posts in 24 hrs and X posts per group equals ban, use the ## setting below. While this gives you a finer granularity, it can pose problems for ## groups of users legitimately posting to one newsgroup. I recommend leaving the setting ## as is above. ## $group=$mheader; - To use this setting, you must place it in ## the ### set group = mheader here ## marker further down in the script. ## Set directory to store session keys. $sessdir = "/home/httpd/cgi-bin/anonnews"; ## Set session key expire in minutes $sessexp = "15"; ## Remove expired session keys. ## ## This can be removed and run as a cron job to lighten the processing ## if needed. I left it here so that the script is self-contained. $runline = "find $sessdir -amin +$sessexp | perl -nle 'unlink'"; system($runline); ################### End user customizable entries ##################### ## Create a new query object for CGI and HTML parsing. $query = new CGI::Form(); ## Take offline &offline() if $offline; ## Path to temp file for autobans. $file_name="$blockpath/ban_file.txt"; ## Check for banned Class A ranges open(ISBLOCK, "$blockpath/classablock.txt"); ($FST, $SND, $TRD, $FTH) = split /\./,$ENV{'REMOTE_ADDR'},4; $classa = "$FST"; foreach $ISBLK () { &banned() if $classa eq $ISBLK; chop $ISBLK; &banned() if $classa eq $ISBLK; } close(ISBLOCK); ## Check for banned Class B ranges open(ISBLOCK, "$blockpath/classbblock.txt"); ($FST, $SND, $TRD, $FTH) = split /\./,$ENV{'REMOTE_ADDR'},4; $classb = "$FST.$SND"; foreach $ISBLK () { &banned() if $classb eq $ISBLK; chop $ISBLK; &banned() if $classb eq $ISBLK; } close(ISBLOCK); ## Check for banned Class C ranges open(ISBLOCK, "$blockpath/classcblock.txt"); ($FST, $SND, $TRD, $FTH) = split /\./,$ENV{'REMOTE_ADDR'},4; $classc = "$FST.$SND.$TRD"; foreach $ISBLK () { &banned() if $classc eq $ISBLK; chop $ISBLK; &banned() if $classc eq $ISBLK; } close(ISBLOCK); ## Check for banned IP addresses. open(IBLOCK, "$blockpath/ipblock.txt"); foreach $IBLK () { &banned() if $ENV{'REMOTE_ADDR'} eq $IBLK; chop $IBLK; &banned() if $ENV{'REMOTE_ADDR'} eq $IBLK; } close(IBLOCK); ################################## ## Go get form entries $mheader ||=$query->param("header"); $xtra1 ||=$query->param("xtra1"); $xtra2 ||=$query->param("xtra2"); $subject ||= $query->param("subject"); $from1 ||= $query->param("from1"); $refs ||= $query->param("references"); $body ||= $query->param("body"); $key ||= $query->param("key"); $from1 ||="Anon"; $key ||="new"; ### set group = mheader here ## # call the autoblock function &ban_users($file_name,$number_to_ban,$max_time,$group,$total_max_time); # If this is initial get generate key and show form. &show_start_form() if $key eq "new"; ## Check to see if script is being called from proper page. $test1 = $ENV{'HTTP_REFERER'}; ¬local() if $test1 ne $scriptpage; ## Check to see if session key exists: ## if it does, delete it. ## if not bounce to expired key screen. if (-e "$sessdir/$key") { unlink("$sessdir/$key"); } else { &nomatch(); } ## Block crossposting and commas in the from line. ($mult1, $mult2) = split /\,/,$mheader,2; ($from1, $mult2) = split /\@/,$from1,2; ## Lowercase the newsgroup so it can be standard for checking against blocks. $mheader = lc $mult1; ## Check to see if newsgroup is blocked from posting. open(BLOCK, "$blockpath/blockit.txt"); foreach $BLK () { &HTMLdie() if $mheader eq $BLK; chop $BLK; &HTMLdie() if $mheader eq $BLK; } close(BLOCK); ## Check for restricted from lines per groups open(FBLOCK, "$blockpath/forgeblock.txt"); foreach $FBLK () { &Noforge() if $mheader eq $FBLK; chop $FBLK; &Noforge() if $mheader eq $FBLK; } close(FBLOCK); ## Append a warning to the message about forgery and origin. $body .= <<"EOHF"; EOHF ## Open sendmail open(MAIL,"| $sendmail -oi -t") || do { print $query->header(); print $query->start_html(-title => "Error Posting", -author => $admin); print "

Error Posting

\n"; print "

Form exited with error: $!, please go back in your\n"; print "browser and resubmit the email form or report the error\n"; print "to the administrator $admin<\/a>\n"; print "

\n"; print $query->end_html(), "\n"; ## CGI::Carp makes semi-useful error entries in the httpd error log. die "Anon News exited with error: $!\n"; }; ## Print message to sendmail print MAIL <<"EOHF"; To: $to From: "$from1" <$from> Subject: $subject Newsgroups: $mheader References: $refs Organization: $org X-Anon-1: This is a pseudo-anonymous message, the sender cannot be verified. X-Anon-2: It did not originate from any address listed in the message. X-Abuse-to: $xabuse X-Comments: Anonymous mail2news gate web interface - $scriptpage. X-Comments-2: Our policies about abuse - $abusepage $xtra1 $body EOHF close(MAIL); ## Check exit status. if($?) { print $query->header(); print $query->start_html(-title => "Error Posting", -author => $admin); print "

Error

\n"; print "

Error: $!, please go back in your\n"; print "browser and correct the form or report the error\n"; print "to the administrator $admin<\/a>\n"; print "

\n"; print $query->end_html(), "\n"; ## Make entry for error in httpd log. croak "Sendmail exited with error: $!\n"; ## Page to display if successful. } else { print $query->header(); print $query->start_html(-title => "Your Post has been sent.", -author => $admin); print "
\n"; print ""; print $HEADER; print "
\n"; print "
\n"; print "Your Post has been sent!
Thank you for using $pagename
Please note that it may take up to 24 hrs to see your post in the newsgroup, although average times range around a couple of hours.
\n"; print "
\n"; print "
$pagename<\/a>\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; } ## Page to display if service is offline sub offline { print $query->header(); print $query->start_html(-title => "Down!", -author => $admin); print "
\n"; print ""; print "
\n"; print "
\n"; print "$offline\n"; print "
\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Page to display if newsgroup is blocked. sub HTMLdie { print $query->header(); print $query->start_html(-title => "Temporary block.", -author => $admin); print "
\n"; print ""; print $HEADER; print "
\n"; print "
\n"; print "Posting to this newsgroup has been temporarily blocked.\n"; print "
\n"; print "
$pagename<\/a>\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Page to display if script is remotely executed. sub notlocal { print $query->header(); print $query->start_html(-title => "Oops", -author => $admin); print "
\n"; print ""; print $HEADER; print "
\n"; print "
\n"; print "You can only use this from the address below.

Note: You might be receiving this message because you are using a proxy.


\n"; print "
\n"; print "
$pagename<\/a>\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Page to display if domain or IP are banned. sub banned { print $query->header(); print $query->start_html(-title => "Blocked!", -author => $admin); print "
\n"; print ""; print "
\n"; print "
\n"; print "This domain has been banned from using the $pagename for abuse. If you feel this is in error, please send your ip address along with a message to $xabuse and we will investigate.\n"; print "
\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Page to display if no key match. sub nomatch { print $query->header(); print $query->start_html(-title => "Session key expired!", -author => $admin); print "
\n"; print ""; print $HEADER; print "
\n"; print "
\n"; print "Your session key has either expired or been used to post already. To generate a new key please reload the page linked below."; print "

\n"; print "
$pagename<\/a>\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Page to display on raw get. sub show_start_form { $outfile="anonnews." . $randfile; open(OUT_FILE,">anonnews/$outfile") or die "Could not open file $outfile\n"; close(OUT_FILE); print $query->header(); print $query->start_html(-title => $pagename, -author => $admin); print "
\n"; print "
"; print "
"; print "
"; print "

"; print ""; print ""; print ""; print ""; print "
"; print "

$pagename
Get the source here!

"; print "
    "; print "
  • This is just a web interface to our local "; print "remailer which sends to $to mail to news interface."; print "
  • To view our terms of service regarding the use of this interface"; print ", go here."; print "
"; print "

Please enter in newsgroup:
"; print "

"; print "

Enter from:
"; print "

"; print "

References:
"; print "Input the message ID of the post you are following
"; print "if you want to appear within the thread

"; print "

"; print "

Input any extra headers you would like:
"; print "Leave blank if unsure, if used hit enter for each extra header."; print "
"; print "

"; print "

Please input the subject of your post:
"; print "

"; print "

Please input the body of the post:
"; print "

"; print "
"; print "

"; print "

"; print "\n"; print "
"; print "

"; print "
"; print "
\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } ## Force anon for groups where from line has been abused. sub Noforge { $from1 = "Anon"; } ## Subroutine for autoban of abusive use. sub ban_users { # nice tiddy vars my($file_name,$number_to_ban,$max_time,$group,$total_max_time) = @_ if @_; my($lip,$lnum,$ltime,$lgroup,$my_num,$found,$ban); my($USERS_IP,$l_time,$file_open,$my_outfile,@my_file); # get the browsers ip, this could be spoofed $USERS_IP=$ENV{'REMOTE_ADDR'}; # time of this script running $l_time=time(); # open the out/infile for writing of ip# and such if (! -e $file_name ) { open(OUTFILE,">".$file_name); $file_open=0; } else { open(OUTFILE,"<".$file_name); $file_open=1; } # temp file with date $my_outfile=$file_name . "." . time(); open(MYFILE,">".$my_outfile); # init vars $found=0; $ban=0; # is the file open if ($file_open) { @my_file=; foreach(@my_file) { $lip=""; $lnum=""; $ltime=""; $lgroup=""; # get the ip,num,time,group from the var ($lip,$lnum,$ltime,$lgroup)=split(/ /,$_); # if the users_ip (where they are comming from) is in the string then we have a winner, log them if ($_ =~ /$USERS_IP/) { if (!$group) { # no group sent in to function call. This means that you want any match on IP not IP AND GROUP # start of new add less granular add/edit if ($lnum>=$number_to_ban && (($l_time-$ltime)<$max_time)) { # NO CHANGE NEEDED # print "Sorry but you must wait at least ",$max_time-($l_time-$ltime)," seconds before you can post"; # print " to $group again
\n"; print MYFILE "$lip $lnum $ltime $lgroup"; $found=1; # this ip has posted to some news group before $ban=1; } else { # re-write them to the file + 1 post ($lip,$lnum,$ltime,$lgroup)=split(/ /,$_); $my_num=$lnum; $my_num++; if (($my_num>=$number_to_ban) && (($l_time-$ltime)>$total_max_time)) # reset the count to 1 only if they have waited the allocated time {$my_num=1; } print MYFILE "$lip $my_num $l_time $group\n"; $found=1; } # end rewrite to file + 1 end } # end group found # end of less granular edits else { # group does exsist so lets see if we have any matches if ($_ =~ /$group/) { if ($lnum>=$number_to_ban && (($l_time-$ltime)<$max_time)) { # NO CHANGE NEEDED # print "Sorry but you must wait at least ",$max_time-($l_time-$ltime)," seconds before you can post"; # print " to $group again
\n"; print MYFILE "$lip $lnum $ltime $lgroup"; $found=1; # this ip has posted to some news group before $ban=1; } else { # re-write them to the file + 1 post ($lip,$lnum,$ltime,$lgroup)=split(/ /,$_); $my_num=$lnum; $my_num++; if (($my_num>=$number_to_ban) && (($l_time-$ltime)>$total_max_time)) # reset the count to 1 only if they have waited the allocated time {$my_num=1; } print MYFILE "$lip $my_num $l_time $group\n"; $found=1; } # end rewrite to file + 1 end } # end group found } # group exsists as string not found though } # end of users_ip in list else { # re-write old entry print MYFILE "$lip $lnum $ltime $lgroup"; } } # for loop # if not found then we have a new entry to write to the file if (!$found) { print MYFILE "$USERS_IP 1 $l_time $group\n"; } close(OUTFILE); close(MYFILE); # NOT SURE IF THE -f is a GNU util or it will work with all standard UN*X OS's need to CHECK THIS! $a=`mv -f $my_outfile $file_name`; } # end file open ok else # file open was == 0 { # first write to new file print OUTFILE "$USERS_IP 1 $l_time $group\n"; close(OUTFILE); } ## Page to display when user has been temp banned. if ($ban) { print $query->header(); print $query->start_html(-title => "Oops", -author => $admin); print "
\n"; print ""; print "
\n"; print "
\n"; print "You have reached your daily limit of session keys, please come back tomorrow.\n"; print "
\n"; print "
$pagename<\/a>\n"; print "<\CENTER>\n"; print "
"; print $FOOTER; print "<\FONT>"; print $query->end_html(), "\n"; exit; } } ## Random function for cache busting banners. my($numchar, $retvalue, $retvaluetemp, $feed, $i, $randc); sub randname { return randword((rand 4) + 8); } sub randword { my ($numchar) = @_; $retvalue = ""; for ($i=0; $i<$numchar; $i++) { $randc = randchar(); $retvaluetemp = sprintf "%c", randchar(); $retvalue = $retvalue.$retvaluetemp; # printf "%c%c\n", $retvaluetemp,$retvalue; } return $retvalue; } sub randchar { $feed = sprintf("%d", rand 36); if ($feed > 25) { $feed +=22; } else { $feed +=97; } return $feed; } ## End of anonnews.cgi. exit;