File Coverage

blib/lib/Net/DHCP/Config/Utilities/INI_check.pm
Criterion Covered Total %
statement 20 121 16.5
branch 0 48 0.0
condition 0 24 0.0
subroutine 7 10 70.0
pod 3 3 100.0
total 30 206 14.5


line stmt bran cond sub pod time code
1             package Net::DHCP::Config::Utilities::INI_check;
2              
3 1     1   54734 use 5.006;
  1         10  
4 1     1   5 use strict;
  1         1  
  1         27  
5 1     1   5 use warnings;
  1         2  
  1         23  
6 1     1   397 use Config::Tiny;
  1         854  
  1         25  
7 1     1   444 use File::Find::Rule;
  1         6720  
  1         8  
8 1     1   521 use Net::CIDR;
  1         4829  
  1         65  
9 1     1   490 use Net::CIDR::Set;
  1         8682  
  1         817  
10             #use Net::DHCP::Config::Utilities::Options;
11              
12             =head1 NAME
13              
14             Net::DHCP::Config::Utilities::INI_check - Runs various checks for DHCP info stored via INI.
15              
16             =head1 VERSION
17              
18             Version 0.0.1
19              
20             =cut
21              
22             our $VERSION = '0.0.1';
23              
24             =head1 SYNOPSIS
25              
26             use Net::DHCP::Config::Utilities::Options
27             use Data::Dumper;
28              
29             my $ini_checker;
30             eval { $ini_checker=Net::DHCP::Config::Utilities::INI_check->new( $dir )) };
31             if ( $@ ){
32             die "Initing the checker failed with... ".$@;
33             }
34              
35             my %overlaps;
36             eval { %overlaps = $ini_checker->overlap_check; };
37             if ($@){
38             warn('Overlap check failed... ');
39             }else{
40             use Data::Dumper;
41             $Data::Dumper::Terse=1;
42             print Dumper( \%overlaps );
43             }
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             This initiates the object.
50              
51             One arguments is required and that is the directory to process.
52              
53             The section optional argument is the glob to use to match the files to process.
54             If left undefined, "*.dhcp.ini" is used.
55              
56             my $checker;
57             eval { $checker=Net::DHCP::Config::Utilities::INI_check->new( $dir )) };
58             if ( $@ ){
59             die "Initing the checker failed with... ".$@;
60             }
61              
62             =cut
63              
64             sub new {
65 0     0 1   my $dir = $_[1];
66 0           my $name = $_[2];
67              
68 0 0         if ( !defined($dir) ) {
    0          
69 0           die 'No directory defined';
70             }
71             elsif ( !-d $dir ) {
72 0           die '"' . $dir . '" is not a dir';
73             }
74              
75 0 0         if ( !defined($name) ) {
76 0           $name = '*.dhcp.ini';
77             }
78              
79 0           my $self = {
80             dir => $dir,
81             name => $name,
82             };
83 0           bless $self;
84              
85 0           return $self;
86             }
87              
88             =head2 overlap_check
89              
90             Finds every DHCP INI file in the directory file in the directory and
91             checks for overlaps.
92              
93             $returned{$file}{$section}{$file_containing_conflicts}[$sections]
94              
95             The returned values is a hash. $file is the name of file containing the checked
96             subnet. $subnet is the name of subnet in conflict. $file_containing_conflicts them
97             the name of the file containing the conflict. $sections is the name of the INI
98             sections in the previously mentioned file containing the conflict.
99              
100             my %overlaps;
101             eval { %overlaps = $ini_checker->overlap_check; };
102             if ($@){
103             warn('Overlap check failed... ');
104             }else{
105             use Data::Dumper;
106             $Data::Dumper::Terse=1;
107             print Dumper( \%overlaps );
108             }
109              
110             =cut
111              
112             sub overlap_check {
113 0     0 1   my $self = $_[0];
114              
115             # the files to find
116 0           my @files = File::Find::Rule->file()->name( $self->{name} )->in( $self->{dir} );
117              
118             # make ainitial pass through, loading them all
119 0           my %loaded;
120 0           foreach my $file (@files) {
121 0           my $ini;
122              
123             #$ini = Config::Tiny->new;
124             #my $parsed_it;
125 0           eval { $ini = Config::Tiny->read($file) };
  0            
126 0 0 0       if ( $@ || $! ) {
127              
128             # die if we can't load any of them
129 0 0         if ($@) {
130 0           die 'Died parsing "' . $file . '"... ' . $@;
131             }
132             else {
133 0           die 'Error parsing "' . $file . '"... ' . $ini->errstr;
134              
135             }
136             }
137 0           $loaded{$file} = $ini;
138             }
139              
140 0           my %to_return;
141              
142             # go through and check each file
143 0           foreach my $file ( keys(%loaded) ) {
144 0           my @ini_keys_found = keys( %{ $loaded{$file} } );
  0            
145 0           my %subnets;
146 0           foreach my $current_key (@ini_keys_found) {
147 0           my $ref_test = $loaded{$file}->{$current_key};
148              
149             # if it is a hash and has a subnet mask, add it to the list
150 0 0 0       if ( ( ref($ref_test) eq 'HASH' )
151             && defined( $loaded{$file}->{$current_key}{mask} ) )
152             {
153 0           $subnets{$current_key} = 1;
154             }
155             }
156              
157             # Config::Tiny uses _ for variables not in a section
158             # This really should never be true as there is no reason for this section
159             # to contain the mask variable.
160 0 0         if ( defined( $subnets{_} ) ) {
161 0           delete( $subnets{_} );
162             }
163              
164             # check each subnet in the current file
165 0           foreach my $current_subnet ( keys(%subnets) ) {
166 0           my $subnet = $current_subnet;
167 0           my $mask = $loaded{$file}->{$current_subnet}{mask};
168              
169             # if we have a base specified, use it instead of the section name
170 0 0         if ( defined( $loaded{$file}->{$current_subnet}{base} ) ) {
171 0           $subnet = $loaded{$file}->{$current_subnet}{base};
172             }
173              
174             # try to generate a CIDR
175 0           my $cidr;
176 0           eval { $cidr = Net::CIDR::addrandmask2cidr( $subnet, $mask ); };
  0            
177              
178             # only process this subnet if we can generate a CIDR
179 0 0 0       if ( !$@ && defined($cidr) ) {
180              
181             # go through and test the current subnet against each file
182 0           foreach my $in_file ( keys(%loaded) ) {
183              
184             # only ignore this subnet if it is found
185 0           my $ignore;
186 0 0         if ( $in_file eq $file ) {
187 0           $ignore = $current_subnet;
188             }
189              
190             # look for overlaps
191 0           my @overlaps = $self->cidr_in_file( $cidr, $in_file, $ignore );
192              
193             # handle the overlaps if found, adding it to the return data
194 0 0         if ( defined( $overlaps[0] ) ) {
195              
196 0 0         if ( !defined( $to_return{$file} ) ) {
197 0           $to_return{$file} = {};
198             }
199 0 0         if ( !defined( $to_return{$file}{$current_subnet} ) ) {
200 0           $to_return{$file}{$current_subnet} = {};
201             }
202 0           $to_return{$file}{$current_subnet}{$in_file} = \@overlaps;
203             }
204             }
205             }
206             }
207             }
208              
209 0           return %to_return;
210             }
211              
212             =head2 cidr_in_file
213              
214             This goes through the INI file and checks the subnets there for any
215             overlap with the specified CIDR.
216              
217             Two arguments are required. The first is the CIDR to check for and the
218             second is the INI DHCP file to check for overlaps in.
219              
220             Any subnets with bad base/mask that don't convert properly to a CIDR
221             are skipped.
222              
223             The returned value is a array reference of any found conflicts.
224              
225             my $overlaps=$ini_check->cidr_in_file( $cidr, $file );
226             if ( defined( $overlaps->[0] ) ){
227             print "Overlap(s) found\n";
228             }
229              
230             =cut
231              
232             sub cidr_in_file {
233 0     0 1   my $self = $_[0];
234 0           my $cidr = $_[1];
235 0           my $file = $_[2];
236 0           my $ignore = $_[3];
237              
238             # make sure they are both defined before going any further
239 0 0 0       if ( ( !defined($cidr) )
240             || ( !defined($file) ) )
241             {
242 0           die 'Either CIDR or file undefined';
243             }
244              
245             # make sure the CIDR has a /, the next test will pass regardless
246 0 0         if ( $cidr !~ /\// ) {
247 0           die 'The value passed for the CIDR does not contain a /';
248             }
249              
250             # make sure the CIDR is valid
251 0           my $cidr_test;
252 0           eval { $cidr_test = Net::CIDR::cidrvalidate($cidr); };
  0            
253 0 0 0       if ( $@ || ( !defined($cidr_test) ) ) {
254 0           die '"' . $cidr . '" is not a valid CIDR';
255             }
256              
257             # make sure we can read the INI file
258 0           my $ini;
259 0           eval { $ini = Config::Tiny->read($file); };
  0            
260 0 0 0       if ( $@ || $! ) {
261 0           my $extra_dead = '';
262 0 0         if ($@) {
263 0           $extra_dead = '... ' . $@;
264             }
265             else {
266 0           $extra_dead = '... ' . $ini->errstr;
267             }
268 0           die 'Failed to load the INI file';
269             }
270              
271             # build a list of the sections with masks
272 0           my @ini_keys_found = keys( %{$ini} );
  0            
273 0           my %subnets;
274 0           foreach my $current_key (@ini_keys_found) {
275 0           my $ref_test = $ini->{$current_key};
276              
277             # if it is a hash and has a subnet mask, add it to the list
278 0 0 0       if ( ( ref($ref_test) eq 'HASH' )
279             && defined( $ini->{$current_key}{mask} ) )
280             {
281 0           $subnets{$current_key} = 1;
282             }
283             }
284              
285             # Config::Tiny uses _ for variables not in a section
286             # This really should never be true as there is no reason for this section
287             # to contain the mask variable.
288 0 0         if ( defined( $subnets{_} ) ) {
289 0           delete( $subnets{_} );
290             }
291              
292             # If a ignore is specified, remove it, if it is defined
293 0 0 0       if ( defined($ignore)
294             && defined( $subnets{$ignore} ) )
295             {
296 0           delete( $subnets{$ignore} );
297             }
298              
299             # holds the overlaps
300 0           my @overlaps;
301              
302             # go through and test each CIDR
303 0           foreach my $subnet_current ( keys(%subnets) ) {
304 0           my $subnet = $subnet_current;
305 0           my $mask = $ini->{$subnet_current}{mask};
306              
307 0 0         if ( defined( $ini->{$subnet_current}{base} ) ) {
308 0           $subnet = $ini->{$subnet_current}{base};
309             }
310              
311 0           my $cidr_other;
312 0           eval { $cidr_other = Net::CIDR::addrandmask2cidr( $subnet, $mask ); };
  0            
313 0 0         if ( !$@ ) {
314              
315 0           my $set = Net::CIDR::Set->new($cidr);
316              
317 0 0         if ( $set->contains_any($cidr_other) ) {
318 0           push( @overlaps, $subnet_current );
319             }
320             }
321             }
322              
323 0           return @overlaps;
324             }
325              
326             =head1 AUTHOR
327              
328             Zane C. Bowers-Hadley, C<< >>
329              
330             =head1 BUGS
331              
332             Please report any bugs or feature requests to C, or through
333             the web interface at L. I will be notified, and then you'll
334             automatically be notified of progress on your bug as I make changes.
335              
336              
337              
338              
339             =head1 SUPPORT
340              
341             You can find documentation for this module with the perldoc command.
342              
343             perldoc Net::DHCP::Config::Utilities
344              
345              
346             You can also look for information at:
347              
348             =over 4
349              
350             =item * RT: CPAN's request tracker (report bugs here)
351              
352             L
353              
354             =item * AnnoCPAN: Annotated CPAN documentation
355              
356             L
357              
358             =item * CPAN Ratings
359              
360             L
361              
362             =item * Search CPAN
363              
364             L
365              
366             =back
367              
368              
369             =head1 ACKNOWLEDGEMENTS
370              
371              
372             =head1 LICENSE AND COPYRIGHT
373              
374             This software is Copyright (c) 2020 by Zane C. Bowers-Hadley.
375              
376             This is free software, licensed under:
377              
378             The Artistic License 2.0 (GPL Compatible)
379              
380              
381             =cut
382              
383             1; # End of Net::DHCP::Config::Utilities