#!/usr/bin/perl

use Net::Pcap qw(pcap_open_live pcap_compile pcap_setfilter pcap_loop pcap_close);
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP qw(:strip);
use NetPacket::TCP;
use Net::RawIP;
use Time::HiRes qw(usleep);
use threads;
use threads::shared;
use strict;

unless ($> == 0) { print "needs moar root\n"; exit(); }

$|++;

sub addrtoint { return(unpack("N", pack("C4", split(/\./,$_[0])))); }
sub inttoaddr { return(join(".", unpack("C4", pack("N", $_[0])))); }

my $my_ip = ''; # !!! LOOK LOOK FILL ME IN LOL !!!
my $my_device = ''; # SAME HERE
my $my_netmask = ''; # take a guess.

my @ranges;
my $total = 0;
my @ports;
my $filter_str = '(src port ';
my $packet_rate = 0;
my $logfile = 'log.txt';

foreach my $arg (@ARGV) {
	my ($temp_ip_from, $temp_ip_to) = ('', '');

	if (($arg =~ /^-{1,2}help$/) || ($arg =~ /^-h$/)) {
		print "\n";
		print "rscan v0.2 - autoconfig edition\n";
		print "\n";
		print "Usage:\n";
		print "    $0 [arguments] [ip range(s), files, port(s)]\n";
		print "\n";
		print "\n";
		print "Arguments:\n";
		print "\n";
		print "Switch    | Description            | Example\n";
		print "    -if   | interface to use       | -if=eth0\n";
		print "    -ip   | src ip to use          | -ip=192.168.1.1\n";
		print "    -mask | netmask of interface   | -mask=255.255.255.0\n";
		print "    -log  | logfile                | -log=packetz.txt\n";
		print "    -rate | packets/second to send | -rate=100\n";
		print "\n";
		print "\n";
		print "Examples:\n";
		print "\n";
		print "Scan ports 80, 81, 82 and 8080 on 192.168.*.* with eth0 from ip 192.168.1.1:\n";
		print "    $0 -if=eth0 -ip=192.168.1.1 192.168.*.* 80 81 82 8080\n";
		print "    OR\n";
		print "    $0 -if=eth0 -ip=192.168.1.1 192.168.*.* 80-82 8080\n";
		print "\n";
		print "Scan port 80 on 192.168.5.* and 192.168.2.* from the first available interface, using whatever ip and netmask it has:\n";
		print "    $0 192.168.5.* 192.168.2.* 80\n";
		print "\n";
		print "Scan port 21 and 22 on 192.168.2.5-192.168.2.50\n";
		print "    $0 192.168.2.5-192.168.2.50 21 22\n";
		print "\n";
		print "Scan port 80, 81, 82 and 8080 on all the ips/ranges in \"ips.txt\"\n";
		print "    $0 ips.txt 80-82 8080\n";
		print "    NOTE: The file containing ips/ranges must have one ip/range on each line.\n";
		print "\n";
		exit();
	} elsif ($arg =~ /^-{1,2}rate=(\d+)$/) {
		if ($1 != 0) { $packet_rate = int(1000000/$1); }
	} elsif ($arg =~ /^-{1,2}if=(.+)$/) {
		$my_device = $1;
	} elsif ($arg =~ /^-{1,2}ip=([0-9\.]+)$/) {
		$my_ip = $1;
	} elsif ($arg =~ /^-{1,2}mask=([0-9\.]+)$/) {
		$my_netmask = $1;
	} elsif ($arg =~ /^-{1,2}log=(.+)$/) {
		$logfile = $1;
	} elsif (-e $arg) {
		open(my $fh, $arg);
		while (my $line = <$fh>) {
			chomp $line;

			if ($line =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
				($temp_ip_from, $temp_ip_to) = split(/-/, $line, 2);
				if (addrtoint($temp_ip_from) < addrtoint($temp_ip_to)) { push(@ranges, $line); }
			} elsif ($line =~ /^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/) {
				($temp_ip_from, $temp_ip_to) = ("$1.$2.$3.$4", "$1.$2.$3.$4");
				$temp_ip_from =~ s/\*/0/g;
				$temp_ip_to =~ s/\*/255/g;
				push(@ranges, $temp_ip_from.'-'.$temp_ip_to);
			}
		}
		close($fh);
	} elsif ($arg =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
		($temp_ip_from, $temp_ip_to) = split(/-/, $arg, 2);
		if (addrtoint($temp_ip_from) < addrtoint($temp_ip_to)) { push(@ranges, $arg); }
	} elsif ($arg =~ /^(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)\.(\d{1,3}|\*)$/) {
		($temp_ip_from, $temp_ip_to) = ("$1.$2.$3.$4", "$1.$2.$3.$4");
		$temp_ip_from =~ s/\*/0/g;
		$temp_ip_to =~ s/\*/255/g;
		push(@ranges, $temp_ip_from.'-'.$temp_ip_to);
	} elsif ($arg =~ /^\d+-\d+$/) {
		my ($port_from, $port_to) = split(/-/, $arg, 2);

		if ($port_to >= $port_from) {
			for ($port_from .. $port_to) {
				push(@ports, $_);
			}
		}
	} elsif ($arg =~ /^\d+$/) {
		push(@ports, $arg);
	}
}

unless (@ports) { push(@ports, '80'); }

foreach my $range (@ranges) {
	my ($temp_ip_from, $temp_ip_to) = split(/-/, $range);
	$total += ((addrtoint($temp_ip_to) - addrtoint($temp_ip_from)) + 1) * scalar(@ports);
}

$filter_str .= join(' or src port ', @ports).') and dst host '.$my_ip;

unless ($my_device) { # lol auto-config, this sucks
	my $temp = `ifconfig`;
	if ($temp =~ /^([a-z0-9:]+)/) { $my_device = $1; print "AUTOCONFIGURED: \$my_device = $1\n"; }
	undef $temp;
}

if ($my_device && ((!$my_ip) || (!$my_netmask))) { # less crap, but still great
	my $temp = `ifconfig $my_device`;
	if ($temp =~ /inet addr:([0-9\.]+)/) { unless ($my_ip) { $my_ip = $1; print "AUTOCONFIGURED: \$my_ip = $1\n"; } }
	if ($temp =~ /Mask:([0-9\.]+)/) { unless ($my_netmask) { $my_netmask = $1; print "AUTOCONFIGURED: \$my_netmask = $1\n"; } }
	undef $temp;
}

unless ($my_device && $my_ip && $my_netmask) {
	print "\nPlease check your configuration.\n";
	unless ($my_ip) { print "- \$my_ip of -ip needs to be set\n"; }
	unless ($my_device) { print "- \$my_device or -if needs to be set\n"; }
	unless ($my_netmask) { print "- \$my_netmask or -mask needs to be set\n"; }
	print "\n";
	exit();
}

unless (@ranges) {
	print "\nPlease specify at least one IP range.\n";
	print "eg 192.168.*.* OR 192.168.0.0-192.168.255.255 OR a file with an IP range on each line.\n";
	print "\n";
	exit();
}

my $hosts = 0;
share($hosts);
my $sent = 0;
my $packets_since = 0;
my $pps = 0;

sub process_packet {
	my($user_data, $header, $packet) = @_;
	my $ip_obj = NetPacket::IP->decode(eth_strip($packet));
	my $tcp_obj = NetPacket::TCP->decode(ip_strip(eth_strip($packet)));

	if ($tcp_obj->{flags} == 18) {
		open(my $fh, '>>'.$logfile);
		print $fh $ip_obj->{src_ip}.':'.$tcp_obj->{src_port}."\n";
		close($fh);
		$hosts++;
	}
}

sub sniff {
	my $err = '';
	my $filter;

	my $pcap = pcap_open_live($my_device, 1024, 1, 0, \$err) || die $err;

	pcap_compile($pcap, \$filter, $filter_str, 1, $my_netmask);
	pcap_setfilter($pcap, $filter);

	pcap_loop($pcap, 0, \&process_packet, "just for the demo");

	pcap_close($pcap);
}

threads->new(\&sniff);

my $time_last = time();
my $hosts_last = $hosts;
my $percent = 0;
my $percent_last = 0;

print "[Progress  ] [   %] [Hosts] [Current IP     ] [Port ] [PPS  ]\n";

my $n = Net::RawIP->new({
	ip  => {
		saddr => $my_ip,
	},
	tcp => {
		source => int(rand()*64511)+1024,
		syn    => 1,
	},
});

foreach my $range (@ranges) {
	my ($ip_from, $ip_to, $ip_cur);
	($ip_from, $ip_to) = split(/-/, $range);

	foreach my $port (@ports) {
		$n->set({ tcp => { dest => $port } });
		for ($ip_cur=addrtoint($ip_from);$ip_cur<=addrtoint($ip_to);$ip_cur++) {
			$sent++;
			$packets_since++;
			next if inttoaddr($ip_cur) =~ /\.(0|255)$/;

			$n->set({ ip => { daddr => inttoaddr($ip_cur) } });

			$n->send;

			$percent = int((($sent+1)/$total)*100);

			if ((time() > $time_last) || ($hosts > $hosts_last) || ($percent > $percent_last)) {
				$hosts_last = $hosts;
				$percent_last = $percent;

			       printf("\r[%-10s] [%3d%%] [%5d] [%-15s] [%-5d] [%-5d]", ('#' x int($percent/10)), $percent, $hosts, inttoaddr($ip_cur), $port, $pps);
			}

			if (time() > $time_last) {
				$time_last = time();
				$pps = $packets_since;
				$packets_since = 0;
			}

			if ($packet_rate != 0) {
				usleep($packet_rate);
			}
		}
	}
}

print "\nSent all packets, waiting 5 seconds for any late replies...\n";

sleep 5;

system("kill $$");

