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, F, F, F, F, F and F. Generate the following configuration files: F, F, F, F, F. =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, ""; 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, ""; } } $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 Kevin Amorin Olivier Bilodeau =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;