#!/usr/bin/perl -Tw
#
#  LMS version 1.11.7 Bastet
#
#  Copyright (C) 2001-2009 LMS Developers
#
#  Please, see the doc/AUTHORS for more information about authors!
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License Version 2 as
#  published by the Free Software Foundation.
#
#  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
#  USA.
#
#  $Id: lms-makewarnings,v 1.37 2009/01/13 07:45:28 alec Exp $

use strict;
use DBI;
use Config::IniFiles;
use Getopt::Long;
use vars qw($configfile $quiet $help $version);
use POSIX qw(strftime);
use Time::Local;

sub mask2prefix($)
{
	my $mask = shift @_;
	my @tmp = split('\.',$mask,4);
	my $q = sprintf("%b%b%b%b",$tmp[0],$tmp[1],$tmp[2],$tmp[3]);
	$q =~ s/0*$//;
	if ($q =~ /0/) {
		print " You idiot. error in mask\n";
	}
	my $len = length($q) ;
	return $len;
}

sub prefix2mask
{
	my $host_bits = 32-$_[0];
	my $net_mask = (~0 << $host_bits) & 0xffffffff;
	my @bytes = unpack "CCCC", pack "N", $net_mask;
	my $dec_rep = sprintf "%d.%d.%d.%d", @bytes;
	return $dec_rep;
}					

sub matchip($$$)
{
	my ($ip,$net,$mask) = @_;
	my $prefix = mask2prefix($mask);
	my $bmask = 2**32 - 2**(32-$prefix);
	my @net = split('\.',$net,4);
	my $bnet = dotquad2u32($net);
	if(($bnet & $bmask)!= $bnet) {
		print "EEediot net &mask != net\n"; return 1==0
	}
	my $bip = dotquad2u32($ip);
	return (($bip&$bmask) == $bnet);
}

sub u32todotquad($)
{
	my $p = shift @_;
	return sprintf "%d.%d.%d.%d", ($p>>24)&0xff,($p>>16)&0xff, ($p>>8)&0xff,$p&0xff;
}

sub dotquad2u32($)
{
	my $dq = shift||'0.0.0.0';
	my @dq = split('\.',$dq,4);
	return ((($dq[0] << 8) + $dq[1] << 8) + $dq[2] << 8) + $dq[3];
}

sub isprivate($)
{
	my ($ip) = @_;
	return matchip($ip,"192.168.0.0","255.255.0.0") || matchip($ip,"10.0.0.0","255.0.0.0") || matchip($ip,"172.16.0.0","255.240.0.0");
}

my $_version = '1.11.7 Bastet';

my %options = (
	"--config-file|C=s"	=>	\$configfile,
	"--quiet|q"		=>	\$quiet,
	"--help|h"		=>	\$help,
	"--version|v"		=>	\$version
);

Getopt::Long::config("no_ignore_case");
GetOptions(%options);

if($help)
{
	print STDERR <<EOF;
lms-makewarnings, version $_version
(C) 2001-2009 LMS Developers

-C, --config-file=/etc/lms/lms.ini	alternate config file (default: /etc/lms/lms.ini);
-h, --help			print this help and exit;
-v, --version			print version info and exit;
-q, --quiet			suppress any output, except errors;

EOF
	exit 0;
}

if($version)
{
	print STDERR <<EOF;
lms-makewarnings, version $_version
(C) 2001-2009 LMS Developers
EOF
	exit 0;
}

if(!$configfile)
{
	$configfile = "/etc/lms/lms.ini";
}

if(!$quiet)
{
	print STDOUT "lms-makewarnings, version $_version\n";
	print STDOUT "(C) 2001-2009 LMS Developers\n";
	print STDOUT "Using file $configfile as config.\n";
}

if(! -r $configfile)
{
	print STDERR "Fatal error: Unable to read configuration file $configfile, exiting.\n";
	exit 1;
}

my $ini = new Config::IniFiles -file => $configfile;
print @Config::IniFiles::errors;

my $networks_list = $ini->val('warnings', 'networks') || '';
my $excluded_networks_list = $ini->val('warnings', 'excluded_networks') || '';
my $customergroups_list = $ini->val('warnings', 'customergroups') || '';
my $cfile = $ini->val('warnings', 'config_file') || '/etc/rc.d/rc.warnings';
my $cuid = $ini->val('warnings', 'config_owneruid') || '0';
my $cgid = $ini->val('warnings', 'config_ownergid') || '0';
my $cperm = $ini->val('warnings', 'config_permission') || '700';
my $redir = $ini->val('warnings', 'redirect_address') || '127.0.0.1:80';
my $ipbin = $ini->val('warnings', 'iptables_binary') || '/sbin/iptables';
my $chain = $ini->val('warnings', 'chain') || 'WARNINGS';
my $limit = $ini->val('warnings', 'limit') || '0';
my $invoice_expiration_check = $ini->val('warnings', 'invoice_expiration_check') || '0';

my $dbtype = $ini->val('database', 'type') || 'mysql';
my $dbhost = $ini->val('database', 'host') || 'localhost';
my $dbuser = $ini->val('database', 'user') || 'root';
my $dbpasswd = $ini->val('database', 'password') || '';
my $dbname = $ini->val('database', 'database') || 'lms';

my $dbase;

if($dbtype =~ /mysql/)
{
	$dbase = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
}
elsif($dbtype eq "postgres")
{
	$dbase = DBI->connect("DBI:Pg:dbname=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
}
else
{
	print STDERR "Fatal error: unsupported database type: $dbtype, exiting.\n";
	exit 1;
}

use constant CONTINUOUS => 6;
use constant YEAR       => 5;
use constant QUARTER    => 4;
use constant MONTH      => 3;
use constant WEEK       => 2;
use constant DAY        => 1;
use constant DISPOSABLE => 0;

open(WARNFILE, ">$cfile") or die("Fatal error: Unable to write $cfile, exiting.\n");

print WARNFILE $ipbin." -t nat -F ".$chain."\n\n";

my $allnetworks = "";

my $dbq = $dbase->prepare("SELECT name FROM networks");
$dbq->execute();
while (my $row = $dbq->fetchrow_hashref()) {
	$allnetworks = "$allnetworks $row->{'name'}";
}
$dbq->finish();

if(!$networks_list)
{
	$networks_list = $allnetworks;
}

my @networks = split ' ',$networks_list;

if($excluded_networks_list)
{
    my @excluded_networks = split ' ', $excluded_networks_list;
    foreach my $excluded_network (@excluded_networks)
    {
	@networks = grep(!/^$excluded_network$/, @networks);
    }
}

my $inicustomergroups = $customergroups_list;

if(!$customergroups_list)
{
	$dbq = $dbase->prepare("SELECT name FROM customergroups");
	$dbq->execute();
	while (my $row = $dbq->fetchrow_hashref()) 
	{
		$customergroups_list = "$customergroups_list $row->{'name'}";
	}
	$dbq->finish();
}

my @customergroups = split ' ',$customergroups_list;

my $currtime = strftime("%s",localtime());
my $plimit = 0;
if (substr($limit, length($limit) - 1, 1) eq "%")
{
	$plimit = 1;
	$limit = substr($limit, 0, length($limit) - 1);
}

#
# Build nodes list
#

$dbq = $dbase->prepare("SELECT id, name FROM customergroups");
$dbq->execute();
my %customergrouplist;

while (my $row = $dbq->fetchrow_hashref())
{
	$customergrouplist{$row->{'name'}} = $row->{'id'};
}
$dbq->finish();

my %notexpired;
$dbq = $dbase->prepare("SELECT documents.customerid AS customerid, SUM(invoicecontents.value) AS notexpired 
		FROM invoicecontents LEFT JOIN documents ON invoicecontents.docid = documents.id 
		WHERE documents.type = 1 AND cdate + paytime * 86400 > $currtime 
		GROUP BY documents.customerid ORDER BY documents.customerid");
$dbq->execute();
while (my $row = $dbq->fetchrow_hashref())
{
	$notexpired{$row->{'customerid'}} = $row->{'notexpired'};
}
$dbq->finish();

$dbq = $dbase->prepare("SELECT customers.id AS id, lastname, name, SUM(cash.value) AS balance FROM customers LEFT JOIN cash ON customers.id = cash.customerid GROUP BY customers.id, lastname, name HAVING SUM(cash.value) < 0");
$dbq->execute();
while (my $row = $dbq->fetchrow_hashref())
{
	my $customergroupscount = 0;
	foreach my $key2 (@customergroups)
	{
		my $sdbq = $dbase->prepare("SELECT count(customerid) AS total FROM customerassignments WHERE customerid=$row->{'id'} AND customergroupid=".$customergrouplist{$key2});
		$sdbq->execute();
		my $srow = $sdbq->fetchrow_hashref();
		$sdbq->finish();
		if ($srow->{'total'} eq 1)
		{
			$customergroupscount++;
		}
	}
	my $srow;
	if($customergroupscount || !$inicustomergroups)
	{
		my $balance = $row->{'balance'};
		my $sdbq = $dbase->prepare("SELECT MAX(at) AS at, SUM((value * (100 - discount)) / 100) AS value FROM assignments, tariffs WHERE tariffid = tariffs.id AND period = ".MONTH." AND (datefrom <= $currtime OR datefrom = 0) AND (dateto >= $currtime OR dateto = 0) AND customerid = $row->{'id'} GROUP BY customerid HAVING SUM(value * ((100 - discount) / 100)) > 0");
		$sdbq->execute();
		$srow = $sdbq->fetchrow_hashref();
		$sdbq->finish();

		if (!defined($srow->{'value'}))
		{
			$srow->{'value'} = 0;
		}
		if((!$plimit && $balance <= $limit) || ($plimit && $balance <= ($limit * -$srow->{'value'}) / 100))
		{
			if($invoice_expiration_check)
			{
				if($notexpired{$row->{'id'}} && $notexpired{$row->{'id'}} * -1 <= $balance) { next; }
				print STDERR "CID:$row->{'id'}\thas expired invoices.\n" if not $quiet;
			}

			$sdbq = $dbase->prepare("SELECT ipaddr, name FROM nodes WHERE ownerid='$row->{'id'}'");
			$sdbq->execute();
			while ($srow = $sdbq->fetchrow_hashref())
			{
			        my $tmpname = lc($srow->{'name'});
			        my $ipaddr = u32todotquad($srow->{'ipaddr'});
				my $networkscount = 0;
				foreach my $key (@networks)
				{
					my $sdbq2 = $dbase->prepare("SELECT address, mask FROM networks WHERE name = UPPER('$key')");
					$sdbq2->execute();
					while (my $srow2 = $sdbq2->fetchrow_hashref()) 
					{
						my $net = u32todotquad($srow2->{'address'});
						if (matchip($ipaddr, $net, $srow2->{'mask'}))
						{
							$networkscount++;
						}
					}
					$sdbq2->finish();
				}
				if ($networkscount)
				{
					print WARNFILE "# $tmpname\n".$ipbin." -t nat -A ".$chain." -p tcp -s ".$ipaddr." -j DNAT --to ".$redir."\n\n";
				}
			}
			$sdbq->finish();
		}
	}
}

$dbq->finish();
$dbase->disconnect();
close(WARNFILE);
chown $cuid, $cgid, $cfile or print "Warning! Unable to set owner of $cfile to $cuid.$cgid.\n";
chmod oct($cperm), $cfile or print "Warning! Unable to set permission $cperm to $cfile.\n";
