File Coverage

blib/lib/Net/P0f/Backend/CmdFE.pm
Criterion Covered Total %
statement 15 72 20.8
branch 0 44 0.0
condition 0 5 0.0
subroutine 5 7 71.4
pod 2 2 100.0
total 22 130 16.9


line stmt bran cond sub pod time code
1             package Net::P0f::Backend::CmdFE;
2 1     1   4761 use strict;
  1         2  
  1         39  
3 1     1   6 use Carp;
  1         1  
  1         75  
4 1     1   852 use IO::File;
  1         13030  
  1         178  
5 1     1   1066 use IPC::Open3;
  1         3507  
  1         74  
6              
7 1     1   11 { no strict;
  1         3  
  1         1306  
8             $VERSION = 0.02;
9             @ISA = qw(Net::P0f);
10             }
11              
12             =head1 NAME
13              
14             Net::P0f::Backend::CmdFE - Back-end for C that pilots the B utility
15              
16             =head1 VERSION
17              
18             Version 0.01
19              
20             =head1 SYNOPSIS
21              
22             use Net::P0f;
23              
24             my $p0f = Net::P0f->new(backend => 'cmd', program_path => '/usr/local/bin/p0f');
25             ...
26              
27             =head1 DESCRIPTION
28              
29             This module is a back-end helper for C.
30             It provides an interface to pilot the B utility by parsing its output.
31              
32             See L for more general information and examples.
33              
34             =head1 METHODS
35              
36             =over 4
37              
38             =item init()
39              
40             This method initializes the backend-specific part of the object.
41             It is automatically called by C during the object creation.
42              
43             B
44              
45             =over 4
46              
47             =item *
48              
49             C - indicates the path of the p0f program.
50             If not specified, uses C.
51              
52             =back
53              
54             =cut
55              
56             sub init {
57 0     0 1   my $self = shift;
58 0           my %opts = @_;
59              
60             # declare my specific options
61 0           $self->{options}{program_path} = 'p0f';
62            
63             # initialize my options
64 0           for my $opt (keys %opts) {
65 0 0 0       exists $self->{options}{$opt} ?
66             ( $self->{options}{$opt} = $opts{$opt} and delete $opts{$opt} )
67             : carp "warning: Unknown option '$opt'";
68             }
69             }
70              
71             =item run()
72              
73             This method runs the backend engine.
74             It is called by the C method.
75              
76             =cut
77              
78             sub run {
79 0     0 1   my $self = shift;
80              
81             # check that the program_path is defined
82 0 0         croak "fatal: Please set the path to p0f with the 'program_path' option"
83             unless length $self->{options}{program_path};
84              
85             # construct program arguments
86 0           my @program_args = qw(-q -l -t);
87 0           my %opt2arg = (
88             chroot_as => '-u', # arg: user
89             fingerprints_file => '-f', # arg: fingerprints file
90             fuzzy => '-F',
91             promiscuous => '-p',
92             masquerade_detection => '-M',
93             masquerade_detection_threshold => '-T', # arg: threshold
94             resolve_names => '-r',
95             );
96              
97             # detection mode
98 0 0         if($self->{options}{detection_mode} == 1) {
    0          
99 0           push @program_args, '-A'
100             } elsif($self->{options}{detection_mode} == 2) {
101 0           push @program_args, '-R'
102             }
103              
104             # set input source
105 0 0         if($self->{options}{interface}) {
    0          
106 0           push @program_args, '-i', $self->{options}{interface}
107             } elsif($self->{options}{dump_file}) {
108 0           push @program_args, '-s', $self->{options}{dump_file}
109             }
110            
111             # set switch options
112 0           for my $opt (qw(promiscuous fuzzy resolve_names masquerade_detection)) {
113 0 0         push @program_args, $opt2arg{$opt} if $self->{options}{$opt}
114             }
115              
116             # set options with argument
117 0           for my $opt (qw(chroot_as fingerprints_file masquerade_detection_threshold)) {
118 0 0         push @program_args, $opt2arg{$opt}, $self->{options}{$opt} if $self->{options}{$opt}
119             }
120              
121             # BPF filter
122 0 0         push @program_args, $self->{options}{filter} if $self->{options}{filter};
123              
124             # launch p0f
125 0           my($stdin,$stdout,$stderr) = (new IO::File, new IO::File, new IO::File);
126 0           my $pid = open3($stdin, $stdout, $stderr,
127             $self->{options}{program_path}, @program_args);
128              
129 0 0         croak "fatal: Can't exec '", $self->{options}{program_path}, "': $!" unless $pid;
130              
131             # initialize looping
132 0           my $callback = $self->{loop}{callback};
133 0           $self->{loop}{keep_on} = 1;
134 0           my $loops = 0;
135            
136 0           while($self->{loop}{keep_on}) {
137 0           my %header = (
138             timestamp => '',
139             ip_src => '', name_src => '', port_src => '',
140             ip_dest => '', name_dest => '', port_dest => '',
141             );
142 0           my %os_info = ( genre => '', details => '', uptime => '' );
143 0           my %link_info = ( distance => '', link_type => '' );
144            
145             # read next line
146 0           my $line = <$stdout>;
147            
148             # masquerade detected
149 0 0         if(index($line, '>> ') == 0) {
150             # ...
151             next
152 0           }
153            
154             # parse the output line
155 0           $line =~ s/^<([^>]+)> *//; # timestamp
156 0           $header{timestamp} = $1;
157            
158 0           my($src,$dest) = split(' -> ', $line);
159              
160             # source IP addr, name and port
161 0 0         $src =~ s{^([\d.]+)(?:/([\w.]+))?:(\d+) +- +}{}
162             and @header{qw(ip_src name_src port_src)} = ($1, $2, $3);
163              
164             # OS uptime
165 0 0         $src =~ s{ \(up: (\d+) \w+\)}{}
166             and $os_info{uptime} = $1;
167              
168             # OS genre and details
169 0 0         $src =~ m/^(\w+) *(.*)$/
170             and @os_info{qw(genre details)} = ($1, $2);
171              
172             # destination IP addr, name and port
173 0 0         $dest =~ s{^([\d.]+)(?:/([\w.]+))?:(\d+) +}{}
174             and @header{qw(ip_dest name_dest port_dest)} = ($1, $2, $3);
175            
176             # distance information
177 0 0         $dest =~ s/distance (\d+), //
178             and $link_info{distance} = $1;
179            
180             # link type
181 0 0         $dest =~ s/\(link: (.+)\)//
182             and $link_info{link_type} = $1;
183            
184             # replace undef values with empty strings to avoid warnings
185 0 0         map { defined $header{$_} or $header{$_} = '' } keys %header;
  0            
186 0 0         map { defined $os_info{$_} or $os_info{$_} = '' } keys %os_info;
  0            
187 0 0         map { defined $link_info{$_} or $link_info{$_} = '' } keys %link_info;
  0            
188            
189             # invoque the callback
190 0           eval {
191 0           &$callback($self, \%header, \%os_info, \%link_info);
192             };
193 0 0 0       carp "error: The callback died with the following error: $@" and last if $@;
194            
195 0 0         $self->{loop}{keep_on} = 0 if ++$loops == $self->{loop}{count};
196             }
197              
198             # close the filehandles, kill the child process and wait for the zombie
199 0           close($stdin); close($stdout); close($stderr);
  0            
  0            
200 0           kill 2, $pid;
201 0           waitpid $pid, 0;
202             }
203              
204             =back
205              
206              
207             =head1 DIAGNOSTICS
208              
209             These messages are classified as follows (listed in increasing order of
210             desperatin):
211              
212             =over 4
213              
214             =item *
215              
216             B<(W)> A warning, usually caused by bad user data.
217              
218             =item *
219              
220             B<(E)> An error caused by external code.
221              
222             =item *
223              
224             B<(F)> A fatal error caused by the code of this module.
225              
226             =back
227              
228             =over 4
229              
230             =item Can't exec '%s': %s
231              
232             B<(F)> This module was unable to execute the program. Detailed error follows.
233              
234             =item Please set the path to p0f with the 'program_path' option
235              
236             B<(F)> You must set the C option with the path to the p0f binary.
237              
238             =item The callback died with the following error: %s
239              
240             B<(E)> As the message says, the callback function died. Its error was catched
241             and follows.
242              
243             =item Unknown option '%s'
244              
245             B<(W)> You called an accesor which does not correspond to a known option.
246              
247             =back
248              
249              
250             =head1 SEE ALSO
251              
252             L
253              
254             =head1 AUTHOR
255              
256             SEbastien Aperghis-Tramoni Esebastien@aperghis.netE
257              
258             =head1 BUGS
259              
260             Please report any bugs or feature requests to
261             L, or through the web interface at
262             L.
263             I will be notified, and then you'll automatically be notified
264             of progress on your bug as I make changes.
265              
266             =head1 ACKNOWLEDGEMENTS
267              
268             =head1 COPYRIGHT & LICENSE
269              
270             Copyright 2004 SEbastien Aperghis-Tramoni, All Rights Reserved.
271              
272             This program is free software; you can redistribute it and/or modify it
273             under the same terms as Perl itself.
274              
275             =cut
276              
277             1; # End of Net::P0f::Backend::CmdFE