File Coverage

blib/lib/Firewall/PaloAlto.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Firewall::PaloAlto;
2 1     1   19816 use Moose;
  0            
  0            
3              
4             use 5.006;
5             use strict;
6             use warnings;
7              
8             use XML::Simple;
9             use LWP::UserAgent;
10             use Carp;
11             use Data::Dumper qw(Dumper);
12             use Modern::Perl;
13             use Params::Validate qw(:all);
14              
15              
16             =head1 NAME
17              
18             Firewall::PaloAlto - Interact with a Palo Alto firewall's API through Perl.
19              
20             =head1 VERSION
21              
22             Version 0.02
23              
24             =cut
25              
26             our $VERSION = '0.02';
27              
28             =head1 SYNOPSIS
29              
30             The Firewall::PaloAlto module provides interfaces into the XML API of a Palo Alto firewall.
31              
32             use Firewall::PaloAlto;
33              
34             my $fw = Firewall::PaloAlto->new(host => 'pa.local', username => 'admin', password => 'admin');
35             $fw->connect();
36              
37             #Add a new virtual system
38             $fw->address('set', vsys_id => 6, 'display-name' => "Script_Tenant");
39              
40             #Add a virtual router to the chassis
41             $fw->virtual_router('set', vr_name => 'NEW_VR', interface => [ 'ae1.65', 'ae1.66' ]);
42              
43             #Add a new address - if the vsys is not specified it defaults to vsys1.
44             #This works for devices such as the VM series, which only have a vsys1.
45             $fw->address('set', name => 'Google_DNS', ip-netmask => '8.8.8.8/32');
46              
47             #Get the configuration for the newly created address:
48             my $address_config = $fw->address('get', name => 'Google_DNS');
49              
50             #Delete the newly created address
51             $fw->address('delete', name => 'Google_DNS);
52              
53             A list of functions that can be exported. You can delete this section
54             if you don't export anything, such as for a purely object-oriented module.
55              
56             =cut
57              
58             =head1 CLASS METHODS
59              
60             =head2 connect(%parameters)
61              
62             This function connects to the Palo Alto with the credentials specified.
63              
64             Host, username and password are mandatory.
65             If not specified SSL is used, but it can be disabled using the argument ssl => 0
66              
67             Detailed debugging can be turned on using the debu => 1 argument. It is off by default.
68              
69             =head3 Arguments
70              
71             =over
72              
73             =item *
74             host - the hostname or IP of the firewall to connect to.
75              
76             =item *
77             username - a username to connect to the firewall.
78              
79             =item *
80             password - a password to connect to the firewall.
81              
82             =item *
83             ssl (optional, default: 1) - use SSL to connect to the firewall.
84              
85             =item *
86             debug (optional, default: 0) - print debugging messages.
87              
88             =back
89              
90             =cut
91              
92             sub connect {
93             my $self = shift;
94              
95             my $user_agent = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 });
96             my $key_request = HTTP::Request->new(GET => $self->base_uri.'type=keygen&user='.$self->username.'&password='.$self->password);
97             my $key_response = $user_agent->request($key_request);
98              
99             my $pa_response = $self->_check_response_and_parse($key_response);
100              
101             if ($pa_response->{status} eq 'success') {
102             $self->_api_key($pa_response->{result}->{key});
103             return 1;
104             }
105             else {
106             return 0;
107             }
108             }
109              
110             has host => ( is => 'ro',
111             isa => 'Str',
112             required => '1',
113             );
114              
115             has username => ( is => 'ro',
116             isa => 'Str',
117             required => '1',
118             );
119              
120             has password => ( is => 'ro',
121             isa => 'Str',
122             required => '1',
123             );
124              
125             has api_key => ( is => 'rw', isa => 'Str', init_arg => undef, writer => '_api_key');
126              
127             has ssl => ( is => 'ro', isa => 'Bool', default => 1 );
128             has base_uri => ( is => 'ro',
129             isa => 'Str',
130             lazy => '1',
131             init_arg => undef,
132             default => sub { return 'http' . ($_[0]->ssl ? 's' : '') . '://' . $_[0]->host . '/api/?'; }
133              
134             );
135              
136             has 'debug' => ( is => 'ro', isa => 'Bool', required => 0, default => 0 );
137              
138             sub _create_requester {
139             my $self = shift;
140             my %args = @_;
141              
142             my $request = $self->base_uri. 'key=' . $self->api_key;
143              
144             for my $key (keys %args) {
145             #Add the argument on to the command line
146             $request .= "&$key=$args{$key}";
147             }
148              
149              
150             return sub {
151             my (%request_args) = @_;
152             my $user_agent = LWP::UserAgent->new;
153              
154             for my $key (keys %request_args) {
155             $request .= "&$key=$request_args{$key}";
156             }
157              
158             $self->_debug_print((caller(1))[3], "Sending HTTP Request");
159              
160             my $http_request = HTTP::Request->new(GET => $request);
161             my $http_response = $user_agent->request($http_request);
162              
163             return $self->_check_response_and_parse($http_response);
164             }
165             }
166              
167             sub _check_response_and_parse {
168             my $self = shift;
169             my $http_response = shift;
170              
171             my $response_codes = {
172             400 => {is_error => 1, string => "Bad Request", parse => sub { "Error 400" } },
173             403 => { is_error => 1, string => "Forbidden", parse => sub { $_[0]->{result}->{msg}; } },
174             1 => { is_error => 1, string => "Unknown Command", parse => sub { "Error 1" } },
175             2 => { is_error => 1, string => "Internal Error (2)", parse => sub { "Error 2" } },
176             3 => { is_error => 1, string => "Internal Error (3)", parse => sub { "Error 3" } },
177             4 => { is_error => 1, string => "Internal Error (4)", parse => sub { "Error 4" } },
178             5 => { is_error => 1, string => "Internal Error (4)", parse => sub { "Error 5" } },
179             6 => { is_error => 1, string => "Bad XPath", parse => sub { "Error 6" } },
180             7 => { is_error => 1, string => "Object not present", parse => sub { "Error 7" } },
181             8 => { is_error => 1, string => "Object not unique", parse => sub { "Error 8" } },
182             9 => { is_error => 1, string => "Internal Error (9)", parse => sub { "Error 9" } },
183             10 => { is_error => 1, string => "Reference count not zero", parse => sub { "Error 10" } },
184             11 => { is_error => 1, string => "Internal Error (11)", parse => sub { "Error 11" } },
185             12 => { is_error => 1, string => "Invalid Object", parse => sub { join(", ", @{$_[0]->{msg}->{line}}); } },
186             13 => { is_error => 1, string => "Operation Failed", parse => sub { "Error 13" } },
187             14 => { is_error => 1, string => "Operation Not Possible", parse => sub { "Error 14" } },
188             15 => { is_error => 1, string => "Operation Denied", parse => sub { "Error 15" } },
189             16 => { is_error => 1, string => "Unauthorized", parse => sub { "Error 16" } },
190             17 => { is_error => 1, string => "Invalid Command", parse => sub { "Error 16" } },
191             18 => { is_error => 1, string => "Malformed XML", parse => sub { $_[0]->{msg}->{line} } },
192             19 => { is_error => 0, string => "Get Request Successful", parse => sub{} },
193             20 => { is_error => 0, string => "Set Request Successful", parse => sub{} },
194             21 => { is_error => 1, string => "Internal Error (21)", parse => sub { "Error 21" } },
195             22 => { is_error => 1, string => "Session Timed Out", parse => sub { "Error 22" } },
196             #Custom code for keygen success
197             1023 => { is_error => 0, string => "KeyGen Successful", parse => sub {} },
198             };
199            
200             #We locally redefine croak so we can get some nice red printing around it.
201             no warnings 'redefine';
202             local *original_croak = \&croak;
203             local *croak = sub { original_croak("\e[31m".$_[0]."\e[0m"); };
204            
205             #Check the http response message - croak if its not successful
206             croak "*[HTTP Request] Failed" if !$http_response->is_success;
207            
208             my $palo_response = XMLin($http_response->content, KeyAttr => 'name');
209            
210             #If the response doesn't contain a code (i.e. the keygen request doesn't), then we check the status string.
211             #If its successful, we assign our own 'custom code' as a workaround
212             if (!defined $palo_response->{code} && $palo_response->{status} eq "success") {
213             $palo_response->{code} = 1023;
214             }
215            
216             #Check the response from the Palo Alto:
217             my $code = $response_codes->{$palo_response->{code}};
218             my $api_error = $code->{parse}->($palo_response);
219             croak "*[API Request] $code->{string}: $api_error" if $code->{is_error};
220            
221             $self->_debug_print((caller(1))[3], "API Request $palo_response->{status}");
222            
223            
224             return $palo_response;
225             }
226              
227             sub _generate_elements {
228             my $self = shift;
229             my %element_hash = @_;
230             my $element_string = "";
231              
232             for my $key (keys %element_hash) {
233             $element_string .= "<$key>";
234              
235             #If our hash points to an array reference, we iterate through the array and add member.
236             #This creates ab
237             if (ref($element_hash{$key}) eq "ARRAY") {
238             for my $member (@{ $element_hash{$key} }) {
239             $element_string .= "$member";
240             }
241             }
242             #If we're pointing to another hash, we recurse down, as the structure will be the same.
243             #This allows us to do data
244             elsif (ref($element_hash{$key}) eq "HASH") {
245             $element_string .= $self->_generate_elements(%{ $element_hash{$key} });
246             }
247             #Otherwise its just a normal value
248             else {
249             $element_string .= "$element_hash{$key}";
250             }
251              
252             $element_string .= "";
253             }
254              
255             return $element_string;
256             }
257              
258             sub _debug_print {
259             my $self = shift;
260             my ($category, $debug_string, $colourise_sub) = @_;
261             my $string_colour = "\e[0;36m";
262             my $string_norm = "\e[0m";
263              
264             if (!$self->debug()) {
265             return 0;
266             }
267              
268             #We pass code in $colorise_sub - if it evaluates to true, we print the category in green
269             #If its false, we print in red. If its not defined, we print in orange.
270             if (defined $colourise_sub) {
271             $string_colour = $colourise_sub->() ? "\e[32m" : "\e[31m";
272             }
273              
274             say "*[".$string_colour.$category.$string_norm."] $debug_string";
275             }
276              
277             =head1 OBJECT METHODS
278              
279             =head2 commit(%parameters)
280              
281             The commit function commits the current candidate configuration to the Palo Alto firewall.
282              
283             $pa_firewall->commit(vsys_id => 10, poll => 10);
284              
285             =head3 Arguments
286              
287             =over
288              
289             =item *
290             vsys_id (optional) - if supplied, performs a partial commit on the vsys specified. If not provided, performs a full commit on the device.
291              
292             =item *
293             poll (optional, default: 5 seconds) - defines the interval (seconds) that the method will poll the device to get the current status of the commit job.
294              
295             =back
296              
297             =cut
298              
299             sub commit {
300             my $self = shift;
301             my %args = @_;
302             my $requester = $self->_create_requester(type => 'commit');
303             my $operate = $self->_create_requester(type => 'op');
304             my $cmd = "";
305              
306             #If a poll interval is not defined, we default to 5 seconds.
307             $args{poll} //= 5;
308              
309             if (defined $args{vsys_id}) {
310             $cmd = "vsys$args{vsys_id}";
311             }
312              
313             my $response = $requester->(cmd => $cmd);
314              
315             my $job_result;
316              
317             do {
318             $job_result = $operate->(cmd => "$response->{result}->{job}");
319             $self->_debug_print((caller(0))[3], "JobID: $response->{result}->{job}, Status: $job_result->{result}->{job}->{result}, Progress: $job_result->{result}->{job}->{progress}",
320             sub { $job_result->{result}->{job}->{result} ne "FAILED" });
321              
322             sleep($args{poll});
323             } while ($job_result->{result}->{job}->{result} eq 'PEND');
324             }
325              
326              
327             =head2 vsys($action, %parameters)
328              
329             =head3 Arguments
330              
331             =over
332              
333             =item * $action - perform an action: ['get' | 'set' | 'delete']
334              
335             =item * vsys_id - the ID of the virtual system to perform the action on.
336              
337             =item * display-name - sets the display name of the virtual system.
338              
339             =back
340              
341             =cut
342              
343             sub vsys {
344             my $self = shift;
345             my $action = shift;
346             my (%args) = @_;
347              
348             #Validate our parameters.
349             validate(
350             @_, {
351             vsys_id => 1,
352             "display-name" => 1
353             }
354             );
355              
356             my $vsys_id = delete $args{vsys_id};
357             my $requester = $self->_create_requester(type => 'config', action => $action);
358              
359             my $elements = $self->_generate_elements(%args);
360              
361             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/vsys/entry[\@name='vsys$vsys_id']", element => $elements);
362             }
363              
364             =head2 vsys_import($action, %parameters);
365              
366             $pa_firewall->vsys_import('set', vsys_id => 4, interface => [ ethernet1/1, ethernet1/2 ], virtual-router => [ default ]);
367              
368             =head3 Arguments
369              
370             =over
371              
372             =item * $action - perform an action: ['get' | 'set' | 'delete']
373              
374             =item * vsys_id - the ID of the virtual system to perform the action on.
375              
376             =item * interface - an anonymous array of one or more interfaces to add to the virtual system.
377              
378             =item * virtual-router - an anonymous array of one or more virtual routers to add to the virtual system.
379              
380             =back
381              
382             =cut
383              
384             sub vsys_import {
385             my $self = shift;
386             my $action = shift;
387             my (%args) = @_;
388              
389             #validate(
390             # @_, {
391             # vsys_id => 1,
392             # interface => 0;
393             # "virtual-router" => 0,
394             # }
395             #);
396              
397             my $vsys_id = delete @args{vsys_id};
398              
399             my $requester = $self->_create_requester(type => 'config', action => $action);
400             my $elements = $self->_generate_elements(%args);
401              
402             #Add the interface or virtual router to a vsys
403             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/vsys/entry[\@name='vsys$vsys_id']/import/network", element => $elements);
404             }
405              
406             =head2 l3_subinterface($action, %parameters)
407              
408             =head3 Arguments
409              
410             =over
411              
412              
413             =item * $action - perform an action: ['get' | 'set' | 'delete']
414              
415             =item * parent - the parent interface of the new subinterface
416              
417             =item * tag - the VLAN tag to use on the sub-interface. This is also used as the logical sub-interface identifier.
418              
419             =item * description - a description to add to the sub-interface.
420              
421             =back
422              
423             =cut
424              
425             sub l3_subinterface {
426             my $self = shift;
427             my ($action, %args) = @_;
428              
429             my $parent_interface = delete @args{'parent'};
430              
431             my $requester = $self->_create_requester(type => 'config', action => $action);
432             my $elements = $self->_generate_elements(%args);
433              
434             #Create the sub-interface
435             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/network/interface/aggregate-ethernet/entry[\@name='$parent_interface']/layer3/units/entry[\@name='$parent_interface.$args{tag}']",
436             element => $elements);
437              
438             }
439              
440              
441             =head2 virtual_router($action, %parameters)
442              
443             =head3 Arguments
444              
445             =over
446              
447              
448             =item * $action - perform an action: ['get' | 'set' | 'delete']
449              
450             =item * vr_name - the name of the virtual router to perform the action on.
451              
452             =item * interface - an anonymous array of one or more interfaces to add to the virtual router.
453              
454             =back
455              
456             =cut
457              
458             sub virtual_router {
459             my $self = shift;
460             my ($action, %args) = @_;
461              
462             my $vr_name = delete @args{'vr_name'};
463              
464             my $requester = $self->_create_requester(type => 'config', action => $action);
465             my $elements = $self->_generate_elements(%args);
466            
467             #Create the virtual router
468             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/network/virtual-router/entry[\@name='$vr_name']",
469             element => $elements);
470             }
471              
472             =head2 zone($action, %parameters)
473              
474             =head3 Arguments
475              
476             =over
477              
478             =item * $action - perform an action: ['get' | 'set' | 'delete']
479              
480             =item * vsys_id - the virtual system ID to which the zone is/should be a member of.
481              
482             =item * zone - the name of the zone to create/update/delete.
483              
484             =item * layer3 - an anonymous array of one or more interfaces to add to the zone.
485              
486             =back
487              
488             =cut
489              
490             sub zone {
491             my $self = shift;
492             my ($action, %args) = @_;
493             my $requester = $self->_create_requester(type => 'config', action => $action);
494              
495             my ($vsys_id, $zone) = delete @args{'vsys_id', 'zone'};
496             $vsys_id //= 1;
497              
498             my $elements = $self->_generate_elements(%args);
499              
500             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/vsys/entry[\@name='vsys$vsys_id']/zone/entry[\@name='$zone']/network",
501             element => $elements);
502             }
503              
504             =head2 ipv4_static_route($action, %parameters)
505              
506             =head3 Arguments
507              
508             =over
509              
510             =item * $action - perform an action: ['get' | 'set' | 'delete']
511              
512             =item * vr_name - the name of the virtual router in which the static route resides.
513              
514             =item * route_name - the name of the route to perform the action on.
515              
516             =item * destination - the IPv4 destination of the route (IP/prefix)
517              
518             =item * nexthop - an anonymous hash specifying the next hop
519              
520             =item * ip-address - the next hop IP address
521              
522             =item * interface - the next hop interface
523              
524             =back
525              
526             =cut
527              
528             sub ipv4_static_route {
529             my $self = shift;
530             my ($action, %args) = @_;
531             my $requester = $self->_create_requester(type => 'config', action => $action);
532              
533             my ($vr_name, $route_name) = delete @args{'vr_name', 'route_name'};
534             $route_name = defined $route_name ? "\@name='$route_name'" : "'*'";
535              
536             my $elements = $self->_generate_elements(%args);
537              
538             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/network/virtual-router/entry[\@name='$vr_name']/routing-table/ip/static-route/entry[$route_name]",
539             element => $elements);
540             }
541              
542              
543             =head2 ipv6_static_route($action, %parameters)
544              
545             =head3 Arguments
546              
547             =over
548              
549              
550             =item * $action - perform an action: ['get' | 'set' | 'delete']
551              
552             =item * vr_name - the name of the virtual router in which the static route resides.
553              
554             =item * route_name - the name of the route to perform the action on.
555              
556             =item * destination - the IPv6 destination of the route (IP/prefix)
557              
558             =item * nexthop - an anonymous hash specifying the next hop
559              
560             =item * ipv6-address - the next hop IPv6 address
561              
562             =item * interface - the next hop interface
563              
564             =back
565              
566             =cut
567              
568             sub ipv6_static_route {
569             my $self = shift;
570             my ($action, %args) = @_;
571             my $requester = $self->_create_requester(type => 'config', action => $action);
572              
573             my ($vr_name, $route_name) = delete @args{'vr_name', 'route_name'};
574             $route_name = defined $route_name ? "\@name='$route_name'" : "'*'";
575              
576             my $elements = $self->_generate_elements(%args);
577              
578             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/network/virtual-router/entry[\@name='$vr_name']/routing-table/ipv6/static-route/entry[$route_name]",
579             element => $elements);
580             }
581              
582             =head2 address($action, %parameters)
583              
584             =head3 Arguments
585              
586             =over
587              
588             =item * vsys_id - the vsys ID in which the resides/shall reside.
589              
590             =item * name - the name of the address.
591              
592             =item * ip-netmask - the IP/netmask combination which defines the address.
593              
594             =item * ip-range - an IP address range (format: 'IPstart-IPend')
595              
596             =back
597              
598             =cut
599              
600             sub address {
601             my $self = shift;
602             my ($action, %args) = @_;
603             my $requester = $self->_create_requester(type => 'config', action => $action);
604              
605             #Keys to be extracted and deleted because they aren't part of the element
606             #delete returns the values that were deleted.
607             my ($vsys, $address) = delete @args{'vsys_id', 'name'};
608              
609             #If the vsys if not defined, we default to vsys1
610             $vsys //= "vsys1";
611              
612             $address = defined $address ? "\@name='$address'" : "'*'";
613              
614             my $elements = $self->_generate_elements(%args);
615              
616             return $requester->(xpath => "/config/devices/entry[\@name='localhost.localdomain']/vsys/entry[\@name=\'$vsys\']/address/entry[$address]/static", element => $elements);
617             }
618              
619              
620             =head2 address_group($action, %parameters)
621              
622             =head3 Arguments
623              
624             =over
625              
626              
627             =item * $action - perform an action: ['get' | 'set' | 'delete']
628              
629             =item * vsys_id - the vsys ID in which the address group resides/shall reside.A
630              
631             =item * name - the name of the address group.
632              
633             =item * member - an anonymous array of one or more addresses. These can be either address entries created with address(), or explicit IP/netmasks (e.g. 9.8.8.8/32)
634              
635             =back
636              
637             =cut
638              
639             sub address_group {
640             my $self = shift;
641             my ($action, %args) = @_;
642             my $requester = $self->_create_requester(type => 'config', action => $action);
643              
644             #Keys to be extracted and deleted because they aren't part of the element
645             #delete returns the values that were deleted.
646             my ($vsys, $name) = delete @args{'vsys_id', 'name'};
647              
648             #If the vsys if not defined, we default to vsys1
649             $vsys //= "vsys1";
650              
651             $name = defined $name ? "\@name='$name'" : "'*'";
652              
653             my $elements = $self->_generate_elements(%args);
654              
655             return $requester->(xpath => "/config/devices/entry/vsys/entry[\@name=\'$vsys\']/address-group/entry[$name]", element => $elements);
656             }
657              
658             =head2 security_rule($action, %parameters)
659              
660             =head3 Arguments
661              
662             =over
663              
664              
665             =item * $action - perform an action: ['get' | 'set' | 'delete']
666              
667             =item * vsys_id - the virtual system ID of the vsys in which the security rule resides/will reside. Defaults to 1 if not supplied.
668              
669             =item * name - the name of the rule.
670              
671             =item * from - the source zone, defaults to 'any' if not supplied.
672              
673             =item * to - the destination zone, defaults to 'any' if not supplied.
674              
675             =item * source - an anonymous array of source addresses - can be addresses, address groups or explicit IP/netmask entries. Defaults to 'any' if not supplied.
676              
677             =item * destination - an anonymous array of destination addresses - can be addresses, address groups or explicit IP/netmask entries. Defaults to 'any' if not supplied.
678              
679             =item * service - an anonymous array of one or more services. Defaults to 'application-default' if not supplied.
680              
681             =item * appplication - an anonymous array of one or more Palo Alto applications. Defaults to 'any' if not supplied.
682              
683             =item * source-user - an anonymous array of one or more Palo Alto source user mappings. Defaults to 'any' if not supplied.
684              
685             =item * hip-profile - an anonymous array of Host Information Profiles. defaults to 'any' if not supplied.
686              
687             =item * action - an action for the rule, either 'allow', 'deny' or 'drop'. Defaults to 'allow' if not supplied.
688              
689             =back
690              
691             =cut
692              
693              
694             sub security_rule {
695             my $self = shift;
696             my ($action, %args) = @_;
697             my $requester = $self->_create_requester(type => 'config', action => $action);
698              
699             #If the name isn't defined, default to all rules
700             my $rule_name = defined $args{name} ? "\@name='$args{name}'" : "'*'";
701             delete $args{name};
702            
703             #If the vsys if not defined, we default to vsys1
704             my $vsys = $args{vsys_id} // "vsys1";
705             delete $args{vsys_id};
706              
707             #If any of the following items aren't defined, we default to an anon array of type 'any'
708             my @default_any = qw(to from source destination application source-user hip-profiles);
709             for my $key (@default_any) {
710             $args{$key} //= ['any'];
711             }
712              
713             #If the service isn't defined, we default to 'application-default'
714             $args{service} //= ['application-default'];
715              
716             #If the action isn't defined, we defailt to 'allow'
717             $args{action} //= 'allow';
718              
719             my $elements = $self->_generate_elements(%args);
720              
721             return $requester->(xpath => "/config/devices/entry/vsys/entry[\@name=\'$vsys\']/rulebase/security/rules/entry[$rule_name]", element => $elements);
722             }
723              
724             =head1 AUTHOR
725              
726             Greg Foletta, C<< >>
727              
728             =head1 BUGS
729              
730             Please report any bugs or feature requests to C, or through
731             the web interface at L. I will be notified, and then you'll
732             automatically be notified of progress on your bug as I make changes.
733              
734              
735              
736             =head1 SUPPORT
737              
738             You can find documentation for this module with the perldoc command.
739              
740             perldoc Firewall::PaloAlto
741              
742              
743             You can also look for information at:
744              
745             =over 4
746              
747             =item * RT: CPAN's request tracker (report bugs here)
748              
749             L
750              
751             =item * AnnoCPAN: Annotated CPAN documentation
752              
753             L
754              
755             =item * CPAN Ratings
756              
757             L
758              
759             =item * Search CPAN
760              
761             L
762              
763             =back
764              
765              
766             =head1 ACKNOWLEDGEMENTS
767              
768              
769             =head1 LICENSE AND COPYRIGHT
770              
771             Copyright 2015 Greg Foletta.
772              
773             This program is free software; you can redistribute it and/or modify it
774             under the terms of the the Artistic License (2.0). You may obtain a
775             copy of the full license at:
776              
777             L
778              
779             Any use, modification, and distribution of the Standard or Modified
780             Versions is governed by this Artistic License. By using, modifying or
781             distributing the Package, you accept this license. Do not use, modify,
782             or distribute the Package, if you do not accept this license.
783              
784             If your Modified Version has been derived from a Modified Version made
785             by someone other than you, you are nevertheless required to ensure that
786             your Modified Version complies with the requirements of this license.
787              
788             This license does not grant you the right to use any trademark, service
789             mark, tradename, or logo of the Copyright Holder.
790              
791             This license includes the non-exclusive, worldwide, free-of-charge
792             patent license to make, have made, use, offer to sell, sell, import and
793             otherwise transfer the Package with respect to any patent claims
794             licensable by the Copyright Holder that are necessarily infringed by the
795             Package. If you institute patent litigation (including a cross-claim or
796             counterclaim) against any party alleging that the Package constitutes
797             direct or contributory patent infringement, then this Artistic License
798             to you shall terminate on the date that such litigation is filed.
799              
800             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
801             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
802             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
803             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
804             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
805             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
806             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
807             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
808              
809              
810             =cut
811              
812             1; # End of Firewall::PaloAlto