package pf::services;
=head1 NAME
pf::services - module to manage the PacketFence services and daemons.
=head1 DESCRIPTION
pf::services contains the functions necessary to control the different
PacketFence services and daemons. It also contains the functions used
to generate or validate some configuration files.
=head1 CONFIGURATION AND ENVIRONMENT
Read the following configuration files: F<dhcpd_vlan.conf>,
F<named-vlan.conf>, F<named-isolation.ca>, F<named-registration.ca>,
F<networks.conf>, F<violations.conf> and F<switches.conf>.
Generate the following configuration files: F<dhcpd.conf>, F<named.conf>,
F<snort.conf>, F<httpd.conf>, F<snmptrapd.conf>.
=cut
use strict;
use warnings;
use File::Basename;
use Config::IniFiles;
use Log::Log4perl;
use UNIVERSAL::require;
use pf::config;
use pf::util;
use pf::violation qw(violation_view_open_uniq);
use pf::node qw(nodes_registered_not_violators);
use pf::trigger qw(trigger_delete_all);
use pf::class qw(class_view_all class_merge);
use pf::SwitchFactory;
my %flags;
$flags{'apache2'} = "-f $conf_dir/httpd.conf";
$flags{'pfdetect'} = "-d -p $install_dir/var/alert &";
$flags{'pfmon'} = "-d &";
$flags{'pfdhcplistener'} = "-d &";
$flags{'pfredirect'} = "-d &";
$flags{'pfsetvlan'} = "-d &";
$flags{'dhcpd'}
= " -lf $conf_dir/dhcpd/dhcpd.leases -cf $conf_dir/dhcpd.conf "
. join( " ", get_dhcp_devs() );
$flags{'named'} = "-u pf -c $install_dir/conf/named.conf";
$flags{'snmptrapd'}
= "-n -c $conf_dir/snmptrapd.conf -C -A -Lf $install_dir/logs/snmptrapd.log -p $install_dir/var/snmptrapd.pid -On";
if ( isenabled( $Config{'trapping'}{'detection'} ) && $monitor_int ) {
$flags{'snort'}
= "-u pf -c $conf_dir/snort.conf -i "
. $monitor_int
. " -o -N -D -l $install_dir/var";
}
=head1 SUBROUTINES
=over
=item * service_ctl
=cut
sub service_ctl {
my ( $daemon, $action, $quick ) = @_;
my $logger = Log::Log4perl::get_logger('pf::services');
my $service
= ( $Config{'services'}{$daemon} || "$install_dir/sbin/$daemon" );
my $exe = basename($service);
$logger->info("$service $action");
if ( $exe
=~ /^(named|dhcpd|pfdhcplistener|pfmon|pfdetect|pfredirect|snort|apache2|snmptrapd|pfsetvlan)$/
)
{
$exe = $1;
CASE: {
$action eq "start" && do {
return (0)
if (
$exe =~ /dhcpd/
&& (( $Config{'network'}{'mode'} =~ /^arp$/ )
|| ( ( $Config{'network'}{'mode'} =~ /^vlan$/i )
&& ( !isenabled( $Config{'vlan'}{'dhcpd'} ) ) )
)
);
return (0)
if ( $exe =~ /snort/
&& !isenabled( $Config{'trapping'}{'detection'} ) );
return (0)
if ( $exe =~ /pfdhcplistener/
&& !isenabled( $Config{'network'}{'dhcpdetector'} ) );
return (0)
if ( $exe =~ /snmptrapd/
&& !( $Config{'network'}{'mode'} =~ /vlan/i ) );
return (0)
if ( $exe =~ /pfsetvlan/
&& !( $Config{'network'}{'mode'} =~ /vlan/i ) );
return (0)
if (
$exe =~ /named/
&& !(
( $Config{'network'}{'mode'} =~ /vlan/i )
&& ( isenabled( $Config{'vlan'}{'named'} ) )
)
);
if ( $daemon =~ /(named|dhcpd|snort|apache2|snmptrapd)/
&& !$quick )
{
my $confname = "generate_" . $daemon . "_conf";
$logger->info(
"Generating configuration file for $exe ($confname)");
my %serviceHash = (
'named' => \&generate_named_conf,
'dhcpd' => \&generate_dhcpd_conf,
'snort' => \&generate_snort_conf,
'apache2' => \&generate_httpd_conf,
'snmptrapd' => \&generate_snmptrapd_conf
);
if ( $serviceHash{$daemon} ) {
$serviceHash{$daemon}->();
} else {
print "No such sub: $confname\n";
}
}
if ( ( $service =~ /named|dhcpd|pfdhcplistener|pfmon|pfdetect|pfredirect|snort|apache2|snmptrapd|pfsetvlan/ )
&& ( $daemon =~ /named|dhcpd|pfdhcplistener|pfmon|pfdetect|pfredirect|snort|apache2|snmptrapd|pfsetvlan/ )
&& ( defined( $flags{$daemon} ) ) ) {
if ( $daemon ne 'pfdhcplistener' ) {
if ( ( $daemon eq 'pfsetvlan' )
&& ( !switches_conf_is_valid() ) )
{
$logger->error(
"errors in switches.conf. pfsetvlan will NOT be started"
);
return 0;
}
$logger->info(
"Starting $exe with '$service $flags{$daemon}'");
my $cmd_line = "$service $flags{$daemon}";
if ($cmd_line =~ /(.+)/) {
$cmd_line = $1;
return ( system($cmd_line) );
}
} else {
if ( isenabled( $Config{'network'}{'dhcpdetector'} ) )
{
my @devices = @listen_ints;
push @devices, @dhcplistener_ints;
@devices = get_dhcp_devs()
if (
$Config{'network'}{'mode'} =~ /^dhcp$/i );
foreach my $dev (@devices) {
my $cmd_line = "$service -i $dev $flags{$daemon}";
if ($cmd_line =~ /^(.+)$/) {
$cmd_line = $1;
$logger->info(
"Starting $exe with '$cmd_line'"
);
system($cmd_line);
}
}
return 1;
}
}
}
last CASE;
};
$action eq "stop" && do {
#my @debug= system('pkill','-f',$exe);
$logger->info("Stopping $exe with 'pkill $exe'");
eval { `pkill $exe`; };
if ($@) {
$logger->logcroak("Can't stop $exe with 'pkill $exe': $@");
return;
}
#$logger->info("pkill shows " . join(@debug));
my $maxWait = 10;
my $curWait = 0;
while (( $curWait < $maxWait )
&& ( service_ctl( $exe, "status" ) ne "0" ) )
{
$logger->info("Waiting for $exe to stop");
sleep(2);
$curWait++;
}
if ( -e $install_dir . "/var/$exe.pid" ) {
$logger->info("Removing $install_dir/var/$exe.pid");
unlink( $install_dir . "/var/$exe.pid" );
}
last CASE;
};
$action eq "restart" && do {
service_ctl( "pfdetect", "stop" ) if ( $daemon eq "snort" );
service_ctl( $daemon, "stop" );
service_ctl( "pfdetect", "start" ) if ( $daemon eq "snort" );
service_ctl( $daemon, "start" );
last CASE;
};
$action eq "status" && do {
my $pid;
chop( $pid = `pidof -x $exe` );
$pid = 0 if ( !$pid );
$logger->info("pidof -x $exe returned $pid");
return ($pid);
}
}
} else {
$logger->logcroak("unknown service $exe!");
return 0;
}
return 1;
}
=item * service_list
return an array of enabled services
=cut
sub service_list {
my @services = @_;
my @finalServiceList = ();
my $snortflag = 0;
foreach my $service (@services) {
if ( $service eq "snort" ) {
$snortflag = 1
if ( isenabled( $Config{'trapping'}{'detection'} ) );
} elsif ( $service eq "pfdetect" ) {
push @finalServiceList, $service
if ( isenabled( $Config{'trapping'}{'detection'} ) );
} elsif ( $service eq "pfredirect" ) {
push @finalServiceList, $service
if ( $Config{'ports'}{'listeners'} );
} elsif ( $service eq "dhcpd" ) {
push @finalServiceList, $service
if (
( $Config{'network'}{'mode'} =~ /^dhcp$/i )
|| ( ( $Config{'network'}{'mode'} =~ /^vlan$/i )
&& ( isenabled( $Config{'vlan'}{'dhcpd'} ) ) )
);
} elsif ( $service eq "snmptrapd" ) {
push @finalServiceList, $service
if ( $Config{'network'}{'mode'} =~ /vlan/i );
} elsif ( $service eq "named" ) {
push @finalServiceList, $service
if ( ( $Config{'network'}{'mode'} =~ /vlan/i )
&& ( isenabled( $Config{'vlan'}{'named'} ) ) );
} elsif ( $service eq "pfsetvlan" ) {
push @finalServiceList, $service
if ( $Config{'network'}{'mode'} =~ /vlan/i );
} else {
push @finalServiceList, $service;
}
}
#add snort last
push @finalServiceList, "snort" if ($snortflag);
return @finalServiceList;
}
=item * generate_named_conf
=cut
sub generate_named_conf {
my $logger = Log::Log4perl::get_logger('pf::services');
require Net::Netmask;
import Net::Netmask;
my %tags;
$tags{'template'} = "$conf_dir/templates/named_vlan.conf";
$tags{'install_dir'} = $install_dir;
my %network_conf;
tie %network_conf, 'Config::IniFiles',
( -file => "$conf_dir/networks.conf", -allowempty => 1 );
my @errors = @Config::IniFiles::errors;
if ( scalar(@errors) ) {
$logger->error(
"Error reading networks.conf: " . join( "\n", @errors ) . "\n" );
return 0;
}
my @routed_isolation_nets_named;
my @routed_registration_nets_named;
foreach my $section ( tied(%network_conf)->Sections ) {
foreach my $key ( keys %{ $network_conf{$section} } ) {
$network_conf{$section}{$key} =~ s/\s+$//;
}
if ( ( $network_conf{$section}{'named'} eq 'enabled' )
&& ( exists( $network_conf{$section}{'type'} ) ) ) {
if ( lc($network_conf{$section}{'type'}) eq 'isolation' ) {
my $isolation_obj = new Net::Netmask( $section,
$network_conf{$section}{'netmask'} );
push @routed_isolation_nets_named, $isolation_obj;
} elsif ( lc($network_conf{$section}{'type'}) eq 'registration' ) {
my $registration_obj = new Net::Netmask( $section,
$network_conf{$section}{'netmask'} );
push @routed_registration_nets_named, $registration_obj;
}
}
}
$tags{'registration_clients'} = "";
foreach my $net ( @routed_registration_nets_named ) {
$tags{'registration_clients'} .= $net . "; ";
}
$tags{'isolation_clients'} = "";
foreach my $net ( @routed_isolation_nets_named ) {
$tags{'isolation_clients'} .= $net . "; ";
}
parse_template(
\%tags,
"$conf_dir/templates/named_vlan.conf",
"$install_dir/conf/named.conf"
);
my %tags_isolation;
$tags_isolation{'template'} = "$conf_dir/templates/named-isolation.ca";
$tags_isolation{'hostname'} = $Config{'general'}{'hostname'};
$tags_isolation{'incharge'}
= "pf."
. $Config{'general'}{'hostname'} . "."
. $Config{'general'}{'domain'};
parse_template(
\%tags_isolation,
"$conf_dir/templates/named-isolation.ca",
"$install_dir/conf/named/named-isolation.ca"
);
my %tags_registration;
$tags_registration{'template'}
= "$conf_dir/templates/named-registration.ca";
$tags_registration{'hostname'} = $Config{'general'}{'hostname'};
$tags_registration{'incharge'}
= "pf."
. $Config{'general'}{'hostname'} . "."
. $Config{'general'}{'domain'};
parse_template(
\%tags_registration,
"$conf_dir/templates/named-registration.ca",
"$install_dir/conf/named/named-registration.ca"
);
return 1;
}
=item * generate_dhcpd_vlan_conf
=cut
sub generate_dhcpd_vlan_conf {
my $logger = Log::Log4perl::get_logger('pf::services');
my %tags;
$tags{'template'} = "$conf_dir/templates/dhcpd_vlan.conf";
$tags{'networks'} = '';
my %network_conf;
tie %network_conf, 'Config::IniFiles',
( -file => "$conf_dir/networks.conf", -allowempty => 1 );
my @errors = @Config::IniFiles::errors;
if ( scalar(@errors) ) {
$logger->error(
"Error reading networks.conf: " . join( "\n", @errors ) . "\n" );
return 0;
}
foreach my $section ( tied(%network_conf)->Sections ) {
foreach my $key ( keys %{ $network_conf{$section} } ) {
$network_conf{$section}{$key} =~ s/\s+$//;
}
if ( $network_conf{$section}{'dhcpd'} eq 'enabled' ) {
$tags{'networks'} .= <<"EOT";
subnet $section netmask $network_conf{$section}{'netmask'} {
option routers $network_conf{$section}{'gateway'};
option subnet-mask $network_conf{$section}{'netmask'};
option domain-name "$network_conf{$section}{'domain-name'}";
option domain-name-servers $network_conf{$section}{'dns'};
range $network_conf{$section}{'dhcp_start'} $network_conf{$section}{'dhcp_end'};
default-lease-time $network_conf{$section}{'dhcp_default_lease_time'};
max-lease-time $network_conf{$section}{'dhcp_max_lease_time'};
}
EOT
}
}
parse_template( \%tags, "$conf_dir/templates/dhcpd_vlan.conf",
"$conf_dir/dhcpd.conf" );
return 1;
}
=item * generate_dhcpd_conf
=cut
sub generate_dhcpd_conf {
if ( $Config{'network'}{'mode'} =~ /vlan/i ) {
generate_dhcpd_vlan_conf();
return;
}
my %tags;
my $logger = Log::Log4perl::get_logger('pf::services');
$tags{'template'} = "$conf_dir/templates/dhcpd.conf";
$tags{'domain'} = $Config{'general'}{'domain'};
$tags{'hostname'} = $Config{'general'}{'hostname'};
$tags{'dnsservers'} = $Config{'general'}{'dnsservers'};
parse_template( \%tags, "$conf_dir/templates/dhcpd.conf",
"$conf_dir/dhcpd.conf" );
my %shared_nets;
$logger->info("generating $conf_dir/dhcpd.conf");
foreach my $dhcp ( tied(%Config)->GroupMembers("dhcp") ) {
my @registered_scopes;
my @unregistered_scopes;
my @isolation_scopes;
if ( defined( $Config{$dhcp}{'registered_scopes'} ) ) {
@registered_scopes
= split( /\s*,\s*/, $Config{$dhcp}{'registered_scopes'} );
}
if ( defined( $Config{$dhcp}{'unregistered_scopes'} ) ) {
@unregistered_scopes
= split( /\s+/, $Config{$dhcp}{'unregistered_scopes'} );
}
if ( defined( $Config{$dhcp}{'isolation_scopes'} ) ) {
@isolation_scopes
= split( /\s+/, $Config{$dhcp}{'isolation_scopes'} );
}
foreach my $registered_scope (@registered_scopes) {
my $reg_obj = new Net::Netmask(
$Config{ 'scope ' . $registered_scope }{'network'} );
$reg_obj->tag( "scope", $registered_scope );
foreach my $shared_net ( keys(%shared_nets) ) {
if ( $shared_net ne $dhcp
&& defined(
$shared_nets{$shared_net}{ $reg_obj->desc() } ) )
{
$logger->logcroak( "Network "
. $reg_obj->desc()
. " is defined in another shared-network!\n" );
}
}
push(
@{ $shared_nets{$dhcp}{ $reg_obj->desc() }{'registered'} },
$reg_obj
);
}
foreach my $isolation_scope (@isolation_scopes) {
my $iso_obj = new Net::Netmask(
$Config{ 'scope ' . $isolation_scope }{'network'} );
$iso_obj->tag( "scope", $isolation_scope );
foreach my $shared_net ( keys(%shared_nets) ) {
if ( $shared_net ne $dhcp
&& defined(
$shared_nets{$shared_net}{ $iso_obj->desc() } ) )
{
$logger->logcroak( "Network "
. $iso_obj->desc()
. " is defined in another shared-network!\n" );
}
}
push(
@{ $shared_nets{$dhcp}{ $iso_obj->desc() }{'isolation'} },
$iso_obj
);
}
foreach my $unregistered_scope (@unregistered_scopes) {
my $unreg_obj = new Net::Netmask(
$Config{ 'scope ' . $unregistered_scope }{'network'} );
$unreg_obj->tag( "scope", $unregistered_scope );
foreach my $shared_net ( keys(%shared_nets) ) {
if ($shared_net ne $dhcp
&& defined(
$shared_nets{$shared_net}{ $unreg_obj->desc() }
)
)
{
$logger->logcroak( "Network "
. $unreg_obj->desc()
. " is defined in another shared-network!\n" );
}
}
push(
@{ $shared_nets{$dhcp}{ $unreg_obj->desc() }{'unregistered'}
},
$unreg_obj
);
}
}
#open dhcpd.conf file
my $dhcpdconf_fh;
open( $dhcpdconf_fh, '>>', "$conf_dir/dhcpd.conf" )
|| $logger->logcroak("Unable to append to $conf_dir/dhcpd.conf: $!");
foreach my $internal_interface ( get_internal_devs_phy() ) {
my $dhcp_interface = get_internal_info($internal_interface);
print {$dhcpdconf_fh} "subnet "
. $dhcp_interface->base()
. " netmask "
. $dhcp_interface->mask()
. " {\n not authoritative;\n}\n";
}
foreach my $shared_net ( keys(%shared_nets) ) {
my $printable_shared = $shared_net;
$printable_shared =~ s/dhcp //;
print {$dhcpdconf_fh} "shared-network $printable_shared {\n";
foreach my $key ( keys( %{ $shared_nets{$shared_net} } ) ) {
my $tmp_obj = new Net::Netmask($key);
print {$dhcpdconf_fh} " subnet "
. $tmp_obj->base()
. " netmask "
. $tmp_obj->mask() . " {\n";
if (defined( @{ $shared_nets{$shared_net}{$key}{'registered'} } )
)
{
foreach my $reg (
@{ $shared_nets{$shared_net}{$key}{'registered'} } )
{
my $range = normalize_dhcpd_range(
$Config{ 'scope ' . $reg->tag("scope") }{'range'} );
if ( !$range ) {
$logger->logcroak( "Invalid scope range: "
. $Config{ 'scope ' . $reg->tag("scope") }
{'range'} );
}
print {$dhcpdconf_fh} " pool {\n";
print {$dhcpdconf_fh} " # I AM A REGISTERED SCOPE\n";
print {$dhcpdconf_fh} " deny unknown clients;\n";
print {$dhcpdconf_fh}
" allow members of \"registered\";\n";
print {$dhcpdconf_fh} " option routers "
. $Config{ 'scope ' . $reg->tag("scope") }{'gateway'}
. ";\n";
my $lease_time;
if ( defined( $Config{$shared_net}{'registered_lease'} ) )
{
$lease_time
= $Config{$shared_net}{'registered_lease'};
} else {
$lease_time = 7200;
}
print {$dhcpdconf_fh}
" max-lease-time $lease_time;\n";
print {$dhcpdconf_fh}
" default-lease-time $lease_time;\n";
print {$dhcpdconf_fh} " range $range;\n";
print {$dhcpdconf_fh} " }\n";
}
}
if (defined( @{ $shared_nets{$shared_net}{$key}{'isolation'} } ) )
{
foreach my $iso (
@{ $shared_nets{$shared_net}{$key}{'isolation'} } )
{
my $range = normalize_dhcpd_range(
$Config{ 'scope ' . $iso->tag("scope") }{'range'} );
if ( !$range ) {
$logger->logcroak( "Invalid scope range: "
. $Config{ 'scope ' . $iso->tag("scope") }
{'range'} );
}
print {$dhcpdconf_fh} " pool {\n";
print {$dhcpdconf_fh} " # I AM AN ISOLATION SCOPE\n";
print {$dhcpdconf_fh} " deny unknown clients;\n";
print {$dhcpdconf_fh}
" allow members of \"isolated\";\n";
print {$dhcpdconf_fh} " option routers "
. $Config{ 'scope ' . $iso->tag("scope") }{'gateway'}
. ";\n";
my $lease_time;
if ( defined( $Config{$shared_net}{'isolation_lease'} ) )
{
$lease_time = $Config{$shared_net}{'isolation_lease'};
} else {
$lease_time = 120;
}
print {$dhcpdconf_fh}
" max-lease-time $lease_time;\n";
print {$dhcpdconf_fh}
" default-lease-time $lease_time;\n";
print {$dhcpdconf_fh} " range $range;\n";
print {$dhcpdconf_fh} " }\n";
}
}
if (defined(
@{ $shared_nets{$shared_net}{$key}{'unregistered'} }
)
)
{
foreach my $unreg (
@{ $shared_nets{$shared_net}{$key}{'unregistered'} } )
{
my $range = normalize_dhcpd_range(
$Config{ 'scope ' . $unreg->tag("scope") }{'range'} );
if ( !$range ) {
$logger->logcroak( "Invalid scope range: "
. $Config{ 'scope ' . $unreg->tag("scope") }
{'range'} );
}
print {$dhcpdconf_fh} " pool {\n";
print {$dhcpdconf_fh}
" # I AM AN UNREGISTERED SCOPE\n";
print {$dhcpdconf_fh} " allow unknown clients;\n";
print {$dhcpdconf_fh} " option routers "
. $Config{ 'scope ' . $unreg->tag("scope") }
{'gateway'} . ";\n";
my $lease_time;
if (defined( $Config{$shared_net}{'unregistered_lease'} )
)
{
$lease_time
= $Config{$shared_net}{'unregistered_lease'};
} else {
$lease_time = 120;
}
print {$dhcpdconf_fh}
" max-lease-time $lease_time;\n";
print {$dhcpdconf_fh}
" default-lease-time $lease_time;\n";
print {$dhcpdconf_fh} " range $range;\n";
print {$dhcpdconf_fh} " }\n";
}
}
print {$dhcpdconf_fh} " }\n";
}
print {$dhcpdconf_fh} "}\n";
}
print {$dhcpdconf_fh} "include \"$conf_dir/isolated.mac\";\n";
print {$dhcpdconf_fh} "include \"$conf_dir/registered.mac\";\n";
close $dhcpdconf_fh;
#close(DHCPDCONF);
generate_dhcpd_iso();
generate_dhcpd_reg();
return 1;
}
=item * generate_dhcpd_iso
open isolated.mac file
=cut
sub generate_dhcpd_iso {
my $logger = Log::Log4perl::get_logger('pf::services');
my $isomac_fh;
open( $isomac_fh, '>', "$conf_dir/isolated.mac" )
|| $logger->logcroak("Unable to open $conf_dir/isolated.mac : $!");
my @isolated = violation_view_open_uniq();
my @isolatednodes;
foreach my $row (@isolated) {
my $mac = $row->{'mac'};
my $hostname = $mac;
$hostname =~ s/://g;
print {$isomac_fh}
"host $hostname { hardware ethernet $mac; } subclass \"isolated\" 01:$mac;";
}
close( $isomac_fh );
return 1;
}
=item * generate_dhcpd_reg
open registered.mac file
=cut
sub generate_dhcpd_reg {
my $logger = Log::Log4perl::get_logger('pf::services');
if ( isenabled( $Config{'trapping'}{'registration'} ) ) {
my $regmac_fh;
open( $regmac_fh, '>', "$conf_dir/registered.mac" )
|| $logger->logcroak(
"Unable to open $conf_dir/registered.mac : $!");
my @registered = nodes_registered_not_violators();
my @registerednodes;
foreach my $row (@registered) {
my $mac = $row->{'mac'};
my $hostname = $mac;
$hostname =~ s/://g;
print {$regmac_fh}
"host $hostname { hardware ethernet $mac; } subclass \"registered\" 01:$mac;";
}
close( $regmac_fh );
}
return 1;
}
=item * generate_snort_conf
=cut
sub generate_snort_conf {
my $logger = Log::Log4perl::get_logger('pf::services');
my %tags;
$tags{'template'} = "$conf_dir/templates/snort.conf";
$tags{'internal-ips'} = join( ",", get_internal_ips() );
$tags{'internal-nets'} = join( ",", get_internal_nets() );
$tags{'gateways'} = join( ",", get_gateways() );
$tags{'dhcp_servers'} = $Config{'general'}{'dhcpservers'};
$tags{'dns_servers'} = $Config{'general'}{'dnsservers'};
$tags{'install_dir'} = $install_dir;
my %violations_conf;
tie %violations_conf, 'Config::IniFiles',
( -file => "$conf_dir/violations.conf" );
my @errors = @Config::IniFiles::errors;
if ( scalar(@errors) ) {
$logger->error( "Error reading violations.conf: "
. join( "\n", @errors ) . "\n" );
return 0;
}
my @rules;
foreach my $rule (
split( /\s*,\s*/, $violations_conf{'defaults'}{'snort_rules'} ) )
{
#append install_dir if the path doesn't start with /
$rule = "\$RULE_PATH/$rule" if ( $rule !~ /^\// );
push @rules, "include $rule";
}
$tags{'snort_rules'} = join( "\n", @rules );
$logger->info("generating $conf_dir/snort.conf");
parse_template( \%tags, "$conf_dir/templates/snort.conf",
"$conf_dir/snort.conf" );
return 1;
}
=item * generate_snmptrapd_conf
=cut
sub generate_snmptrapd_conf {
my $logger = Log::Log4perl::get_logger('pf::services');
my %tags;
$tags{'authLines'} = '';
$tags{'userLines'} = '';
my %SNMPv3Users;
my %SNMPCommunities;
my $switchFactory
= new pf::SwitchFactory( -configFile => "$conf_dir/switches.conf" );
my %switchConfig = %{ $switchFactory->{_config} };
foreach my $key ( sort keys %switchConfig ) {
if ( $key ne 'default' ) {
my $switch = $switchFactory->instantiate($key);
if ( $switch->{_SNMPVersionTrap} eq '3' ) {
$SNMPv3Users{ $switch->{_SNMPUserNameTrap} }
= '-e ' . $switch->{_SNMPEngineID} . ' '
. $switch->{_SNMPUserNameTrap} . ' '
. $switch->{_SNMPAuthProtocolTrap} . ' '
. $switch->{_SNMPAuthPasswordTrap} . ' '
. $switch->{_SNMPPrivProtocolTrap} . ' '
. $switch->{_SNMPPrivPasswordTrap};
} else {
$SNMPCommunities{ $switch->{_SNMPCommunityTrap} } = 1;
}
}
}
foreach my $userName ( sort keys %SNMPv3Users ) {
$tags{'userLines'}
.= "createUser " . $SNMPv3Users{$userName} . "\n";
$tags{'authLines'} .= "authUser log $userName priv\n";
}
foreach my $community ( sort keys %SNMPCommunities ) {
$tags{'authLines'} .= "authCommunity log $community\n";
}
$tags{'template'} = "$conf_dir/templates/snmptrapd.conf";
$logger->info("generating $conf_dir/snmptrapd.conf");
parse_template( \%tags, "$conf_dir/templates/snmptrapd.conf",
"$conf_dir/snmptrapd.conf" );
return 1;
}
=item * generate_httpd_conf
=cut
sub generate_httpd_conf {
my ( %tags, $httpdconf_fh, $authconf_fh );
my $logger = Log::Log4perl::get_logger('pf::services');
$tags{'template'} = "$conf_dir/templates/httpd.conf";
$tags{'internal-nets'} = join( " ", get_internal_nets() );
$tags{'routed-nets'} = join( " ", get_routed_isolation_nets() ) . " "
. join( " ", get_routed_registration_nets() );
$tags{'hostname'} = $Config{'general'}{'hostname'};
$tags{'domain'} = $Config{'general'}{'domain'};
$tags{'admin_port'} = $Config{'ports'}{'admin'};
$tags{'install_dir'} = $install_dir;
my @proxies;
my %proxy_configs = %{ $Config{'proxies'} };
foreach my $proxy ( keys %proxy_configs ) {
if ( $proxy =~ /^\// ) {
if ( $proxy !~ /^\/(content|admin|redirect|cgi-bin)/ ) {
push @proxies,
"ProxyPassReverse $proxy $proxy_configs{$proxy}";
push @proxies, "ProxyPass $proxy $proxy_configs{$proxy}";
$logger->warn(
"proxy $proxy is not relative - add path to apache rewrite exclude list!"
);
} else {
$logger->warn("proxy $proxy conflicts with PF paths!");
next;
}
} else {
push @proxies,
"ProxyPassReverse /proxies/"
. $proxy . " "
. $proxy_configs{$proxy};
push @proxies,
"ProxyPass /proxies/" . $proxy . " " . $proxy_configs{$proxy};
}
}
$tags{'proxies'} = join( "\n", @proxies );
my @contentproxies;
if ( $Config{'trapping'}{'passthrough'} eq "proxy" ) {
my @proxies = class_view_all();
foreach my $row (@proxies) {
my $url = $row->{'url'};
my $vid = $row->{'vid'};
next if ( ( !defined($url) ) || ( $url =~ /^\// ) );
if ( $url !~ /^(http|https):\/\// ) {
$logger->warn(
"vid " . $vid . ": unrecognized content URL: " . $url );
next;
}
if ( $url =~ /^((http|https):\/\/.+)\/$/ ) {
push @contentproxies, "ProxyPass /content/$vid/ $url";
push @contentproxies, "ProxyPassReverse /content/$vid/ $url";
push @contentproxies, "ProxyHTMLURLMap $1 /content/$vid";
} else {
$url =~ /^((http|https):\/\/.+)\//;
push @contentproxies, "ProxyPass /content/$vid/ $1/";
push @contentproxies, "ProxyPassReverse /content/$vid/ $1/";
push @contentproxies, "ProxyHTMLURLMap $url /content/$vid";
}
push @contentproxies, "ProxyPass /content/$vid $url";
push @contentproxies, "<Location /content/$vid>";
push @contentproxies, " SetOutputFilter proxy-html";
push @contentproxies, " ProxyHTMLDoctype HTML";
push @contentproxies, " ProxyHTMLURLMap / /content/$vid/";
push @contentproxies,
" ProxyHTMLURLMap /content/$vid /content/$vid";
push @contentproxies, " RequestHeader unset Accept-Encoding";
push @contentproxies, "</Location>";
}
}
$tags{'content-proxies'} = join( "\n", @contentproxies );
$logger->info("generating $conf_dir/httpd.conf");
parse_template( \%tags, "$conf_dir/templates/httpd.conf",
"$conf_dir/httpd.conf" );
return 1;
}
=item * switches_conf_is_valid
=cut
sub switches_conf_is_valid {
my $logger = Log::Log4perl::get_logger('pf::services');
my %switches_conf;
tie %switches_conf, 'Config::IniFiles',
( -file => "$conf_dir/switches.conf" );
my @errors = @Config::IniFiles::errors;
if ( scalar(@errors) ) {
$logger->error(
"Error reading switches.conf: " . join( "\n", @errors ) . "\n" );
return 0;
}
foreach my $section ( tied(%switches_conf)->Sections ) {
foreach my $key ( keys %{ $switches_conf{$section} } ) {
$switches_conf{$section}{$key} =~ s/\s+$//;
}
}
foreach my $section ( keys %switches_conf ) {
if ( ( $section ne 'default' )
&& ( $section ne '127.0.0.1' ) ) {
# check type
my $type
= "pf::SNMP::"
. ( $switches_conf{$section}{'type'}
|| $switches_conf{'default'}{'type'} );
if ( ! $type->require() ) {
$logger->error(
"Unknown switch type: $type for switch $section: $@");
return 0;
}
if ( !valid_ip($section) ) {
$logger->error("switch IP is invalid for $section");
return 0;
}
# check SNMP version
my $SNMPVersion
= ( $switches_conf{$section}{'SNMPVersion'}
|| $switches_conf{$section}{'version'}
|| $switches_conf{'default'}{'SNMPVersion'}
|| $switches_conf{'default'}{'version'} );
if ( !( $SNMPVersion =~ /^1|2c|3$/ ) ) {
$logger->error("switch SNMP version is invalid for $section");
return 0;
}
my $SNMPVersionTrap
= ( $switches_conf{$section}{'SNMPVersionTrap'}
|| $switches_conf{'default'}{'SNMPVersionTrap'} );
if ( !( $SNMPVersionTrap =~ /^1|2c|3$/ ) ) {
$logger->error(
"switch SNMP trap version is invalid for $section");
return 0;
}
# check uplink
my $uplink = $switches_conf{$section}{'uplink'}
|| $switches_conf{'default'}{'uplink'};
if (( !defined($uplink) )
|| ( ( $uplink ne 'dynamic' )
&& ( !( $uplink =~ /(\d+,)*\d+/ ) ) )
)
{
$logger->error( "switch uplink ("
. ( defined($uplink) ? $uplink : 'undefined' )
. ") is invalid for $section" );
return 0;
}
# check mode
my @valid_switch_modes = (
'testing', 'ignore', 'production', 'registration',
'discovery'
);
my $mode = $switches_conf{$section}{'mode'}
|| $switches_conf{'default'}{'mode'};
if ( !grep( { lc($_) eq lc($mode) } @valid_switch_modes ) ) {
$logger->error("switch mode ($mode) is invalid for $section");
return 0;
}
}
}
return 1;
}
=item * read_violations_conf
=cut
sub read_violations_conf {
my $logger = Log::Log4perl::get_logger('pf::services');
my %violations_conf;
tie %violations_conf, 'Config::IniFiles',
( -file => "$conf_dir/violations.conf" );
my @errors = @Config::IniFiles::errors;
if ( scalar(@errors) ) {
$logger->error( "Error reading violations.conf: "
. join( "\n", @errors ) . "\n" );
return 0;
}
my %violations = class_set_defaults(%violations_conf);
#clear all triggers at startup
trigger_delete_all();
foreach my $violation ( keys %violations ) {
# parse triggers if they exist
my @triggers;
if ( defined $violations{$violation}{'trigger'} ) {
foreach my $trigger (
split( /\s*,\s*/, $violations{$violation}{'trigger'} ) )
{
my ( $type, $tid ) = split( /::/, $trigger );
$type = lc($type);
if ( !grep( { lc($_) eq lc($type) } @valid_trigger_types ) ) {
$logger->warn(
"invalid trigger '$type' found at $violation");
next;
}
if ( $tid =~ /(\d+)-(\d+)/ ) {
push @triggers, [ $1, $2, $type ];
} else {
push @triggers, [ $tid, $tid, $type ];
}
}
}
#print Dumper(@triggers);
class_merge(
$violation,
$violations{$violation}{'desc'},
$violations{$violation}{'auto_enable'},
$violations{$violation}{'max_enable'},
$violations{$violation}{'grace'},
$violations{$violation}{'priority'},
$violations{$violation}{'url'},
$violations{$violation}{'max_enable_url'},
$violations{$violation}{'redirect_url'},
$violations{$violation}{'button_text'},
$violations{$violation}{'disable'},
$violations{$violation}{'vlan'},
# actions are expected to be in this position (handled in a special way)
$violations{$violation}{'actions'},
\@triggers
);
}
return 1;
}
=item * class_set_defaults
=cut
sub class_set_defaults {
my %violations_conf = @_;
my %violations = %violations_conf;
foreach my $violation ( keys %violations_conf ) {
foreach my $default ( keys %{ $violations_conf{'defaults'} } ) {
if ( !defined( $violations{$violation}{$default} ) ) {
$violations{$violation}{$default}
= $violations{'defaults'}{$default};
}
}
}
delete( $violations{'defaults'} );
return (%violations);
}
=item * normalize_dhcpd_range
=cut
sub normalize_dhcpd_range {
my ($range) = @_;
if ( $range
=~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*-\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/
)
{
$range =~ s/\s*\-\s*/ /;
return ($range);
} elsif (
$range =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3})\.(\d{1,3})\s*-\s*(\d{1,3})$/ )
{
my $net = $1;
my $start = $2;
my $end = $3;
return ("$net.$start $net.$end");
} elsif ( $range =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
return ("$range $range");
} else {
return;
}
}
=back
=head1 AUTHOR
David LaPorte <david@davidlaporte.org>
Kevin Amorin <kev@amorin.org>
Olivier Bilodeau <obilodeau@inverse.ca>
=head1 COPYRIGHT
Copyright (C) 2005 David LaPorte
Copyright (C) 2005 Kevin Amorin
Copyright (C) 2009 Inverse inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
=cut
1;