#!/usr/local/bin/perl
#
#  Copyright (c) 2004, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of SWITCH nor the names of its contributors may be
#     used to endorse or promote products derived from this software without
#     specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#
#  $Author: peter $
#
#  $Id: Nfsources.pm 59 2006-08-10 17:47:53Z peter $
#
#  $LastChangedRevision: 59 $

package Nfsources;

use strict;
use warnings;
use Log;

use NfSenRC;

sub AddSource {
	my $new_source = shift;

	my $dir = "$NfConf::PROFILEDATADIR/live/$new_source";
	my ($login,$pass,$uid,$gid) = getpwnam($NfConf::WWWUSER);
	if ( !defined $login ) {
			warn "NFSEN user '$NfConf::WWWUSER' not found on this system ";
			return 0;
	}

	if ( defined $NfConf::WWWGROUP ) {
		$gid  = getgrnam($NfConf::WWWGROUP) || 
			warn "NFSEN group '$NfConf::WWWGROUP' not found on this system ",
			return 0;
	}
	# print "Use UID/GID $uid $gid\n";
	if ( !mkdir $dir ) {
		warn "Can't mkdir '$dir': $! ";
		return 0;
	}
	if ( !chmod 0775, $dir ) {
		warn "Can't chown '$dir': $! ";
		return 0;
	}
	if ( !chown $uid, $gid, $dir ) {
		warn "Can't chown '$dir': $! ";
		return 0;
	}

	my $now = time();
	my $tstart = $now - ( $now % 300 );
	NfSenRRD::SetupRRD("$NfConf::PROFILESTATDIR/live", $new_source, $tstart - 300, 0);

	if ( !chown $uid, $gid, "$NfConf::PROFILESTATDIR/live/$new_source.rrd" ) {
		warn "Can't chown '$dir': $! ";
		return 0;
	}

	if ( !chmod 0664, "$NfConf::PROFILESTATDIR/live/$new_source.rrd" ) {
		warn "Can't chmod '$dir': $! ";
		return 0;
	}

	return 1;

} # End of AddSource

sub DeleteSource {
	my $delete_source = shift;

} # End of DeleteSource

sub CheckReconfig {

	# profile 'live' contains by definition all netflow sources currently in use.
	my %profileinfo = NfSen::ReadProfile('live');
	if ( $profileinfo{'status'} eq 'empty' ) {
		# it's an error reading this profile
		print STDERR "Error reading profile 'live'";
		if ( defined $Log::ERROR ) {
			print STDERR ": $Log::ERROR";
		}
		print STDERR "\n";
		return 2;
	}

	my @current_sources = split /:/, $profileinfo{'sourcelist'};
    my %_tmp;
    @_tmp{@current_sources} = 1;

	# Building source lists
	my @AddSourceList;
	my @DeleteSourceList;
	foreach my $source ( keys %NfConf::sources ) {
		if ( exists $_tmp{$source} ) {
			delete $_tmp{$source};
		} else {
			return 0;
		}
	}

	if ( scalar keys %_tmp > 0 ) {
		return 0;
	}

	return 1;

} # End of CheckReconfig

sub Reconfig {

	# profile 'live' contains by definition all netflow sources currently in use.
	my %profileinfo = NfSen::ReadProfile('live');
	if ( $profileinfo{'status'} eq 'empty' ) {
		# it's an error reading this profile
		print STDERR "Error reading profile 'live'";
		if ( defined $Log::ERROR ) {
			print STDERR ": $Log::ERROR";
		}
		print STDERR "\n";
		return;
	}

	my @current_sources = split /:/, $profileinfo{'sourcelist'};
    my %_tmp;
    @_tmp{@current_sources} = 1;

	# Building source lists
	my @AddSourceList;
	my @DeleteSourceList;
	foreach my $source ( keys %NfConf::sources ) {
		if ( exists $_tmp{$source} ) {
			delete $_tmp{$source};
		} else {
			push @AddSourceList, $source;
		}
	}
	@DeleteSourceList = keys %_tmp;

	# For builing new source list - start with current list
	my @NewSourceList = keys %NfConf::sources;
	foreach my $source ( @AddSourceList ) {
		push @NewSourceList, $source;
	}
    %_tmp = ();
    @_tmp{@NewSourceList} = 1;
	foreach my $source ( @DeleteSourceList ) {
		delete $_tmp{$source};
	}
	@NewSourceList = keys %_tmp;
	if ( scalar @NewSourceList == 0 ) {
		print "Empty source list does not make sense - nothing done!\n";
		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			print STDERR "Error writing profile 'live': $Log::ERROR\n";
		}
		return;
	}

	# Nothing to do ?
	if ( (scalar @AddSourceList) == 0 && (scalar @DeleteSourceList) == 0 ) {
		print "Reconfig: No changes found!\n";
		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			print STDERR "Error writing profile 'live': $Log::ERROR\n";
		}
		return;
	}

	# Confirm add/delete sources
	print "New sources to configure : ", join(' ', @AddSourceList), "\n" if scalar @AddSourceList;
	print "Remove configured sources: ", join(' ', @DeleteSourceList), "\n" if scalar @DeleteSourceList;
	if ( NfSen::UserInput("Continue?") ne 'y') {
		print "Faire enough! - Nothing changed!\n";
		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			print STDERR "Error writing profile 'live': $Log::ERROR\n";
		}
		return;
	}
	print "\n";

	# lock live profile
	%profileinfo = NfSen::LockProfile('live');
	if ( $profileinfo{'status'} eq 'empty' ) {
		# profile already locked and in use
		if ( $profileinfo{'locked'} == 1 ) {
			print STDERR "Profile 'live' is locked. Try later\n";
			return;
		}
	
		# it's an error reading this profile
		if ( defined $Log::ERROR ) {
			print STDERR "Error profile 'live': $Log::ERROR\n";
			return;
		}
	}

	# Add all new netflow sources
	if ( scalar @AddSourceList > 0 ) {
		print "Add source(s): ", join(' ', @AddSourceList), ":\n";
		# Add sources
		foreach my $source ( @AddSourceList ) {
			print "Add source '$source'";
			if ( &AddSource($source) > 0 ) {
				# Start collector only, if NfSen is running at this time
				if ( -f "$NfConf::VARDIR/run/nfsend.pid") {
					print "\tStart nfcapd: ";
					NfSenRC::StartCollector($source);
				}
			} else {
				print "No collector started! ";
			}
			print "\n";
		}
		print "\n";
	}

	# Delete sources, no longer configured
	if ( scalar @DeleteSourceList > 0 ) {
		print "Delete source(s): ", join(' ', @DeleteSourceList), ":\n";
		# Delete sources
		foreach my $source ( @DeleteSourceList ) {
			print "Delete source '$source' ";
			if ( &DeleteSource($source) ) {
				# Stop collector only, if NfSen is running at this time
				if ( -f "$NfConf::VARDIR/run/nfsend.pid") {
					print "\tShutdown nfcapd: ";
					NfSenRC::StopCollector($source);
				}
				unlink <$NfConf::PROFILEDATADIR/live/$source/nfcapd*>;
				rmdir "$NfConf::PROFILEDATADIR/live/$source" || warn "Can't remove directory '$source': $! ";
				unlink "$NfConf::PROFILEDATADIR/live/$source.rrd" || warn "Can't remove rrd DB: $!";
			}
			print " \n";
		}
	}

	# New sourcelist
	$profileinfo{'sourcelist'} = join(':', sort @NewSourceList);

	# Unlock/Write profile
	$profileinfo{'locked'} = 0;
	if ( !NfSen::WriteProfile(\%profileinfo) ) {
		print STDERR "Error writing profile 'live': $Log::ERROR\n";
	}

	print "\n";
	NfSenRC::NfSen_reload();

	# Cleanup work, when deleting sources
	if ( scalar @DeleteSourceList > 0 ) {

		print "\nCleaning profiles:\n";
		my $delete_flows = NfSen::UserInput("Delete netflow data files from removed source(s): ", join " ", @DeleteSourceList );
		my @AllProfiles = NfSen::ProfileList();
		if ( scalar @AllProfiles == 0 ) {
			print STDERR "$Log::ERROR\n" if defined $Log::ERROR;
			return;
		}

		# Loop over all available profiles
		foreach my $profilename ( @AllProfiles ) {
			# not a valid profile -> continue
			next unless NfSen::InfoFileExists($profilename);

			my %profileinfo = NfSen::LockProfile($profilename);
			# Make sure profile is not empty - means it exists and is not locked
			if ( $profileinfo{'status'} eq 'empty' ) {
				# profile already locked and in use
				next if $profileinfo{'locked'} == 1;
			
				# it's an error reading this profile
				if ( defined $Log::ERROR ) {
					print STDERR "Error $profilename: $Log::ERROR";
					next;
				}
			}

			# don't touch live and history profiles
			if ( $profileinfo{'type'} == 1 || $profileinfo{'type'} == 0 ) {
				$profileinfo{'locked'} = 0;
				if ( !NfSen::WriteProfile(\%profileinfo) ) {
					print SRDERR "Error writing profile '$profilename': $Log::ERROR";
				}
				next;
			}

			print "Update profile '$profilename':\n";
			my @ProfileSources = split /:/, $profileinfo{'sourcelist'};
    		my %_tmp = ();
    		@_tmp{@ProfileSources} = 1;
			foreach my $source ( @DeleteSourceList ) {
				print "	Delete source '$source'\n";
				if ( exists $_tmp{$source} ) {
					delete $_tmp{$source};
					if ( $delete_flows eq 'y' ) {
						unlink <$NfConf::PROFILEDATADIR/$profilename/$source/nfcapd*>;
						rmdir "$NfConf::PROFILEDATADIR/$profilename/$source" || warn "Can't remove directory '$source': $! ";
					}
				}
			}
			@ProfileSources = keys %_tmp;

			if ( scalar @ProfileSources == 0 ) {
				# This profile has no valid sources any more
				# tag it as history profile
				$profileinfo{'type'} = 1;

				# Keep this profile ?
				if ( NfSen::UserInput("Profile '$profilename' has no longer active sources! Keep this profile?" ) eq 'y') {
					# keep the profile, don't touch the sources
					print "Flag profile '$profilename' as history profile\n";
					$profileinfo{'locked'} = 0;
					if ( !NfSen::WriteProfile(\%profileinfo) ) {
						print SRDERR "Error writing profile '$profilename': $Log::ERROR";
					}
					next;
				} else {
					$profileinfo{'locked'} = 0;
					NfSen::WriteProfile(\%profileinfo);
					NfProfile::DeleteProfile( *STDOUT, { 'profile'	=> $profilename } );
				}
			} else {
				$profileinfo{'sourcelist'} = join(':', @ProfileSources);
				# Unlock/Write profile
				$profileinfo{'locked'} = 0;
				if ( !NfSen::WriteProfile(\%profileinfo) ) {
					print STDERR "Error writing profile 'live': $Log::ERROR\n";
				}
			}
		}

	}

} # End of Reconfig

1;
