#!/usr/bin/perl
#
#	Veganizer v0.04
#	  by f roque
#
#get the headers, get ips/domains from headers, get ppl in charge 
#of those ip/domains, mail all of them a spam complaint.
#
#As of v0.04, all user changeable variables have been moved to the
#resource file, .veganizerrc
#
#Think any of this is sloppy?  Send me mail about how to improve it.
#	frisco@blackant.net


#read in the resource file
require("$ENV{HOME}/.veganizerrc");

#Make sure the appropriate variables have been configured
die "You need to specify some variables!!\n"
	unless (defined($mail_prog) &&
		defined($nslookup) &&
		defined($whois) &&
		defined($mailfile));

use Getopt::Std;

$version	= "0.04";
$|=1;

#read in options
PrintUsage() unless getopts('f:n:sdpa');

$verbose	= defined($opt_s) ? 0 : 1;
$dont_mail	= defined($opt_d) ? 1 : 0;
$mailfile	= $opt_f if defined($opt_f);
$mail_num	= defined($opt_n) ? $opt_n : undef;
$piped		= defined($opt_p) ? 1 : undef;
$skip_body	= defined($opt_a) ? 0 : 1;
$mail_num++ if $use_pine && $mail_num;

#make sure we were called correctly
if (defined($opt_f) && !defined($opt_n)) {
	print "You must specify a message number to work on.\n";
	PrintUsage();
}
#make reading from STDIN the default, if nothing else specified
$piped = 1 unless(defined($opt_n) || (defined($opt_f) && defined($opt_n)));
$verbose = 0 if $piped;

#read in the headers of the spam message.
if ($piped) {
	while (<>) {
		push(@spam_lines, $_);
	}
}
else {
	open(I, $mailfile) or die "Error opening $mailfile: $!";
	while (<I>) {
		$num++ if /^From /;
		if ($num == $mail_num) {
			push(@spam_lines, $_);
			while (<I>) {
				last if /^From /;
				push(@spam_lines, $_);
			}
			last;
		}
	}
	close(I);
}

foreach (@spam_lines) {
	$line = $_;

	#we've reached the end of the headers when we get a blank line
	last if $line =~ /^$/ && $skip_body;

	#find all IP's then domains
	while ( $line =~ s/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})// ) {
		$ip = ReturnIP($1,$2,$3,$4);
		push(@ip, $ip) if $ip;
	}
	while ($line =~ s/((?:[a-z0-9_-]+\.)+(?:[a-z]){2,3})//i) {
		$domain = $1;
		push(@domain, $domain) if NotMine($domain);
	}
}

#Resolve all the IP's into domains as well, and vice-versa
#This helps us get everyone, i suppose.
#also sort the @domain's and @ip's into unique lists
UniqueArray(\@ip);
foreach $ip (@ip) {
	$domain = ResolveDomain($ip);
	push(@domain, $domain) if NotMine($domain);
}

foreach (@domain) {tr/A-Z/a-z/;}
UniqueArray(\@domain);
foreach $domain (@domain) {
	$ip = ResolveIP($domain);
	push @ip, $ip;
}
UniqueArray(\@ip);


#Find any addresses associated with the IP's and domain's
#sort those into a unique list
foreach $ip (@ip) {
	foreach (split(/,/,IPWhois($ip))) {
		push @pot_mail_to, $_ if !/^$/;
	}
}
UniqueArray(\@domain);
foreach $domain (@domain) {
	foreach (split(/,/, DomainWhois($domain))) {	
		push @pot_mail_to, $_ if !/^$/;
	}
}
UniqueArray(\@pot_mail_to);


if ($verbose) {
	#prompt the user if s/he wants to send mail to the addresses
	foreach $host (@domain) {
		next if $host =~ /^\s*$/;

		foreach $mailto (@default_mailto) {
			print "Send mail to $mailto\@$host ?\t";
			$ans = <>;
			push @mailto, "$mailto\@$host" if $ans =~ /y/i;
		}
	}
	foreach $address (@pot_mail_to) {
		print "Send mail to $address ?\t";
		$ans=<>;
		push @mailto, $address if $ans =~ /y/i;
	}

	print "\nAny other addresses to mail? (Ctrl-D to end)\n";
	while (<>) {
		chomp;
		last if /^$/;
		push @mailto, $_;
	}

	print "\nAnything to add to message? (Ctrl-D to end)\n";
	while (<>) { $added .= $_; }
}
else {
	@mailto = (@pot_mail_to);
	foreach $host (@domain) {
		next if $host =~ /^\s*$/;
		foreach $mailto (@default_mailto) {
			push(@mailto, "$mailto\@$host");
		}
	}
}


#sending the mail...  first we read in the template, then we send the mail.
unless ($dont_mail) {
	print "Sending mail..." if $verbose;
	foreach $address (@mailto) {
		open(M, "|$mail_prog -s 'SPAM Alert' $address")
			or die "Error opening $mail_prog: $!";
		print M $added;
		print M $message;
		print M "\nMessage sent by The Veganizer, v", $version;
		print M "\n\"Getting rid of SPAM one message at a time\"\n";
		print M "<------original message follows------>\n";
		print M @spam_lines;
		close(M);
	}
	print "...done\n" if $verbose;
}

#if run silently, send mail to person running
if (!$verbose && !$dont_mail) {
	$whoami = `whoami`;
	open(M, "|$mail_prog -s 'Veganizer Report' $whoami")
		or die "Error opening $mail_prog: $!";
	print M "The Veganizer v", $version, "has sent mail to:\n";
	$,=", "; print M @mailto; $,="";
	print M "\n<----------original message follows---------->\n\n";
	print M @spam_lines;
}

print "Thank you for using the Veganizer.\n" if $verbose;

exit 1;


#check to see if it's an IP address, return it if it is
sub ReturnIP {
	my $one = shift;
	my $two = shift;
	my $three = shift;
	my $four = shift;

	if ($one < 256 && $two < 256 && $three < 256 && $four < 256) {
		return "$one.$two.$three.$four"; }

	return undef;
}

#given an array, it makes all elements of the array unique
sub UniqueArray {
	my $array = shift;
	my $c, $l;
	my @new = ();

	@$array = sort { $a cmp $b } @$array;
	foreach $c (@$array) {
		push(@new, $c) if $l ne $c;
		$l = $c;
	}
	@$array = @new;
}


#nslookup an IP, return a domain
sub ResolveDomain {
	my $ip = shift;

	print "Resolving $ip\n" if $verbose;
	my $return = `$nslookup $ip`;
	return "" if $return !~ /Name:/s;
	$return =~ s/^.*Name:\s+([^\n]+).*$/$1/s;
	$return =~ tr/A-Z/a-z/;

	return $return;
}

#nslookup a domain, return an IP
sub ResolveIP {
	my $domain = shift;
	return undef if $domain eq "";

	print "Resolving $domain\n" if $verbose;
	my $return = `$nslookup $domain`;
	return "" if $return !~ /Name:/s;
	$return =~ s/^.*Name:\s+[^\n]+\nAddress:\s+([^\n]+).*$/$1/s;

	return $return;
}

#Get all the addresses associated with an IP
sub IPWhois {
	my $ip = shift;
	return undef if $ip eq "";

	print "Looking up $ip\n" if $verbose;
	my $addresses = `$whois -h arin.net $ip |grep @`;
	my $return;
	while ($addresses =~ s/\s([^\s]+\@[^\s]+)\s//) { $return .= $1.","; }
	$return =~ tr/A-Z/a-z/;

	#if we find @ripe.net or @apnic.net, then assume we should be 
	#searching those databases instead.

	if ($return =~/\@ripe.net/) {
		$addresses = `$whois -h whois.ripe.net $ip |grep @`;
		while ($addresses =~ s/\s([^\s]+\@[^\s]+)\s//) { $return .= $1.","; }
		$return =~ tr/A-Z/a-z/;
	}
	elsif ($return =~ /\@apnic.net/) {
		$addresses = `$whois -h whois.apnic.net $ip|grep @`;
		while ($addresses =~ s/\s([^\s]+\@[^\s]+)\s//) { $return .= $1.","; }
		$return =~ tr/A-Z/a-z/;
	}

	return $return;
}

#Get all addresses associated with a domain
sub DomainWhois {
	my $domain = shift;
	$domain =~ s/^(?:[^\.]+\.)*([^\.]+\.[^\.]+)$/$1/;

	return undef if $domain eq "";

	print "Looking up $domain\n" if $verbose;

	#need to do 2 whois queries due to addition of other registrars
	my $whoisserver = `$whois $domain`;
	$whoisserver =~ s/^.*Whois Server:\s+([^\n]+).*$/$1/s;
	return "" if $whoisserver =~ /No match for/s;
	my $addresses = `$whois -h $whoisserver $domain`;

	my $return;

	while ($addresses =~ s/\s([^\s]+\@[^\s]+)\s//s) { $return .= $1.","; }
	chop($return);

	$return =~ tr/A-Z/a-z/;

	return $return;
}

sub PrintUsage {
	print "Usage: $0 [options]\n";
	print "\t-a\t\tsearch all of message, headers and body\n";
	print "\t-p\t\tpiped: read from STDIN\n";
	print "\t-s\t\tsilent\n";
	print "\t-d\t\tdon't actually send mail out\n";
	print "\t-f filename\talternate mailfile\n";
	print "\t-n num\t\tmessage number to use as spam\n\n";

	exit 1;
}


sub NotMine {
	my $checking = shift;

	foreach $domain (@my_domains) {
		return 0 if $checking =~ /$domain$/;
	}
	return 1;
}

__END__

=head1 NAME

veganizer - a spam counter-attack

=head1 SYNOPSIS

B<veganizer> -s -d -a -p

B<veganizer> -s -d -a -n num

B<veganizer> -s -d -a -f filename -n num

=head1 DESCRIPTION

Given a mailfile and a message number, or a file containing only on message
with headers, the Veganizer attempts to determine all addresses associated
with the servers that the message came through and mail them a "stop this spam"
message.

=head1 OPTIONS

I<veganizer> accepts the following options:

=over 4

=item -s

Run in silent mode; no prompting is done.

=item -d

Don't actually send mail.  For testing purposes.

=item -a

Check body for IP's/domains as well.

=item -p

read from STDIN, used for piping from mailers.

=item -f filename

alternate mailfile

=item -n num

message number to use as spam

=back

=head1 SETUP

The B<veganizer> needs to know where to find certain programs/files:
a mail program, nslookup, whois, and your mailfile.

You will also want to change the @my_domains array to include only those
domains that you are in charge of.

Additionally, Pine users will need to set the $use_pine variable to a true
value ($use_pine = '1').  This is because Pine creates these silly messages
at the top of your mail file which aren't shown to you in Pine, but every
other mail program thinks they're a real message.  Refer to
http://www.washington.edu/pine/faq/problems.html#pseudo-message
for more information.

All this user-changeable info is kept in the resource file, .veganizerrc, which is expected to be in your home directory.

=head1 USAGE

There are three ways to use B<veganizer>.  The first is to have it read
straight from your mail file, such as /var/mail/login or the like.  For
this method, you will need to specify a message number, such as:

	veganizer -n 12

This will work on the 12th message in your mailfile.


The second is to specify an alternate mailfile to use instead of your inbox:

	veganizer -f /home/login/spam_file -n 7

The third is to pipe a message to the B<veganizer>, such as from your
mail program.  This option assumes -s in it, because i haven't been able to
get any mail program to wait for input when i pipe through it. Piping is the
default method if nothing else is specified (-p is not necessary in this
case), so all the following are the same:

	| veganizer -p

	| veganizer

	| veganizer -s

	| veganizer -sp


Any of these three main methods can have the -s, -a, and -d options,
only the first two will notice the -n option.

=head1 BUGS

Should only grep the Technical contact for a domain/IP

Incorrect Domains/IPs are sometimes matched.

=head1 AUTHOR

francisco roque I<frisco@blackant.net>

=head1 WEBPAGE

http://www.blackant.net/veganizer/

=head1 COPYRIGHT and LICENSE

This program is copyright (c) Francisco Luis Roque 1999

This program is free and open software. You may use, modify, distribute,
and sell this program (and any modified variants) in any way you wish,
provided you do not restrict others from doing the same.
