File Coverage

blib/lib/App/Monport.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # ABSTRACT: Monitor network ports for changes
2             package App::Monport;
3             $App::Monport::VERSION = '1.09';
4 1     1   13735 use strict;
  1         1  
  1         22  
5 1     1   3 use warnings;
  1         1  
  1         20  
6 1     1   443 use IO::Socket;
  1         17131  
  1         3  
7 1     1   449 use List::Util qw(shuffle);
  1         1  
  1         68  
8 1     1   565 use Nmap::Parser;
  0            
  0            
9             use Exporter qw(import);
10             use YAML::Tiny;
11              
12             our @EXPORT = qw(scan_ports create_config compare_config nmap_path);
13              
14             =for HTML
15              
16             =head1 NAME
17              
18             App::Monport - Monitor network ports for changes
19              
20             =head1 SYNOPSIS
21              
22             # generate the configuration file
23             $ monport localhost scanme.nmap.org
24              
25             # review/edit the configuration file
26             $ vi $HOME/.monport.yml
27              
28             # compare against the configuration file
29             $ monport
30              
31             =head1 DESCRIPTION
32              
33             Use this module to find out whether some new ports have been opened or
34             existing ones have been closed. New open ports mean bigger attack surface and
35             consequently higher security risk. On the other hand if a port gets closed it
36             might indicate a problem with a network service.
37              
38             The application works by comparing the actual state of ports (open or closed)
39             with the expected state defined in the configuration file.
40              
41             =head1 FUNCTIONS
42              
43             See F for how to use these functions.
44              
45             =head2 create_config($conf_file, @hosts)
46              
47             Create configuration file F<$HOME/.monport.yml> containing hosts with
48             corresponding open ports. This file will be used as the expected list of
49             open ports in the consequent scans.
50              
51             =cut
52              
53             sub create_config {
54             my $args = shift;
55             my $conf_file = $args->{conf_file};
56             my @hosts = @{$args->{hosts}};
57             my $verbose = $args->{verbose};
58              
59             my $yaml;
60             die "'$conf_file' already exists. You can remove it or edit it.\n"
61             if -e $conf_file;
62             $yaml = YAML::Tiny->new();
63             for my $host (@hosts) {
64             my $open_ports = scan_ports($host, $verbose);
65             push @$yaml, { $host => $open_ports };
66             $yaml->write($conf_file);
67             }
68             }
69              
70             =head2 compare_config($conf_file)
71              
72             Compare list of open ports defined in the F<$conf_file> with the current list
73             of open ports. Print newly opened or closed ports.
74              
75             =cut
76              
77             sub compare_config {
78             my $args = shift;
79             my $conf_file = $args->{conf_file};
80             my $verbose = $args->{verbose};
81              
82             my $yaml = YAML::Tiny->read($conf_file);
83             for my $hashref (@$yaml) {
84             for my $host ( sort keys %$hashref ) {
85              
86             my $open = scan_ports($host, $verbose);
87             my $expected_open = $hashref->{$host} // [];
88              
89             my @report_closed;
90             for my $port ( sort @$open ) {
91             push @report_closed, $port
92             unless grep $port == $_, @$expected_open;
93             }
94              
95             my @report_open;
96             for my $port ( sort @$expected_open ) {
97             push @report_open, $port
98             unless grep $port == $_, @$open;
99             }
100              
101             # print report
102             print "$host\n" if @report_open or @report_closed;
103             print " $_ open\n" for @report_closed;
104             print " $_ closed\n" for @report_open;
105             }
106             }
107             }
108              
109             =head2 scan_ports($host, $verbose)
110              
111             Return an array reference containing list of ports open on $host.
112              
113             =cut
114              
115             sub scan_ports {
116             my ($host, $verbose) = @_;
117              
118             print "--> scanning $host ...\n" if $verbose;
119             my $np = new Nmap::Parser;
120              
121             #runs the nmap command with hosts and parses it automagically
122             my $nmap = nmap_path();
123             $np->parsescan( $nmap, '', $host );
124              
125             my ($h) = $np->all_hosts();
126             my @ports = $h->tcp_ports(q(open));
127              
128             return \@ports;
129             }
130              
131             #sub scan_ports {
132             # my $host = shift;
133             # my @open;
134             # print "scanning $host: \n";
135             # my $ports = default_ports();
136             # for my $port (@$ports) {
137             # my $socket = IO::Socket::INET->new(
138             # PeerAddr => $host,
139             # PeerPort => $port,
140             # Proto => 'tcp',
141             # Type => SOCK_STREAM,
142             # Timeout => 1,
143             # );
144             #
145             # if ($socket) {
146             # push @open, $port;
147             # shutdown( $socket, 2 );
148             # }
149             # }
150             #
151             # return \@open;
152             #}
153              
154             =head2 nmap_path()
155              
156             Return absolute path to nmap executable or die.
157              
158             =cut
159              
160             sub nmap_path {
161             my @paths = qw(
162             /usr/bin/nmap
163             /usr/local/bin/nmap
164             );
165             for my $p (@paths) {
166             return $p if -x $p;
167             }
168             die "can't find exacutable nmap; searched @paths\n";
169             }
170              
171             =head1 INSTALLATION
172              
173             To install this module run:
174              
175             $ cpan App::Monport
176              
177             or
178              
179             $ cpanm App::Monport
180              
181             when using L.
182              
183             To install manually clone the L and then run (on Unix):
184              
185             perl Build.PL
186             ./Build
187             ./Build test
188             ./Build install
189              
190             For more see L
191             or L instructions.
192              
193             =head1 SOURCE REPOSITORY
194              
195             L
196              
197             =head1 AUTHOR
198              
199             Jozef Reisinger, Ereisinge@cpan.orgE
200              
201             =head1 COPYRIGHT AND LICENSE
202              
203             This software is copyright (c) 2015-2016 by Jozef Reisinger.
204              
205             This is free software; you can redistribute it and/or modify it under
206             the same terms as the Perl 5 programming language system itself.
207              
208             =cut
209              
210             1;