At the moment, prtscn
takes a server, and a list of port ranges to test (of the form x,y-z,a-c,d,e etc). The -v
option will show all ports being scanned, and the -r
option will return the first open port in the list. If -r
is passed, the list of port ranges is optional. In this case, if the port list is missing, -r
will just return the first open port on the target OS.
Going forward, the next option I would like to add is a pretty printer for the open ports (IE, if it returns 22, 80, 4200, 4201, 4202, then that should print 22, 80, 4200-4202) and potentially make this async.
#!/usr/bin/perl
# prtscn.pl - scan server for open
# TODO
# Make async
# if any ports are sequential, form a range (ie if 22, 4200, 4201, and 4202 are open, print 22, 4200-4202)
use strict;
use warnings;
use IO::Socket;
use Getopt::Long;
my $verbosity = undef;
my $use_first_port = undef;
GetOptions('v' => \$verbosity, 'u' => \$use_first_port);
sub scan {
my $server = shift;
my $start_port = shift;
my $end_port = shift;
my @open_ports = ();
while($start_port-1 < $end_port) {
if($verbosity) {
print "scanning port $start_port.\n";
}
my $sock = IO::Socket::INET->new(PeerHost => $server, PeerPort => $start_port);
if($sock) {
push @open_ports, $start_port;
if($use_first_port) {
return @open_ports;
}
}
$start_port += 1;
}
return @open_ports;
}
my $server = shift || die "err: need server.\n";
my $range_list = shift || "1-65536";
my @ranges = split /,/, $range_list || die "err: need range list.\n";
my @open_ports = ();
foreach my $range (@ranges) {
my ($start, $end) = (undef, undef);
if($range =~ /-/) {
($start, $end) = split /-/, $range;
} else {
($start, $end) = ($range, $range);
}
push @open_ports, scan($server, $start, $end);
}
if(scalar @open_ports != 0) {
print "open port(s): ${\join ', ', sort @open_ports}\n";
} else {
print "no ports listed are open.\n";
}
1 Answer 1
Layout
Place the scan
sub at the end of the file instead of in the middle of the procedural code.
use
as little as possible
I prefer to only import the necessary functions form the modules (in the use
lines). Since you are not using anything from IO::Socket
, you can pass it the empty list: qw()
.
You only use one function from Getopt::Long
.
Variable declarations
There is no need to explicitly initialize variables to undef
or the empty list in your code.
It is not possible to hit the die
statement on the @ranges
line.
Comments
Remove the unnecessary TODO
comments.
Style
for
is simlper than foreach
.
Add space between control keywords (if
, while
) and the opening paren (
.
Use single quotes to avoid unnecessary variable interpolation for the $range_list
default string.
There is no need for scalar
.
The \join
code is a little hard to understand.
Use ++
instead of += 1
.
User documentation and argument checking
Add usage documentation with the standard Pod::Usage module and plain old documentation (POD). Move argument parsing and checking to it's own sub. Check the return value of GetOptions for enhanced argument checking.
Your question text mentions the -r
option, but your code uses -u
instead.
Here is new code with the suggestions above:
use strict;
use warnings;
use IO::Socket qw();
use Getopt::Long qw(GetOptions);
use Pod::Usage qw(pod2usage);
my $verbosity;
my $use_first_port;
my $server;
my $range_list;
parse_args();
my @ranges = split /,/, $range_list;
my @open_ports;
for my $range (@ranges) {
my ($start, $end);
if ($range =~ /-/) {
($start, $end) = split /-/, $range;
} else {
($start, $end) = ($range, $range);
}
push @open_ports, scan($server, $start, $end);
}
if (@open_ports) {
print 'open port(s): ', (join ', ', sort @open_ports), "\n";
} else {
print "no ports listed are open.\n";
}
exit;
sub scan {
my $server = shift;
my $start_port = shift;
my $end_port = shift;
my @open_ports;
while (($start_port - 1) < $end_port) {
if ($verbosity) {
print "scanning port $start_port.\n";
}
my $sock = IO::Socket::INET->new(PeerHost => $server, PeerPort => $start_port);
if ($sock) {
push @open_ports, $start_port;
if ($use_first_port) {
return @open_ports;
}
}
$start_port++;
}
return @open_ports;
}
sub parse_args {
my $help;
GetOptions(
'help' => \$help,
'v' => \$verbosity,
'u' => \$use_first_port,
) or pod2usage();
$help and pod2usage(-verbose => 2);
if (@ARGV) {
$server = shift @ARGV;
$range_list = (shift @ARGV) || '1-65536';
} else {
pod2usage('Error: server required.');
}
@ARGV and pod2usage("Error: unexpected args: @ARGV");
}
=head1 NAME
prtscn
=head1 SYNOPSIS
prtscn [options] server [range]
Options:
-help Verbose help
-v Verbose output for debug
-u Use first port
=head1 DESCRIPTION
Show ports for a server.
=head1 ARGUMENTS
=over 4
=item B<server>
A server is required.
=item B<range>
A range is optional. The default range is C<1-65536>
=back
=head1 OPTIONS
All options can be abbreviated.
=over 4
=item B<-v>
Show verbose output on stdout
prtscn -v 0.0.0.0
=item B<-u>
By default, show all ports in the range.
To show just the first port in the range, use the C<-u> option.
prtscn -u 0.0.0.0 111-555
=item B<-help>
Show verbose usage information.
=back
=cut