#!/usr/bin/perl -w # 5/6/02 # David Aslanian # nightelf@cableone.net use Getopt::Long; use IO::Socket::INET; use strict; ## options config my($port,$host,$proto,$help) = ('', '', 'tcp',''); ## proto defaults to tcp GetOptions('ports|port=s' => \$port, 'host=s' => \$host, 'proto=s' => \$proto, 'help' => \$help) or die $!; die usage() if($help); ## validate and expand the port listing(s) in the --port option my @ports = expand_ports($port); ## validate options validate_opts($host, $proto); ######################################### ## main ######################################### my $sock; my @openports = (); my $i = -1; print "Scanning $host for open ports.....\n\n"; foreach my $cport (@ports) { $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $cport, Proto => $proto); if($sock) { $i++; $openports[$i] = $cport; } } print "Open ports on $host (using $proto):\n\t", join("\n\t", @openports), "\n"; ######################################### ## subs ######################################### sub validate_opts { my($host, $proto) = (shift, shift); ## validate $host ## no ports, proto spec, or file path in the hostname allowed ## below: this is not right #if(($host =~ /^(\w+\.)?\w+\.\w+$/i) || ($host =~ /^(\d{1,3}\.){3}(\d{1,3})$/) || ($host =~ /^[a-z-]+$/) { # ## we're fine #} else { # die usage("Bad host specification."); #} ## validate protocol die usage("Bad protocol specification.") unless($proto =~ /^tcp$|^udp$/); } sub expand_ports { ## input: string containing port information, commas and ranges (1-10) can be used. ## output: a expanded, unique list of sorted ports ready to be used for scanning. my $port = shift; my @ports = (); my($from, $to); ## used for ranges (1-10) if($port =~ /^(\d+)$/) { ## just a plain port number alone $ports[0] = $1; return(@ports); } if($port =~ /^(\d+)-(\d+)$/) { ## a range of ports alone (not in a comma-seperated list) return(@ports = ($1 .. $2)); } if($port =~ /^([\d-]+,)+([\d-]+),?$/) { ## a list possibly containing ranges of ports my @splitted = split(',', $port); foreach my $splitted (@splitted) { if($splitted =~ /^(\d+)-(\d+)$/) { ## if it is a range of ports push(@ports, ($1 .. $2)); next; } if($splitted =~ /^(\d+)$/) { push(@ports, $1); next; } ## if we get to this point in the iteration something went wrong... die "bad port spec.\n"; } ### take out duplicate values from @ports my %seen = (); my @uniq = (); foreach my $item (@ports) { unless($seen{$item}) { ##if we get here we haven't seen it before $seen{$item} = 1; push(@uniq, $item); } } @ports = @uniq; ### sort the ports @ports = sort { $a <=> $b } @ports; ### .. and finally return the expanded, unique, sorted ports to the caller return @ports; } die usage("Bad or no port specification.\n"); } sub usage { my $message = shift; print "Fatal error: $message\n\n" if($message); ## only print an error if there was one print < --ports= [--proto=tcp|udp] Options: --host, -h (ie --host=localhost) The hostname or ip adress to be scanned. --port, --ports, -po (ie --ports=1,2,3-30) The ports to be scanned. A single port can be specified (ie --port=1), or a single range of ports can be specified (ie --ports=1-1024), or a list of ports and/or ranges can be specified (ie --ports=1,2,3-30). --proto, -pr (ie --proto=tcp) The protocol to use for the scan. This can be either tcp or udp, this option is optionable and can be omitted, in which case the default protocol will be used (tcp). --help, -h (ie --help) Display this usage information. ENDUSAGE }