File Coverage

blib/lib/Net/DHCP/Windows/Netsh/Parse.pm
Criterion Covered Total %
statement 88 98 89.8
branch 30 44 68.1
condition 12 21 57.1
subroutine 10 10 100.0
pod 5 6 83.3
total 145 179 81.0


line stmt bran cond sub pod time code
1             package Net::DHCP::Windows::Netsh::Parse;
2              
3 2     2   119839 use 5.006;
  2         15  
4 2     2   13 use strict;
  2         3  
  2         57  
5 2     2   12 use warnings;
  2         2  
  2         52  
6 2     2   1269 use JSON;
  2         25961  
  2         9  
7              
8             =head1 NAME
9              
10             Net::DHCP::Windows::Netsh::Parse - Parses the output from 'netsh dhcp server dump'
11              
12             =head1 VERSION
13              
14             Version 0.1.0
15              
16             =cut
17              
18             our $VERSION = '0.1.0';
19              
20              
21             =head1 SYNOPSIS
22              
23             use Net::DHCP::Windows::Netsh::Parse;
24              
25             my $parser=Net::DHCP::Windows::Netsh::Parse->new;
26            
27             eval{
28             $parser->parse( $dump );
29             };
30             if ( $@ ){
31             print "It failed with... ".$@."\n";
32             }
33            
34             # no white space
35             my $json=$parser->json(0);
36            
37             # now with useful white space
38             $json=$parser->json(0);
39              
40             =head1 METHODS
41              
42             =head2 new
43              
44             This initiates the object.
45              
46             No arguments are taken.
47              
48             my $parser=Net::DHCP::Windows::Netsh::Parse->new;
49              
50             =cut
51              
52             sub new {
53 1     1 1 78 my $self={
54             servers=>{},
55             };
56 1         2 bless $self;
57              
58 1         2 return $self;
59             }
60              
61             =head2 parse
62              
63             This parses a dump from netsh.
64              
65             Only one option is taken and that is a string.
66              
67             Nothing is returned. It will die if it fails to parse.
68              
69             eval{
70             $parser->parse( $dump );
71             };
72             if ( $@ ){
73             print "It failed with... ".$@."\n";
74             }
75              
76             =cut
77              
78             sub parse{
79 1     1 1 542 my $self=$_[0];
80 1         35 my $data=$_[1];
81              
82 1 50       6 if ( ! defined( $data ) ){
83 0         0 die( 'Nothing defined to parse' );
84             }
85              
86             # break it appart and grab only the relevant lines
87             # removing the pointless comments and blank lines
88 1         409 my @lines=grep( /^Dhcp\ Server/ , split( /\n/, $data ));
89              
90             # Don'y really care about lines matching like....
91             # Dhcp Server \\winboot Add Class "Default Routing and Remote Access Class" "User class for remote access clients" 525241532e4d6963726f736f6674 0 b
92             # Dhcp Server \\winboot Set DatabaseName "dhcp.mdb"
93             # Dhcp Server \\winboot Add Optiondef 36 "Ethernet Encapsulation" BYTE 0 comment="0=>client should use ENet V2; 1=> IEEE 802.3" 0
94             # Dhcp Server \\winboot v6 Add Class "Microsoft Windows Options" "Microsoft vendor-specific options for Windows Clients" 4d53465420352e30 1 b 311
95             # Dhcp Server \\winboot v6 Add Optiondef 21 "SIP Server Domain Name List " STRING 1 comment="Domain Name of SIP servers available to the client " ""
96             #
97             # set is case sensitive... we want stuff like...
98             # Dhcp Server \\winboot set optionvalue 15 STRING "foo.bar"
99 1         131 @lines=grep( !/^Dhcp\ Server\ [\\A-Za-z\.0-9]+\ Add\ Class/ , @lines );
100 1         96 @lines=grep( !/^Dhcp\ Server\ [\\A-Za-z\.0-9]+\ v6\ Add\ Class/ , @lines );
101 1         134 @lines=grep( !/^Dhcp\ Server\ [\\A-Za-z\.0-9]+\ Set/ , @lines );
102 1         81 @lines=grep( !/^Dhcp\ Server\ [\\A-Za-z\.0-9]+\ Add\ Optiondef/ , @lines );
103 1         30 @lines=grep( !/^Dhcp\ Server\ [\\A-Za-z\.0-9]+\ v6\ Add\ Optiondef/ , @lines );
104              
105 1         3 foreach my $line( @lines ){
106             # these will always be the same, just need to define something there
107             # garbage1=Dhcp garbage2=Server
108 57         345 my ( $garbage1, $garbage2, $server, $command, $the_rest)=split( /\ +/, $line, 5);
109              
110 57 100       245 if ( $command eq 'set' ){
    100          
    100          
111             # Dhcp Server \\winboot set optionvalue 15 STRING "foo.bar"
112             # Dhcp Server \\winboot set optionvalue 6 IPADDRESS "10.202.97.1" "10.202.97.2"
113             # Dhcp Server \\winboot set optionvalue 66 STRING "10.93.192.10"
114             # Dhcp Server \\winboot set optionvalue 67 STRING "linux"
115             # Dhcp Server \\winboot set optionvalue 60 STRING "PXEClient"
116 5         23 my @the_rest=split(/\ +/, $the_rest);
117              
118 5 50 33     38 if (
      33        
119             ( $the_rest[0] eq 'optionvalue' ) &&
120             ( $the_rest[1] =~ /^[0-9]+$/ ) &&
121             defined( $the_rest[3] )
122             ){
123              
124 5         8 my @values;
125 5         7 my $the_rest_location=3;
126 5         11 while(defined( $the_rest[$the_rest_location] )){
127 6         13 push(@values, $the_rest[$the_rest_location]);
128 6         13 $the_rest_location++;
129             }
130              
131 5         13 $self->add_option($server, 'default', $the_rest[1], \@values);
132             }
133             }elsif( $command eq 'add' ){
134             # Dhcp Server \\winboot add scope 10.40.10.0 255.255.254.0 "it.ord" ""
135             # Dhcp Server \\winboot add scope 10.31.129.248 255.255.255.248 "ipkvm.sjc" "The NEW ipkvm.sjc after 10.93.180.216/29 was swiped"
136 7         27 my @the_rest=split(/\ +/, $the_rest, 4);
137              
138 7 50 33     41 if (
      33        
139             ( $the_rest[0] eq 'scope' ) &&
140             defined( $the_rest[1] ) &&
141             defined( $the_rest[2] )
142             ){
143 7         16 $self->add_scope($server, $the_rest[1], $the_rest[2], $the_rest[3]);
144             }
145             }elsif( $command =~ /^[Ss]cope$/ ){
146             # Dhcp Server \\winboot Scope 10.31.129.248 Add iprange 10.31.129.251 10.31.129.254
147             # Dhcp Server \\winboot Scope 10.31.110.0 set optionvalue 51 DWORD "1800"
148             # Dhcp Server \\winboot Scope 10.31.110.0 set optionvalue 3 IPADDRESS "10.31.110.1"
149 37         158 my @the_rest=split(/\ +/, $the_rest);
150              
151 37 100 100     176 if (
    100 66        
152             ( $the_rest[1] eq 'set' ) &&
153             ( $the_rest[2] eq 'optionvalue' )
154             ){
155 16         21 my @values;
156              
157 16         21 my $the_rest_location=5;
158 16         32 while(defined( $the_rest[$the_rest_location] )){
159 20         37 push(@values, $the_rest[$the_rest_location]);
160 20         38 $the_rest_location++;
161             }
162              
163 16         29 $self->add_option($server, $the_rest[0], $the_rest[3], \@values);
164             }elsif(
165             ( $the_rest[1] eq 'Add' ) &&
166             ( $the_rest[2] eq 'iprange' )
167             ){
168 7         26 my @values=($the_rest[3].' '.$the_rest[4]);
169 7         15 $self->add_option($server, $the_rest[0], 'range', \@values);
170             }
171             }
172             }
173             }
174              
175             =head2 hash_ref
176              
177             This returns the current hash reference for the parsed data.
178              
179             my $hash_ref=$parser->hash_ref;
180              
181             =cut
182              
183             sub hash_ref{
184 1     1 1 253 return $_[0]->{servers};
185             }
186              
187             =head2 json
188              
189             This returns the parsed data as JSON.
190              
191             One option is taken and that is either a 0/1 for
192             if it should be made nice and pretty.
193              
194             # no white space
195             my $json=$parser->json(0);
196            
197             # now with useful white space
198             $json=$parser->json(0);
199              
200             =head1 DATA STRUCTURE
201              
202             The structure of it is as below for both the return
203             hash ref or JSON.
204              
205             $hostname=>{$scope}=>{
206             $options=>{
207             $option_id=>[]
208             },
209             mask=>subnet mask,
210             desc=>description,
211             }
212              
213             The $option_id will always be numeric, except for one special
214             case, which is range. That option contains a array of ranges
215             that the scope in question uses with in that subnet. Each item
216             the array represents one range. The format is as below for the
217             string.
218              
219             $start_ip $end_ip
220              
221             Hostname will always have \\ removed, so \\winboot
222             becomes just winboot.
223              
224             $scope is going to be the base address of the subnet.
225              
226             =cut
227              
228             sub json{
229 1     1 1 1008 my $self=$_[0];
230 1         2 my $pretty=$_[1];
231              
232 1         46 my $json=JSON->new;
233 1         7 $json->pretty( $pretty );
234              
235 1         30 return $json->encode( $self->{servers} );
236             }
237              
238             =head1 INTERNAL FUNCTIONS
239              
240             =head2 add_options
241              
242             This adds a option for a scope.
243              
244             $hostname = Hostname of the DHCP server.
245             $scope = scope name
246             $option = DHCP option integer
247             $values = array ref of values
248              
249             $parser->( $hostname, $scope, $option, \@values );
250              
251             =cut
252              
253             sub add_option{
254 28     28 0 53 my $self=$_[0];
255 28         43 my $hostname=$_[1];
256 28         44 my $scope=$_[2];
257 28         42 my $option=$_[3];
258 28         33 my $values=$_[4];
259              
260             # make sure we have everything we need
261             # split up so we produce a more useful error
262 28 50       83 if ( !defined( $hostname ) ){
    50          
    50          
    50          
263 0         0 die('No hostname specified');
264             }elsif( !defined( $scope ) ){
265 0         0 die('No scope specified');
266             }elsif( !defined( $option ) ){
267 0         0 die('No option specified');
268             }elsif( !defined( $values->[0] ) ){
269 0         0 die('No option specified');
270             }
271              
272             # skip over lines like this...
273             # Dhcp Server \\winboot Scope 10.40.10.0 set optionvalue 51 DWORD user="Default BOOTP Class" "1800"
274 28 100 100     80 if (
275             ( $option eq '51' ) &&
276             ( $values->[0] =~ /^[Uu]/ )
277             ){
278 1         6 return 1;
279             }
280              
281 27         59 $hostname=~s/^\\+//;
282              
283 27 100       65 if ( ! defined( $self->{servers}{$hostname} ) ){
284 1         3 $self->{servers}{$hostname}={};
285             }
286              
287 27 100       57 if ( ! defined( $self->{servers}{$hostname}{$scope} ) ){
288 1         3 $self->{servers}{$hostname}{$scope}={};
289             }
290              
291 27 50       56 if ( ! defined( $self->{servers}{$hostname}{$scope}{$option} ) ){
292 27         62 $self->{servers}{$hostname}{$scope}{$option}=[];
293             }
294              
295             # process each value
296 27         35 foreach my $value ( @{ $values } ){
  27         41  
297             # windows adds " to each of these
298 29         64 $value=~s/^\"//;
299 29         66 $value=~s/\"$//;
300 29         49 push( @{ $self->{servers}{$hostname}{$scope}{$option} }, $value );
  29         86  
301             }
302              
303 27         112 return 1;
304             }
305              
306             =head2 add_scope
307              
308             This adds a new scope.
309              
310             $hostname = Hostname of the DHCP server.
311             $scope = scope name
312             $mask = subnet mask for the scope
313             $desc = description
314              
315             $parser->( $hostname, $scope, $mask, $desc );
316              
317             =cut
318              
319             sub add_scope{
320 7     7 1 9 my $self=$_[0];
321 7         13 my $hostname=$_[1];
322 7         10 my $scope=$_[2];
323 7         13 my $mask=$_[3];
324 7         9 my $desc=$_[4];
325              
326             # make sure we have everything we need
327             # split up so we produce a more useful error
328 7 50       25 if ( !defined( $hostname ) ){
    50          
    50          
    50          
329 0         0 die('No hostname specified');
330             }elsif( !defined( $scope ) ){
331 0         0 die('No scope specified');
332             }elsif( !defined( $mask ) ){
333 0         0 die('No subnet mask specified');
334             }elsif( !defined( $desc ) ){
335 0         0 die('No subnet description specified');
336             }
337              
338 7         18 $hostname=~s/^\\+//;
339              
340 7 50       17 if ( ! defined( $self->{servers}{$hostname} ) ){
341 0         0 $self->{servers}{$hostname}={};
342             }
343              
344 7 50       17 if ( ! defined( $self->{servers}{$hostname}{$scope} ) ){
345 7         18 $self->{servers}{$hostname}{$scope}={};
346             }
347              
348 7         16 $self->{servers}{$hostname}{$scope}{mask}=$mask;
349 7         15 $self->{servers}{$hostname}{$scope}{desc}=$desc;
350              
351 7         25 return 1;
352             }
353              
354             =head1 AUTHOR
355              
356             Zane C. Bowers-Hadley, C<< >>
357              
358             =head1 BUGS
359              
360             Please report any bugs or feature requests to C, or through
361             the web interface at L. I will be notified, and then you'll
362             automatically be notified of progress on your bug as I make changes.
363              
364              
365              
366              
367             =head1 SUPPORT
368              
369             You can find documentation for this module with the perldoc command.
370              
371             perldoc Net::DHCP::Windows::Netsh::Parse
372              
373              
374             You can also look for information at:
375              
376             =over 4
377              
378             =item * RT: CPAN's request tracker (report bugs here)
379              
380             L
381              
382             =item * AnnoCPAN: Annotated CPAN documentation
383              
384             L
385              
386             =item * CPAN Ratings
387              
388             L
389              
390             =item * Search CPAN
391              
392             L
393              
394             =back
395              
396              
397             =head1 ACKNOWLEDGEMENTS
398              
399              
400             =head1 LICENSE AND COPYRIGHT
401              
402             This software is Copyright (c) 2019 by Zane C. Bowers-Hadley.
403              
404             This is free software, licensed under:
405              
406             The Artistic License 2.0 (GPL Compatible)
407              
408              
409             =cut
410              
411             1; # End of Net::DHCP::Windows::Netsh::Parse