#!/usr/bin/perl

use CGI;
use DBI;
use XML::Simple;
use Crypt::OpenSSL::RSA; 
use Convert::PEM;
use Data::Dumper;

my $log_dir = '/var/www/stat/cgi-bin/easysoft/xml/';
my $xml_dir = '/var/www/stat/cgi-bin/easysoft/xml/';
my $log_file = "test.log";
my $sign_file = "sign.bin";
my $xml_file = "";
#my $public_key = '/var/www/stat/cgi-bin/easysoft/easysoft-publickey.pem';
my $public_key = '/var/www/stat/cgi-bin/easysoft/EasySoftPublicKey2.pem';
my $private_key = '/var/www/stat/cgi-bin/easysoft/welan_easysoft.key';
my $private_pass = "121212";

my $CategoryId = "95";
my $mid;

# load additional services
#require '/usr/local/nodeny/nodeny.cfg.pl';
#foreach my $i (1..31) {
#	if ($srvs{$i}=~/^(.+)-(.+)$/) 
#	{
#		$srv_n[$i]=$1;
#		$srv_p[$i]=$2;
#	} else {
#		$srv_n[$i]='';
#		$srv_p[$i]=0;
#	}
#}

my $q = new CGI;

if ($q->param('POSTDATA') eq "")
{
	print $q->header(-charset => 'windows-1251');
	exit;
}

$DSN="DBI:mysql:database=$db_name;host=localhost;mysql_connect_timeout=$db_conn_timeout";
unless ($dbh = DBI->connect($DSN,$user,$pw)) 
{
	print $q->header(-charset => 'windows-1251');
	print "Connection to database failed";
	exit;
}

$dbh->do("SET NAMES UTF8");

my $xml = XML::Simple->new(ForceArray => 1, KeepRoot => 1);
my $data = $xml->XMLin($q->param('POSTDATA'));

my $OUT = "";

if (&CheckSign($q->param('POSTDATA'),$data->{Request}[0]->{Sign}[0]) == 1)
{
	$OUT .= &Check($data) if (exists $data->{Request}[0]->{Check});
	$OUT .= &Payment($data) if (exists $data->{Request}[0]->{Payment});
	$OUT .= &Confirm($data) if (exists $data->{Request}[0]->{Confirm});
	$OUT .= &Cancel($data) if (exists $data->{Request}[0]->{Cancel});
}
else
{
        $OUT = "<Response>\n";
        $OUT .= "<StatusCode>-1</StatusCode>\n";
        $OUT .= "<StatusDetail>ERR: Error checking signatures</StatusDetail>\n";
        $OUT .= "<DateTime>".&curr_time."</DateTime>\n";
        $OUT .= "<Sign></Sign>\n";
        $OUT .= "</Response>\n";

}

print $q->header(-charset => 'windows-1251');
print $OUT;

exit;

###################################################################################################

sub log
{
	my ($time);
	open LOG, ">>$log_dir$log_file";
	$time = CORE::localtime;
	print LOG "$time: $_[0]\n";
	close LOG;
}

sub curr_time
{
	my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=CORE::localtime(time);
	return sprintf "%4d-%02d-%02dT%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec;
}

sub StrWithoutSign()
{
	my ($in_data, $sign) = @_;
	$in_data =~ s/(^|)$sign(|$)/$1 && $2/eg;
	return $in_data;
}

sub StrWithSign()
{
	my ($in_data, $sign) = @_;
	$in_data =~ m/^([\s\S]+<Sign>)([\s\S]+)/;

	return $1.$sign.$2;

}

sub CheckSign()
{
	my ($in_data,$es_sign) = @_;

        my $public_key_str = "";
        open(PUB,$public_key) || die "public key: $!";
        read(PUB,$public_key_str,-s PUB);
        close(PUB);

	$sign =  pack 'H*', $es_sign;

        my $rsa = Crypt::OpenSSL::RSA->new_public_key($public_key_str);

	&log($es_sign);
	&log($sign);
	&log($in_data);


        if ($rsa->verify(&StrWithoutSign($in_data,$es_sign), $sign))
        {
		return 1;
	}

	return -1;
}


sub readPrivateKey {
	my ($file,$password) = @_;
	my $key_string;

	if (!$password) {
		open(PRIV,$file) || die "$file: $!";
		read(PRIV,$key_string,-s PRIV); 
		close(PRIV);
	} else {
		$key_string = &decryptPEM($file,$password);
	}
	return $key_string;
}

sub decryptPEM {
	my ($file,$password) = @_;

	my $pem = Convert::PEM->new(Name => 'RSA PRIVATE KEY', 
					ASN  => qq(RSAPrivateKey SEQUENCE {
							version INTEGER,
							n INTEGER,
							e INTEGER,
							d INTEGER,
							p INTEGER,
							q INTEGER,
							dp INTEGER,
							dq INTEGER,
							iqmp INTEGER
					}
				));
	my $pkey = $pem->read(Filename => $file, Password => $password);
	return $pem->encode(Content => $pkey);
}

sub Signing()
{
	my ($in_data) = @_;

	my $key_string = readPrivateKey($private_key,$private_pass);
	my $private = Crypt::OpenSSL::RSA->new_private_key($key_string) || die "$!";
	my $sign = $private->sign($in_data);

	return (unpack("H*",$sign));
}

sub Check()
{
	my $out = "";
	my ($in_data) = shift;

 	my $status_code = -1; 
	my $status_detail='ERR: Account does not exist';  

	my ($sth,$p);
        $sth=$dbh->prepare("SELECT id, mid, fio, name, contract FROM users WHERE contract='".$in_data->{Request}[0]->{Check}[0]->{Account}[0]."'");
	$sth->execute;

    	if ($p = $sth->fetchrow_hashref) {
		$status_code = 0; 
  		$status_detail='OK'; 

		$mid = $p->{mid} == 0 ? $p->{id} : $p->{mid};

		$sth2 = $dbh->prepare("SELECT u.id as id, u.balance as balance, p.price as abonplata, u.srvs as srvs  FROM users u, plans2 p
		                       WHERE u.paket=p.id AND u.id='$mid'");
		$sth2->execute;

		if ($pm = $sth2->fetchrow_hashref)
		{

    			$srvs_sum = 0;
 			$sr = $pm->{srvs};
			if (!($pm->{srvs} & 0x80000000)) 
			{
				for (my $i=1;$i<32;$i++,$sr>>=1) 
				{
					next unless $srv_n[$i];
					next if !($sr & 1);
					$srvs_sum += $srv_p[$i];
				}
			}

    			$p->{abonplata} = $pm->{abonplata};
    			$p->{balance} = $pm->{balance} - $pm->{abonplata} - $srvs_sum;
		}
		else
		{
			$status_code = -1;
               		$status_detail='Err: Master account does no exist';
		}
	}

	$out = "<Response>\n";
	$out .= "<StatusCode>$status_code</StatusCode>\n";
	$out .= "<StatusDetail>$status_detail</StatusDetail>\n";
	$out .= "<DateTime>".&curr_time."</DateTime>\n";
	$out .= "<Sign></Sign>\n";
	$out .= "<AccountInfo>\n";

	if ($status_code == 0)
	{
		$out .= "<Login>".$p->{Name}."</Login>\n";
		$out .= "<FIO>".$p->{fio}."</FIO>\n";
		$out .= "<Balance>".$p->{balance}."</Balance>\n";

		if ($p->{balance} < $p->{abonplata})
		{
			$add = $p->{abonplata} - $p->{balabce} + 10; 
			$out .= "<Pay>".$add."</Pay>\n";
		}
		else
		{
			$add = $p->{abonplata} + 10;
			$out .= "<Pay>".$add."</Pay>\n";
		}
	}
	$out .= "</AccountInfo>\n";
	$out .= "</Response>\n";

  	return &StrWithSign($out, uc(&Signing($out)));
}

sub Payment()
{
	my $in_data = shift;
	my $out = "";
	my $paymentid = time;
        my $status_code = -1;
	my $status_detail='ERR: User does not exist';  

	
	my $p;
        my $sth=$dbh->prepare("SELECT id, mid, fio, name, contract FROM users WHERE contract='".$in_data->{Request}[0]->{Payment}[0]->{Account}[0]."'");
       	$sth->execute;

        if ($p = $sth->fetchrow_hashref) {
        	$status_code = 0;
	        $status_detail='OK';

		$mid = $p->{mid} == 0 ? $p->{id} : $p->{mid};
	}

        $out = "<Response>\n";
        $out .= "<StatusCode>$status_code</StatusCode>\n";
        $out .= "<StatusDetail>$status_detail</StatusDetail>\n";
        $out .= "<DateTime>".&curr_time."</DateTime>\n";
        $out .= "<Sign></Sign>\n";
        $out .= "<PaymentId>$paymentid</PaymentId>\n";
        $out .= "<Mid>$mid</Mid>\n";
	$out .= "</Response>\n";

	my $xml = XML::Simple->new(forcearray => 1, keeproot => 1);
	my $response = $xml->XMLin($out);

        my $res = {};
	$res->{opt}[0]->{payment_request} = $in_data;
	$res->{opt}[0]->{payment_response} = $response;

	open my $fh, '>:encoding(utf-8)', $xml_dir.$paymentid or die "open($xml_dir.$paymentid): $!";
	$xml->XMLout($res, keyattr=>[], outputfile => $fh);

        $out = "<Response>\n";
        $out .= "<StatusCode>$status_code</StatusCode>\n";
        $out .= "<StatusDetail>$status_detail</StatusDetail>\n";
        $out .= "<DateTime>".&curr_time."</DateTime>\n";
        $out .= "<Sign></Sign>\n";
        $out .= "<PaymentId>$paymentid</PaymentId>\n";
	$out .= "</Response>\n";

	return &StrWithSign($out, uc(&Signing($out)));
}

sub Confirm()
{
        my $in_data = shift;
        my $out = "";
        my $status_code = -1;
        my $status_detail='SOME_ERROR';
        my $data = {};

	$xml_file = $in_data->{Request}[0]->{Confirm}[0]->{PaymentId}[0] if exists($in_data->{Request}[0]->{Confirm}[0]->{PaymentId}); 
	my $xml = XML::Simple->new(ForceArray => 1, KeepRoot => 1);

	if (-e $xml_dir.$xml_file) 
	{
        	$data = $xml->XMLin($xml_dir.$xml_file);

		if ($data->{opt}[0]->{payment_response}[0]->{Response}[0]->{PaymentId}[0] eq $in_data->{Request}[0]->{Confirm}[0]->{PaymentId}[0])
		{
               		$status_code = 0;
			$status_detail='OK';
		
			my $p;
		        my $sth = $dbh->prepare("SELECT DATE_FORMAT(FROM_UNIXTIME(time),'%Y-%m-%d %H:%i:%s') as time, cash, mid FROM pays
				               WHERE category='".$CategoryId."' AND reason='$data->{opt}[0]->{payment_response}[0]->{PaymentId}[0]'");
		        $sth->execute;

		        if ($sth->rows == 0)
			{

				$dbh->do("INSERT INTO pays SET
							   mid='".$data->{opt}[0]->{payment_response}[0]->{Response}[0]->{Mid}[0]."',
							   cash='".$data->{opt}[0]->{payment_request}[0]->{Request}[0]->{Payment}[0]->{Amount}[0]."',
							   time=UNIX_TIMESTAMP('".$data->{opt}[0]->{payment_response}[0]->{Response}[0]->{DateTime}[0]."'),
							   admin_id=0,
							   admin_ip=0,
							   office=0,
							   bonus='y',
							   reason='$data->{opt}[0]->{payment_response}[0]->{Response}[0]->{PaymentId}[0]',
							   coment='Easysoft OrderId ".$data->{opt}[0]->{payment_request}[0]->{Request}[0]->{Payment}[0]->{OrderId}[0]."',
							   type=10,
							   category=$CategoryId");

				$dbh->do("UPDATE users SET state='on', balance=balance + ".$data->{opt}[0]->{payment_request}[0]->{Request}[0]->{Payment}[0]->{Amount}[0]." WHERE id='".$data->{opt}[0]->{payment_response}[0]->{Response}[0]->{Mid}[0]."'");

				$dbh->do("UPDATE users SET state='on' WHERE mid='".$data->{opt}[0]->{payment_response}[0]->{Response}[0]->{Mid}[0]."'");
			}
			else
			{
        	                $status_code = -1;
	                        $status_detail='SOME_ERROR';
			}
		}
	        else
		{
                	$status_code = -1;
			$status_detail='SOME_ERROR';
		}	
	}

        $out = "<Response>\n";
        $out .= "<StatusCode>$status_code</StatusCode>\n";
        $out .= "<StatusDetail>$status_detail</StatusDetail>\n";
        $out .= "<DateTime>".&curr_time."</DateTime>\n";
        $out .= "<Sign></Sign>\n";
	if ($status_code == 0)
	{
        	$out .= "<OrderDate>".&curr_time."</OrderDate>\n";
	}
	$out .= "</Response>\n";

        if (!-e $xml_dir.$xml_file)
        {
		$xml_file = "err_".time;
	}

       	my $response = $xml->XMLin($out);

        $data->{opt}[0]->{confirm_request} = $in_data;
       	$data->{opt}[0]->{confirm_response} = $response;

        open my $fh, '>:encoding(utf-8)', $xml_dir.$xml_file or die "open($xml_dir.$xml_file): $!";
	$xml->XMLout($data, keyattr=>[], outputfile => $fh);

	return &StrWithSign($out, uc(&Signing($out)));
}

sub Cancel()
{
	my $in_data = shift;
	my $out = "";
	my $status_code = -1;
	my $status_detail='SOME_ERROR';

	$xml_file = $in_data->{Request}[0]->{Cancel}[0]->{PaymentId}[0] if exists($in_data->{Request}[0]->{Cancel}[0]->{PaymentId});
	my $xml = XML::Simple->new(ForceArray => 1, KeepRoot => 1);
	my $data = {};

	if (-e $xml_dir.$xml_file)
	{
		$data = $xml->XMLin($xml_dir.$xml_file);

		if ($data->{opt}[0]->{payment_response}[0]->{Response}[0]->{PaymentId}[0] eq $in_data->{Request}[0]->{Cancel}[0]->{PaymentId}[0])
		{
			$status_code = 0;
			$status_detail='OK';
		}
	}

	$out = "<Response>\n";
	$out .= "<StatusCode>$status_code</StatusCode>\n";
	$out .= "<StatusDetail>$status_detail</StatusDetail>\n";
	$out .= "<DateTime>".&curr_time."</DateTime>\n";
        $out .= "<Sign></Sign>\n";
        $out .= "<CancelDate>".&curr_time."</CancelDate>\n";
	$out .= "</Response>\n";

        if (!-e $xml_dir.$xml_file)
	{
		$xml_file = "err_".time;
	}

	my $response = $xml->XMLin($out);

	$data->{opt}[0]->{cancel_request} = $in_data;
	$data->{opt}[0]->{cancel_response} = $response;

	open my $fh, '>:encoding(UTF-8)', $xml_dir.$xml_file or die "open($xml_dir.$paymentid): $!";
       	$xml->XMLout($data, KeyAttr=>[], OutputFile => $fh);

        return &StrWithSign($out, uc(&Signing($out)));
}

