#!/usr/bin/perl

use strict;
use Fcntl;
use POSIX;
use Socket;
use IO::Handle;
use strict;

my $arg = join(' ',@ARGV);

my @opts = map { [ split(/\=/,$_) ] } @ARGV;
my $ve_id = undef;
my $sve_ip = undef;
my $path = undef;
my $wdir = undef;
my $user = undef;
my $group = undef;
my $test_mode = 0;

foreach my $opt (@opts) {
	$ve_id = $opt->[1] if $opt->[0] eq '--vps_id';
	$sve_ip = $opt->[1] if $opt->[0] eq '--service_ve_ip';
	$path = $opt->[1] if $opt->[0] eq '--path';
	$wdir = $opt->[1] if $opt->[0] eq '--workdir';
	$user = $opt->[1] if $opt->[0] eq '--user';
	$group = $opt->[1] if $opt->[0] eq '--group';
	$test_mode = 1 if $opt->[0] eq '--test_mode';
}

&exit_error("VPS ID is invalid or not specified")
	unless $ve_id>0;
&exit_error("service VE IP not specified")
	unless $sve_ip;
&exit_error("local path not specified or does not exists")
	unless (length($path) && -d $path);
&exit_error("working directory on remove VPS not specified")
	unless length($wdir);
&exit_error("user not specified")
	unless length($user);

$group = $user unless length($group);

my $mkdir_packet = <<PACKET;
<packet version="2.6.0">
	<target>filem</target>
	<data>
		<filem>
			<mkdir>
				<veid>%%VEID%%</veid>
				<cwd>%%WDIR%%</cwd>
				<path>%%DIRPATH%%</path>
				<mode>755</mode>
				<recursively/>
			</mkdir>
		</filem>
	</data>
</packet>
PACKET

my $root_dir = &encode_base64('/');
my $dir_path = &encode_base64($wdir);

$mkdir_packet =~ s/\%\%VEID\%\%/$ve_id/g;
$mkdir_packet =~ s/\%\%WDIR\%\%/$root_dir/g;
$mkdir_packet =~ s/\%\%\DIRPATH\%\%/$dir_path/g;

my $chown_packet = <<PACKET;
<packet version="2.6.0">
<target>filem</target>
<data>
<filem>
<chown>
<veid>%%VEID%%</veid>
<cwd>%%CWD%%</cwd>
<path>%%CPATH%%</path>
<recursively/>
<wildcard/>
<owner>
<user>%%USER%%</user>
<group>%%GROUP%%</group>
</owner>
</chown>
</filem>
</data>
</packet>
PACKET

my $chown_publish_dir_packet = <<PACKET;
<packet version="2.6.0">
<target>filem</target>
<data>
<filem>
<chown>
<veid>%%VEID%%</veid>
<cwd>%%WDIR%%</cwd>
<path>%%DIRPATH%%</path>
<wildcard/>
<owner>
<user>%%USER%%</user>
<group>%%GROUP%%</group>
</owner>
</chown>
</filem>
</data>    
</packet>
PACKET

$chown_publish_dir_packet =~ s/\%\%VEID\%\%/$ve_id/g;
$chown_publish_dir_packet =~ s/\%\%WDIR\%\%/$root_dir/g;
$chown_publish_dir_packet =~ s/\%\%\DIRPATH\%\%/$dir_path/g;
$chown_publish_dir_packet =~ s/\%\%USER\%\%/$user/g;
$chown_publish_dir_packet =~ s/\%\%GROUP\%\%/$group/g;

my $chmoddatadir_packet = <<PACKET;
<packet version="2.6.0">
<target>filem</target>
<data>
<filem>
<chmod>
    <veid>%%VEID%%</veid>
    <cwd>%%CWD%%</cwd>
    <path>%%CPATH%%</path>
    <mode>511</mode>
    <recursively/>
</chmod>
</filem>
</data>
</packet>
PACKET

my $cr_socket = <<PACKET;
<packet version="2.6.0">
<target>file_transfer</target>
<data>
<create_socket/>
</data>
</packet>
PACKET

my $upl_file = <<PACKET;
<packet version="2.6.0">
<target>filem</target>
<data>
<filem>
<upload>
<veid>%%VEID%%</veid>
<file>
<path>%%PATH%%</path>
<size>%%SIZE%%</size>
</file>
</upload>
</filem>
</data>
</packet>
PACKET

my $ex_file = <<PACKET;
<packet version="2.6.0">
<target>progm</target>
<data>
<progm>
<exec>
<veid>%%VEID%%</veid>
%%ARGS%%
<cred>
<chdir>%%CHDIR%%</chdir>
</cred>
<stdio>
<output/>
<error/>
</stdio>
</exec>
</progm>
</data>
</packet>
PACKET

my $filem_move_packet = <<PACKET;
<packet version="3.0.0" id="235" progress="on">
<target>filem</target>
<data>
<filem>
<move>
<veid>%%VEID%%</veid>
<cwd>%%CWD%%</cwd>
<path>%%PATH%%</path>
<dst_path>%%DPATH%%</dst_path>
</move>
</filem>
</data>
</packet>
PACKET

my $filem_stat_packet = <<PACKET;
<packet version="3.0.0" id="149" progress="on">
<target>filem</target>
<data>
<filem>
<stat>
<veid>%%VEID%%</veid>
<cwd>%%CWD%%</cwd>
<path>%%PATH%%</path>
<cred>
<user>%%USER%%</user>
<group>%%GROUP%%</group>
</cred>
</stat>
</filem>
</data>
</packet>
PACKET

my $packet_list = <<PACKET;
<packet version="3.0.0" progress="on">
	<target>filem</target>
	<data>
		<filem>
			<list>
				<veid>%%VEID%%</veid>
				<cwd>%%DIR%%</cwd>
				<path>%%DIR%%</path>
				<info>
					<type/>
				</info>
			</list>
		</filem>
	</data>
</packet>
PACKET
$packet_list =~ s/\%\%VEID\%\%/$ve_id/g;

my $packet_remove = <<PACKET;
<packet version="3.0.0">
	<target>filem</target>
	<data>
		<filem>
			<remove>
				<veid>%%VEID%%</veid>
				<cwd>%%WDIR%%</cwd>
				<path>%%ITEM%%</path>
			</remove>
		</filem>
	</data>
</packet>
PACKET
$packet_remove =~ s/\%\%VEID\%\%/$ve_id/g;


############################################################################
##                                                                        ##
## Test mode                                                              ##
##                                                                        ##
############################################################################
if ($test_mode) {
	warn "vps_publish: testing vps connection";
	&exit_error("failed to get test connection")
		unless &test();
	exit(0);
} else {
	warn "vps_publish: try to publish website";
}
############################################################################

## Site content archive file name
my $arc_file = 'vpspublish_archive.tar.gz';
## Full path to site content archive on local system
my $local_arc = $path.'/'.$arc_file;
## Full path to site content archive on remote system
my $remote_arc = $wdir.'/'.$arc_file;


## Tar site content directory
my $cmd  = "/bin/tar -cz -f $local_arc -C $path . >/dev/null 2>&1";
my $output = `$cmd `;
#&exit_error("$output");
if(($? >> 8) != 0) {
	&exit_error("failed to create site archive - $output");
}

## Connect to VZAgent in service VPS
my ($rd,$wr,$pid) = &vza_ssh_connect("/usr/bin/ssh","-oPasswordAuthentication=no","-oProtocol=1","-lvzagent0","-T","-oStrictHostKeyChecking=no","-oConnectionAttempts=1",$sve_ip);

## Read greetings
my $packet = undef;
my $resp = &recv_packet($rd);
&exit_error("failed to get greeting from VZAgent")
	unless &check_ok($resp);
## Send create_socket packet
&send_packet($wr,$cr_socket);
my $resp = &recv_packet($rd);

if(&check_ok($resp)) {
	#:INFO: adobrenko@ create dirname where publish site
	&send_packet($wr,$mkdir_packet);
	&recv_packet($rd);

	#:INFO: adobrenko@ chown user and group for dirname where publish site
	&send_packet($wr,$chown_publish_dir_packet);
	$resp = &recv_packet($rd);
	#print $resp; exit(1);
	
	my @fstat = stat($local_arc);
	&exit_error("failed to stat archive file $local_arc")
		unless ( @fstat && $fstat[7] >= 0 );
	my $arc_size = $fstat[7];
	my $en_remote_arc = &encode_base64($remote_arc);
	$packet = $upl_file;
	$packet =~ s/\%\%VEID\%\%/$ve_id/g;
	$packet =~ s/\%\%PATH\%\%/$en_remote_arc/g;
	$packet =~ s/\%\%SIZE\%\%/$arc_size/g;
	&send_packet($wr,$packet);
	$resp = &recv_packet($rd);
	if($resp =~ /\<switch_to_raw(\/)??\>/m) {
		## Write local archive in raw mode to remote VPS
		sysopen(ARC, $local_arc, O_RDONLY)
		    or &exit_error("failed to open file \'$local_arc\' for reading - $!");
		my ($buf,$offset) = ('',0);
		my $ln = undef;
		while (defined($ln = sysread(ARC, $buf, 262144, $offset))) {
			last if $ln == 0;
			my $r = syswrite($wr, $buf, $ln, $offset);
			&exit_error("failed to upload file \'$local_arc\' into remote VPS - $!")
				unless $r == $ln;
			$offset+=$ln;
		}
		close(ARC);
		unless(defined($ln)) {
			close(ARC);
			&exit_error("failed to read from file \'$local_arc\' - $!");
		}
		## Check switch_to_vza
		$resp = &recv_packet($rd);
		## Omit filem Ok
		&recv_packet($rd);
		if($resp =~ /\<switch_to_vza(\/)??\>/m) {
			## Extract archive on remote side
			my $ex_cmd ='';
			foreach my $arg ('/bin/tar','-xz','-f'.$arc_file) {
				$ex_cmd.="<argv>".&encode_base64($arg)."</argv>";
			}
			$packet = $ex_file;
			my $chdir = &encode_base64($wdir);
			$packet =~ s/\%\%VEID\%\%/$ve_id/g;
			$packet =~ s/\%\%ARGS\%\%/$ex_cmd/g;
			$packet =~ s/\%\%CHDIR\%\%/$chdir/g;
			&send_packet($wr,$packet);
			$resp = &recv_packet($rd);
			if($resp =~ /\<status\>(.*)\<\/status\>/m) {

				my $status = $1;
				chomp($status);
				if($status eq "0") {
					## Change ownership
					$packet = $chown_packet;
					$packet =~ s/\%\%VEID\%\%/$ve_id/g;
					my $tdir = &encode_base64($wdir);
					$packet =~ s/\%\%CWD\%\%/$tdir/g;
					$tdir = &encode_base64('*');
					$packet =~ s/\%\%CPATH\%\%/$tdir/g;
					$packet =~ s/\%\%USER\%\%/$user/g;
					$packet =~ s/\%\%GROUP\%\%/$group/g;
					&send_packet($wr,$packet);
					$resp = &recv_packet($rd);
					if(&check_ok($resp)) {
						my $ex_cmd ='';

						#foreach my $arg ('find','./','-type','d','-exec','chmod','0755','{}',';') {
						#	$ex_cmd.="<argv>".&encode_base64($arg)."</argv>";
						#}

						foreach my $arg ('find','./','-exec','chmod','0755','{}',';') {
							$ex_cmd.="<argv>".&encode_base64($arg)."</argv>";
						}

						$packet = $ex_file;
						my $chdir = &encode_base64($wdir);
						$packet =~ s/\%\%VEID\%\%/$ve_id/g;
						$packet =~ s/\%\%ARGS\%\%/$ex_cmd/g;
						$packet =~ s/\%\%CHDIR\%\%/$chdir/g;
						&send_packet($wr,$packet);
						$resp = &recv_packet($rd);
						if($resp =~ /\<status\>(.*)\<\/status\>/m) {
							my $status = $1;
							chomp($status);
							if($status eq "0") {
								$packet = $chmoddatadir_packet;
								$packet =~ s/\%\%VEID\%\%/$ve_id/g;
								my $tdir = &encode_base64($wdir);
								$packet =~ s/\%\%CWD\%\%/$tdir/g;
								$tdir = &encode_base64('data');
								$packet =~ s/\%\%CPATH\%\%/$tdir/g;
								&send_packet($wr,$packet);
								$resp = &recv_packet($rd);
								if($resp =~ /\<status\>(.*)\<\/status\>/m) {
									my $status = $1;
									chomp($status);
									if($status eq "0") {
										$packet = $filem_stat_packet;
										my $chdir = &encode_base64($wdir);
										my $from_path = &encode_base64('index.shtml');
										$packet =~ s/\%\%VEID\%\%/$ve_id/g;
										$packet =~ s/\%\%PATH\%\%/$from_path/g;
										$packet =~ s/\%\%CWD\%\%/$chdir/g;
										$packet =~ s/\%\%USER\%\%/$user/g;
										$packet =~ s/\%\%GROUP\%\%/$group/g;
										&send_packet($wr,$packet);
										$resp = &recv_packet($rd);
										if($resp =~ /\<code\>(.*)\<\/code\>/m) {
											warn "vps_publish: can not find index.shtml, may be moved allready?";
											&exit_success();	##no file, not need to move something
										} else {
											$packet = $filem_move_packet;
											my $chdir = &encode_base64($wdir);
											my $from_path = &encode_base64('index.shtml');
											my $to_path = &encode_base64('index.shtml.orig');
											$packet =~ s/\%\%VEID\%\%/$ve_id/g;
											$packet =~ s/\%\%PATH\%\%/$from_path/g;
											$packet =~ s/\%\%DPATH\%\%/$to_path/g;
											$packet =~ s/\%\%CWD\%\%/$chdir/g;
											&send_packet($wr,$packet);
											$resp = &recv_packet($rd);
											if($resp =~ /\<filem\>(.*)\<\/filem\>/ms) {
												my $status = $1;
												$status =~ s|(.*)(<ok/>)(.*)|$2|ms;
												if($status eq "<ok/>") {
													warn "vps_publish: index.shtml to index.shtml.orig moved successfully";
													&exit_success();
												}
												else {
													##can not move, not error because site content transfered already
													my $error = &packet_error($resp,'output');
													$error = &decode_base64($error) if length($error);
													warn "vps_publish: can not move index.shtml to index.shtml.orig - $error";
													&exit_success();
												}
											}
										}
									} else {
										my $error = &packet_error($resp,'output');
										$error = &decode_base64($error) if length($error);
										&exit_error("failed to change permissions of storage - $error");
									}
								}
							}
							else {
								my $error = &packet_error($resp,'output');
								$error = &decode_base64($error) if length($error);
								&exit_error("failed to change permissions of site content - $error");
							}
						}
						else {
							&exit_error("failed to change permissions of site content - invalid response");
						}
					}
					else {
						&exit_error("failed to change owner of site content - ".&packet_error($resp));
					}
				}
				else {
					my $error = &packet_error($resp,'output');
					$error = &decode_base64($error) if length($error);
					&exit_error("failed to extract archive on remote VPS - $error");
				}
			}
			else {
				&exit_error("failed111 to extract archive on remote VPS - invalid response");
			}
		}
	}
	else {
		&exit_error("failed to switch to raw mode - ".&packet_error($resp));
	}
}
else {
	&exit_error("create_socket command failed - ".&packet_error($resp));
}

#after gzunzip need delete vpspublish_archive.tar.gz 
#print $wdir; exit(1);
my $remove_arc_path = &encode_base64($wdir);
my $remove_item = &encode_base64($arc_file);
$packet_remove =~ s/\%\%WDIR\%\%/$remove_arc_path/g;
$packet_remove =~ s/\%\%\ITEM\%\%/$remove_item/g;
&send_packet($wr,$packet_remove);
&recv_packet($rd);

&exit_success();

sub exit_success {
	kill 9,$pid;
	waitpid($pid,0);
	warn("vps_publish: files transferred successfully");
	exit(0);
}

sub check_ok {
	my $resp = shift;
	return ($resp && $resp =~ /\<ok\/\>/m) ? 1 : 0;
}

sub packet_error {
	my $resp = shift;
	my $tag = shift || 'message';

	if($resp =~ /\<$tag\>(.*)\<\/$tag\>/m) {
		return $1;
	}

	return undef;
}

sub send_packet {
	my $wr = shift;
	my $packet = shift;
	# warn("Send:");
	# warn($packet);
	my $bytes = syswrite($wr,$packet."\0",length($packet)+1);
	return $bytes;
}

sub recv_packet {
	my $rd = shift;
	my $buf = undef;

	my $old_sep = $/;
	$/ = "\0";

	while(<$rd>) {
		$buf = $_;
		# warn("Rec:");
		# warn($buf);
		chomp($buf);
		length($buf) ? last : next;
	}
	$/ = $old_sep;
	return $buf;
}

sub vza_ssh_connect {
	my ($rd, $wr);
	my $rd_ssh = undef; my $wr_ssh = undef;

	&exit_error("failed to connect to VZAgent")
	    unless (pipe ($rd, $wr_ssh) && pipe ($rd_ssh, $wr));

	$rd->autoflush(1);
	$wr->autoflush(1);
	$rd_ssh->autoflush(1);
	$wr_ssh->autoflush(1);

	my $pid = fork();
	&exit_error("fork failed")
	    unless defined($pid);

	if (!$pid)
	{
		# Ssh channel
		close($rd); close($wr);

		close(STDOUT); open(STDOUT, ">&".fileno($wr_ssh));
		close(STDIN); open(STDIN, "<&".fileno($rd_ssh));

		exec(@_);
		exit(1);
	}

	# Main program
	close($wr_ssh);
	close($rd_ssh);
	return ($rd,$wr,$pid);
}

sub encode_base64 {
	my ($str, $eol) = @_;
	use integer;
	return '' unless length($str);
	$eol = '' unless defined($eol);
	my $res = pack("u", $str);
	# Remove first character of each line, remove newlines
	$res =~ s|^.||mg;
	$res =~ s|\n||g;
	$res =~ tr|` -_|AA-Za-z0-9+/|;
	# fix padding at the end
	my $padding = (3 - length($str) % 3) % 3;
	$res =~ s/.{$padding}$/'=' x $padding/e
		if $padding;
    if (length $eol) {
		$res =~ s/(.{1,76})/$1$eol/g;
    }
	return $res;
}

sub decode_base64 {
	my $str = shift;
	use integer;
	return '' unless length($str);
	## removing SAFENESS if it is
	if ($str =~ /[_\-]/){
		$str =~ tr/\_/\//;
		$str =~ tr/\-/\+/;
	}
	$str =~ tr|A-Za-z0-9+=/||cd;
	$str =~ s|=+$||;
	$str =~ tr|A-Za-z0-9+/| -_|;
	return '' unless length $str;
	my ($i, $l, $uustr);
	$l = length($str) - 60;
	for ($i = 0; $i <= $l; $i += 60) {
		$uustr .= "M".substr($str, $i, 60);
	}
	$str = substr($str, $i);
	# any leftover chars
	if ($str ne '') {
		$uustr .= chr(32 + length($str)*3/4).$str;
	}
	return unpack ("u", $uustr);
}

sub exit_error {
	my $error = shift;
	print "Error: $error\n";
	warn "vps_publish error: $error";
	exit(1);
}

sub test () {
	my $result = 0;
	my ($rd, $wr, $pid) = &vza_ssh_connect(
		"/usr/bin/ssh",
		"-oPasswordAuthentication=no",
		"-oProtocol=1",
		"-lvzagent0",
		"-T",
		"-oStrictHostKeyChecking=no",
		"-oConnectionAttempts=1",
		$sve_ip);
	my $packet = undef;
	my $resp = &recv_packet($rd);
	$result = &check_ok($resp);
	if ($result) {
		&send_packet($wr,$cr_socket);
		$resp = &recv_packet($rd);
		$result = &check_ok($resp);
	}
	return $result;
}
