#!/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: NfProfile.pm 56 2006-04-12 08:45:35Z peter $
#
#  $LastChangedRevision: 56 $

package NfProfile;

use strict;
use Sys::Syslog; 
Sys::Syslog::setlogsock('unix');
use POSIX 'setsid';

use NfSen;
use NfSenRRD;
use Log;

#
# Default profile description
my @ProfileTag = ( 
	"# \n",
);


local $SIG{'__DIE__'} = sub {
	my $message = shift;

	syslog("err","PANIC!: I'm dying: '$message'");

}; # End of __DIE__ 


sub ProfileHistory {
	my $profileref = shift;

	my $name   = $$profileref{'name'};
	my $tstart = $$profileref{'tstart'};
	my $tend   = $$profileref{'tend'};
	my $filter = "$NfConf::PROFILEDATADIR/$name/$$profileref{'filter'}";

	my @sourcelist = split /:/, $$profileref{'sourcelist'};

	# we have to process that many files:
	my $numfiles = ((( $tend - $tstart ) / 300 ) + 1 ) * scalar @sourcelist;
	
	$$profileref{'status'} = 'built 0';
	if ( !NfSen::WriteProfile($profileref) ) {
		syslog('err', "Error writing profile '$name': $Log::ERROR");
		return;
	}

	my $counter  = 0;
	my $progress = 0;
	my $percent  = $numfiles / 100;

	syslog('info', "ProfileHistory for profile '$name': $numfiles files."),
	# Update the profile status every .1% of all files, at least every 2 files
	# more often does not makes senes
	my $modulo	 = int ($percent / 10) < 2 ? 2 : int ($percent / 10);
	my $profile_size = 0;
	for ( my $t = $tstart; $t <= $tend; $t += 300 ) {
		my $iso = NfSen::UNIX2ISO($t);
		my %statinfo    = ();
		foreach my $source ( @sourcelist ) {
			$counter++;
			my $infile  = "$NfConf::PROFILEDATADIR/live/$source/nfcapd.$iso";
			my $outfile = "$NfConf::PROFILEDATADIR/$name/$source/nfcapd.$iso";

			my $completed = sprintf "%.1f%%", $counter / $percent;

			if ( ($counter % $modulo ) == 0 ) {
				$$profileref{'status'} = "built $completed\n";
				NfSen::WriteProfile($profileref);
				syslog('info', "Build Profile '$name':     Completed: $completed\%"),
			}

			if ( ! -f $infile ) {
				syslog('info', "Build Profile '$name':     Skip missing file '$infile'");
				next; #	source in foreach $source
			}

			if ( system( "$NfConf::PREFIX/nfdump -r $infile -w $outfile -f $filter" ) != 0 ) {
            	my $exit_value  = $? >> 8;
            	my $signal_num  = $? & 127;
            	my $dumped_core = $? & 128;
            	syslog('err', "nfdump unexpectedly died: Exit: $exit_value, Signal: $signal_num, Core: $dumped_core\n");
				next; #	source in foreach $source
			} 
			# array element 7 contains the size in bytes
			$profile_size += (stat($outfile))[7];

			# get statinfo from this slot
			$statinfo{$source} = NfSen::ReadStatInfo($profileref, $source, $iso);
			if ( defined $Log::ERROR ) {
				syslog('err', $Log::ERROR);
				next; #	source in foreach $source
			}
		}

		# update each RRD database
		foreach my $dbtype ( @NfSenRRD::RRDdb ) {
			my @_tmp = ();
			foreach my $source ( @sourcelist ) {
				# if the stat file was missing for this source ( should
				# not happen anyway, but who knows ... ) use '0'
				push @_tmp, $statinfo{$source}{$dbtype} == - 1 ? 0 : $statinfo{$source}{$dbtype};
			}
			NfSenRRD::UpdateDB("$NfConf::PROFILESTATDIR/$name", $dbtype, $t,
				$$profileref{'sourcelist'}, join ':', @_tmp);
			if ( $Log::ERROR ) {
				syslog('err', "ERROR Update RRD time: '$iso', db: '$dbtype', profile: '$name': $Log::ERROR");
			}
		}
	}
	syslog('info', "ProfileHistory: Done."),

	return $profile_size;

} # end of ProfileHistory

#
# ListProfiles is called only from cmd line
sub ListProfile {
	my $socket 	= shift;
	my $opts 	= shift;

	my $profile  = exists $$opts{'profile'} ? $$opts{'profile'} : undef;
	my $raw_mode = exists $$opts{'quiet'};

	if ( defined $profile ) {
		my $err = undef;
		if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
			$err = "ERR Illegal characters in profile name '$profile'!\n";
		} elsif ( !-d "$NfConf::PROFILESTATDIR/$profile") {
			$err = "ERR Profile '$profile' does not exists\n";
		} elsif ( !-f "$NfConf::PROFILESTATDIR/$profile/profile.dat") {
			$err = "ERR Missing profile descriptor file of profile '$profile'\n";
		}
		if ( defined $err ) {
			print $socket $err;
			return;
		}
	} 
	
	my @AllProfiles;
	if ( defined $profile ) {
		push @AllProfiles, $profile;
	} else {
		@AllProfiles = NfSen::ProfileList();
		if ( defined $Log::ERROR ) {
			print $socket "ERR $Log::ERROR\n";
			return;
		}
	}

	foreach my $profilename ( @AllProfiles ) {
		if ( !NfSen::InfoFileExists($profilename) ) {
			print $socket "No info for profile '$profilename'\n";
			next;
		}
		my %profileinfo = NfSen::ReadProfile($profilename);
		if ( $profileinfo{'status'} eq 'empty' ) {
			print $socket "ERR Profile '$profilename': $Log::ERROR\n";
			next;
		}

		if ( $raw_mode ) {
			# raw output format
			foreach my $key ( keys %profileinfo ) {
				if ( $key eq '_comment_' ) {
					my $tmp = join '', @{$profileinfo{$key}};
					# prepare sending the multiline profile description in a single line
					# escape ', | and \n
					$tmp =~ s/\\/\\134/g;	# escape escape char
					$tmp =~ s/'/\\047/g;	# escape '
					$tmp =~ s/\|/\\174/g;	# escape |
					$tmp =~ s/\n/\\012/g;	# escape \n
					print $socket "description|$tmp\n";
				} else {
					print $socket  "$key|$profileinfo{$key}\n";
				}
			}
		} else {
			# formated output
			my $type = $profileinfo{'type'};

			print $socket  @{$profileinfo{'_comment_'}} if  exists $profileinfo{'_comment_'};
			print $socket  "name\t$profilename\n";
			print $socket  "tstart\t"  . scalar localtime($profileinfo{'tstart'}) . "\n";
			print $socket  "tend\t"    . scalar localtime($profileinfo{'tend'}) . "\n";
			print $socket  "updated\t" . scalar localtime($profileinfo{'updated'}) . "\n";
			if ( $type == 0 ) {
				print $socket  "filter\t<none>\n";
			} else {
				print $socket  "filter\t$profileinfo{'filter'}\n";
			}
			my ($d, $h);
			$d = int($profileinfo{'expire'} / 24 );
			$h = $profileinfo{'expire'} % 24;
			my ( $d_str, $h_str );
			$d_str = $h_str = '';
			if ( $d ) {
				$d_str = "$d day";
				if ( $d == 0 || $d > 1 ) {
					$d_str .= "s";
				}
				$d_str .= " ";
			}
			$h_str = "$h hour";
			if ( $h == 0 || $h > 1 ) {
				$h_str .= "s";
			}
			print $socket  "expire\t${d_str}${h_str}\n";
			print $socket  "size\t" .   NfSen::ScaledValue($profileinfo{'size'}) . "\n";
			print $socket  "maxsize\t". NfSen::ScaledValue($profileinfo{'maxsize'}) . "\n";
			print $socket  "sources\t$profileinfo{'sourcelist'}\n";
			print $socket  "type\tlive\n"	     if $type == 0;
			print $socket  "type\thistory\n"	 if $type == 1;
			print $socket  "type\tcontinuous\n"  if $type == 2;
			print $socket  "locked\t$profileinfo{'locked'}\n";
			print $socket  "status\t$profileinfo{'status'}\n";
			print $socket  "\n";
		}
	}
	
	print $socket "OK Command completed\n" if exists $$opts{'quiet'};

} # End of ListProfiles

sub AddProfile {
	my $socket = shift;
	my $opts   = shift;

	# both options can be set at the same time
	my $history_profile    = 0;	# Profile start back in time; needs to be builded
	my $continuous_profile = 0;	# Profile continuous in time
	
	my $profile;
	# Parameter checking
	if ( exists $$opts{'profile'} ) {
		$profile	= $$opts{'profile'};
		if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
			print $socket "ERR Illegal characters in profile name '$profile'!\n";
			return;
		}
		if ( NfSen::InfoFileExists($profile) ) {
			print $socket "ERR Profile '$profile' already exists!\n";
			return;
		}
	} else {
		print $socket "ERR Missing profile name!\n";
		return;
	}

	my $now 	= time();
	my $tlast	= $now - ( $now % 300 ) - 300;
	my $tstart	= $tlast;
	my $tend 	= $tstart;

	if ( exists $$opts{'tstart'} ) {
		$tstart = $$opts{'tstart'};
		if ( !NfSen::ValidISO($tstart) ) {
			print $socket "ERR Time format wrong '$tstart'!\n";
			return;
		}
		$tstart = NfSen::ISO2UNIX($tstart);
		$history_profile = 1;
	} else {
		# otherwise we have a ontinuous profile
		$continuous_profile = 1;
	}

	if ( exists $$opts{'tend'} ) {
		$tend = $$opts{'tend'};
		if ( !NfSen::ValidISO($tend) ) {
			print $socket "ERR Time format wrong '$tend'!\n";
			return;
		}
		$tend = NfSen::ISO2UNIX($tend);
		$history_profile = 1;
	} else {
		# otherwise we have a ontinuous profile
		$continuous_profile = 1;
	}

	# new profile must fit into live profile
	my %liveprofile = NfSen::ReadProfile('live');
	if ( $liveprofile{'status'} eq 'empty' ) {
		# Could not read live profile
		print $socket "ERR profile 'live': $Log::ERROR\n";	
		return;
	}
	if ( $history_profile && ($tstart < $liveprofile{'tstart'}) ) {
		print $socket "ERR '$$opts{'tstart'}' not within 'live' profile time window\n";
		return;
	}
	if ( $history_profile && ($tend > $liveprofile{'tend'}) ) {
		print $socket "ERR '$$opts{'tend'}' not within 'live' profile time window\n";
		return;
	}

	# prevent overflow to future timestamps
	$tend = $tlast if $tend > $tlast;	

	# expire time
	my $lifetime   = exists $$opts{'expire'}  ? NfSen::ParseExpire($$opts{'expire'}) : 0;
	if ( $lifetime < 0 ) {
		print $socket "ERR Unknown expire time '$$opts{'expire'}'\n";
		return;
	}

	# max size
	my $maxsize	= exists $$opts{'maxsize'} ? NfSen::ParseMaxsize($$opts{'maxsize'}) : 0;
	if ( $maxsize < 0 ) {
		print $socket "ERR Unknown max size '$$opts{'maxsize'}'\n";
		return;
	}

	# source list
	my $sourcelist = exists $$opts{'sources'} ? $$opts{'sources'} : join ':', keys %NfConf::sources;
	foreach my $source ( split /:/, $sourcelist ) {
		if ( !exists $NfConf::sources{$source} ) {
			print $socket "ERR Unknown source '$source'\n";
			return;
		}
	}
	
	# Profile filter:
	# A given 'filter' overwrites the filter in the file 'filterfile'
	my $filter = undef;
	if ( exists $$opts{'filter'} ) {
		$filter = $$opts{'filter'};
	} elsif ( exists $$opts{'filterfile'} ) {
		my $file = $$opts{'filterfile'};
		open(FILTER, "$file" ) or
			syslog('err', "Can't open filter file '$filter': $!"),
			print $socket "ERR Can't open filter file '$filter': $!\n",
			return;
		$filter = join '', <FILTER>;
		close FILTER;
	}
	if ( !defined $filter || $filter eq '' ) {
		print $socket "ERR Missing profile filter\n";
		return;
	}
	my @out = NfSen::VerifyFilter($filter);
	if ( scalar @out > 0 ) {
		print $socket "Filter syntax error:\n";
		foreach my $line ( @out ) {
			print $socket $line;
		}
		print $socket "ERR Filter contains errors\n";
		return;
	}
	
	# Do the work now:
	umask 0002;
	if ( !mkdir "$NfConf::PROFILESTATDIR/$profile" ) {
		my $err = $!;
		syslog("err", "Can't create profile stat directory '$NfConf::PROFILESTATDIR/$profile': $err");
		print $socket "ERR Can't create profile stat directory: $err!\n";
		return;
	}

	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
		if ( !mkdir "$NfConf::PROFILEDATADIR/$profile" ) {
			my $err = $!;
			syslog("err", "Can't create profile data directory '$NfConf::PROFILEDATADIR/$profile': $err");
			print $socket "ERR Can't create profile data directory: $err\n";
			rmdir "$NfConf::PROFILESTATDIR/$profile";
			return;
		}
	}

	foreach my $source ( split /:/, $sourcelist  ) {
		mkdir "$NfConf::PROFILEDATADIR/$profile/$source" or
			print $socket "ERR Can't create source directory: $!\n",
			return;
	}

	my %profileinfo;
	if ( exists $$opts{'description'}  ) {

		$$opts{'description'} =~ s/\\073/;/g;      # replace back all ;
		$$opts{'description'} =~ s/\\012/\n# /g;   # replace back all '\n', and add '#' to new line
		$$opts{'description'} =~ s/\\047/'/g;      # replace back all '
		$$opts{'description'} =~ s/\\134/\\/g;     # replace back replace char '\'

		my @_tmp = split /\n/, "# $$opts{'description'}";
		@_tmp = map "$_\n", @_tmp;
		$profileinfo{'_comment_'} 	= \@_tmp;
	} else {
		$profileinfo{'_comment_'} 	= \@ProfileTag;
	}
	$profileinfo{'name'}		= $profile;
	$profileinfo{'tstart'} 		= $tstart;
	$profileinfo{'tend'} 		= $tend;
	$profileinfo{'sourcelist'}	= $sourcelist;

	# the first slot to be updated is 'updated' + 300
	$profileinfo{'updated'}		= $tstart - 300;	

	# expiring profiles makes only sense for continous profiles
	$profileinfo{'expire'} 		= $continuous_profile ? $lifetime : 0;
	$profileinfo{'maxsize'} 	= $continuous_profile ? $maxsize : 0;

	# the profile starts we 0 size
	$profileinfo{'size'} 		= 0;

	# 'filter.txt' triggers nfprofile, otherwise use any other name
	$profileinfo{'filter'}		= $continuous_profile ? "filter.txt" : "history_filter.txt";

	# continuous profile overwrites history profile
	$profileinfo{'type'} 		= 1 if $history_profile;
	$profileinfo{'type'} 		= 2 if $continuous_profile;
	
	# if we need to create the profile from
	# history data, lock the profile until done.
	$profileinfo{'locked'} 		= $history_profile;

	# status of profile
	$profileinfo{'status'}		= 'new';

	# copy the filter to it's file in the profile directory
	open FILTER, ">$NfConf::PROFILEDATADIR/$profile/$profileinfo{'filter'}";
	print FILTER $filter;
	if ( !close FILTER ) {
		my $err = $!;
		syslog('err', "Error writing filter file for profile'$profile': $err");
		$$opts{'force'} = 1;
		DeleteProfile($socket, $opts);
		print $socket "ERR writing filter file for profile '$profile': $err\n";
		return;
	}

	if ( !NfSen::WriteProfile(\%profileinfo) ) {
		syslog('err', "Error writing profile '$profile': $Log::ERROR");
		print $socket "ERR writing profile '$profile': $Log::ERROR\n";
		return;
	}
	
	# $tstart is the first value we need in the RRD DB, therefore specify 
	# $tstart - 300 ( 1 slot )
	foreach my $source ( split /:/, $sourcelist  ) {
		NfSenRRD::SetupRRD("$NfConf::PROFILESTATDIR/$profile", $source, $tstart - 300, 1);
	}
	if ( defined $Log::ERROR ) {
		syslog('err', "Error creating RRD DB for '$profile': $Log::ERROR");
		print $socket "ERR creating profile '$profile' failed!\n";
		$$opts{'force'} = 1;
		DeleteProfile($socket, $opts);
		return;
	}

	if ( $history_profile ) {
    	chdir '/';
		my $pid = fork;
		if ( !defined $pid ) {
			$Log::ERROR = $!;
			syslog('err', "Can't fork: $Log::ERROR");
			print $socket "ERR Can't fork: $Log::ERROR\n";
			return;
		}
		if ( $pid ) { 
			# we are the parent processs
			if ( exists $$opts{'quiet'} ) {
				print $socket "OK Profiling netflow data\n";
			} else {
				ListProfile($socket, $opts);
			}
			open PID, ">$NfConf::VARDIR/run/build.${pid}" || 
				warn "Can't open pid file: $!";
            print PID "$pid\n";
            close PID;
			return;
		}
		setsid or die "Can't start a new session: $!";
		# the child starts to build the profile from history data
		close STDIN;
		close STDOUT;
		# STDERR may be tied too syslog. 
		if ( $$opts{'quiet'} ) {
			untie *STDERR;
			close STDERR;
		}
		open STDIN, '/dev/null'   || die "Can't read /dev/null: $!";
		open STDOUT, '>/dev/null' || die "Can't write to /dev/null: $!";
		open STDERR, '>/dev/null' || die "Can't write to /dev/null: $!";

		$profileinfo{'size'} 	= ProfileHistory(\%profileinfo);
		$profileinfo{'updated'}	= $profileinfo{'tend'};
		unlink "$NfConf::VARDIR/run/build.$$";
	}

	syslog('debug', "Graph update profile: $profile, Time: $profileinfo{'tend'}.");
	if ( NfSenRRD::UpdateGraphs($profile, $profileinfo{'tend'}, 1) ) {
		syslog('err', "Error graph update: $Log::ERROR");
		$profileinfo{'status'}	= 'FAILED';	
	} else {
		$profileinfo{'status'}	= 'OK';	
	}

	# unlock profile
	$profileinfo{'locked'} 		= 0;

	NfSen::WriteProfile(\%profileinfo);
	
	# for the continuous profile
	if ( !$history_profile ) {
		if ( exists $$opts{'quiet'} ) {
			print $socket "OK Add profile completed\n" if exists $$opts{'quiet'};
		} else  {
			ListProfile($socket, $opts);
		}
	}

} # End of AddProfile

sub DeleteProfile {
	my $socket	= shift;
	my $opts 	= shift;

	my $profile;
	# Parameter checking
	if ( exists $$opts{'profile'} ) {
		$profile	= $$opts{'profile'};
		if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
			print $socket "ERR Illegal characters in profile name '$profile'!\n";
			return;
		}
		if ( !NfSen::InfoFileExists($profile) ) {
			print $socket "ERR Profile '$profile' does not exists!\n";
			return;
		}
	} else {
		print $socket "ERR Missing profile name!\n";
		return;
	}

	if ( $profile eq 'live' ) {
		print $socket "ERR Don't want to delete profile 'live'!\n";
		return;
	}

	my %profileinfo = NfSen::ReadProfile($profile);
	if ( $profileinfo{'status'} eq 'empty' ) {
		# Could not read live profile
		print $socket "ERR Profile '$profile': $Log::ERROR\n";	
		return;
	}
	if ( ! exists $$opts{'force'} && $profileinfo{'locked'} ) {
		print $socket "ERR Profile '$profile' is locked and can not be deleted now!\n";
		return;
	}
	if ( ! exists $$opts{'force'} && $profileinfo{'status'} ne 'OK' ) {
		print $socket "ERR Profile '$profile' is not in status 'OK' and can not be deleted now!\n";
		return;
	}

	if ( !rename "$NfConf::PROFILESTATDIR/$profile", "$NfConf::PROFILESTATDIR/.$profile" ) {
		print $socket "ERR Failed to rename stat profile '$profile' in order to delete: $!\n";
		return;
	}
	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
		if ( !rename "$NfConf::PROFILEDATADIR/$profile", "$NfConf::PROFILEDATADIR/.$profile" ) {
			print $socket "ERR Failed to rename data profile '$profile' in order to delete: $!\n";
			return;
		}
		system("/bin/rm -rf $NfConf::PROFILEDATADIR/.$profile &");
	}
	system("/bin/rm -rf $NfConf::PROFILESTATDIR/.$profile &");

	print $socket "OK Profile '$profile' deleted.\n";

} # End of DeleteProfile

sub ModifyProfile {
	my $socket	= shift;
	my $opts 	= shift;

	my $profile;
	# Parameter checking
	if ( exists $$opts{'profile'} ) {
		$profile	= $$opts{'profile'};
		if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
			print $socket "ERR Illegal characters in profile name '$profile'!\n";
			return;
		}
		if ( !NfSen::InfoFileExists($profile) ) {
			print $socket "ERR Profile '$profile' does not exists!\n";
			return;
		}
	} else {
		print $socket "ERR Missing profile name!\n";
		return;
	}
	my %profileinfo = NfSen::ReadProfile($profile);
	if ( $profileinfo{'status'} eq 'empty' ) {
		# Could not read profile
		print $socket "ERR Profile '$profile': $Log::ERROR\n";	
		return;
	}

	my $continuous_profile = $profileinfo{'type'} == 2 || $profileinfo{'type'} == 0;

	my $changed = 0;

	if ( exists $$opts{'lock'} ) {
		$profileinfo{'locked'} = 0 if $$opts{'lock'} == 0;
		$profileinfo{'locked'} = 1 if $$opts{'lock'} == 1;
		$changed = 1;
	}

	if ( $continuous_profile ) {
		# these changes make only sense for continuous profiles
		# expire time
		if ( exists $$opts{'expire'} ) {
			my $lifetime   = NfSen::ParseExpire($$opts{'expire'});
			if ( $lifetime < 0 ) {
				print $socket "ERR Unknown expire time '$$opts{'expire'}'\n";
				return;
			}
			$profileinfo{'expire'} = $lifetime;
			$changed = 1;
		}
	
		# max size
		if ( exists $$opts{'maxsize'} ) {
 			my $maxsize = NfSen::ParseMaxsize($$opts{'maxsize'});
			if ( $maxsize < 0 ) {
				print $socket "ERR Unknown max size '$$opts{'maxsize'}'\n";
				return;
			}
			$profileinfo{'maxsize'}	= $maxsize;
			$changed = 1;
		}
	}
	
	if ( $changed ) {
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			syslog('err', "Error writing profile '$profile': $Log::ERROR");
			print $socket "ERR writing profile '$profile': $Log::ERROR\n";
			return;
		}
		if ( exists $$opts{'quiet'} ) {
			print $socket "OK profile modified\n" if exists $$opts{'quiet'};
			return;
		} else {
			ListProfile($socket, $opts);
			return;
		}
	} else {
		print $socket "OK Nothing modified\n" if exists $$opts{'quiet'};
	}

} # End of ModifyProfile

sub RebuildProfile {
	my $socket	= shift;
	my $opts 	= shift;

	my $profile;
	# Parameter checking
	if ( exists $$opts{'profile'} ) {
		$profile	= $$opts{'profile'};
		if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
			print $socket "ERR Illegal characters in profile name '$profile'!";
			return;
		}
		if ( !NfSen::InfoFileExists($profile) ) {
			print $socket "ERR Profile '$profile' does not exists!\n";
			return;
		}
	} else {
		print $socket "ERR Missing profile name!\n";
		return;
	}

	print $socket "Query your data files ... please wait\n" unless exists $$opts{'quiet'};

	opendir(PROFILEDIR, "$NfConf::PROFILEDATADIR/$profile" ) or
		syslog('err', "Rebuild: Can't open profile directory '$profile': $!"),
		print $socket "ERR Can't opendir\n",
		return;

	my @AllSources = grep { -d "$NfConf::PROFILEDATADIR/$profile/$_" } readdir(PROFILEDIR);
	closedir PROFILEDIR;

	my @sourcelist;
	foreach my $source ( @AllSources ) {
		push @sourcelist, $source if exists $NfConf::sources{$source};
	}

	if ( scalar @sourcelist == 0 ) {
		syslog('err', "No valid sources found in profile '$profile'");
		print $socket "ERR No valid sources found in profile '$profile'\n";
		return;
	}

	opendir(PROFILESRC, "$NfConf::PROFILEDATADIR/$profile/$sourcelist[0]" ) or
		syslog('err', "Rebuild: Can't open profile directory '$profile/$sourcelist[0]': $!"),
		print $socket "ERR Can't opendir '$profile/$sourcelist[0]'\n",
		return;

	my @files = grep { /^nfcapd\.\d+$/ && 
				-f "$NfConf::PROFILEDATADIR/$profile/$sourcelist[0]/$_" } readdir(PROFILESRC);

	closedir PROFILESRC;
	if ( scalar @files == 0 ) {
		syslog("info",  "Rebuild: No data file found in profile '$profile'");
		print $socket "OK No data file found in profile '$profile'\n" if exists $$opts{'quiet'};
		return;
	}

	my @sortedfiles = sort @files;
	my ( $first ) = (shift @sortedfiles) =~ /nfcapd\.(\d+)/;
	my ( $last )  = (pop @sortedfiles)   =~ /nfcapd\.(\d+)/;

	print $socket "done.\n" unless exists $$opts{'quiet'};

	my %profileinfo = NfSen::LockProfile($profile);
	if ( ! exists $$opts{'zero'} && $profileinfo{'status'} eq 'empty' ) {
		if ( $profileinfo{'locked'} == 1 ) {
			print $socket "ERR Profile '$profile' is already locked. Can't rebuild now\n";
		} else {
			# Could not read live profile
			print $socket "ERR Profile '$profile': $Log::ERROR\n";	
		}
		return;
	}
	my $continuous_profile = -f "$NfConf::PROFILEDATADIR/$profile/filter.txt";
	my $history_profile	= -f "$NfConf::PROFILEDATADIR/$profile/history_filter.txt";

	if ( !$profileinfo{'name'} ) {
		$profileinfo{'_comment_'} 	= \@ProfileTag;
		$profileinfo{'name'}		= $profile;

	} else {
		if ( $profileinfo{'name'} ne $profile ) {
			syslog("warning", "Profile name missmatch - Set '$profileinfo{'name'}' to '$profile'");
			$profileinfo{'name'} = $profile;
		}

	}
	$profileinfo{'tstart'} 		= NfSen::ISO2UNIX($first);
	$profileinfo{'tend'} 		= NfSen::ISO2UNIX($last);
	$profileinfo{'sourcelist'}	= join ':', @sourcelist;
	# 'updated' + 300 -> next update = $tstart
	$profileinfo{'updated'}		= $profileinfo{'tstart'} - 300;	

	# the profile starts with size 0
	$profileinfo{'size'} 		= 0;
	$profileinfo{'maxsize'} 	= defined $profileinfo{'maxsize'} ? $profileinfo{'maxsize'} + 0 : 0;
	$profileinfo{'expire'} 		= defined $profileinfo{'expire'} ? $profileinfo{'expire'} + 0 : 0;
	$profileinfo{'filter'}		= '';
	$profileinfo{'filter'}		= "filter.txt" if $continuous_profile;
	$profileinfo{'filter'}		= "history_filter.txt" if $history_profile;

	# continuous profile overwrites history profile
	$profileinfo{'type'} 		= 0;
	$profileinfo{'type'} 		= 1 if $history_profile;
	$profileinfo{'type'} 		= 2 if $continuous_profile;
	
	# if we need to create the profile from
	# history data, lock the profile until down.
	$profileinfo{'locked'} 		= 0;

	# state rebuild prevents to run the plugins for each updated timeslot
	$profileinfo{'status'} 		= 'rebuild';

	umask 0002;
	# $tstart is the first value we need in the RRD DB, therefore specify 
	# $tstart - 300 ( 1 slot )
	foreach my $db ( @sourcelist ) {
		NfSenRRD::SetupRRD("$NfConf::PROFILESTATDIR/$profile", $db, $profileinfo{'tstart'} - 300, 1);	
	}
	if ( defined $Log::ERROR ) {
		syslog('err', "Error creating RRD DB for '$profile': $Log::ERROR");
	}
	if ( !NfSen::WriteProfile(\%profileinfo) ) {
		syslog('err', "Error writing profile '$profile': $Log::ERROR");
		print $socket "ERR writing profile '$profile': $Log::ERROR\n";
		return;
	}

	if ( exists $$opts{'quiet'} ) {
		print $socket "OK profile modified\n";
		return;
	} else {
		ListProfile($socket,$opts);
		return;
	}

} # End of RebuildProfile

sub ExpireProfile {
	my $socket	= shift;
	my $opts 	= shift;

	my $profilename;
	# Parameter checking
	if ( exists $$opts{'profile'} ) {
		$profilename	= $$opts{'profile'};
		if ( $profilename =~ /[^A-Za-z0-9\-+_]+/ ) {
			print $socket "ERR Illegal characters in profile name '$profilename'!";
			return;
		}
		if ( !NfSen::InfoFileExists($profilename) ) {
			print $socket "ERR Profile '$profilename' does not exists!\n";
			return;
		}
	} else {
		print $socket "ERR Missing profile name!\n";
		return;
	}

	my %profileinfo = NfSen::LockProfile($profilename);
	# Make sure profile is not empty - means it exists and is not locked
	if ( $profileinfo{'status'} eq 'empty' ) {
		if ( $profileinfo{'locked'} == 1 ) {
			print $socket "ERR Profile is locked!\n";
			syslog('info', "Profile is locked. Can't expire");
			return;
		}
	
		# it's an error reading this profile
		if ( defined $Log::ERROR ) {
			print $socket "ERR $Log::ERROR\n";
			syslog('err', "Error $profilename: $Log::ERROR");
			return;
		}
	}

	# history profiles do not want to be expired
	if ( $profileinfo{'type'} == 1 ) {
		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			syslog('err', "Error writing profile '$profilename': $Log::ERROR");
		}
		syslog('info', "Can't expire history profile");
		print $socket "ERR Can't expire history profile\n";
		return;
	}

	syslog('info', "Force expire for profile '$profilename'");

	# stat info for expiring
	my $stat_filenum   = 0;	# number of files expired
	my $stat_filesize  = 0;	# total disksize expired
	my $stat_timerange = 0;	# time range expired

	my $timestamp		= $profileinfo{'tstart'};
	my $profilesize 	= $profileinfo{'size'};
	my $low_water_mark	= $profileinfo{'size'} * $NfConf::low_water;

	# loop until profile size is less then low water mark
	while ( $profilesize >= $low_water_mark ) {
		my $t_iso = NfSen::UNIX2ISO($timestamp);

		# Delete this timeslot in all sources
		foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
			my $filename = "$NfConf::PROFILEDATADIR/$profilename/$source/nfcapd.$t_iso";
			next unless -f $filename;

			$profilesize -= 512 * (stat($filename))[12];
			$profilesize -= 512 * (stat("${filename}.stat"))[12];
			unlink $filename;
			unlink "${filename}.stat";
			$stat_filenum += 1;
		}
		$timestamp += 300;

		#
		# panic exit: if we still loop and we reached the end of the profile
		# this should never happen
		if ( $timestamp > $profileinfo{'tend'} ) {
			syslog('err', "Profile Size: Tried to expire beyond end of profile: " .  
				scalar localtime($timestamp));
			syslog('err', "Max: $profileinfo{'maxsize'}, Size: $profilesize, Low: $low_water_mark");
			last;
		}
	}
	$stat_filesize 			= $profileinfo{'size'} - $profilesize;
	$profileinfo{'size'}	= $profilesize;
	$stat_timerange 		= $timestamp - $profileinfo{'tstart'};
	$profileinfo{'tstart'} 	= $timestamp;

	$profileinfo{'locked'} = 0;
	if ( !NfSen::WriteProfile(\%profileinfo) ) {
		syslog('err', "Error writing profile '$profilename': $Log::ERROR");
	}

	if ( $stat_filenum ) {
		my ($h, $m);
		$h = int($stat_timerange / 3600 );
		$m = ($stat_timerange % 3600) / 60;
		syslog('info', "Expire stat profile $profilename");
		syslog('info', "    Expired files : $stat_filenum");
		syslog('info', "    Expired size  : $stat_filesize");
		syslog('info', join '',"    Expired timeframe : $h hour" , $h == 1 ? " " : "s " , "$m minutes");
	} 

	syslog('info', "End force expire");

	if ( exists $$opts{'quiet'} ) {
		print $socket "OK profile expired\n";
	} else {
		ListProfile($socket,$opts);
	}

	return;

} # End of ExpireProfile

sub QueryProfiles {
	my $socket	= shift;
	my $opts 	= shift;

	my @ProfileList = NfSen::ProfileList();

	foreach my $profile ( @ProfileList ) {
		print $socket "$profile\n";
	}

	if ( defined $Log::ERROR ) {
		print $socket "ERR $Log::ERROR\n";
	} else {
		print $socket "OK Profile Listing\n"  if exists $$opts{'quiet'};
	}

	# print $socket ".\n";

	return;

} # End of QueryProfiles

1;
