6
\$\begingroup\$

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";
}
toolic
15.1k5 gold badges29 silver badges211 bronze badges
asked Apr 14, 2016 at 14:05
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

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
answered Feb 29, 2024 at 23:52
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.