#!/usr/bin/perl

use strict;
use Socket;
use IPC::Open3;
use vars qw( @local_nets %lists $prefix );

push @INC, '/usr/local/etc/tas';
require 'AcctLog.conf';
require 'tas.conf';
require 'cgi.conf';

my $MySelf = $0;			# program name to refer from generated documents
my $QueryString = ( ( exists $ENV{QUERY_STRING} && $ENV{QUERY_STRING} ) ? $ENV{QUERY_STRING} : <> );
					# input parameters
my $LightColor = 'WHITE';
my $DarkColor = 'ORANGE';
my $BGColor = '#50a0a0';
my $FGColor = 'YELLOW';
my $AcctLog = '/usr/local/sbin/AcctLog';

# ACTION values:
my $ActionMenu = 'menu';
my $ActionEmpty = 'empty';
my $ActionModel = $lang::menu_submit_model;
my $ActionReport = $lang::menu_submit_report;
my $ActionCancelAll = $lang::menu_submit_reset;
my $ActionAddList = $lang::menu_submit_add_list;
my $ActionEditList = $lang::menu_submit_edit_list;
my $ActionAddColumn = $lang::menu_submit_add_column;
my $ActionEditColumn = $lang::menu_submit_edit_column;
my $ActionDeleteColumn = $lang::menu_submit_delete_column;


############################################################################
# Error page
############################################################################
sub error
{
	print <<END;
Content-type: text/html

<html>
<body bgcolor="$BGColor" text="$FGColor">
<H3>$lang::error_bad_params</H3>
</body>
</html>
END
}


############################################################################
# Create a table model
############################################################################
sub model
{
	my $traffic_type = shift;
	my $filename_part = shift;
	my $cat_type = shift;
	my $host = shift;
	my $list = shift;
	my $sort = shift;
	my $sort_dir = shift;
	my $do_resolve = shift;
	my @columns = parse_columns( shift );
	my $cat;
	my $timeperiod = get_timeperiod( $filename_part );
	my $msg;
	my $i;

	if( $cat_type eq 'host' ){
		$cat = $do_resolve ? resolve( $host ) : $host;
	}elsif( $cat_type eq 'list' ){
		$cat = $list;
	}else{
		$cat = $cat_type;
	}

	$msg = $lang::report_table_header;
	$msg =~ s/%1/$traffic_type/;
	$msg =~ s/%2/$timeperiod/;

	print <<END;
Content-type: text/html

<html>
<body bgcolor="$BGColor">
<form name=MODELFORM method=get action="$MySelf">
<center>
<table>
<tr><td>
<font color="$FGColor"><h4>$msg</h4></font>
<table border>
<tr>
	<th bgcolor="$LightColor">
		<font color="$DarkColor">Group</font>
	</th>
END
	foreach ( @columns ){
		print <<END;
	<th bgcolor="$DarkColor">
		<font color="$LightColor">$_->{'header'}</font>
	</th>
END
	}
	print <<END;
</tr>
<tr>
	<td bgcolor="$DarkColor" align=right valign=top>
END
		if( $cat_type eq 'total' || $cat_type eq 'host' ){
			print "<b>$cat</b>";
		}elsif( $cat_type eq 'each' ){
			print '<b>&lt;$lang::model_table_category_each&gt;</b>';
		}elsif( $cat_type eq 'list' ){
			$msg = $lang::model_table_category_list;
			$msg =~ s/%1/$list/;
			print "<b>&lt;$msg&gt;</b>";
		}
		if( $sort == 1 && $cat_type ne 'total' && $cat_type ne 'host' ){
			if( $sort_dir eq 'asc' ){
				print "<p><i>($lang::model_table_sort_asc)</i>"
			}else{
				print "<p><i>($lang::model_table_sort_desc)</i>"
			}
		}
	print <<END;
	</th>
END
	$i = 2;
	foreach ( @columns ){
		print <<END;
	<td bgcolor="$LightColor" valign=top>
END
		if( $_->{'cat'} eq 'total' || $_->{'cat'} eq 'each' ){
			if( $_->{'dir'} eq 'to' ){
				print "&lt;$lang::model_column_category_total_to&gt;";
			}elsif( $_->{'dir'} eq 'from' ){
				print "&lt;$lang::model_column_category_total_from&gt;";
			}else{
				print "&lt;$lang::model_column_category_total_both&gt;";
			}
		}elsif( $_->{'cat'} =~ /^[^\.]+$/ ){	# group list
			if( $_->{'dir'} eq 'to' ){
				$msg = $lang::model_column_category_list_to;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}elsif( $_->{'dir'} eq 'from' ){
				$msg = $lang::model_column_category_list_from;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}else{
				$msg = $lang::model_column_category_list_both;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}
		}else{
			if( $_->{'dir'} eq 'to' ){
				$msg = $lang::model_column_category_host_to;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}elsif( $_->{'dir'} eq 'from' ){
				$msg = $lang::model_column_category_host_from;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}else{
				$msg = $lang::model_column_category_host_both;
				$msg =~ s/%1/$_->{'cat'}/;
				print "&lt;$msg&gt;";
			}
		}

		if( $i == $sort && $cat_type ne 'total' && $cat_type ne 'host' ){
			if( $sort_dir eq 'asc' ){
				print "<p><i>($lang::model_column_sort_asc)</i>"
			}else{
				print "<p><i>($lang::model_column_sort_desc)</i>"
			}
		}

		print <<END;
	</td>
END
		$i++;
	}
	print <<END;
</tr>
<tr>
	<th bgcolor="$DarkColor" align=right>
		TOTAL
	</th>
END
	foreach ( @columns ){
		print <<END;
	<td bgcolor="$LightColor">
		&lt;$lang::model_column_total&gt;
	</td>
END
	}
	print <<END;
</tr>
</table>
</td></tr>
</table>
<input type=hidden name="ACTION" value="$ActionEmpty">
<input type=submit value="$lang::model_submit">
</center>
</form>
</body>
</html>
END
}


############################################################################
# Proceed with the report
############################################################################
sub report
{
	my $traffic_type = shift;
	my $filename_part = shift;
	my $cat_type = shift;
	my $host = shift;
	my $list = shift;
	my $sort = shift;
	my $sort_dir = shift;
	my $do_resolve = shift;
	my @columns = parse_columns( shift );
	my $rounding =  shift;
	my %lists = parse_lists( shift );
	my $listname;
	my $cat;
	my $conffile = '/tmp/$MySelf.$$.' . time();
	my $dbprefix = "$prefix/$traffic_type.$filename_part";
	my $pi;		# progress indicator
	my $timeperiod = get_timeperiod( $filename_part );
	my $msg;

	# setup $cat:
	if( $cat_type eq 'host' ){
		$cat = $host;
	}elsif( $cat_type eq 'list' ){
		$cat = "*$list";
	}else{
		$cat = $cat_type;
	}

	# setup $sort:
	$sort = -$sort if $sort_dir eq 'desc';

	# setup $do_resolve:
	$do_resolve = 'false' unless $do_resolve;

	# setup table header
	$msg = $lang::report_table_header;
	$msg =~ s/%1/$traffic_type/;
	$msg =~ s/%2/$timeperiod/;

	$SIG{'PIPE'} = $SIG{'TERM'} = sub { unlink $conffile; exit 1; };

	open( CONF, ">$conffile" ) || die "Can't open $conffile: $!";

	print CONF <<END;
\@local_nets = (
END
	foreach ( @local_nets ){
		print CONF <<END;
	"$_",
END
	}
	print CONF <<END;
);

\%lists = (
END
	foreach $listname ( keys %lists ){
		print CONF <<END;
	"$listname" => [
END
		foreach ( @{$lists{$listname}} ){
			print CONF <<END;
		"$_",
END
		}
		print CONF <<END;
	],
END
	}
	print CONF <<END;
);

\%tables = (
	"$traffic_type" => [
		[
			"$msg",
			"$cat",
			$sort,
			"$do_resolve",
			[
END
	foreach( @columns ){
	print CONF <<END;
				[ "$_->{'header'}", "$_->{'cat'}", "$_->{'dir'}", "$_->{'units'}", "$_->{'agent_host'}", "$_->{'proto'}", "$_->{'status'}" ],
END
	}
	print CONF <<END;
			],
			"$rounding",
		]
	]
);
END
	close CONF;

	open3( \*WRTRFH, \*RDRFH, \*ERRFH, $AcctLog, '-v', '-h', '-f', $conffile, '-d', $dbprefix, $traffic_type );
	close WRTRFH;

	$| = 1;

	print <<END;
Content-type: text/html

<html>
<head>
<title>$lang::report_title</title>
</head>
<body bgcolor="$BGColor" text="$FGColor">
<form name="PIFORM">
$lang::report_dir
<input type=radio name="DIR" checked> $lang::report_dir_incoming
&nbsp;
<input type=radio name="DIR"> $lang::report_dir_outgoing
<br>
$lang::report_progress <font color="BLACK"><input type=button name="PI" value=" 0 "></font>
</form>
END
	while( defined( $_ = getc ERRFH ) ){
		if( $_ ne "\r" && $_ ne "\n" ){
			$pi .= $_;
		}else{
			last if $pi eq 'Complete';
			if( $pi =~ /^\d+$/ ){
				print "<script language=\"JavaScript\">\n";
				print "document.PIFORM.PI.value = '$pi';\n";
				print "</script>\n";
			}elsif( $pi =~ /^\s*Incoming/ ){
				print "<script language=\"JavaScript\">\n";
				print "document.PIFORM.DIR[ 0 ].checked = true;\n";
				print "</script>\n";
			}elsif( $pi =~ /^\s*Outgoing/ ){
				print "<script language=\"JavaScript\">\n";
				print "document.PIFORM.DIR[ 1 ].checked = true;\n";
				print "</script>\n";
			}
			$pi = '';
		}
	}
	while( $_ = <RDRFH> ){
		print;
	}

	print <<END;
</body>
</html>
END
	unlink $conffile;
}


############################################################################
# Edit table column or create a new one
############################################################################
sub edit_column
{
	my $header = shift;
	my $msg;

	print <<END;
Content-type: text/html

<script language="JavaScript1.2">

	//--------------------------------------------------------------------
	// Initialize column parameters
	//--------------------------------------------------------------------
	function InitColumn( header ){
		var headerobj = document.COLUMNFORM.HEADER;
		var dirobj = document.COLUMNFORM.DIRECTION;
		var unitsobj = document.COLUMNFORM.UNITS;
		var cattypeobj = document.COLUMNFORM.CAT_TYPE;
		var hostobj = document.COLUMNFORM.HOST;
		var listobj = document.COLUMNFORM.LIST;
		var agenthostobj = document.COLUMNFORM.AGENT_HOST;
		var protoobj = document.COLUMNFORM.PROTO;
		var statusobj = document.COLUMNFORM.STATUS;
		var columnobj = parent.upper.document.MENUFORM.COLUMN;
		var i;
		var j;

		for( i = 0; i < parent.ColumnList.length && parent.ColumnList[ i ].header != header; i++ );
		if( i == parent.ColumnList.length ) return;	// column not found

		headerobj.value = header;
				
		if( parent.ColumnList[ i ].cattype == 'host' ){
			cattypeobj[ 0 ].checked = true;
			hostobj.value = parent.ColumnList[ i ].cat;
		}else if( parent.ColumnList[ i ].cattype == 'list' ){
			cattypeobj[ 1 ].checked = true;
			for( j = 0; j < listobj.options.length; j++ ){
				if( listobj.options[ j ].value == parent.ColumnList[ i ].cat ){
					listobj.selectedIndex = j;
					break;
				}
			}
		}else{
			cattypeobj[ 2 ].checked = true;
		}
		for( j = 0; j < unitsobj.length; j++ ){
			if( unitsobj[ j ].value == parent.ColumnList[ i ].units ){
				unitsobj[ j ].checked = true;
				break;
			}
		}
		for( j = 0; j < dirobj.length; j++ ){
			if( dirobj[ j ].value == parent.ColumnList[ i ].dir ){
				dirobj[ j ].checked = true;
				break;
			}
		}
		agenthostobj.value=parent.ColumnList[ i ].agent_host;
		protoobj.value=parent.ColumnList[ i ].proto;
		statusobj.value=parent.ColumnList[ i ].status;
	}

	//--------------------------------------------------------------------
	// Commit column
	//--------------------------------------------------------------------
	function CommitColumn(){
		var headerobj = document.COLUMNFORM.HEADER;
		var dirobj = document.COLUMNFORM.DIRECTION;
		var unitsobj = document.COLUMNFORM.UNITS;
		var cattypeobj = document.COLUMNFORM.CAT_TYPE;
		var hostobj = document.COLUMNFORM.HOST;
		var listobj = document.COLUMNFORM.LIST;
		var agenthostobj = document.COLUMNFORM.AGENT_HOST;
		var protoobj = document.COLUMNFORM.PROTO;
		var statusobj = document.COLUMNFORM.STATUS;
		var columnobj = parent.upper.document.MENUFORM.COLUMN;
		var header_ok = false;
		var str;
		var len;
		var dir;
		var units;
		var cattype;
		var cat;
		var i;

		// check for empty or spaces-only header:
		for( i = 0; i < headerobj.value.length; i++ ){
			if( headerobj.value.charAt( i ) != ' '
			&& headerobj.value.charAt( i ) != '\t' ){
				header_ok = true;
			}
		}
		if( header_ok == false ){
			alert( '$lang::edit_column_alert_header' );
			return false;
		}

		// check for valid host address/name:
		str = new String( hostobj.value );
		if( cattypeobj[ 0 ].checked == true
		&& str.search( /^\\d+\\.\\d+\\.\\d+\\.\\d+\$/ ) == -1
		&& str.search( /^\\w+(\\.\\w+)+\$/ ) == -1 ){
			alert( '$lang::edit_column_alert_host' );
			return false;
		}

		// add to the list:
		for( i = 0; i < dirobj.length && dirobj[ i ].checked != true; i++ );
		dir = dirobj[ i ].value;
		for( i = 0; i < unitsobj.length && unitsobj[ i ].checked != true; i++ );
		units = unitsobj[ i ].value;
		for( i = 0; i < cattypeobj.length && cattypeobj[ i ].checked != true; i++ );
		cattype = cattypeobj[ i ].value;

		// check if column already exists:
		for( i = 0; i < parent.ColumnList.length; i++ ){
END
	unless( $header ){	# new column is being added
		$msg = $lang::edit_column_alert_duplicate;
		$msg =~ s/%1/' + headerobj.value + '/;
		print <<END;
			if( headerobj.value == parent.ColumnList[ i ].header ){
				alert( '$msg' );
				return false;
			}
END
	}else{
		print <<END;
			if( parent.ColumnList[ i ].header == '$header' ){
				break;
			}
END
	}
	print <<END;
		}

		if( cattype == 'host' ) cat = hostobj.value;
		else if( cattype == 'list' ) cat = listobj[ listobj.selectedIndex ].value;
		else cat = cattype;

		parent.ColumnList[ i ] = new parent.Column(	headerobj.value,
								cattype,
								cat,
								dir,
								units, 
								agenthostobj.value,
								protoobj.value,
								statusobj.value
								);

		// add to select box in the menu frame:
		parent.upper.InitColumns();

		return true;
	}
</script>
<html>
<body bgcolor="$BGColor" onLoad="	parent.InitLists( document.COLUMNFORM.LIST, '' );
					InitColumn( '$header' );">
<form name="COLUMNFORM" action="$MySelf" method=post>
<center>
<table border>
<tr>
	<th bgcolor="$DarkColor" colspan=2>
END
	if( $header ){
		print <<END;
		<font color="$LightColor">$lang::edit_column_header_edit</font>
END
	}else{
		print <<END;
		<font color="$LightColor">$lang::edit_column_header_add</font>
END
	}
	print <<END;
	</th>
</tr>
<tr>
	<td bgcolor="$LightColor">
		$lang::edit_column_column_header<br>
		<input type=text width=200 name="HEADER"><br>
	</td>
	<td bgcolor="$LightColor" rowspan=2>
		$lang::edit_column_tags<p>
		$lang::edit_column_agent_host<br>
		<input type=text width=200 name="AGENT_HOST" value="*"><br>
		$lang::edit_column_proto<br>
		<input type=text width=200 name="PROTO" value="*"><br>
		$lang::edit_column_status<br>
		<input type=text width=200 name="STATUS" value="*"><br>
	</td>
</tr>
<tr>
	<td bgcolor="$LightColor">
		$lang::edit_column_units<br>
		<input type=radio name="UNITS" value="bytes"> $lang::edit_column_units_bytes<br>
		<input type=radio name="UNITS" value="kbytes" checked> $lang::edit_column_units_kbytes<br>
		<input type=radio name="UNITS" value="mbytes"> $lang::edit_column_units_mbytes<br>
		<input type=radio name="UNITS" value="gbytes"> $lang::edit_column_units_gbytes<br>
		<input type=radio name="UNITS" value="items"> $lang::edit_column_units_items<br>
	</td>
</tr>
<tr>
	<td bgcolor="$LightColor">
		$lang::edit_column_category
		<br>
		<input type=radio name="CAT_TYPE" value="host" checked>
		$lang::edit_column_category_host
		<br>
		<input type=text width=200 name="HOST">
		<br>
		<input type=radio name="CAT_TYPE" value="list">
		$lang::edit_column_category_list
		<br>
		<select name="LIST">
		<option value="none">	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
					...
					&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
		</select>
		<br>
		<input type=radio name="CAT_TYPE" value="total">
		$lang::edit_column_category_total
	</td>
	<td bgcolor="$LightColor">
		$lang::edit_column_direction<br>
		<input type=radio name="DIRECTION" value="to" checked> $lang::edit_column_direction_to<br>
		<input type=radio name="DIRECTION" value="from"> $lang::edit_column_direction_from<br>
		<input type=radio name="DIRECTION" value="both"> $lang::edit_column_direction_both<br>
	</td>
</tr>
</table>
<input type=hidden name="ACTION" value="$ActionEmpty">
<input type=submit value="$lang::edit_column_submit_ok" onClick="return CommitColumn();">
<input type=submit value="$lang::edit_column_submit_cancel">
</center>
</body>
</html>
END
}

############################################################################
# Edit group list or create a new one
############################################################################
sub edit_list
{
	my $list = shift;	# list to edit
	my $combosize = ( $list ? 10 : 8 );	# select box size
	my $msg;

	print <<END;
Content-type: text/html

<script language="JavaScript1.2">

	var OldSelected = -1;		// deselected group
	var HostGroups = new Array();	// groups for current group list
END
	if( $list ){
		print <<END;

	for( var i in parent.HostGroups[ '$list' ] ){
		HostGroups[ i ] = new parent.Group(	parent.HostGroups[ '$list' ][ i ].group,
							parent.HostGroups[ '$list' ][ i ].type,
							parent.HostGroups[ '$list' ][ i ].split );
	}

	//--------------------------------------------------------------------
	// Initialize group list contents
	//--------------------------------------------------------------------
	function InitGroups(){
		document.LISTFORM.GROUPS.options.length = parent.HostGroups[ '$list' ].length;
		for( var i = 0; i < parent.HostGroups[ '$list' ].length; i++ ){
			document.LISTFORM.GROUPS.options[ i ].value = parent.HostGroups[ '$list' ][ i ].group;
			document.LISTFORM.GROUPS.options[ i ].text = parent.HostGroups[ '$list' ][ i ].group;
		}
	}
END
	}else{
		print <<END;
	//--------------------------------------------------------------------
	// Initialize group list contents
	//--------------------------------------------------------------------
	function InitGroups(){
	}
END
	}

	print <<END;

	//--------------------------------------------------------------------
	// Display corresponding split option for a selected group
	// and commit split option for deselected one 
	//--------------------------------------------------------------------
	function GroupSplitOption(){
		var selectobj = document.LISTFORM.GROUPS;
		var radioobj = document.LISTFORM.SPLIT;
		var i;

		// commit [changed] split option for deselected group:
		if( OldSelected != -1 ){		// at least once something was already selected
			for( i = 0; i < radioobj.length && radioobj[ i ].checked != true; i++ );
			if( i == 1	// split rule is '?'
			&& HostGroups[ OldSelected ].type != 'domain' ){
				selectobj.selectedIndex = OldSelected;
				alert( '$lang::edit_list_alert_split' );
				return false;
			}
			HostGroups[ OldSelected ].split = radioobj[ i ].value;
		}

		// display split options according to the newly selected group:
		if( selectobj.selectedIndex != -1 ){	// at least something is now selected
			for( i = 0; i < radioobj.length && radioobj[ i ].value != HostGroups[ selectobj.selectedIndex ].split; i++ );
			radioobj[ i ].checked = true;
		}

		// memorize previously selected group:
		OldSelected = selectobj.selectedIndex;

		return true;
	}

	//--------------------------------------------------------------------
	// Check whether the given string looks like a domain name
	//--------------------------------------------------------------------
	function ValidateDomain( value ){
		var str = new String( value );
		return ( str.search( /^\\w+(\\.\\w+)*\$/ ) == -1 ? false : true );
	}

	//--------------------------------------------------------------------
	// Check whether the given string looks like a host ip-address or net
	//--------------------------------------------------------------------
	function ValidateNet( value ){
		str = new String( value );
		return ( str.search( /^\\d+\\.\\d+\\.\\d+\\.\\d+(\\/\\d+)?\$/ ) == -1 ? false : true );
	}

	//--------------------------------------------------------------------
	// Add new group
	//--------------------------------------------------------------------
	function AddGroup(){
		var groupsobj = document.LISTFORM.GROUPS;
		var nestedobj = document.LISTFORM.NESTED_LIST;
		var netdomainobj = document.LISTFORM.NET_DOMAIN;
		var addtypeobj = document.LISTFORM.ADD_TYPE;
		var splitobj = document.LISTFORM.SPLIT;
		var value;
		var type;
		var split;
		var last_index = groupsobj.options.length;
		var i;

		for( i = 0; i < splitobj.length && splitobj[ i ].checked != true; i++ );
		split = splitobj[ i ].value;

		if( addtypeobj[ 1 ].checked == true ){	// nested list
			value = nestedobj.options[ nestedobj.selectedIndex ].value;
			type = 'list';
		}else{		// ip-address, net or domain
			value = netdomainobj.value;
			if( ValidateNet( value ) ){
				type = 'net';
			}else if( ValidateDomain( value ) ){
				type = 'domain';
			}else{
				alert( '$lang::edit_list_alert_group' );
				return;
			}
		}
		for( i in HostGroups ){
			if( value == HostGroups[ i ].group ){
				alert( '$lang::edit_list_alert_duplicate' );
				return;
			}
		} 
		if( type != 'domain' && split == '?' ){
			alert( 'Selected accounting option is only applicable to groups specified as domains!' );
			return;
		}
		HostGroups[ last_index ] = new parent.Group( value, type, split );
		groupsobj.options.length++;
		groupsobj.options[ last_index ].value = value;
		groupsobj.options[ last_index ].text = value;
		groupsobj.selectedIndex = last_index;
		// erase the input field:
		if( type != 'list' ){
			netdomainobj.value = '';
		}
	}

	//--------------------------------------------------------------------
	// Delete selected group
	//--------------------------------------------------------------------
	function DeleteGroup(){
		var selectobj = document.LISTFORM.GROUPS;
		if( selectobj.selectedIndex == -1 ) return;
		for( i = selectobj.selectedIndex; i < selectobj.options.length - 1; i++ ){
			HostGroups[ i ] = HostGroups[ i + 1 ];
			selectobj.options[ i ].text = selectobj.options[ i + 1 ].text;
			selectobj.options[ i ].value = selectobj.options[ i + 1 ].value;
		}
		HostGroups.length--;
		selectobj.options.length--;
	}

	//--------------------------------------------------------------------
	// Commit list modifications
	//--------------------------------------------------------------------
	function CommitList(){
		var listname = document.LISTFORM.LIST.value;

		// first of all commit split option for last-selected group:
		if( GroupSplitOption() == false ) return false;

END
	unless( $list ){	# new list is being added 

		print <<END;	
		if( listname == '' ){
			alert( '$lang::edit_list_alert_name' );
		}

		// Check for duplication:
		for( var i in parent.GroupLists ){
			if( parent.GroupLists[ i ] == listname ){
				alert( 'List "' + listname + '" already exists!' );
				return false;
			}
		}

		// Adding new list object:
		parent.GroupLists[ parent.GroupLists.length ] = listname;
		parent.HostGroups[ listname ] = new Array();

		// Adding list name to select box of the menu frame:
		parent.InitLists( parent.upper.document.MENUFORM.LIST, '' );
END
	}
	print <<END;	
		parent.HostGroups[ listname ].length = HostGroups.length;
		for( i in HostGroups ){
			parent.HostGroups[ listname ][ i ] = new parent.Group(	HostGroups[ i ].group,
										HostGroups[ i ].type,
										HostGroups[ i ].split );
		}

		return true;
	}
</script>

<html>
<body bgcolor="$BGColor" onLoad="	parent.InitLists( document.LISTFORM.NESTED_LIST, '$list' );
					InitGroups();">
<form name="LISTFORM" action="$MySelf" method=post>
<center>
<table border>
<tr>
	<th bgcolor="$DarkColor" colspan=3>
END
	if( $list ){
		$msg = $lang::edit_list_edit;
		$msg =~ s/%1/$list/;
		print <<END;
		<font color="$LightColor">$msg</font>
END
	}else{
		print <<END;
		<font color="$LightColor">$lang::edit_list_add</font>
END
	}
	print <<END;
	</th>
</tr>
<tr>
	<td bgcolor="$LightColor">
END
	if( $list ){
		print <<END;
		<input type=hidden width=200 name="LIST" value="$list"><br>
END
	}else{
		print <<END;
		$lang::edit_list_name<br>
		<input type=text width=200 name="LIST"><br>
END
	}

	print <<END;
		$lang::edit_list_groups<br>
		<select size=$combosize width=200 name="GROUPS" onChange="return GroupSplitOption();">
		</select>
	</td>
	<td bgcolor="$LightColor">
		$lang::edit_list_new_group<br>
		<input type=radio name="ADD_TYPE" value="net_domain" checked>
		$lang::edit_list_new_group_net_domain<br>
		<input type=text width=200 name="NET_DOMAIN"><br>
		<input type=radio name="ADD_TYPE" value="nested">
		$lang::edit_list_new_group_nested
		<select name="NESTED_LIST">
		<option value="none">	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
					...
					&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
		</select>
		<br>
		<input type=button value="$lang::edit_list_submit_add_group" onClick="AddGroup();">
		<input type=button value="$lang::edit_list_submit_delete_group" onClick="DeleteGroup();">
	</td>
	<td bgcolor="$LightColor">
		$lang::edit_list_split<br>
		<input type=radio name="SPLIT" value="" checked> $lang::edit_list_split_no<br>
		<input type=radio name="SPLIT" value="?"> $lang::edit_list_split_domain<br>
		<input type=radio name="SPLIT" value="*"> $lang::edit_list_split_host
		<br>
	</td>
</tr>
</table>
<input type=hidden name="ACTION" value="$ActionEmpty">
<input type=submit value="$lang::edit_list_submit_ok" onClick="return CommitList();">
<input type=submit value="$lang::edit_list_submit_cancel">
</center>
</form>
</body>
</html>
END
}


############################################################################
# Create empty document
############################################################################
sub empty
{
	print <<END;
Content-type: text/html

<html>
<body bgcolor="$BGColor">
</body>
</html>
END
}


############################################################################
# Create frameset
############################################################################
sub frameset
{
	my ( $i, $j );		# loop counters
	my $key;		# a hash key
	my $group;		# group of hosts
	my $type;		# group type ('net'/'domain'/'list')
	my $split;		# split flag (''/'*'/'?')

	print <<END;
Content-type: text/html

<html>
<head>
<title>$lang::frameset_title</title>
</head>
<script language=JavaScript>

	var GroupLists = new Array();
	var ColumnList = new Array();
	var SortColumn = 1;

	//--------------------------------------------------------------------
	// Construcror for Group structure
	//--------------------------------------------------------------------
	function Group( group, type, split ){
		this.group = group;
		this.type = type;
		this.split = split;
	}

	//--------------------------------------------------------------------
	// Construcror for Column structure
	//--------------------------------------------------------------------
	function Column( header, cattype, cat, dir, units, agent_host, proto, status ){
		this.header = header;
		this.cattype = cattype;
		this.cat = cat;
		this.dir = dir;
		this.units = units;
		this.agent_host = agent_host;
		this.proto = proto;
		this.status = status;
	}

END
	$i = 0;
	foreach $key ( keys %lists ){
		print "	GroupLists[ $i ] = '$key';\n";
		$i++;
	}
	print "	var HostGroups = new Array();\n";
	foreach $key ( keys %lists ){
		print "	HostGroups[ '$key' ] = new Array();\n";
		$j = 0;
		foreach ( @{$lists{$key}} ){
			/^([*\?]?)(.+)$/;
			$split = $1;
			$group = $2;
			if( $group =~ /^\d+\.\d+\.\d+\.\d+(\/\d+)?$/ ){
				$type = 'net';
			}elsif( $group =~ /\./ ){
				$type = 'domain';
			}else{
				$type = 'list';
			}
			print "	HostGroups[ '$key' ][ $j ] = new Group( '$group', '$type', '$split' );\n";
			$j++;
		}
	}

	print <<END;
	//--------------------------------------------------------------------
	// Initialize pull-down list of group lists optionally excluding
	// the given list name
	//--------------------------------------------------------------------
	function InitLists( selectobj, exclude ) {
		if( GroupLists.length == 0 ){
			selectobj.options.length = 1;
			selectobj.options[ 0 ].value = '';
			selectobj.options[ 0 ].text = '               ...               ';
		}else{
			selectobj.options.length = GroupLists.length;
			var j = 0;
			for( var i = 0; i < GroupLists.length; i++ ){
				if( GroupLists[ i ] != exclude ){
					selectobj.options[ j ].value = GroupLists[ i ];
					selectobj.options[ j ].text = GroupLists[ i ];
					j++;
				}
			}
			// now shrink the options list if something was excluded:
			if( j < i ) selectobj.options.length = j;
		}
		selectobj.selectedIndex = 0;
}

</script>
<frameset rows="50%,*" border=1>
	<frame src="$MySelf?ACTION=$ActionMenu" name="upper">
	<frame src="$MySelf?ACTION=$ActionEmpty" name="lower">
</frameset>
</html>
END
}


############################################################################
# Create menu frame
############################################################################
sub menu
{
	my %traffic_type;		# available time periods for each traffic type
	my @keys;			# hash keys
	my $type;			# a traffic type
	my $time;			# an available time period
	my ( $i, $j );			# loop counters
	my $msg = $lang::menu_do_resolve;

	$msg =~ s/%1/@local_nets/;

	# Read the database names and decide which timeperiods are available:
	opendir( DIR, $prefix );
	foreach ( sort( grep( /\.from\.db$/, readdir( DIR ) ) ) ){
		/^([^\.]+)\.(.+)\.from\.db$/;
		$type = $1;
		$time = $2;
		push @{$traffic_type{$type}}, $time;
	}
	closedir( DIR );

	# Construct the output document :
	print <<END;
Content-type: text/html

<html>
<script language="JavaScript">

	var TimeOptions = new Array();
	var OldSelected = 0;		// previously selected column

	//--------------------------------------------------------------------
	// Construcror for DBDescr structure
	//--------------------------------------------------------------------
	function DBDescr( time_part, time_string ){
		this.time_part = time_part;
		this.time_string = time_string;
	}
END

	$i = 0;
	foreach $type ( keys %traffic_type ){
		print "	TimeOptions[ $i ] = new Array();\n";
		$j = 0;
		foreach ( @{$traffic_type{$type}} ){
			print "	TimeOptions[ $i ][ $j ] = new DBDescr( '$_', '" . get_timeperiod( $_ ) . "' );\n";
			$j++;
		}
		$i++;
	}

	print <<END;

	//--------------------------------------------------------------------
	// Replace the options list in <SELECT> box
	//--------------------------------------------------------------------
	function ReplaceOptions( selectobj, newoptions ){
		selectobj.options.length = newoptions.length;
		for( var i = 0; i < newoptions.length; i++ ){
			selectobj.options[ i ].value = newoptions[ i ].time_part;
			selectobj.options[ i ].text = newoptions[ i ].time_string;
		}
	}

	//--------------------------------------------------------------------
	// Setup the corresponding list of available timeperiods according to
	// the selected traffic type.
	//--------------------------------------------------------------------
	function ChangeType() {
		ReplaceOptions( document.MENUFORM.TIME,
				TimeOptions[ document.MENUFORM.TRAFFIC_TYPE.selectedIndex ] );
	}

	//--------------------------------------------------------------------
	// Init columns list
	//--------------------------------------------------------------------
	function InitColumns() {
		var selectobj = document.MENUFORM.COLUMN;
		if( parent.ColumnList.length > 0 ){
			selectobj.options.length = parent.ColumnList.length;
			for( var i = 0; i < selectobj.options.length; i++ ){
				selectobj.options[ i ].value = parent.ColumnList[ i ].header;
				selectobj.options[ i ].text = parent.ColumnList[ i ].header;
			}
		}else{
			selectobj.options.length = 1;
			selectobj.options[ 0 ].value = 'none';
			selectobj.options[ 0 ].text = '               ...               ';
		}
		selectobj.selectedIndex = 0;
		document.MENUFORM.SORT.checked = (	parent.SortColumn == 2
							? true
							: false );
		
	}

	//--------------------------------------------------------------------
	// Delete column
	//--------------------------------------------------------------------
	function DeleteColumn() {
		var selectobj = document.MENUFORM.COLUMN;
		for( var i = selectobj.selectedIndex; i < parent.ColumnList.length - 1; i++ ){
			parent.ColumnList[ i ] = parent.ColumnList[ i + 1 ];
		}
		parent.ColumnList.length--;

		// reset sort column index if sort column is being deleted:
		if( parent.SortColumn == selectobj.selectedIndex + 2 ) parent.SortColumn = 1;

		InitColumns();
		return false;	// we don't want to submit
	}

	//--------------------------------------------------------------------
	// Display corresponding sort option for a selected column
	// and commit sort column index for deselected one if changed
	//--------------------------------------------------------------------
	function SortColumn() {
		var columnobj = document.MENUFORM.COLUMN;
		var sortobj = document.MENUFORM.SORT;

		// commit [changed] sort option for deselected column:
		if( sortobj.checked == true ){
			parent.SortColumn = OldSelected + 2;
		}

		// re-display sort option according to the newly selected column:
		sortobj.checked = (	columnobj.selectedIndex + 2 == parent.SortColumn
					? true : false );

		// memorize previously selected column:
		OldSelected = columnobj.selectedIndex;

		return true;
	}

	//--------------------------------------------------------------------
	// Restore default sort column index if unchecked
	//--------------------------------------------------------------------
	function ResetSortColumn() {
		var sortobj = document.MENUFORM.SORT;

		if( sortobj.checked == false ) parent.SortColumn = 1;	// reset
		if( parent.ColumnList.length == 0 ) sortobj.checked = false;	// no column defined yet
	}

	//--------------------------------------------------------------------
	// Commit changes
	//--------------------------------------------------------------------
	function Commit(){
		var alllistsobj = document.MENUFORM.ALL_LISTS;
		var allcolumnsobj = document.MENUFORM.ALL_COLUMNS;
		var cattypeobj = document.MENUFORM.CAT_TYPE;
		var hostobj = document.MENUFORM.HOST;
		var str;

		// checking for defined columns:
		if( parent.ColumnList.length == 0 ){
			alert( '$lang::menu_alert_column' );
			return false;
		}

		// check for valid host address/name:
		str = new String( hostobj.value );
		if( cattypeobj[ 0 ].checked == true
		&& str.search( /^\\d+\\.\\d+\\.\\d+\\.\\d+\$/ ) == -1
		&& str.search( /^\\w+(\\.\\w+)+\$/ ) == -1 ){
			alert( '$lang::menu_alert_host' );
			return false;
		}

		SortColumn();

		// Group lists:
		alllistsobj.value = '';
		for( var i = 0; i < parent.GroupLists.length; i++ ){
			alllistsobj.value += parent.GroupLists[ i ];
			alllistsobj.value += ':';
			for( var j = 0; j < parent.HostGroups[ parent.GroupLists[ i ] ].length; j++ ){
				alllistsobj.value += parent.HostGroups[ parent.GroupLists[ i ] ][ j ].split;
				alllistsobj.value += parent.HostGroups[ parent.GroupLists[ i ] ][ j ].group;
				alllistsobj.value += ':';
			}
			alllistsobj.value += '\\n';
		}

		// Columns:
		allcolumnsobj.value = ''; 
		for( var i = 0; i < parent.ColumnList.length; i++ ){
			allcolumnsobj.value += parent.ColumnList[ i ].header;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].cat;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].dir;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].units;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].agent_host;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].proto;
			allcolumnsobj.value += ':';
			allcolumnsobj.value += parent.ColumnList[ i ].status;
			allcolumnsobj.value += '\\n';
		}

		// Sort column:
		document.MENUFORM.SORT_COLUMN.value = parent.SortColumn;

		return true;
	}
</script>

<body bgcolor="$BGColor" onLoad="	parent.InitLists( document.MENUFORM.LIST, '' );
					InitColumns();">
END

	print <<END;
<form name="MENUFORM" method=post action="$MySelf">
<center>
<table border>
<tr>
	<th bgcolor="$LightColor" colspan=2>
		<font color="$DarkColor">$lang::menu_header</font>
	</th
</tr>
<tr>
	<td bgcolor="$DarkColor">
		$lang::menu_traffic_type
		<select name="TRAFFIC_TYPE" onChange="ChangeType();">
END

	foreach ( keys %traffic_type ){
		print "<option>$_";
	}

	print <<END;

		</select>
	</td>
	<td bgcolor="$DarkColor">
		$lang::menu_timeperiod
		<select name="TIME">
END

	@keys = keys %traffic_type;
	foreach ( @{$traffic_type{$keys[ 0 ]}} ){
		print "<option value=$_>" . get_timeperiod( $_ ) . "\n";
	}

	print <<END;

		</select>
	</td>
</tr>
<tr>
	<td bgcolor="$DarkColor" rowspan=2>
		$lang::menu_category
		<br>
		<input type=radio name="CAT_TYPE" value="host" checked>
		$lang::menu_category_host
		<br>
		<input type=text size=19 name="HOST">
		<br>
		<input type=radio name="CAT_TYPE" value="list">
		$lang::menu_category_list
		<br>
		<select name="LIST">
		<option value="">...
		</select>
		<br>
		<input type=submit name="ACTION" value="$ActionEditList" onClick="document.MENUFORM.target='lower';">
		<input type=submit name="ACTION" value="$ActionAddList" onClick="document.MENUFORM.target='lower';">
		<br>
		<input type=radio name="CAT_TYPE" value="total">
		$lang::menu_category_total
		<br>
		<input type=radio name="CAT_TYPE" value="each">
		$lang::menu_category_each
	</td>
	<td bgcolor="$DarkColor">
		<input type=checkbox name="RESOLVE" value="true"> $msg
	</td>
</tr>
<tr>
	<td bgcolor="$DarkColor">
		$lang::menu_columns
		<select name="COLUMN" onChange="SortColumn();">
		<option value="">...
		</select>
		<br>
		<input type=submit name="ACTION" value="$ActionAddColumn" onClick="document.MENUFORM.target='lower';">
		<input type=submit name="ACTION" value="$ActionEditColumn" onClick="document.MENUFORM.target='lower';">
		<input type=submit name="ACTION" value="$ActionDeleteColumn" onClick="return DeleteColumn();">
		<br>
		<input type=checkbox name="SORT" onClick="ResetSortColumn();">
		$lang::menu_columns_sort
	</td>
</tr>
<tr>
	<td bgcolor="$DarkColor">
		$lang::edit_column_rounding<br>
		<input type=radio name="ROUNDING" value="nearest" checked> $lang::edit_column_rounding_nearest<br>
		<input type=radio name="ROUNDING" value="up"> $lang::edit_column_rounding_up<br>
		<input type=radio name="ROUNDING" value="down"> $lang::edit_column_rounding_down<br>
	</td>
	<td bgcolor="$DarkColor">
		$lang::menu_sort_order
		<br>
		<input type=radio name="SORT_DIR" value="asc" checked> $lang::menu_sort_order_asc<br>
		<input type=radio name="SORT_DIR" value="desc"> $lang::menu_sort_order_desc
	</td>
</tr>
</table>
<input type=hidden name="ALL_LISTS" value="">
<input type=hidden name="ALL_COLUMNS" value="">
<input type=hidden name="SORT_COLUMN" value="">

<input type=submit name="ACTION" value="$ActionModel" onClick="document.MENUFORM.target='lower'; return Commit();">
<input type=submit name="ACTION" value="$ActionReport" onClick="document.MENUFORM.target='report'; return Commit();">
<input type=submit name="ACTION" value="$ActionCancelAll" onClick="document.MENUFORM.target='_top';">
</center>
</form>
</body>
</html>

END
}


############################################################################
# Obtain a CGI parameter
############################################################################
sub get_param
{
	my $name = shift;

	if( $QueryString && $QueryString =~ /^(.+\&)?$name=([^&]+)/ ){
		return decode_url( $2 );
	}else{
		return undef;
	}
}


############################################################################
# Decode url-encoded text
############################################################################
sub decode_url
{
	my $value = shift;
	$value =~ tr/+/ /;
	$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
	return $value;
}


############################################################################
# Parse group lists specifications encoded in a single string
############################################################################
sub parse_lists
{
	my @str_lists = split /\r\n|\r|\n/, shift;
	my %lists = ();
	my $listname;
	my @groups;

	foreach ( @str_lists ){
		( $listname, @groups ) = split /\:/;
		$lists{$listname} = [ @groups ];
	}

	return %lists;
}


############################################################################
# Parse columns specifications encoded in a single string
############################################################################
sub parse_columns
{
	my @str_columns = split /\r\n|\r|\n/, shift;
	my @columns = ();
	my ( $header, $cat, $dir, $units, $agent_host, $proto, $status );

	foreach( @str_columns ){
		( $header, $cat, $dir, $units, $agent_host, $proto, $status ) = split /\:/;
		push @columns, {
			'header' => $header,
			'cat' => $cat,
			'dir' => $dir,
			'units' => $units,
			'agent_host' => $agent_host,
			'proto' => $proto,
			'status' => $status
		};
	}
	return @columns;
}


############################################################################
# Resolve ip address to name
############################################################################
sub resolve
{
	my $address = shift;
	my $name = '';
	my $bin_net;
	my $bin_mask;
	my $bin_addr = inet_aton( $address );
	my $belongs = 0;

	foreach ( @local_nets ) {
		if( /^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/ ){
			$bin_net = inet_aton( $1 );
			$bin_mask = pack( "N", ~( 0xffffffff >> $2 ) );
			if( ( $bin_addr & $bin_mask ) eq $bin_net ){
				$belongs = 1;
				last;
			}
		}
	}
	return $address unless $belongs;
	$name = gethostbyaddr( $bin_addr, AF_INET );
	return $name ? $name : $address;
}


############################################################################
# Get human-readable representation of timeperiod from the database filename
############################################################################
sub get_timeperiod
{
	my $filename_part = shift;

	if( $filename_part eq 'today' ){
		return "$lang::timeperiod_today";
	}elsif( $filename_part eq 'month' ){
		return "$lang::timeperiod_month";
	}elsif( $filename_part =~ /^(.+)(\d\d)$/ ){
		return "$2/$1";
	}else{	# failed to translate
		return $filename_part;
	}
}


### main () ###

my $action = get_param( 'ACTION' );	# requested action

# strip the directory path:
$MySelf =~ s#.*\/##;

if( !defined $action ){
	frameset();
}elsif( $action eq $ActionMenu ){
	menu();
}elsif( $action eq $ActionEmpty ){
	empty();
}elsif( $action eq $ActionAddList ){
	edit_list( '' );
}elsif( $action eq $ActionEditList ){
	edit_list( get_param( 'LIST' ) );
}elsif( $action eq $ActionAddColumn ){
	edit_column( '' );
}elsif( $action eq $ActionEditColumn ){
	edit_column( get_param( 'COLUMN' ) );
}elsif( $action eq $ActionCancelAll ){
	frameset();
}elsif( $action eq $ActionModel ){
	model(	get_param( 'TRAFFIC_TYPE' ),
		get_param( 'TIME' ),
		get_param( 'CAT_TYPE' ),
		get_param( 'HOST' ),
		get_param( 'LIST' ),
		get_param( 'SORT_COLUMN' ),
		get_param( 'SORT_DIR' ),
		get_param( 'RESOLVE' ),
		get_param( 'ALL_COLUMNS' ),
		 );
}elsif( $action eq $ActionReport ){
	report(	get_param( 'TRAFFIC_TYPE' ),
		get_param( 'TIME' ),
		get_param( 'CAT_TYPE' ),
		get_param( 'HOST' ),
		get_param( 'LIST' ),
		get_param( 'SORT_COLUMN' ),
		get_param( 'SORT_DIR' ),
		get_param( 'RESOLVE' ),
		get_param( 'ALL_COLUMNS' ),
		get_param( 'ROUNDING' ),
		get_param( 'ALL_LISTS' ),
		 );
}else{
	error();
}

### end of main() ###


