# Copyright 1999-2015. Parallels IP Holdings GmbH. All Rights Reserved.
package genericAgent;

use ApacheAgent;
use QmailAgent;
use SendmailAgent;
use MailAccount;
use DomainInfo;
use Logging;

use strict;
use warnings;

use vars qw|@ISA|;

#agent.include.pl is removed. This module should be rewritten.
#if (-f 'agent.include.pl') {
#  require 'agent.include.pl';
#} else {
#  require '../agent.include.pl';
#}

sub new {
  my $self = {};
  bless($self, shift);
  $self->_init(@_);
  return $self;
}

sub _init {
  my ($self, $storage, $status, $mysqldbpassword, $options ) = @_;

  #my %o = ${$options};
  $self->{do_web} = 1 if exists $options->{web};
  $self->{do_mail} = 1 if exists $options->{mail};
  $self->{do_db} = 1 if exists $options->{db};

# Information dumps.
#
# apache_dump - contain information about virtual hosts gathered from apache configuration files
#
# Format:
#           <domain name> => Apache::ConfigFile object.
#
# apache_ip_dump - contain information about conformity virtual hosts and their ip addresses
#
# Format:
#           <domain name> => <ip address>
#
# qmail_dump - contain information about mailnames, domains, redirects and so on gathered from qmail conf. files
#
# Format:
#
#           <domain name> =>  { <mail name> => { directory => value, password => value  etc.}
#

  $self->{apache_dump} = {};
  $self->{mail_dump} = {};
  $self->{db_dump} = {};

  $self->{storage} = $storage;
  $self->{dump_status} = $status;

  $self->{db_password} = $mysqldbpassword;

  $self->constructApacheDump() if $self->{do_web};
  $self->constructMailDump() if $self->{do_mail};

  $self->constructDbDump() if $self->{do_db};

# Hosting types.
#
# full_hosting - domain has physical hosting, mail and databases.
# mail_hosting - domain has only mail.
# db_hosting   - domain has only databases.
# web_hosting  - domain has only physical hosting.
# mail_web_hosting - domain has physical hosting and mail.
# db_web_hosting - domain has physical hosting and databases.
#

  $self->{full_hosting} = {};
  $self->{mail_hosting} = {};
  $self->{db_hosting} = {};
  $self->{web_hosting} = {};
  $self->{mail_web_hosting} = {};
  $self->{db_web_hosting} = {};

#  Merge all hash key

  $self->{all_domains} = [];

  my %temporary;

  foreach my $inc ($self->{apache_dump}, $self->{mail_dump}, $self->{db_dump}) {
    while (my ($k, $v) = each %$inc) {
       next if exists $temporary{$k};
       $temporary{$k} = 1;
    }
  }

  @{$self->{all_domains}} = keys %temporary;

# Sorting domains by hosting type

  foreach my $dom (@{$self->{all_domains}}) {
    if (exists $self->{apache_dump}->{$dom} and exists $self->{mail_dump}->{$dom} and exists $self->{db_dump}->{$dom}) {
      $self->{full_hosting}->{$dom} = 1;
    }elsif(exists $self->{apache_dump}->{$dom} and exists $self->{mail_dump}->{$dom} and not exists $self->{db_dump}->{$dom}) {
      $self->{mail_web_hosting}->{$dom} = 1;
    }elsif(exists $self->{apache_dump}->{$dom} and not exists $self->{mail_dump}->{$dom} and exists $self->{db_dump}->{$dom}) {
      $self->{db_web_hosting}->{$dom} = 1;
    }elsif(exists $self->{apache_dump}->{$dom} and not exists $self->{mail_dump}->{$dom} and not exists $self->{db_dump}->{$dom}) {
      $self->{web_hosting}->{$dom} = 1;
    }elsif(not exists $self->{apache_dump}->{$dom} and exists $self->{mail_dump}->{$dom} and not exists $self->{db_dump}->{$dom}) {
      $self->{mail_hosting}->{$dom} = 1;
    }elsif(not exists $self->{apache_dump}->{$dom} and not exists $self->{mail_dump}->{$dom} and exists $self->{db_dump}->{$dom}) {
      $self->{db_hosting}->{$dom} = 1;
    }
  }
}

sub noContent{
  my ($self) = @_;
  $self->{no_content} = 1
}

sub constructApacheDump {
  my ($self) = @_;

  $self->{apache} = ApacheAgent->new();
  return if ! $self->{apache}->get_installed();

  $self->{domaininfo} = $self->{apache}->getApacheDomainInfo();

  for( my $i=0; $i<$self->{domaininfo}->get_count(); $i++ ){
    my $dname = $self->{domaininfo}->get_domain_name( $i );
    $self->{apache_dump}->{$dname} = $dname;
  }
}

sub constructMailDump(){
  my ($self) = @_;

  $self->{mailaccounts} = MailAccount->new();
  $self->{qmail} = QmailAgent->new();
  $self->{qmail}->backup_accounts( $self->{mailaccounts} ) if $self->{qmail}->get_installed();

  $self->{sendmail} = SendmailAgent->new();
  $self->{sendmail}->backup_accounts( $self->{mailaccounts} ) if $self->{sendmail}->get_installed();

  for( my $i=0; $i<$self->{mailaccounts}->get_count(); $i++ ){
     my $accountname = $self->{mailaccounts}->get_account_name( $i );
     my $dname = $self->{mailaccounts}->get_account_domain( $accountname );
     @{$self->{mail_dump}->{$dname}} = () if not exists $self->{mail_dump}->{$dname};
     push( @{$self->{mail_dump}->{$dname}}, $accountname );
  }
}

sub constructDbDump(){
  my ($self) = @_;

  if ( AgentConfig::psqlBin() || AgentConfig::mysqlBin() ) {
    my $default_domain_name = `hostname -f`;
    chomp $default_domain_name;
    $self->{db_dump}->{$default_domain_name} = 1;
  }
}


sub selectDomains {
  my ($self, @domains) = @_;
  # may be do some checks
  $self->{domains} = \@domains;
}

sub selectAll {
 my ($self) = @_;
 $self->{domains} = $self->{all_domains} ;
}

sub dump {
  my ($self) = @_;
  my $root = XmlNode->new('migration-dump', 'attributes' => {'agent-name' => 'generic'});

  if(exists $self->{domains}){

    $self->{dump_status}->start(0, scalar(@{$self->{domains}}));

    foreach my $domain (@{$self->{domains}}) {
      $root->addChild($self->makeDomain($domain));
    }

    $self->{dump_status}->finishObjects();

  }else{
    die "No objects to dump found";
  }

  return $self->{storage}->finish($root);
}

sub makeDomainNodeFromWeb {
  my ($self, $domain_name) = @_;
  my $ip = $self->{domaininfo}->get_domain_ip( $domain_name );
  my $www = $self->{domaininfo}->set_domain_www( $domain_name );

  my $item = XmlNode->new('domain', 'attributes' => {'name' => $domain_name,'www' => (defined($www)) ?  'true' : 'false' });

  $self->makeDomainIpNode($item, $ip ) if $ip;

  $item->addChild(XmlNode->new('status', 'children' => [XmlNode->new('enabled')]));

  return $item;
}

sub makeDomainNodeFromMail {
  my ($self, $domain_name) = @_;

  my $item = XmlNode->new('domain', 'attributes' => {'name' => $domain_name});

  $item->addChild(XmlNode->new('status', 'children' => [XmlNode->new('enabled')]));

  return $item;

}

sub makeDomainNodeFromDb {
  my ($self, $domain_name) = @_;

  my $item = XmlNode->new('domain', 'attributes' => {'name' => $domain_name});
  my $defaultipaddr = `ifconfig eth0 | perl -ne "if(/inet addr:([\\d.]+)/) { print \\\$1; }"`;
  chomp $defaultipaddr;

  $self->makeDomainIpNode($item, $defaultipaddr) if $defaultipaddr;

  $item->addChild(XmlNode->new('status', 'children' => [XmlNode->new('enabled')]));

  return $item;
}

sub makeDomain {
  my ($self, $domain_name) = @_;

  my ($domain);

  if(exists $self->{db_hosting}->{$domain_name}) {
    $domain = $self->makeDomainNodeFromDb($domain_name);
    $self->makeDatabaseNode($domain, $domain_name);
  }elsif(exists $self->{mail_hosting}->{$domain_name}){
    $domain = $self->makeDomainNodeFromMail($domain_name);
    $self->makeMailSystemNode($domain, $domain_name);
  }else{
    $domain = $self->makeDomainNodeFromWeb($domain_name);
    $self->makeMailSystemNode($domain, $domain_name) unless (exists $self->{db_web_hosting}->{$domain_name} or exists $self->{web_hosting}->{$domain_name});
    $self->makeDatabaseNode( $domain, $domain_name ) if exists $self->{db_web_hosting}->{$domain_name} or exists $self->{full_hosting}->{$domain_name};
    $self->makePhostingNode($domain, $domain_name);
  }

  return $domain;

}

sub makeDomainIpNode {
  my ($self, $parent, $ip) = @_;
  $parent->addChild(XmlNode->new('ip', 'children' => [XmlNode->new('ip-type', 'content' => 'shared' ), XmlNode->new('ip-address', 'content' => $ip)]));
}

sub makePhostingNode {
  my ($self, $parent, $domain_name) = @_;

  my $vh = $self->{apache}->getApacheVHostConfig( $domain_name );
  if( !$vh ){
    Logging::error( "Cannot find entry in apache config for domain '$domain_name'!" );
    return;
  }
  my $item = XmlNode->new('phosting');
  my ($cid, $cid_cgi, $cid_ssl);

  #contents
  my $doc_root = $vh->cmd_config('DocumentRoot');

  if(-d $doc_root) {
    $cid = $self->addTar("$domain_name.httpdocs", "directory" => $doc_root);
    $item->setAttribute('cid_docroot', $cid) if $cid;
  }

  my @script_alias = $vh->cmd_config_array('ScriptAlias');

  for my $script (@script_alias) {
    if ($script->[0]=~/cgi-bin/) {
      $cid_cgi = $self->addTar("$domain_name.cgi", "directory" => $script->[1]);
      $item->setAttribute('cid_cgi', $cid_cgi) if $cid_cgi;
    }
  }

#  if (ref($objSsl) eq "ConfigFile"){
#    my $docRootSsl = $objSsl->cmd_config('DocumentRoot');
#    $cidSsl = makeDumpFile("$dumpDir/$serverName\_ssl",$docRootSsl);
#    $item->{'ATTRIBUTE'}->('https','true');
#    $item->{'ATTRIBUTE'}->('cid_docroot_ssl',$cidSsl);
#  }

  # sysuser

  $self->_makeSysUserNode($item, $doc_root);

  # scripting

  $self->makeScriptingNode($item, $vh, $doc_root);

  $parent->addChild($item);
}

sub makeScriptingNode {
  my ($self, $parent, $vh, $doc_root) = @_;
  my $item = XmlNode->new('scripting');

  my $scripts = $vh->cmd_context(Directory => $doc_root);

  if (ref($scripts)=~/ConfigFile/){
    if(ref($scripts->cmd_context(IfModule => 'mod_python.c'))=~/ConfigFile/){
      $item->setAttribute('python','true');
    }

    #cgi
    my @cgi_alias = $vh->cmd_config_array('ScriptAlias');
    for my $script (@cgi_alias){
      if ($script->[0]=~/cgi-bin/){
        $item->setAttribute('cgi','true');
      }
    }

    #php
    my $php = $scripts->cmd_context(IfModule => 'mod_php5.c'); #need to think about other versions of mod_php.
    if (ref($php)=~/ConfigFile/) {
      my @php_opts = $php->cmd_config_array('php_admin_flag');
      for my $php_opt (@php_opts){
        if ($php_opt->[1] eq "on"){
          $item->setAttribute('php','true');
        }
      }
    }

    #ssi
    my @opts = $scripts->cmd_config_array('Options');
    for my $opt (@opts){
      if ($opt->[0] eq "+Includes"){
        $item->setAttribute('ssi','true');
      }
    }

    #mod_perl && asp
    my @mod_perl_opts = $scripts->cmd_context(IfModule => 'mod_perl.c');
    for my $mod_perl_opt (@mod_perl_opts){
      if (ref($mod_perl_opt) eq "ConfigFile"){
        if ($mod_perl_opt->cmd_config('Files') eq "\~ \(\\.asp\$\)") { $item->{'ATTRIBUTE'}->('asp','true')};
        if ($mod_perl_opt->cmd_config('Files') eq "\~ \(\\.pl\$\)") { $item->{'ATTRIBUTE'}->('perl','true')};
      }
    }
  }

  $parent->addChild($item);

}

sub _makeSysUserNode {
  my ($self, $parent, $doc_root) = @_;
  my @dir_props = stat($doc_root);
  my $user_params = $self->parsePasswdShadow($dir_props[4]);
  my $item = XmlNode->new('sysuser');
  my $user_name = $user_params->{$dir_props[4]}->{'name'};

  $item->setAttribute('name', $user_name);
  $item->setAttribute('shell', $user_params->{$dir_props[4]}->{'shell'});

  $item->addChild(makePasswordNode($user_params->{$dir_props[4]}->{'password'}, 'encrypted'));
  $parent->addChild($item);
}

sub parsePasswdShadow {
  my ($self, $uid)=@_;

  my $passwdReader = makeFileParser("/etc/passwd");
  my $shadowReader = makeSafeFileParser("/etc/shadow");

  my (%passwd, %shadow);
  if (defined($passwdReader)) {
    %passwd = %{$passwdReader->{'PARSE'}->('KEYSEPARATOR' => ':', 'VALUESEPARATOR' => ':')};
  } else {
    return undef;
  }

  if (defined($shadowReader)) {
    %shadow = %{$shadowReader->{'PARSE'}->('KEYSEPARATOR' => ':', 'VALUESEPARATOR' => ':')};
  }

  my ($user,$paramsPtr,@params,$password,$shell,@shadowParams,%userParams);

  while (($user,$paramsPtr) = each %passwd){
    @params = @{$paramsPtr};
    if($uid == $params[1]){
      $shell = $params[5];
      if (%shadow && defined($shadow{$user})){
        @shadowParams = @{$shadow{$user}};
        $password = $shadowParams[0];
      }
      $userParams{$uid} = {'name' => $user,'shell' => $shell, 'password' => $password};
    }
  }

  return \%userParams;
}

sub makeMailSystemNode {
  my ($self, $parent, $domain_name) = @_;
  my $mailsystem = XmlNode->new('mailsystem');

  my @domain_mail = @{$self->{mail_dump}->{$domain_name}};
  # Add status
  $mailsystem->addChild(XmlNode->new('status', 'children' => [XmlNode->new('enabled')]));
  if(exists $self->{mail_dump}->{$domain_name}) {
    foreach my $mail(@domain_mail) {
      $self->makeMailUserNode($mailsystem, $domain_name, $mail );
    }
  }
  $parent->addChild($mailsystem);
}

sub makeMailUserNode {
  my ($self, $parent, $domain, $mailname ) = @_;
  my $accounts = $self->{mailaccounts};
  my $shortmail = $mailname;
  $shortmail =~ s/@.+//g;
  my $mailuser = XmlNode->new('mailuser', 'attributes' => {'name' => $shortmail, 'mailgroup-enabled' => 'false' } );
  $mailuser->setAttribute( 'mailgroup-enabled', 'true' ) if $accounts->get_account_isgroup($mailname);
  my $quota = $accounts->get_account_quota($mailname);
  $mailuser->setAttribute( 'mailbox-quota', $quota ) if $quota;
  $mailuser->addChild(makePasswordNode($accounts->get_account_pwd($mailname) || '' , 'encrypted'));
  my $maildir = $accounts->get_account_maildir($mailname);
  if ( $maildir ) {
    my $mailbox = XmlNode->new('mailbox', 'attributes' => {'type' => 'mdir' }); #TO DO qmail can mbox format!
    $mailbox->setAttribute( 'enabled', 'true' ) if $accounts->get_account_postbox($mailname);

    my $cid = $self->addTar("$mailname\@$domain.mdir", 'directory' => $maildir );
    if ($cid ) {
      $mailbox->setAttribute('cid', $cid);
    }
    $mailuser->addChild($mailbox);
  }
  my $redir = $accounts->get_account_redirect($mailname);
  if ($redir ) {
    my @rd = split( ';', $redir );
    my $rsize = scalar(@rd);
    if( $rsize>1 ){
       foreach $redir( @rd ){
           $mailuser->addChild( XmlNode->new( 'mailgroup-member', 'content' => $redir ) );
       }
      $mailuser->setAttribute( 'mailgroup-enabled', 'true' );
    }
    else {
       my $rnode = XmlNode->new( 'redirect', 'content' => $rd[0] );
       $rnode->setAttribute('enabled', $accounts->get_account_redirect_satus($mailname) ? 'true' : 'false' );

       $mailuser->addChild( $rnode ) if $rsize == 1;

    }
  }
 $parent->addChild($mailuser);
}

sub makeDatabaseNode {
  my ($self, $root) = @_;

  my ($usermysqlpasswd, $usermysql) = $self->getUserDbConnect();

  return unless $self->validConnection($usermysql);

  my $sql = "SHOW DATABASES";
  my @dbs;
  if ($usermysql->{'EXECUTE'}->($sql)) {
	while (my $r = $usermysql->{'FETCHROW'}->()) {
	  my $db = $r->[0];
      Logging::debug("Database: ".$db);
	  next if $db eq "mysql" or $db eq "test";
	  push @dbs, $db;
	}
  }
  $usermysql->{'FINISH'}->();

  foreach my $db(@dbs) {
    my $dbnode = XmlNode->new("database", "attributes" => { "name" => $db, "type" => "mysql" });
    my $cid = $self->addDb("$db.mysql", "name" => $db, "type" => "mysql", "user" => "root" , "password" => $usermysqlpasswd);
    $dbnode->setAttribute('cid', $cid) if $cid;
    $dbnode->setAttribute('version', Db::MysqlUtils::getVersion());

	my %dbusers;

	$sql = "SELECT user.Host, user.User, user.Password FROM user, db ".
	  " WHERE db.Select_priv = 'Y' AND db.Host = user.Host AND db.Db = '?' ".
	  " AND db.User = user.User AND user.User <> ''";
	if ($usermysql->{'EXECUTE'}->($sql, $db)) {
	  while (my $h = $usermysql->{'FETCHHASH'}->()) {
		unless (exists $dbusers{$h->{'User'}}) {
		  $dbusers{$h->{'User'}} = {};
		  $dbusers{$h->{'User'}}->{'accesshost'} = [];
		}
		push @{$dbusers{$h->{'User'}}->{'accesshost'}}, $h->{'Host'};

		unless (exists $dbusers{$h->{'User'}}->{'password'} or $h->{'Password'} eq '') {
		  $dbusers{$h->{'User'}}->{'password'} = $h->{'Password'};
		}
	  }
	}
	$usermysql->{'FINISH'}->();

	foreach my $dbuser (keys(%dbusers)) {
	  my $dbusernode = XmlNode->new("dbuser",
							   "attributes" => { "name" => $dbuser },
							   "children" => [makePasswordNode($dbusers{$dbuser}->{'password'})]);
	  foreach my $host (@{$dbusers{$dbuser}->{'accesshost'}}) {
		$dbusernode->addChild(XmlNode->new("accesshost", "content" => $host));
	  }
	  $dbnode->addChild($dbusernode);
	}
	$root->addChild($dbnode);
  }

  return $root;
}

sub getUserDbConnect {
  my ($self) = @_;

  my $usermysql;

#  Logging::debug("Trying to connect to user's DB without password...");
#  $usermysql = getUserDbConnect2("");
#  return ("", $usermysql) if validConnection($usermysql);

  Logging::debug("Trying to connect to user's DB with users password...");
  $usermysql = getUserDbConnect2($self->{db_password});
  return ($self->{db_password}, $usermysql) if $self->validConnection($usermysql);
}

sub validConnection {
  my ($self, $connect) = @_;
  return ref($connect) =~ /HASH/ && ref($connect->{'EXECUTE'}) =~ /CODE/;
}

sub getUserDbConnect2 {
  my ($self, $password) = @_;

  return getDbConnect('mysql', "root", $password, "mysql", "localhost", 1);
}

sub addTar {
  my $self = shift;
  return undef if exists $self->{no_content};
  return $self->{storage}->addTar(@_);
}

sub addDb {
  my $self = shift;
  return undef if exists $self->{no_content};
  return $self->{storage}->addDb(@_);
}

1;