package SB::Utils;

use strict;
use warnings;
use IO::Socket;
use SB::Logging qw($Logger FATAL WARNING ERROR INFO DEBUG);
use User::pwent;
use User::grent;
require SB::Defaults;
require SB::OS;
use Digest::MD5 qw(md5_hex);

BEGIN {
	use Exporter;
	our (@ISA, $VERSION, @EXPORT, @EXPORT_OK);
	$VERSION =  4.00;
	@ISA = qw(Exporter);
    @EXPORT    = qw();
	@EXPORT_OK = qw(getHostAddr listenHostForPort parseIni writeIni restorecon
					isPleskPresent checkParameters configHashDifference 
					changeOwner getUuid run parsePlainConfig writePlainConfig trim);
}

use SB::Defaults qw($CONFIG $SB_PREFIX $SB_HTDOCS);
use SB::Messages;

sub checkPHPSanity($$$) {
	my $php = shift;
	my $ver = shift;
	my $iface = shift;

	return 0 unless (-x $php);

	my $php_ver=`echo '<?php echo phpversion(); ?>' | $php -q 2>/dev/null`;
	chomp($php_ver);
	if ($php_ver =~ m/^(\d\.\d)/) {
		$php_ver = $1;
		substr($php_ver, 1, 1) = '';
	}
	else {
		$php_ver = 0;
	}

	return 0 unless ($php_ver && int($php_ver) >= int($ver));
	my $v = `$php -v 2>/dev/null`;
	return 0 unless ($v =~ m/$iface/ig);

	my $php_info=`$php -m`;
    if ($php_info !~ /ionCube/ig) {
        return 0;
    }

	if ($php_info !~ /sitebuilder3/ig) {
		return 0;
	}
	return 1;
}

sub getPHPIniPath($) {
	my $phpcgi_path = shift;
	my $name;

	open(FIL, "$phpcgi_path -i |") or return;
	while (<FIL>) {
		chomp;
		#$Logger->Log(DEBUG, $_);
		if (/Configuration File/i) {
			s|.*<td class="v">([^<]*)</td>.*|$1|g;
			$name = $_;
			last;
		}
	}
	close(FIL);
	return trim($name);
}

sub checkParameters() {
    my $owner	= $CONFIG->{'internal'}->{"httpd_owner"};
    my $db_host = $CONFIG->{'database'}->{'host'};
    my $db_port = $CONFIG->{'internal'}->{"db_port"};
    my $db_name = $CONFIG->{'database'}->{"dbname"};
    my $db_type = $CONFIG->{'database'}->{"dbtype"};
	my $phpcli_path = $CONFIG->{'internal'}->{"phpcli_path"};
	my $phpcgi_path = $CONFIG->{'internal'}->{"phpcgi_path"};
	my $sbDbName = $CONFIG->{'database'}->{"dbname"};
    my $result = 0;

    #Try to connect to MySQL server
	$Logger->Log(DEBUG, "Checking MySQL credentials");
    my $dbh = SB::DB::getMysqlHandle();
    unless (defined($dbh)) {
        $result = "Error connecting to MySQL on $db_host:$db_port\n"
				 ."Reason: " . $DBI::errstr;
        $Logger->Log(FATAL, $result);
    }

	my $query = qq{select Db from db where Db="$sbDbName"};
	my $sth = $dbh->prepare($query);
	$sth->execute();
	my @row=$sth->fetchrow_array;
	$Logger->Log(DEBUG, "$sbDbName database exists") if defined($row[0]);

	$Logger->Log(FATAL, "No suitable PHP found") unless
		(checkPHPSanity($phpcli_path, 51, "cli") && checkPHPSanity($phpcgi_path, 51, "cgi"));

	$Logger->Log(DEBUG, "Found suitable PHP CGI executable: $phpcgi_path");
	$Logger->Log(DEBUG, "Found suitable PHP CLI executable: $phpcli_path");

    my ($user, $group) = split (':', $owner);

    unless (defined($user) && defined($group)) {
        $Logger->Log(FATAL, getMessage("httpd_owner_invalid"));
    }
	$Logger->Log(DEBUG, "Webserver user:group = $owner");
	$Logger->Log(DEBUG, "Parameters appear sane");

	return 0;
}

sub getHostAddr {
    open(P, "/sbin/ifconfig|") or 
        $Logger->Log(FATAL, "Cannot start process: ifconfig: $!");

    while(<P>) {
        if (/inet[\sA-Za-z:]+([\d\.]+)\s+/) { 
            my $ip = $1;
            return $ip if $ip !~ /127\./;
        }
    }
    close(P);

	my $sb_hostname = $CONFIG->{'internal'}->{'sb_hostname'};
	my (undef, $code) = run("ping -c1 $sb_hostname > /dev/null");
    if ($code) {
        open(P, "host $sb_hostname|") or 
            $Logger->Log(FATAL, "Cannot start process: host: $!");
        while(<P>) {
            return $1 if (/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/);
        }
        close(P);
    } else {
         $Logger->Log(WARNING, "Cannot reliably determine server IP, using 127.0.0.1");
         return "127.0.0.1";
    }
}

# brief Check if anyone's listening on tcp/$_[0]:$_[1]
# param $_[0] local IP address or hostname to check
# param $_[1] local port
# return 0 if port is free
#        1 otherwise
sub listenHostForPort($$)
{
    my ($host, $port) = @_;

    my $proto = getprotobyname('tcp');
    my $iaddr = inet_aton($host) or return 0;

    my $paddr = sockaddr_in($port, $iaddr);

    my $res = socket(SOCKET, PF_INET, SOCK_STREAM, $proto) 
        || $Logger->Log(FATAL, "Socket error: $!");
    # No, we don't wanna share
    setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, pack("l", 0)) 
        || $Logger->Log(FATAL, "setsockopt: $!");

    # Try to bind to the port specified and see if it fails (that's
    # exaclty what apache does)
    $res = bind(SOCKET, $paddr);

    close SOCKET || return 0;
    return $res ? 0 : 1;
}

sub changeOwner($) {
    my $owner = shift;

    my ($user,$group) = ($owner =~ /^(.*):(.*)/ );

    my $pw = getpwnam($user);
    my $gr = getgr($group);
    unless (defined($pw) && defined($gr)) {
        $Logger->Log(ERROR, "User not found: $owner. " . getMessage("try_chown"));
        return;
    }
	foreach my $path   ("$SB_HTDOCS/sites",
						"$SB_HTDOCS/tmp",
						"$SB_HTDOCS/images/logos",
						"$SB_PREFIX/tmp",
						"$SB_PREFIX/etc") {
		my (undef, $code) = run("chown -R $owner $path");
		if ($code > 0) {
			$Logger->Log(ERROR, "Failed to change ownership of $path");
		}
	}
}

sub getUuid() {
    my $random = int(rand(20000));
    my $hash = md5_hex($random);

    return substr($hash, 0, 8) . '-' . substr($hash, 8, 4) . '-' 
			. substr($hash, 12, 4). '-' . substr($hash, 16, 4). '-' 
			. substr($hash, 20);
}

sub trim($) {
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}

sub parseIni($$) {
    my $name = shift;
    my $default = shift;
	my $config = SB::Defaults::makeConfigCopy($default);
    my $section;
    open(F, $name) or return $config;

    while (<F>) {
        chomp;
        next unless $_;
        next if /^\s*;/;
        if (/^\[([-_ \w]+)\]$/) {
            $section = $1;
            $config->{$section} = {} unless defined($config->{$section});
            next;
        }
		my @tokens = split '=';
		my $key = trim(shift @tokens);
		my $value = trim(join ('=', @tokens));
		$config->{$section}->{$key} = $value;
    }

    close(F);
    return $config;
}

sub writeIni($$) {
    my $name = shift;
    my $config = shift;

    return unless $config;
    open(F, "> $name") or return;

	print F ";; Automatically generated, do not change\n\n";
    while (my($section, $pairs) = each(%$config)) {
		#next if $section eq "internal";
        print F "[$section]\n";
        while (my($key,$value) = each(%$pairs)) {
            print F "$key = $value\n" if defined($value);
        }
		print F "\n";
    }
    close(F);
}

sub parsePlainConfig($) {
	my $file = shift;
 	unless (-e $file && open(FILE, $file)) {
 		return undef;
 	}
 	my $res = { };
 	while (<FILE>){
		next if /^\s*#/;
 		my ($name, $val) = /(\S+)=(\S+)/;
 		$res->{$name} = $val if $name && $val;
 	}
    close(FILE);
 	return $res;
}

sub writePlainConfig($$) {
	my $file = shift;
	my $cfg = shift;
	
	return unless $cfg;

	open(F, "> $file") or return 1;
	while (my ($name, $val) = each(%$cfg)) {
		print F "$name=$val\n";
	}
	close(F);
	return 0;
}

sub valuesDiffer {
	my ($oldValue, $newValue) = @_;
	if (defined($newValue) && !defined($oldValue)) {
		$oldValue = "";
	}
	return defined($oldValue) && defined($newValue) &&	$oldValue ne $newValue;
}

sub configHashDifference {
	my $def = shift;
	my $config = shift;
	my %diffKeys = ();
	while (my ($sectionname, $sectionvalue) = each(%$def)) {
		foreach (keys %$sectionvalue) {
			my $key = $_;
			my $defaultValue = $sectionvalue->{$key};
			my $newValue = $config->{$sectionname}->{$key};
			if (valuesDiffer($defaultValue, $newValue)) {
				$diffKeys{"$sectionname:$key"} = "1";
			}
		}
	}
	return \%diffKeys;
}

sub restorecon($) {
    my $file = shift;
	if (( -x "/sbin/restorecon") && (SB::OS::getOsName() eq "redhat")) {
		run("/sbin/restorecon $file");
	}
}

sub run {
	#:FIXME: manual escaping is bad
	my $command = join(' ', grep(defined,@_));
	#$command =~ s|'|\'|;
	$Logger->Log(DEBUG, "Running '$command'");
	my $output = `$command 2>&1`;
	my $code = $?;
	if ($code == -1) {
		$Logger->Log(ERROR, "Cannot run '$command'");
		return;
	}
	else  {
		$code >>= 8;
		$Logger->Log(DEBUG, "Output [$code]: $output");
		return ($output, $code);
	}
}

sub detectVZ() {
	return -e "/proc/vz/veinfo";
}

1;

