# Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
package Dumper;

use strict;

use XmlNode;
use AgentConfig;
use Mailman;
use Parser;
use Packer;
use SpamAssassinCfg;
use Logging;
use HelpFuncs;
use Db::DbConnect;
use Db::MysqlUtils;
use Db::Connection;
use EncodeBase64;
use Config::Tiny;
use JSON::PP;
use YAML;
use Net::DNS::ZoneFile;



my $DEFAULT_RESELLER = 'root';

my %mapLimits = (
  'max_addon'     => 'MAXADDON',
  'max_parked'    => 'MAXPARK',
  'max_ftpusers'  => 'MAXFTP',
  'max_maillists' => 'MAXLST',
  'max_box'       => 'MAXPOP',
  'max_db'        => 'MAXSQL',
  'max_subdom'    => 'MAXSUB',
);

# List of cPanel features.
# Features marked as 2 are included to the dump.
# Do not forget change cPanel.xsd, if change flags.
my %mapFeatures = (
  'addoncgi'               => 1,    #Addon Cgi Scripts
  'addondomains'           => 2,    #Addon Domain Manager
  'advguest'               => 1,    #Advanced Guestbook
  'agora'                  => 1,    #Agora Shopping Cart
  'analog'                 => 1,    #Analog Stats
  'emailauth'              => 1,    #Email Authentication
  'autoresponders'         => 1,    #Autoresponder Manager
  'awstats'                => 1,    #Awstats Stats
  'backup'                 => 2,    #Backup Manager
  'bandwidth'              => 1,    #Bandwidth Stats
  'bbs'                    => 1,    #PHPBB2
  'blockers'               => 1,    #Email Filtering Manager
  'boxtrapper'             => 1,    #BoxTrapper Spam Trap
  'cgi'                    => 1,    #CGI Center
  'changemx'               => 1,    #Ability to Change MX
  'chat'                   => 1,    #Chat Rooms
  'clamavconnector_scan'   => 2,    #Virus Scanner
  'clock'                  => 1,    # Java Clock
  'countdown'              => 1,    #Java Countdown
  'counter'                => 1,    #Counter
  'cpanelpro_support'      => 1,    #Support System Submission
  'cpanelpro_images'       => 1,    #Image Manager
  'cron'                   => 2,    #Crontab
  'defaultaddress'         => 1,    #Default Address Manager
  'diskusageviewer'        => 1,    #Disk Usage Viewer
  'emaildomainfwd'         => 1,    #Email Domain Forwarding
  'emailscripts'           => 1,    #Email Scripts (cgiemail,formmail)
  'entropybanner'          => 1,    #Entropy Banner
  'entropysearch'          => 1,    #Entropy Search
  'errlog'                 => 1,    #Error Log
  'errpgs'                 => 1,    #Customer Error Pages
  'fantastico'             => 1,    #
  'filemanager'            => 1,    #File Manager
  'forwarders'             => 1,    #Forwarder Manager
  'frontpage'              => 1,    #Frontpage
  'ftpaccts'               => 1,    #Ftp Account Manager
  'ftpsetup'               => 2,    #Ftp Settings
  'getstart'               => 1,    #Getting Started Wizard
  'guest'                  => 1,    #Simple Guestbook
  'handlers'               => 1,    #Apache Handlers Manager
  'hotlink'                => 1,    #Hotlink
  'ipdeny'                 => 1,    #IP Deny Manager
  'indexmanager'           => 1,    #File Manager Directory Selection
  'interchange'            => 1,    #Interchange Shopping Cart
  'lastvisits'             => 1,    #Latest Visitors
  'cpanelpro_leechprotect' => 1,    #Leech Protect
  'lists'                  => 2,    #Mailman List Manager
  'mime'                   => 1,    #Mime Types Manager
  'modules-perl'           => 1,    #Install Perl Modules
  'modules-php-pear'       => 1,    #Install PHP Pear Modules
  'modules-ruby'           => 1,    #Install Ruby Modules
  'mysql'                  => 1,    #Mysql
  'nettools'               => 1,    #Network Tools
  'parkeddomains'          => 2,    #Parked Domain Manager
  'password'               => 2,    #Password Change
  'pgp'                    => 1,    #PGP/GPG
  'php-config'             => 1,    #See PHP Configuration
  'phpmyadmin'             => 1,    #PhpMyAdmin
  'phppgadmin'             => 1,    #PhpPgAdmin
  'popaccts'               => 1,    #Email Account Manager
  'postgres'               => 1,    #PostgresSQL
  'randhtml'               => 1,    #Random Html Generator
  'rawlog'                 => 2,    #Raw Access Logs
  'redirects'              => 1,    #Redirect Manage
  'ror'                    => 1,    #Ruby on Rails
  'scgiwrap'               => 1,    #Simple Cgi Wrapper
  'searchsubmit'           => 1,    #Search Engine Submit Tool
  'serverstatus'           => 1,    #Server Status Viewer
  'setlang'                => 1,    #Change Language
  'spamassassin'           => 2,    #SpamAssassin
  'spambox'                => 1,    #SpamAssassin Spam Box
  'ssh'                    => 1,    #SSH Connection Window
  'sslinstall'             => 1,    #SSL Host Installer
  'sslmanager'             => 1,    #SSL Manager
  'statmanager'            => 2,    #Statistics Program Manager
  'statselect'             => 1,    #Choose Log Programs
  'style'                  => 1,    #Change Style
  'subdomains'             => 2,    #Subdomain Manager
  'subdomainstats'         => 1,    #Subdomain Stats
  'traceaddy'              => 1,    #Ability to Trace an email address
  'updatecontact'          => 1,    #Update Contact Information
  'videotut'               => 1,    #Video Tutorials
  'webalizer'              => 1,    #Webalizer Stats
  'webdisk'                => 1,    #Web Disk
  'webmail'                => 1,    #Webmail
  'webprotect'             => 1,    #Webprotect
  'backupwizard'           => 1,    #Backup Wizard
);


#
# global variables
#

my $ptrQuota;

my $ptrUserdomainsHash;

my %trueUserDomains;

my %anonftps;

my %accountNodes;

my $_parsedHttpdConf;
my %_serversIps;
my %_apacheAliases;
my %_docRoots;
my %_scriptAliases;
my $_parsedUserDataDomainsInfo;
my %_userDataDomainsInfo;


my (%cPanelConfig);

my (%featureList);

my $installDir = '/var/cpanel';

#
# end global variables
#

my $mysqlConnection;
my $pgsqlConnection;

my $SHALLOW_DUMP_FILE_NAME = 'shallow_dump.xml';
my $DUMP_FILE_NAME = 'dump.xml';
my $STATUS_FILE_NAME = 'dumping-status.xml';
my $MIGRATION_RESULT_FILE_NAME = 'migration.result';
my $CONTENT_LIST_FILE_NAME = 'content-list.xml';

#
# Initialization routine
#
sub initialize {
  loadMainConfig();
  my @all_accounts = getAllAccounts();
  %featureList = loadFeatureList();
  loadQuota();
}

#
# Parse main config file, 
# Initialize $installDir and cPanelConfig{'engineroot'}
#
sub loadMainConfig {
  my $pathToConf = shift || '';
  my $ptrHash = shift || \%cPanelConfig;

  unless ( -T $pathToConf ) {
    foreach my $prefix ( './', '/var/cpanel/' ) {
      $pathToConf = $prefix . 'cpanel.config';
      if ( -T $pathToConf ) {
        last;
      }
    }
  }

  if ( -T $pathToConf ) {
    if ( ref($ptrHash) =~ /HASH/ ) {
      if ( $pathToConf =~ /(.+)\/[^\/]+/ ) {
        $ptrHash->{'installDir'} = $1;
      }
      if ( open( CF, "<$pathToConf" ) ) {
        binmode CF;
        my ( $key, $value );
        while ( my $line = <CF> ) {
          chomp $line;
          next unless $line;
          next if ( $line =~ /^#/ );
          ( $key, $value ) = split( /\s*=\s*/, $line, 2 );
          $key =~ s/^\s+//;
          $ptrHash->{$key} = $value;
        }
        close(CF);
      }
    }
  }
  else {
    $pathToConf = '';
  }

  $installDir = $cPanelConfig{'installDir'} if defined ( $cPanelConfig{'installDir'} );

  return $pathToConf;
}

# Returns password for system account $user
sub getSystemPassword {
  my ($user) = @_;
  my $password;
  my @pw = getpwnam($user);
  if (-1 != $#pw) {
    $password = $pw[1];
  }
  unless ($password) {
    $password = '';
  }
  if (substr($password,0,8) eq '*LOCKED*') {
    $password = substr($password,8);
  }
  elsif (substr($password,0,2) eq '!!') {
    $password = substr($password,2);
  }

  return $password;
}

sub isUserLocked {
  my ($user) = @_;
  my @pw = getpwnam($user);
  if (-1 != $#pw) {
    if (my $passwd = $pw[1]) {
      if ( (substr($passwd,0,8) eq '*LOCKED*') || (substr($passwd,0,2) eq '!!') ) {
        return 1;
      }
    }
  }
  return;
}

sub getSuspendedShell {
  my ($account) = @_;
  my $fileParser = Parser::makeSafeFileParser("$installDir/suspendinfo/$account");
  if ( defined $fileParser ) {
    my $ptrUserHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' );
    if ( ref($ptrUserHash) =~ /HASH/ ) {
      return $ptrUserHash->{'shell'};
    }
  }
  return;
}

# Returns home directory for the system account $user
sub getHomeDir {
  my ($user) = @_;
  my $home;
  my @pw = getpwnam($user);
  if (-1 != $#pw) {
    $home = $pw[7];
  }
  unless ($home) {
    # how it is by default
    $home = '/home/$account';
  }
  return $home;
}

#######################################################
#
#  Get all client accounts, registered in the system
#
#######################################################
sub getAllAccounts {
  my @all_accounts;

  my $users_dir = "$installDir/users";
  opendir( USRS, $users_dir ) || die "Can't open directory $users_dir: $!";
  my @accts = readdir(USRS);
  closedir(USRS);
  chomp(@accts);

  # remove duplicate and possible wrong entries
  foreach my $account (@accts) {
    next if ( $account =~ /\./ || grep( /^\Q$account\E$/, @all_accounts ) );
    push @all_accounts, $account;
  }

  return @all_accounts;
}

sub getResellerAccounts {
  my @reseller_accounts;
  my $resellers_file = "$installDir/resellers";
  open(my $fh, $resellers_file ) || die "Could not open file '$resellers_file' $!";

  while (my $reseller_info = <$fh>) {
    chomp $reseller_info;
    my $pos=index($reseller_info, ':');
    my $reseller_name = substr($reseller_info, 0, $pos,"");

    # Strip leading spaces for reseller's login - just for reliable processing
    $reseller_name =~ s/^\s+//;
    $reseller_name =~ s/\s+$//;

    # If reseller's file is not valid and contains empty reseller records - just ignore such records
    if ( $reseller_name ne "") {
      push @reseller_accounts, $reseller_name;
    }
  }

  return @reseller_accounts;
}

sub isResellerAccount {
  my $account = shift;
  my $resellers_file = "$installDir/resellers";
  open(my $fh, $resellers_file ) || die "Could not open file '$resellers_file' $!";

  while (my $reseller_info = <$fh>) {
    chomp $reseller_info;
    my $pos=index($reseller_info, ':');
    my $reseller_name = substr($reseller_info, 0, $pos,"");
    if ($account eq $reseller_name) {
      return 1;
    }
  }

  return 0;
}

sub getClientAccounts {
  my @all_accounts;
  my @client_accounts;
  my $reseller_name = shift || "root";

  my $users_dir = "$installDir/users";
  opendir( USRS, $users_dir ) || die "Can't open directory $users_dir: $!";
  my @accts = readdir(USRS);
  closedir(USRS);
  chomp(@accts);

  # remove duplicate and possible wrong entries
  foreach my $account (@accts) {
    next if ( $account =~ /\./ || grep( /^\Q$account\E$/, @all_accounts ) );

    push @all_accounts, $account;

    my $fileParser = Parser::makeFileParser("$installDir/users/$account");
    my $ptrUserHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' );
    unless(isResellerAccount($account)) {
      if ($ptrUserHash->{'OWNER'} eq $reseller_name) {
        push @client_accounts, $account;
      }
    }
  }
  return @client_accounts;
}

sub getServersIps {
  parseHttpdConf();
  return \%_serversIps;
}
 
sub getApacheAliases {
  parseHttpdConf();
  return \%_apacheAliases;
}
 
sub getDocRoots {
  parseHttpdConf();
  return \%_docRoots;
}
 
sub getScriptAliases {
  parseHttpdConf();
  return \%_scriptAliases;
}
 
sub parseHttpdConf {
  if ( $_parsedHttpdConf ) {
    return 1;
  }

  my $httpdconf_file;

  my @httpdconfStandardLocations = (
      "/usr/local/apache/conf/httpd.conf",
      "/etc/httpd/conf/httpd.conf",
      "/etc/apache2/conf/httpd.conf"
  );

  for my $checkedHttpconfFile (@httpdconfStandardLocations) {
    if (-e $checkedHttpconfFile) {
      $httpdconf_file = $checkedHttpconfFile;
      last;
    }
  }

  if (!$httpdconf_file) {
    Logging::error(
      "Failed to find Apache config at the following locations:\n" .
      join("\n", map {"- " . $_} @httpdconfStandardLocations)
    );
    return;
  }

  unless ( open( INPUT, "<$httpdconf_file" ) ) {
    Logging::warning("Error: unable to open '$httpdconf_file': $!");
    return;
  }
  binmode INPUT;
  my $inVirtualHost;

  #for %_docRoots
  my ( $docroot );

  #for %_serversIps
  my ( $ip, @vDomains );

  #for %_apacheAliases
  my ( $vserver, %valiases );

  #for %_scriptAliases
  my ( $scriptalias );

  $inVirtualHost = 0;
  while (<INPUT>) {
    chomp;
    next unless $_;
    next if (/^#/);
    if ( $inVirtualHost == 1 ) {
      if (/<\/VirtualHost>/) {

        foreach my $vDomain (@vDomains) {
          $vDomain =~ s/^www\.//;
          $_serversIps{$vDomain} = $ip;
          $_docRoots{$vDomain} = $docroot if $docroot;
          $_scriptAliases{$vDomain} = $scriptalias if $scriptalias;
        }
        $docroot     = '';
        $scriptalias = '';
        @vDomains    = ();

        if ( $vserver && ( keys %valiases ) ) {
          push @{ $_apacheAliases{$vserver} }, keys %valiases;
        }
        $vserver  = '';
        %valiases = ();

        $inVirtualHost = 0;
      }
      elsif (/^\s*ServerName\s+(\S+)/) {
        $vserver = $1;
        $vserver =~ s/^www\.//;
        push @vDomains, split( ' ', $1 );
      }
      elsif (/^\s*ServerAlias\s+(.*)/) {
        map { s/^www\.//; $valiases{$_} = 1 } split( ' ', $1 );
        push @vDomains, split( ' ', $1 );
      }
      elsif (/^\s*DocumentRoot\s+(.*)/) {
        $docroot = $1;
        if ( substr($docroot,-1,1 ) eq '/' ) {
          chop $docroot;
        }
      }
      elsif (/^\s*ScriptAlias\s+\/cgi-bin\/\s+(.*)/) {
        $scriptalias = $1;
        if ( substr($scriptalias,-1,1 ) eq '/' ) {
          chop $scriptalias;
        }
      }
    }
    else {
      if (/<VirtualHost\s(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*>/) {
        $ip       = $1;
        $docroot  = '';
        $scriptalias = '';
        @vDomains = ();

        $vserver  = '';
        %valiases = ();

        $inVirtualHost = 1;
      }
    }
  }
  close(INPUT);
  $_parsedHttpdConf = 1;
}

my $_parsedErrdocConf;
my %_errdocPaths;

sub getErrdocPaths {
  parseErrdocConf();
  return \%_errdocPaths;
}

sub parseErrdocConf {
  if ( $_parsedErrdocConf ) {
    return 1;
  }

  my $errdocconf_file = "/etc/httpd/conf/includes/errordocument.conf";
  
  unless ( -e $errdocconf_file ) {
    $errdocconf_file = "/usr/local/apache/conf/includes/errordocument.conf";
  }
  
  unless ( open( INPUT, "<$errdocconf_file" ) ) {
    Logging::warning("Error: unable to open '$errdocconf_file': $!");
    return;
  }
  binmode INPUT;
  while (<INPUT>) {
    chomp;
    next unless $_;
    next if (/^#/);
    if (/^\s*ErrorDocument\s+(\d{3})\s+(.*)/) {
      my $errcode = $1;
      my $docpath = $2;
      $_errdocPaths{$errcode} = $docpath;
    }
  }
  close(INPUT);
  $_parsedErrdocConf = 1;
}

sub loadQuota {
  # Get disk quotas
  my $quotaFile = "/etc/quota.conf";
  $ptrQuota = {};
  if (-T $quotaFile) {
    my $fileUser = Parser::makeFileParser($quotaFile);
    $ptrQuota = $fileUser->{'PARSE'}->('KEYSEPARATOR' => '=', 'VALUESEPARATOR' => '');
    unless (ref($ptrQuota) =~ /HASH/) {
      $ptrQuota = {};
    }
  } else {
    Logging::debug("File '$quotaFile' is not found");
  }
  return $ptrQuota;
}

sub loadFeatureList {
  my %list;

  my $features_dir = "$installDir/features";
  opendir( FLST, $features_dir ) || die "Can't open directory $features_dir: $!";
  my @lsts = readdir(FLST);
  closedir(FLST);

  foreach my $lst (@lsts) {
    my $cfg = "$features_dir/$lst";
    if ( -T $cfg ) {
      Logging::debug("Reading feature list: $lst");
      my $cfgParser = Parser::makeFileParser($cfg);
      my $values = $cfgParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' );
      $list{$lst} = $values;
      Logging::debug("Feature list '$lst' is read");
    }
  }

  #convert disabled options
  if ( exists $list{'disabled'} ) {
    Logging::debug("Apply disabled feature list");
    foreach my $key ( keys %list ) {
      if ( not $key eq 'disabled' ) {
        my $vals = $list{$key};
        foreach my $diskey ( keys %{ $list{'disabled'} } ) {
          ${$vals}{$diskey} = 0
            if ${ $list{'disabled'} }{$diskey} == 0
              and exists ${$vals}{$diskey};
        }
      }
    }
  }

  #create default
  if ( exists $list{'default'} ) {
    Logging::debug("Apply default feature list");
    foreach my $mapkey ( keys %mapFeatures ) {
      ${ $list{'default'} }{$mapkey} = 1
        if not exists ${ $list{'default'} }{$mapkey};
    }
  }
  return %list;

}

sub getUserDomains {
  unless ( keys %trueUserDomains ) {
    my $fileParser = Parser::makeFileParser('/etc/trueuserdomains');
    my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' );
    if ( ref($ptrHash) =~ /HASH/ ) {
      %trueUserDomains = ( %{$ptrHash} );
    }
  }
  return \%trueUserDomains;
}

sub getDomainName {
  my $account = shift;
  my $userDomains = getUserDomains();
  if ( ref($userDomains) =~ /HASH/ ) {
    my ( $domain, $owner );
    while ( ( $domain, $owner ) = each( %{$userDomains} ) ) {
      if ( $owner eq $account ) {
        return $domain;
      }
    }
  }
  return;
}

sub getDomainOwner {
  my $domain = shift;

  my $userDomains = getUserDomains();
  if ( ref($userDomains) =~ /HASH/ ) {
    return $userDomains->{$domain};
  }
  return;
}

sub getAccountEmail {
  my $account = shift;
  my $home = getHomeDir($account);
  my $email = '';
  if ( -T "$home/.contactemail" ) {
    my $fileParser = Parser::makeFileParser("$home/.contactemail");
    my $ptrLines = $fileParser->{'PARSE'}->();
    if ( ref($ptrLines) =~ /ARRAY/ ) {
      $email = $ptrLines->[0] || '';
    }
  }
  return $email;
}

sub makeAccountNode {
  
  my ( $account, $ptrUserHash, $isReseller, $shallowMode ) = @_;

  my (
    $ptrLines, $ptrHash, $xmlKey, $userKey,
    $value,    $key,     $domain,   $item
  );

  unless ($shallowMode) {
    if ( exists $accountNodes{ $account } ) {
      return $accountNodes{ $account };
    }
  }

  my $root = XmlNode->new('account');
  $root->setAttribute( 'name', $account );
  $root->setAttribute( 'home', getHomeDir($account) );
  $root->setAttribute( 'email', getAccountEmail($account) ) unless ($shallowMode);

  unless ( ref($ptrUserHash) =~ /HASH/ ) {
    my $fileParser = Parser::makeFileParser("$installDir/users/$account");
    $ptrUserHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' );
  }

  unless ($shallowMode) {
    if ( ref($ptrUserHash) =~ /HASH/ ) {
      $root->setAttribute( 'date', HelpFuncs::epoch2CrDate( $ptrUserHash->{'STARTDATE'} ) );
    }

    if ( isUserLocked( $account ) ) {
      $root->addChild(XmlNode->new('suspended'));
    }

    unless ($isReseller) {
      my $diskSpace = $ptrQuota->{$account};
      if ( $diskSpace =~ /(\d+)/ ) {
        $diskSpace = $1 * 1024 * 1024;
        $root->addChild(Packer::makeLimitNode('disk_space', $diskSpace)) if $diskSpace;
      }

      if ( ref($ptrUserHash) =~ /HASH/ ) {
        # handle limits
        $mapLimits{'max_traffic'} = 'BWLIMIT';
        while ( ( $xmlKey, $userKey ) = each(%mapLimits) ) {
          $value = $ptrUserHash->{$userKey};
          if ( $value =~ /\d+/ ) {
            $root->addChild(Packer::makeLimitNode($xmlKey, $value));
          }
          else {
            $root->addChild(Packer::makeLimitNode($xmlKey));
          }
        }

        delete $mapLimits{'max_traffic'};    ## clean map
      }
    }
    if ( ref($ptrUserHash) =~ /HASH/ ) {
      makeFeaturesNode( $root, $ptrUserHash->{'FEATURELIST'} ) if exists $ptrUserHash->{'FEATURELIST'};
    }

    # SpamAssssin migration
    makeSpamassassinNode( $root, $account );
  }

  #
  # Handle domain
  #
  $domain = getDomainName($account);

  if (defined $domain) {

    Logging::debug("Domain '$domain' is started");

    my $ip = getIp($domain, $ptrUserHash) || getMainIP();

    $item = Packer::makeIpNode($ip);

    $root->addChild($item);

    $item = makeDomainNode( $domain, $account, $shallowMode );
    $root->addChild($item);

    Logging::debug("Domain '$domain' is dumped");
  }

  unless ($shallowMode) {
    $accountNodes{$account} = $root;
  }

  return $root;
}

sub makeFeaturesNode {
  my ( $root, $feature ) = @_;

  if ( exists $featureList{$feature} ) {
    my $fvalues = $featureList{$feature};
    foreach my $fkey ( keys %mapFeatures ) {
      if ( 2 == $mapFeatures{$fkey} and exists ${$fvalues}{$fkey} ) {
        my $fnode = XmlNode->new( 'feature' );
        $fnode->setAttribute( 'name', $fkey );
        if ( ${$fvalues}{$fkey} ) {
          $fnode->setAttribute( 'allowed', 'true' );
        }
        else {
          $fnode->setAttribute( 'allowed', 'false' );
        }
        $root->addChild($fnode);
      }
    }
    return $root;
  }
  else { Logging::debug("Cannot get featureList '$feature'"); }
  return;
}

sub getIp {
  my ( $domain, $ptrUserHash ) = @_;

  my $serversIps = getServersIps();

  my ( $ptrRows, $ip, $item, $dmn );
  unless ( $ip = $serversIps->{$domain} ) {
    my $fileParser = Parser::makeFileParser("$installDir/accts.db");
    $ptrRows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ',' );
    foreach my $ptrRow ( @{$ptrRows} ) {
      $dmn = $ptrRow->[0];
      $dmn =~ s/^\s+//;
      $dmn =~ s/\s+$//;
      if ( $ptrRow->[2] =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ ) {
        $ip = $1;
        unless ( exists( $serversIps->{$domain} ) ) {
          $serversIps->{$domain} = $ip;
        }
      }
    }
    unless ( $ip = $serversIps->{$domain} ) {
      unless ( $ip = $ptrUserHash->{'IP'} ) {
        my $zone = "/var/named/$domain.db";
        my $pattern =
          qr/^\Q$domain\E\.\s+(?:\d+\w?\s+)IN\s+A\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/;
        if ( -T $zone ) {
          if ( open( INPUT, "<$zone" ) ) {
            binmode INPUT;
            while (<INPUT>) {
              if (/$pattern/) {
                $ip = $1;
                last;
              }
            }
            close(INPUT);
            if ($ip) {
              $serversIps->{$domain} = $ip;
            }
          }
        }
      }
    }
  }
  return $ip;
}

my %_accountSubdomains;

sub getSubdomains {
  my ( $account ) = @_;

  if ( exists $_accountSubdomains{ $account } ) {
    return $_accountSubdomains{ $account };
  }

  my $domain = getDomainName( $account );
  my @subdomains = ();
  unless ( ref($ptrUserdomainsHash) =~ /HASH/ ) {
    my $fileParser = Parser::makeFileParser('/etc/userdomains');
    $ptrUserdomainsHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' );
  }
  if ( ref($ptrUserdomainsHash) =~ /HASH/ ) {
    my $apacheAliases = getApacheAliases();
    my $pattern = qr/^(.+)\.\Q$domain\E$/;
    while ( ( my $subdomain, my $acc ) = each %{$ptrUserdomainsHash} ) {
      next unless ( $acc eq $account );

      # Parked domain names could be like subdomains, 
      # To drop them out let's look into apacheAliases
      next unless ( exists $apacheAliases->{ $subdomain } );
      if ( $subdomain =~ /$pattern/ ) {
        push @subdomains, $1;
      }
    }
  }
  $_accountSubdomains{ $account } = \@subdomains;
  return $_accountSubdomains{ $account };
}

my %_proftpdAccountRows;

sub getProftpdAccountRows {
  my ( $account ) = @_;

  if ( exists $_proftpdAccountRows{ $account } ) {
    return $_proftpdAccountRows{ $account };
  }

  my $rows = _getProftpdAccountRows("/etc/proftpd/$account.suspended");
  $rows = _getProftpdAccountRows("/etc/proftpd/$account") unless defined $rows;
  if ( defined $rows ) {
    $_proftpdAccountRows{ $account } = $rows;
    return $_proftpdAccountRows{ $account };
  }
  return;
}

sub _getProftpdAccountRows {
  my ( $file ) = @_;
  if ( -T $file ) {
    my $fileParser = Parser::makeFileParser($file);
    my $rows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':',
                                         'KEYSEPARATOR'   => '',
                                        );
    if ( ref($rows)=~/ARRAY/ ) {
      return $rows;
    }
  }
  return;
}

my %_accountFtpusers;

sub getFtpUsers {
  my ( $account ) = @_;

  if ( exists $_accountFtpusers{ $account } ) {
    return $_accountFtpusers{ $account };
  }
  
  my $home = getHomeDir( $account );

  my $ftpquota;
  my $ftpquota_file = "$home/etc/ftpquota";
  if ( -e $ftpquota_file ) {
    Logging::debug("Parse ftp quota '$ftpquota_file'");
    my $ftpquotaParser = Parser::makeFileParser($ftpquota_file);
    $ftpquota = $ftpquotaParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' );
  }

  my %ftpUsers;

  my $ptrProftpdRows = getProftpdAccountRows( $account );
  if ( defined $ptrProftpdRows ) {
    foreach my $ptrRow ( @{$ptrProftpdRows} ) {
      my $user = $ptrRow->[0];
      if ( $user eq $account ) {
        my $password = $ptrRow->[1];
        if ( '*' eq $password ) {
          $password = getSystemPassword($account);
          if ( '*' eq $password ) {
            $password = '';
          }
        }
        my $shell = getSuspendedShell($account) || $ptrRow->[6];
        $ftpUsers{ $account } = [ $password, undef, $shell ];
      }
      elsif ( $ptrRow->[4] eq $account ) {
        my $pattern = qr/\Q$home\E\/(.*)/;
        my $path = $ptrRow->[5];
        if ( $path =~ /$pattern/ ) {
          my $password = ( $ptrRow->[1] ne '*' )? $ptrRow->[1] : '';
          my $directory = $1;
          my $shell = $ptrRow->[6];
          my $quota;
          if ( ref( $ftpquota ) =~ /HASH/ ) {
            $quota = $ftpquota->{$user}; # undef is possible
          }
          $ftpUsers{ $user } = [ $password, $directory, $shell, $quota ];
        }
      }
    }
  }

  $_accountFtpusers{ $account } = \%ftpUsers;
  return $_accountFtpusers{ $account };
}

sub makeDomainNode {
  my ( $domain, $account, $shallowMode ) = @_;

  my $root = XmlNode->new('domain');
  $root->setAttribute( 'name', $domain );

  unless ($shallowMode) {

    my $docRoots = getDocRoots();
    $root->setAttribute( 'www_root', $docRoots->{$domain} ) if $docRoots->{$domain};

    my $cgiRoots = getScriptAliases();
    $root->setAttribute( 'cgi_root', $cgiRoots->{$domain} ) if $cgiRoots->{$domain};

    my (
      @ftpusersdirs,
      @subdompatterns,
    );

    my $home = getHomeDir($account);

    my $subdomains = getSubdomains( $account );

    my $ftpusers = getFtpUsers( $account );

    my $anonftpNode = makeAnonFtpdNode($account);
    $root->addChild($anonftpNode) if $anonftpNode;

    addFtpUserNodes($root, $domain, $account, $ftpusers, $subdomains);

    my @pdirs = getProtectedDirs($root, $domain, $account, $subdomains);
    foreach my $pdir ( @pdirs ) {
      my $title = getProtectedDirTitle($account, $pdir);
      my $users = getProtectedDirUsers($account, $pdir);
      $root->addChild( Packer::makeProtectedDirNode($pdir, $title, $users) );
    }

    addSubDomains($root, $domain, $account, $subdomains, $shallowMode);

    my $addondomains = getAddOnDomains( $domain );

    addAddOnDomains($root, $account, $addondomains);

    addDefaultMailname($root, $domain, $account);

    addMailnames($root, $domain, $account);

    addMailAliases($root, $domain, $account);

    addDatabases($root, $account);

    addMailLists($root, $domain, $account);

    addDnsZone($root, $domain);
  }

  return $root;
}

sub addDnsZone {
  my ($root, $domain) = @_;

  Logging::debug("Get dns zone of '$domain'");

  my $filename = "/var/named/$domain.db";
  unless ( -e $filename ) {
    return;
  }

  my $dnsZone = XmlNode->new( 'dns-zone' );
  my $zonefile = new Net::DNS::ZoneFile( $filename );
  while ( my $rr = $zonefile->read ) {
    my $dnsRecord = XmlNode->new( 'dnsrec' );
    $dnsRecord->setAttribute( 'type', $rr->type);
    if (($rr->type eq 'A') || ($rr->type eq 'AAAA')) {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->address );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'NS') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->nsdname );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'MX') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->exchange );
      $dnsRecord->setAttribute( 'opt', $rr->preference );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'CNAME') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->cname );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'TXT') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->txtdata );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'SRV') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->target );
      $dnsRecord->setAttribute( 'priority', $rr->priority );
      $dnsRecord->setAttribute( 'weight', $rr->weight );
      $dnsRecord->setAttribute( 'port', $rr->port );
      $dnsZone->addChild($dnsRecord);
    } elsif ($rr->type eq 'CAA') {
      $dnsRecord->setAttribute( 'src', $rr->owner );
      $dnsRecord->setAttribute( 'dst', $rr->value );
      $dnsRecord->setAttribute( 'flags', $rr->flags );
      $dnsRecord->setAttribute( 'tag', $rr->tag );
      $dnsZone->addChild($dnsRecord);
    }
  }

  $root->addChild($dnsZone);
  return $dnsZone;
}

sub addMailLists {
  my ( $root, $domain, $account) = @_;

  unless ( defined Mailman::version() ) {
    Logging::debug("Unable to found Mailman installation. Skip dumping maillists.");
    return;
  }

  Logging::debug("Get maillists of '$domain'");
  my $maillists = XmlNode->new( 'maillists' );

  my $handle = unsuspendDomainMaillists($domain);
  my @lists = Mailman::getMailLists($domain);
  foreach my $listname (@lists) {
    Logging::debug("Found maillist '$listname'");
    my @owners = Mailman::getListOwners($listname);
    if (@owners) {
      if ($listname =~ m/(.*)(_$domain)/) {
        my $maillist = XmlNode->new( 'maillist' );
        $maillist->setAttribute( 'name', $1);
        for my $listOwner (@owners) {
          my $owner_node = XmlNode->new( 'owner' );
          $owner_node->setText( $listOwner );
          $maillist->addChild($owner_node);
        }
        $maillist->addChild( Packer::makePasswordNode( Mailman::getListPassword($listname) , 'encrypted' ));
        my %listMembers = Mailman::getListMembers($listname);
        for my $memberEmail ( keys %listMembers ) {
          my $member = XmlNode->new( 'recipient' );
          $member->setText( $memberEmail );
          if ( $listMembers{$memberEmail} ne "" ) {
            $member->setAttribute( 'fullname', $listMembers{$memberEmail} );
          }
          $maillist->addChild($member);
        }
        $maillists->addChild($maillist);
      }

    }
    else {
      Logging::debug("Bad maillist '$listname', cannot find owner, skipped");
    }
  }
  suspendDomainMaillists($handle);
  $root->addChild($maillists);
  return $maillists;
}

sub addDatabases {
  my ( $root, $account ) = @_;


  unless ( ( ref($mysqlConnection) =~ /HASH/ )
    && ( ref( $mysqlConnection->{'EXECUTE'} ) =~ /CODE/ ) ) {
    $mysqlConnection = getMySQLConnection();
  }
  unless ( ( ref($pgsqlConnection) =~ /HASH/ )
    && ( ref( $pgsqlConnection->{'EXECUTE'} ) =~ /CODE/ ) ) {
    $pgsqlConnection = getPgsqlConnection();
  }
  my $dbUsersXmlNode = XmlNode->new('dbusers');
  addMySQLDatabases( $root, $account );
  addGlobalMysqlUsers($account, $dbUsersXmlNode);
  if ( $pgsqlConnection ) {
	  addPgSQLDatabases( $root, $account );
	  addGlobalPgsqlUsers($account, $dbUsersXmlNode);
  }
  $root->addChild($dbUsersXmlNode);
  return $root;
}

sub getMySQLDBCredentials {
  my $user = 'root';
  my $host = undef;
  my $port = undef;
  my $password = undef;
  my $mysqlConfig = $ENV{'HOME'} . '/.my.cnf';
  if (-f $mysqlConfig) {
    my $config = Config::Tiny->read($mysqlConfig);
    my $section = $config->{client};
    $user = ($section->{user} =~ /^"?(.+?)"?$/)[0] || $user;
    $host = ($section->{host} =~ /^"?(.+?)"?$/)[0] || $host;
    $port = ($section->{port} =~ /^"?(.+?)"?$/)[0] || $port;

    # may be pass= or password=
    my $spass = exists $section->{pass} ? $section->{pass} : $section->{password};

    # Password may be enclosed in double quotes, remove them.
    $password = ($spass =~ /^["']?(.+?)["']?$/)[0] || $password;
  }
  return {
    'user' => $user,
    'host' => $host,
    'port' => $port,
    'password' => $password
  };
}
sub getMySQLConnection {
  my $creds = getMySQLDBCredentials();
  return Db::DbConnect::getDbConnect(
    'mysql',
    $creds->{'user'},
    $creds->{'password'},
    'mysql',
    $creds->{'host'},
    undef,
    undef,
    undef,
    undef,
    $creds->{'port'}
  );
}

sub addMySQLDatabases {
  my ( $root, $account ) = @_;
  my ( $item, @dbNames );

  my $datadir = getMysqlDataDir($mysqlConnection);

  @dbNames = getDatabaseList($account, 'MYSQL');
  foreach my $database (@dbNames) {
    if ('localhost' == $mysqlConnection->{'HOST'}) {
      next unless -d $datadir . escapeMysqlDatabaseName($database);
    }
    $item = XmlNode->new('database');
    $item->setAttribute( 'name',    $database );
    $item->setAttribute( 'type',    'mysql' );
    $item->setAttribute( 'version', Db::MysqlUtils::getVersion() );
    my $allowedUsers = getAllowedDatabaseUsers($account, $database, 'MYSQL');
    Db::DbConnect::addDbUsers($item, $database, $mysqlConnection, $allowedUsers);
    _unsuspendMysqlUsersPasswords($item);
    $root->addChild($item);
  }
}

# Directory name of the Mysql database can be escaped, if database name
# contains special characters such as '-'.
sub escapeMysqlDatabaseName {
  my $rawName = shift;
  my @escapedNameChars;
  for my $rawChar (split "", $rawName) {
    my $escapedChar = $rawChar =~ /[0-9a-zA-Z_]/ ? $rawChar : sprintf('@00%x', ord($rawChar));
    push @escapedNameChars, $escapedChar;
  };
  my $escapedName = join "", @escapedNameChars;
  return $escapedName;  
}

sub addGlobalMysqlUsers {
  my ($account, $dbUsersXmlNode) = @_;
  my @dbList = getDatabaseList($account, 'MYSQL');
  my $config = getDbConfig($account);
  my $userList = getCpanelClientDatabaseUsers($config, 'MYSQL');
  for my $username (keys(%$userList)) {
    next if $username eq $account;
    my $userData = $userList->{$username};
    if(isGlobalUser(\@dbList, $username, $userData)) {
      Db::DbConnect::addDbUser($dbUsersXmlNode, $username, $mysqlConnection);
    }
  }
}

sub getMysqlDataDir {
  my $mysqlConnection = shift;
  my ( $ptrRow, $sql, $datadir);
  $sql = "SHOW variables LIKE 'datadir'";
  if (  $mysqlConnection->{'EXECUTE'}->($sql)
    and $ptrRow = $mysqlConnection->{'FETCHROW'}->() ) {
    $datadir = $ptrRow->[1];
  }
  $mysqlConnection->{'FINISH'}->();
  return $datadir;
}

# Retrieve a list of databases from JSON config file
sub getDatabaseList {
  my ($account, $dbType) = @_;
  my $config = getDbConfig($account);
  my $dbList = $config->{$dbType}->{'dbs'};
  my @dbList = keys(%$dbList);
  Logging::debug("Found '".@dbList."' $dbType databases in the config file.");
  return @dbList;
}

# Return a hash of user info structs, excluding the cPanel internal users.
sub getCpanelClientDatabaseUsers {
  my ($config, $dbType) = @_;
  my $allUserList = $config->{$dbType}->{'dbusers'};
  my @clientUserNames = grep { $_ !~ /^cpses_/ } keys(%$allUserList);
  Logging::debug( "Found cPanel client user names: " . join(' ', @clientUserNames));
  my %clientUserList = map { $_ => $allUserList->{$_} } @clientUserNames;
  return \%clientUserList;
}

# Retrieve a list of database users that should be given access to the database
sub getAllowedDatabaseUsers {
  my ($account, $db, $dbType) = @_;

  my @dbList = getDatabaseList($account, $dbType);
  my $config = getDbConfig($account);
  my $userList = getCpanelClientDatabaseUsers($config, $dbType);

  my @allowedUserList;
  for my $username (keys(%$userList)) {
    my $userData = $userList->{$username};
    my ($chosenDb, @disallowedDbList) = chooseAccessibleDb(\@dbList, $username, $userData);
    if ($chosenDb) {
      Logging::debug("Giving access to the database '$chosenDb' for user $username");
    }
    if (@disallowedDbList && @disallowedDbList) {
      Logging::warning("User '$username' has access to more than one database; it is".
          " not supported in Plesk. Access to the following databases will be".
          " disabled:".join(' ', @disallowedDbList));
    }
    if (defined $chosenDb && $chosenDb eq $db) {
      push @allowedUserList, $username;
    }
  }
  return \@allowedUserList;
}

# If user has access to more than one database in cPanel, for Plesk we need to
# choose only one. By convention, it is the first database that we give the
# user access to.
sub chooseAccessibleDb {
  my($dbList, $username, $userData) = @_;
  if (isGlobalUser($dbList, $username, $userData)) {
    # global user has no preferred DB: he has access to all of them
    return;
  }
  my $allowedDatabases = $userData->{'dbs'};
  return sort keys %$allowedDatabases;
}

# Check, if the user has access to all subsciption databases
sub isGlobalUser {
  my ($allDatabases, $username, $userData) = @_;
  Logging::debug("Checking, if user $username has access to all"
        ." databases (".join(" ", @$allDatabases).")");
  my @userDbList = keys(%{$userData->{'dbs'}});
  for my $dbname ( @$allDatabases ) {
    if (not grep { $_ eq $dbname } @userDbList) {
      return 0;
    }
  }
  Logging::debug("'$username' has access to all databases in the subscription");
  return 1
}

# Read and parse database config file
sub getDbConfig {
  my $account = shift;
  my $configPath = "$installDir/databases/$account";
  my $filename = "$configPath.json";
  my $config = parseYamlFile($filename, \&decode_json);
  unless ($config) {
    Logging::debug("Unable to read the database config file '$filename'");
    $filename = "$configPath.yaml";
    $config = parseYamlFile($filename, \&Load);
  }
  unless ($config) {
    Logging::warning("Cannot find a JSON or YAML database config file for user '$account'");
  }
  return $config;
}

# Read JSON or YAML file and return a Perl ref with the parsed data.
sub parseYamlFile {
  my ($filename, $parser) = @_;
  my ($filehandle, $doc);
  if ( open $filehandle, $filename ) {
    Logging::debug("Reading the database config file '$filename'.");
    my $text = join '', <$filehandle>;
    $doc = &$parser($text);
    close $filehandle;
  }
  return $doc;
}

sub _unsuspendMysqlUsersPasswords {
  my ( $root ) = @_;
  XPath::Select $root, 'dbuser/password', sub {
    my $passwordNode = shift;
    my $password = $passwordNode->getText('password');
    $password = '*' x 41 unless $password;
    if ( length( $password ) == 41 ) {
      if ( substr($password,0,1) eq '-' ) {
        $password =~ s/\-//;
        if ( $password =~ m/^\*+$/ ) {
          $password = '';
        }
        else {
          $password = reverse $password;
          $password = '*' . $password;
        }
      }
      else {
        if ( $password =~ m/^\+/ ) {
          $password =~ s/\+/\*/;
        }
      }
    }
    elsif ( length( $password ) == 40 ) {
      $password = '*' . $password;
      if ( $password =~ m/^\*+$/ ) {
        $password = '';
      }
    }
    $passwordNode->setText($password);
  };
}

sub getPgsqlConnection {
  my $connection =
    Db::DbConnect::getDbConnect( 'postgresql', 'postgres', undef, 'template1', 'localhost' );
  unless ( ref($connection) eq 'HASH' ) {
    Logging::debug("Cannot connect to postgresql");
  }
  unless ( $connection and ref( $connection->{'EXECUTE'} ) eq 'CODE'
      and ref( $connection->{'FETCHROW'} ) eq 'CODE'
      and ref( $connection->{'FINISH'} )   eq 'CODE' ) {
    Logging::debug("Connection to postgresql is not valid");
  }
  return $connection;
}

sub addPgSQLDatabases {
  my ( $root, $account ) = @_;

  my ( $ptrRow, $item, $dumpFile, $sql, @dbNames, %dbGroups );

  if ( !AgentConfig::psqlBin() ) {    
    return;
  }  

  my $userPasswords = getPgsqlUserPasswords();
  my @dbNames = getDatabaseList($account, 'PGSQL');
  my @processedUserNames;
  for my $database (@dbNames) {
    $item = getPostgresDatabaseXmlNode($database);

    Logging::debug("Dumping users of the database '$database'.");
    my $allowedUsers = getAllowedDatabaseUsers($account, $database, 'PGSQL');
    for my $uname (@$allowedUsers) {
      my $useritem = XmlNode->new( 'dbuser' );
      $useritem->setAttribute( 'name', $uname );
      my $pwditem = Packer::makePasswordNode($userPasswords->{$uname}, 'encrypted' );
      if ($pwditem) {
        $useritem->addChild($pwditem);
      }
      $item->addChild($useritem);
    }
    Logging::debug("$database user dumping done");
    $root->addChild($item);
  }
}

sub getPgsqlUserPasswords {
  my ($ptrRow, %userPasswords); 
  if ( $pgsqlConnection->{'EXECUTE'}->("select usename, passwd from pg_shadow;")) {
    while ( $ptrRow = $pgsqlConnection->{'FETCHROW'}->() ) {
      my ($username, $password) = @$ptrRow;
      $userPasswords{$username} = $password;
    }
    $pgsqlConnection->{'FINISH'}->();
  }
  return \%userPasswords;
}

sub addGlobalPgsqlUsers {
  my ($account, $dbUsersXmlNode) = @_;
  my $ptrRow;

  my @dbList = getDatabaseList($account, 'PGSQL');
  my $config = getDbConfig($account);
  my $userList = getCpanelClientDatabaseUsers($config, 'PGSQL');
  my $userPasswords = getPgsqlUserPasswords();
  for my $username (keys(%$userList)) {
    my $userData = $userList->{$username};
    if(isGlobalUser(\@dbList, $username, $userData)) {
      Logging::debug("Dumping account database user '$username'");
      my $useritem = XmlNode->new('dbuser');
      my $pwditem = Packer::makePasswordNode( $userPasswords->{$username}, 'encrypted' );
      $useritem->setAttribute('name', $username);
      if ($pwditem) {
        $useritem->addChild($pwditem);
      }

      my $dbsitem = XmlNode->new('db-server');
      $dbsitem->setAttribute('type', 'postgresql');
      $dbsitem->addChild(XmlNode->new('host', 'content' => 'localhost'));
      $dbsitem->addChild(XmlNode->new('port', 'content' => 5432));
      $useritem->addChild($dbsitem);

      $dbUsersXmlNode->addChild($useritem);
    }
  }

  if (
    $pgsqlConnection->{'EXECUTE'}->(
      "select usename, passwd from pg_shadow where usename = '$account'"
    )
  )
  {
    if ($ptrRow = $pgsqlConnection->{'FETCHROW'}->()) {
      my ($uname, $pwd) = @$ptrRow;

      Logging::debug("Dumping account database user '$uname'");

      my $useritem = XmlNode->new('dbuser');
      $useritem->setAttribute('name', $uname);
      my $pwditem = Packer::makePasswordNode($pwd, 'encrypted');
      if ($pwditem) {
        $useritem->addChild($pwditem);
      }

      my $dbsitem = XmlNode->new('db-server');
      $dbsitem->setAttribute('type', 'postgresql');
      $dbsitem->addChild(XmlNode->new('host', 'content' => 'localhost'));
      $dbsitem->addChild(XmlNode->new('port', 'content' => 5432));
      $useritem->addChild($dbsitem);

      $dbUsersXmlNode->addChild($useritem);
    }
    $pgsqlConnection->{'FINISH'}->();
  }

  Logging::debug("done");
}

sub getPostgresDatabaseXmlNode {
  my $databaseName = shift;
  my $psql = AgentConfig::psqlBin();
  my @out     = `$psql --version | awk '{print \$3}'`;
  my $version = '';
  chomp $out[0];
  if ( $out[0] =~ /(\d+\.\d+\.\d+).*/ ) {
    $version = $1;
  }
  my $item = XmlNode->new('database');
  $item->setAttribute( 'name',    $databaseName );
  $item->setAttribute( 'type',    'postgresql' );
  $item->setAttribute( 'version', $version );
  return $item;
}

sub addSubDomains {
  my ( $root, $domain, $account, $ptrSubDomains, $shallowMode ) = @_;

  my $docRoots = getDocRoots();
  my $cgiRoots = getScriptAliases();

  foreach my $subdomain ( @{$ptrSubDomains} ) {
    Logging::debug( "Subdomain '$subdomain.$domain' ..." );

    my $subdomainRoot;
    $subdomainRoot = $docRoots->{"$subdomain.$domain"} if $docRoots->{"$subdomain.$domain"};
    
    my $subdomainCgiRoot;
    $subdomainCgiRoot = $cgiRoots->{"$subdomain.$domain"} if $cgiRoots->{"$subdomain.$domain"};

    my $nodeSubDomain = XmlNode->new('subdomain');
    $nodeSubDomain->setAttribute( 'name', $subdomain, 'noencode' );
    $nodeSubDomain->setAttribute( 'www_root', $subdomainRoot) if $subdomainRoot;
    $nodeSubDomain->setAttribute( 'cgi_root', $subdomainCgiRoot) if $subdomainCgiRoot;

    my $ftpusers = getFtpUsers( $account );
    if ( exists $ftpusers->{$subdomain} ) {
      my ( $password, $directory, $shell, $quota ) = @{$ftpusers->{$subdomain}};
      my $ftpUserNode = Packer::makeFtpUserNode( $subdomain, $password, $directory, $shell, $quota);
      $nodeSubDomain->addChild($ftpUserNode);
    }

    # Dump mail system and addon domains: if there is 1 addon domain for
    # given subdomain - dump mail system of ADDON DOMAIN, not subdomain.
    # This case reflects common usage of addon domains in cPanel...
    my $addondomains = getAddOnDomains( "$subdomain.$domain" );
    addAddOnDomains( $nodeSubDomain, $account, $addondomains );

    unless ($shallowMode) {
      addMailnames( $nodeSubDomain, "$subdomain.$domain", $account );
      addMailAliases( $nodeSubDomain, "$subdomain.$domain", $account );
    }

    $root->addChild($nodeSubDomain);
    Logging::debug(' OK');

  }
  return $root;
}

# Read information from '/etc/userdatadomains' file
sub parseUserDataDomainsInfo {
  if ($_parsedUserDataDomainsInfo) {
    return;
  }

  my $userDataDomainParser = Parser::makeFileParser('/etc/userdatadomains');
  if (defined($userDataDomainParser)) {
    my %domainParams = %{$userDataDomainParser->{'PARSE'}->('KEYSEPARATOR' => ':', 'VALUESEPARATOR' => '==')};

    foreach my $domainName (keys %domainParams) {
      $_userDataDomainsInfo{$domainName} = {
        'type' => $domainParams{$domainName}[2]
      };
    }
  }

  $_parsedUserDataDomainsInfo = 1;
}

sub getAddOnDomains {
  my ( $domain ) = @_;

  my $apacheAliases = getApacheAliases();

  my @found;
  if ( exists $apacheAliases->{$domain} ) {
    my %aliases = map { $_ => 1 } @{ $apacheAliases->{$domain} };
    foreach my $alias ( keys %aliases ) {
      next if exists $apacheAliases->{$alias};
      push @found, $alias;
    }
  }
  return \@found;
}

sub addAddOnDomains {
  my ( $root, $account, $aliases ) = @_;

  parseUserDataDomainsInfo();

  if ( ref($aliases) =~ /ARRAY/ ) {
    foreach my $alias ( @{$aliases} ) {
      my $addonDomainNode = XmlNode->new( 'addondomain' );
      $addonDomainNode->setAttribute( 'name', $alias );
      if ( exists $_userDataDomainsInfo{$alias} && exists $_userDataDomainsInfo{$alias}->{'type'} ) {
        $addonDomainNode->setAttribute( 'type', $_userDataDomainsInfo{$alias}->{'type'} );
      } else {
        $addonDomainNode->setAttribute( 'type', 'alias' );
      }
      addMailnames($addonDomainNode, $alias, $account);
      addMailAliases($addonDomainNode, $alias, $account );
      addDnsZone($addonDomainNode, $alias);
      $root->addChild($addonDomainNode);
    }
  }

  return $root;
}

my %_userParams;

sub getUserParams {
  my $user = shift;

  unless ( keys %_userParams ) {
    my $passwdReader = Parser::makeFileParser("/etc/passwd");
    if (defined($passwdReader)) {
      %_userParams = %{$passwdReader->{'PARSE'}->('KEYSEPARATOR' => ':', 'VALUESEPARATOR' => ':')};
    }
  }

  return $_userParams{$user};
}

sub addDefaultMailname {
  my ( $parentNode, $domain, $account ) = @_;

  my $mailNode = $parentNode->getChild( 'mail', 1 );

  my $home = getHomeDir($account);
  Logging::debug("Dumping default mailname for account '$account'");

  my $mailnameNode = XmlNode->new('mailname');
  $mailnameNode->setAttribute( 'name', $account );
  $mailnameNode->setAttribute( 'domainname', $domain );

  Logging::debug( "migrating " . $account . "@" . "$domain" );

  my $password = getSystemPassword($account);
  $mailnameNode->setAttribute( 'password', $password );
  $mailNode->addChild($mailnameNode);

}

sub addMailnames {
  my ( $parentNode, $domain, $account ) = @_;

  my $mailNode = $parentNode->getChild( 'mail', 1 );

  my $home = getHomeDir($account);
  Logging::debug("Examining $home/etc/$domain");

  if ( -d "$home/etc/$domain" && -T "$home/etc/$domain/passwd" ) {
    my $fileParser = Parser::makeFileParser("$home/etc/$domain/passwd");
    my $ptrRows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':' );

    if ( ref($ptrRows) =~ /ARRAY/ ) {
      foreach my $ptrRow ( @{$ptrRows} ) {
        my $box  = $ptrRow->[0];
        next if ( $box eq $account);
        my $mailnameNode = XmlNode->new('mailname');
        $mailnameNode->setAttribute( 'name', $box );
        $mailnameNode->setAttribute( 'domainname', $domain );

        Logging::debug( "migrating " . $box . "@" . "$domain" );

        my $ptrHash = undef;

        if ( -T "$home/etc/$domain/shadow" ) {
          my $shadowParser = Parser::makeFileParser("$home/etc/$domain/shadow");
          my $ptrHash = $shadowParser->{'PARSE'}->(
            'KEYSEPARATOR'   => ':',
            'VALUESEPARATOR' => ''
          );
          my $password = '';
          if ( ref($ptrHash) =~ /HASH/ ) {
            if ( my $value = $ptrHash->{$box} ) {
              ($password) = split( ':', $value );
            }
          }
          if (substr($password,0,8) eq '*LOCKED*') {
            $password = substr($password,8);
          }
          elsif (substr($password,0,2) eq '!!') {
            $password = substr($password,2);
          }
          $mailnameNode->setAttribute( 'password', $password );
          Logging::debug( "Password for '$box' is set to $password.");
        } else {
          $mailnameNode->setAttribute( 'password', '' );
          Logging::debug( "Unable to retrieve password for '$box', use empty string instead." );
        }

        if ( -T "$home/etc/$domain/quota" ) {
          my $quotaParser = Parser::makeFileParser("$home/etc/$domain/quota");
          $ptrHash = $quotaParser->{'PARSE'}->( 'KEYSEPARATOR'   => ':',
                                                'VALUESEPARATOR' => '' );
          my $quotaValue = '-1'; # '-1' is for 'unlimited'
          if ( ref($ptrHash) =~ /HASH/ && ( my $value = $ptrHash->{$box} ) ) {
			  $quotaValue = sprintf( "%.0f", $value );
          }
          $mailnameNode->setAttribute( 'quota', $quotaValue);
          Logging::debug( "Quota for '$box' is set to $quotaValue.");
        }

        $mailNode->addChild($mailnameNode);
      }
    }
  }
}

sub addMailAliases {
  my ( $parentNode, $domain, $account ) = @_;

  my $mailNode = $parentNode->getChild( 'mail', 1 );

  my (
    $ptrRows,  $default,  $item,  $box,  $ptrHash,
    $value,  $password,  $dumpFile,  $ptrAliases,  $src, $dst
  );
  my $home = getHomeDir($account);
  my $mainDomain = getDomainName($account);

  Logging::debug("Mail for $domain migration started...");
  $ptrAliases = [];
  if ( -T "/etc/valiases/$domain" ) {
    my $fileParser = Parser::makeFileParser("/etc/valiases/$domain");
    $ptrAliases = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':' );
    if ( ref($ptrAliases) =~ /ARRAY/ ) {
      foreach my $ptrRow ( @{$ptrAliases} ) {
        if ( $ptrRow->[0] eq '*' ) {
          my $value = $ptrRow->[1];
          if ( $value =~ /\s*$account\s*/) {
            #Default domain mail account
            $default = "$account@".$mainDomain;
          }
          elsif ( $ptrRow->[1] =~ /\s*([^@]+\@[^@+]+)\s*/ ) {
            $default = $1;
            last;
          }
          elsif ( $ptrRow->[2] =~ /blackhole/ ) {
            $default = ':blackhole:';
            last;
          }
          elsif ( $ptrRow->[2] =~ /fail/ ) {
            $default = ':fail:' . $ptrRow->[3];
            last;
          }
        }
      }
    }
  }

  if ($default) {
    $item = XmlNode->new('default');
    if ( $default eq ':blackhole:' ) {
      $item->setAttribute( 'target', 'ignore' );
    }
    elsif ( $default =~ /:fail:(.*)/ ) {
      $item->setAttribute( 'target', 'fail' );
      $item->setText($1);
    }
    else {
      $item->setAttribute( 'target', 'email' );
      $item->setText($default);
    }
    $mailNode->addChild($item);
  }

  if ( @{$ptrAliases} ) {
    Packer::addForwarders( $mailNode, $ptrAliases );
    addAutoresponders( $mailNode, $account, $ptrAliases );
  }

  Logging::debug("Finish dumping mail configuration for $domain.");
}

sub makeSpamassassinNode {
  my ( $root, $account ) = @_;
  my $home = getHomeDir($account);

  return unless -e "$home/.spamassassin";

  my $saEnabled = 'off';
  $saEnabled = 'on' if -e "$home/.spamassassinenable";

  my $sanode = XmlNode->new('spamassassin');
  $sanode->setAttribute( 'status', $saEnabled );

  $root->addChild($sanode);

  if ( -e "$home/.spamassassin/user_prefs" ) {
    my $saConfig =
      SpamAssassinCfg::parseConfig( "$home/.spamassassin/user_prefs", $home );

    my $cfgvalues = SpamAssassinCfg::getConfigRequireScore($saConfig);
    $sanode->setAttribute( 'hits', $cfgvalues ) if $cfgvalues;

    $cfgvalues = SpamAssassinCfg::getConfigRewriteHeadr($saConfig);
    if ($cfgvalues) {
      if ( scalar( @{$cfgvalues} ) == 2 ) {
        if (${$cfgvalues}[0] eq 'subject') {
          $sanode->setAttribute( 'subj-text', ${$cfgvalues}[1] );
        }
      }
      $sanode->setAttribute( 'action', 'mark' );
    }

    $cfgvalues = SpamAssassinCfg::getConfigBlackList($saConfig);
    Packer::addSpamassassinLists( $sanode, $cfgvalues, 'blacklist-member' );

    $cfgvalues = SpamAssassinCfg::getConfigWhiteList($saConfig);
    Packer::addSpamassassinLists( $sanode, $cfgvalues, 'whitelist-member' );

    $cfgvalues = SpamAssassinCfg::getConfigUnBlackList($saConfig);
    Packer::addSpamassassinLists( $sanode, $cfgvalues, 'unblacklist-member' );

    $cfgvalues = SpamAssassinCfg::getConfigUnWhiteList($saConfig);
    Packer::addSpamassassinLists( $sanode, $cfgvalues, 'unwhitelist-member' );
  }
  return $sanode;
}

sub addAutoresponders {
  my ( $mailNode, $account, $ptrAliases ) = @_;
  my $home = getHomeDir($account);

  foreach my $ptrRow ( @{$ptrAliases} ) {

    my $src = $ptrRow->[0];
    my $dst = $ptrRow->[1];

    next unless ( $src =~ /^([^@]+)@[^@]+$/ );
    my $mailname = $1;

    $dst =~ s/^\s+//;
    $dst =~ s/\s+$//;

    foreach my $email ( split /,/, $dst ) {
      next unless ( $email =~ /^['"]?\s*\|.*\/cpanel\/bin\/autorespond/ );
      next unless ( open( INPUT, "<$home/.autorespond/$src" ) );
      binmode INPUT;

      my $autoresponderNode = XmlNode->new('autoresponder');
      $autoresponderNode->setAttribute( 'mailname', $mailname );

      while (<INPUT>) {
        chomp;
        last unless $_;

        if (/^From:.+<(.+)>/) {
          $autoresponderNode->setAttribute( 'from', $1 ) if ( $src ne $1 );
        }
        elsif (/^Content-type:\s+text\/([^;]+);\s+charset=(.+)/) {
          $autoresponderNode->setAttribute( 'type',    $1 );
          $autoresponderNode->setAttribute( 'charset', $2 );
        }
        elsif (/^Subject:\s*(.*)/) {
          $autoresponderNode->setAttribute( 'subject', EncodeBase64::encode($1) );
        }
      }

      $autoresponderNode->setText( EncodeBase64::encode( join( '', (<INPUT>) ) ) );
      close(INPUT);

      $mailNode->addChild($autoresponderNode);
    }
  }
  return $mailNode;
}

sub getProtectedDirs {
  my ( $root, $domain, $account, $ptrSubDomains ) = @_;

  my ( $cmd, $exclude, $find, $ptrHash);

  my $home = getHomeDir($account);
  my $rootDir = "$home/.htpasswds";
  unless ( -d $rootDir ) {
    return;
  }

  $find = AgentConfig::findBin();
  $cmd  = " cd $rootDir; $find . ";
  if ( @{$ptrSubDomains} ) {
    $exclude = join( ' -o ', map {"-path './$_' -prune"} @{$ptrSubDomains} );
    $cmd .= "\\( $exclude \\) -o";
  }
  $cmd .= " \\( -type f -name passwd -printf '\%h\\n' \\)";
  my @pdirs;
  foreach my $pdir (`$cmd`) {
    chomp $pdir;
    $pdir =~ s/^\.\///;
    push @pdirs, $pdir;
  }
  return @pdirs;
}

sub getProtectedDirTitle {
  my ( $account, $pdir ) = @_;

  my $home = getHomeDir($account);

  my $title = _getAuthName("$home/$pdir/.htaccess.suspend");
  $title = _getAuthName("$home/$pdir/.htaccess") unless defined $title;
  $title = '' unless defined $title;
  return $title;
}

sub _getAuthName {
  my ( $htaccess_file ) = @_;

  if ( -T $htaccess_file ) {
    my $fileParser = Parser::makeFileParser($htaccess_file);
    my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ' ' );

    if ( ref($ptrHash) =~ /HASH/ ) {
      if ( exists( $ptrHash->{'AuthName'} ) ) {
        if ( $ptrHash->{'AuthName'} =~ /"([^"]+)"/ ) {
          return $1;
        }
      }
    }
  }
  return;
}

sub getProtectedDirUsers {
  my ( $account, $pdir ) = @_;

  my $home = getHomeDir($account);
  my $rootDir = "$home/.htpasswds";
  unless ( -d $rootDir ) {
    return;
  }

  my %users;
  my $fileParser = Parser::makeFileParser("$rootDir/$pdir/passwd");
  my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' );
  if ( ref($ptrHash) =~ /HASH/ ) {
    while ( ( my $name, my $password ) = each( %{$ptrHash} ) ) {
      $name =~ s/ /_/;
      if ( $password !~ /^[\x20-\x7f]*$/ ) {
        $users{$name} = {'password' => EncodeBase64::encode($password),
                         'encoding' => 'base64' };
      }
      else {
        $users{$name} = {'password' => $password };
      }
    }
  }
  return \%users;
}

sub addFtpUserNodes {
  my ( $root, $domain, $account, $ftpUsers, $subdomains ) = @_;

  my $home = getHomeDir($account);

  # ftp users of domain
  while ( ( my $user, my $userData ) = each( %{$ftpUsers} ) ) {
    my ( $password, $directory, $shell, $quota ) = @{$userData};
    if ( $user eq $account ) {
      # makes main domains's ftp user node
      $shell = getSuspendedShell($account);
      if( !defined $shell ) {
        my $userParams = getUserParams($account);
        if ( ( defined $userParams ) && ( @{$userParams} ) ) {
          $shell = $userParams->[5];
        }
      }
      my $ftpUserNode = Packer::makeFtpUserNode( $account, $password, undef, $shell || '' );
      $root->addChild( $ftpUserNode );
    }
    elsif ( ! grep ( /$user/, @{$subdomains} ) ) {
      my $ftpUserNode = Packer::makeFtpUserNode( $user, $password, $directory, $shell, $quota);
      $root->addChild($ftpUserNode);
    }
  }
  return $root;
}

sub makeAnonFtpdNode {
  my $account = shift;
  my ( $node, $publicFtp, $pureFtpd, $dumpFile );
  my $home = getHomeDir($account);

  $publicFtp = "$home/public_ftp";
  unless ( -d $publicFtp ) {
    return;
  }
  $node = XmlNode->new('anonftp');

  #
  # prepare anonftps
  #
  $pureFtpd = '/etc/pure-ftpd';
  if ( -d $pureFtpd ) {
    unless ( keys %anonftps ) {
      my ( $linkTo, $fullPath );
      if ( opendir( FTPD, "$pureFtpd" ) ) {
        foreach my $link ( readdir(FTPD) ) {
          $fullPath = "$pureFtpd/$link";
          next unless ( -l $fullPath );
          $linkTo = readlink($fullPath);
          if ( $linkTo =~ /\Qhome\E\/([^\/]+)\/public_ftp/ ) {
            $anonftps{$1} = $linkTo;
          }
        }
        closedir(FTPD);
      }
    }
  }
  #
  # end prepare anonftps
  #

  #
  # check permissions
  #
  # check for read permission
  if ( exists( $anonftps{$account} ) && ( ( stat($publicFtp) )[2] & 05 ) )
  {
    $node->setAttribute( 'pub', 'true' );
    # check for write permission
    if ( ( stat("$publicFtp/incoming") )[2] & 03 ) {
      $node->setAttribute( 'incoming', 'true' );
    }
  }
  #
  # end check permissions
  #

  return $node;
}

sub getCpanelVersion {
  my ( $engineRoot, $util );

  loadMainConfig();

  $engineRoot = $cPanelConfig{'engineroot'};
  if ( defined $engineRoot) {
    unless ( -d $engineRoot ) {
      return;
    }
    $util = "$engineRoot/cpanel";
    unless ( -x $util ) {
      return;
    }
    my @outs = `$util`;
    foreach my $line (@outs) {
      chomp $line;
      if ( $line =~ /cPanel\s+\[(\d+)\.(\d+).\d+/ ) {
        if ( $1 ne '9' && $1 ne '10' && $1 ne '11' ) {
          return;
        }
        return "$1.$2";
      }
    }
  }
  elsif ( -f "$installDir/cpanel.config" ) {
    return '9';
  }
  return;
}

sub getMainIP {
  my $mainip_file = "$installDir/mainip";
  my $ip;
  if ( open( INPUT, "<$mainip_file" ) ) {
    binmode INPUT;
    my $pattern = qr/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/;
    while (<INPUT>) {
      if (/$pattern/) {
        $ip = $1;
        last;
      }
    }
    close(INPUT);
    if ($ip) {
      return $ip;
    }
  }
  return;
}

sub unsuspendDomainMaillists {
  my ( $domain ) = @_;

  my @handledLists;
 
  my $listsDir = AgentConfig::mailmanRoot()."/lists/";
  my $suspendedListsDir = AgentConfig::mailmanRoot()."/suspended.lists/";

  my @suspendedLists = <$suspendedListsDir*_$domain>;
  if ( @suspendedLists ) {
    foreach my $list ( @suspendedLists ) {
      system("mv $list $listsDir 2>/dev/null");
      push @handledLists, substr($list,length($suspendedListsDir));
    }
  }
  return \@handledLists;
}

sub suspendDomainMaillists {
  my ( $handle ) = @_;

  my @handledLists = @{$handle};

  my $listsDir = AgentConfig::mailmanRoot()."/lists/";
  my $suspendedListsDir = AgentConfig::mailmanRoot()."/suspended.lists/";
  if ( @handledLists ) {
    foreach my $list ( @handledLists ) {
      system("mv $listsDir$list $suspendedListsDir 2>/dev/null");
    }
  }
}

#
# make dump in 'shallow' mode. Corresponds to old "-da -nc" mode
#
sub makeShallowDump {
  my $workDir = shift;

  Agent::setWorkDir($workDir);

  my $adminNode = XmlNode->new( 'admin' );
  $adminNode->setAttribute( 'guid', '' );

  # dump resellers
  my $resellersNode = XmlNode->new( 'resellers' );
  my @resellerIds = Agent::getResellers();
  foreach my $resellerId ( @resellerIds ) {

    my $resellerNode = XmlNode->new( 'reseller' );
    $resellerNode->setAttribute( 'name', $resellerId );
    $resellerNode->setAttribute( 'guid', '' );

    my @resClientIds = Agent::getClients($resellerId);
    if ( @resClientIds ) {
      my $resClientsNode = XmlNode->new( 'clients' );
      foreach my $resClientId ( @resClientIds ) {

        my $resClientNode = XmlNode->new( 'client' );
        $resClientNode->setAttribute( 'name', $resClientId );
        $resClientNode->setAttribute( 'guid', '' );

        my @clDomainIds = Agent::getDomains($resClientId);
        if ( @clDomainIds ) {
          my $clDomainsNode = XmlNode->new( 'domains' );
          foreach my $clDomainId ( @clDomainIds ) {

            my $clDomainNode = XmlNode->new( 'domain' );
            $clDomainNode->setAttribute( 'name', $clDomainId);
            $clDomainNode->setAttribute( 'guid', '' );

            $clDomainNode->addChild( XmlNode->new( 'preferences' ) );

            my $clDomainPropertiesNode = Agent::getDomainProperties($clDomainId);
            $clDomainNode->addChild( $clDomainPropertiesNode );
            $clDomainsNode->addChild( $clDomainNode );

          }
          $resClientNode->addChild( $clDomainsNode );
        }
        $resClientsNode->addChild( $resClientNode );
      }
      $resellerNode->addChild( $resClientsNode );
    }

    my @resDomainIds = Agent::getDomains($resellerId);
    if ( @resDomainIds ) {
      my $resDomainsNode = XmlNode->new( 'domains' );
      foreach my $resDomainId ( @resDomainIds ) {

        my $resDomainNode = XmlNode->new( 'domain' );
        $resDomainNode->setAttribute( 'name', $resDomainId );
        $resDomainNode->setAttribute( 'guid', '' );

        $resDomainNode->addChild( XmlNode->new( 'preferences' ) );

        my $resDomainPropertiesNode = Agent::getDomainProperties($resDomainId);
        $resDomainNode->addChild( $resDomainPropertiesNode );
        $resDomainsNode->addChild( $resDomainNode );

      }
      $resellerNode->addChild( $resDomainsNode );
    }

    $resellersNode->addChild( $resellerNode );
  }
  $adminNode->addChild( $resellersNode );

  # dump clients
  my $clientsNode = XmlNode->new( 'clients' );
  my @clientIds = Agent::getClients( undef );
  foreach my $clientId ( @clientIds ) {
    my $clientNode = XmlNode->new( 'client' );
    $clientNode->setAttribute( 'name', $clientId );
    $clientNode->setAttribute( 'guid', '' );

    my @clDomainIds = Agent::getDomains($clientId);
    if ( @clDomainIds ) {
      my $clDomainsNode = XmlNode->new( 'domains' );
      foreach my $clDomainId ( @clDomainIds ) {

        my $clDomainNode = XmlNode->new( 'domain' );
        $clDomainNode->setAttribute( 'name', $clDomainId );
        $clDomainNode->setAttribute( 'guid', '' );

        $clDomainNode->addChild( XmlNode->new( 'preferences' ) );

        my $clDomainPropertiesNode = Agent::getDomainProperties($clDomainId);
        $clDomainNode->addChild( $clDomainPropertiesNode );
        $clDomainsNode->addChild( $clDomainNode );

      }
      $clientNode->addChild( $clDomainsNode );
    }

    $clientsNode->addChild( $clientNode );
  }
  $adminNode->addChild( $clientsNode );

  # dump domains
  my $domainsNode = XmlNode->new( 'domains' );
  my @domainIds = Agent::getDomains( undef );
  foreach my $domainId ( @domainIds ) {
    my $domainNode = XmlNode->new( 'domain' );
    $domainNode->setAttribute( 'name', $domainId);
    $domainNode->setAttribute( 'guid', '' );

    $domainNode->addChild( XmlNode->new( 'preferences' ) );

    my $domainPropertiesNode = Agent::getDomainProperties($domainId);
    $domainNode->addChild( $domainPropertiesNode );

    $domainsNode->addChild( $domainNode );
  }
  $adminNode->addChild( $domainsNode );

  my $migrationDumpNode = DumpComposer::createMigrationDumpNode();
  $migrationDumpNode->addChild( $adminNode );

  unless ( DumpComposer::serializeXmlNode($migrationDumpNode,"$workDir/$SHALLOW_DUMP_FILE_NAME") ) {
    Logging::error("Dump file '$SHALLOW_DUMP_FILE_NAME' is not saved.");
  }
  return $migrationDumpNode;
}

#
# Pass 'undef' for $resellers, $clients and $domains to make 'dump-all'
#
sub makeDump {
  my ( $workDir, $resellers, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip) = @_;

  Agent::setWorkDir($workDir);

  $resellers = DumpComposer::getAllResellers() unless( defined ( $resellers ) );
  $clients = DumpComposer::getAllClients() unless( defined ( $clients ) );
  $domains = DumpComposer::getAllDomains() unless( defined ( $domains ) );

  my $statusFile = "$workDir/$STATUS_FILE_NAME";
  my $dumpStatus = MigrationDumpStatus->new($statusFile);
  $dumpStatus->start(scalar(@{$resellers}) + scalar(@{$clients}), scalar(@{$domains}));

  my $adminNode = XmlNode->new( 'admin' );
  $adminNode->setAttribute( 'guid', '' );

  # dump resellers
  my $resellersNode = getResellersNode($resellers, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
  $adminNode->addChild( $resellersNode );

  # dump clients
  my $clientsNode = getClientsNode(undef, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
  $adminNode->addChild( $clientsNode );

  # dump domains
  my $domainsNode = getDomainsNode(undef, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
  $adminNode->addChild( $domainsNode );

  my $migrationDumpNode = DumpComposer::createMigrationDumpNode($configOnly);
  $migrationDumpNode->addChild( $adminNode );

  my $contentListNode = DumpComposer::getContentList($migrationDumpNode);

  my $append2content = sub {
    my ($fileName, $isMain) = @_;
    return unless (-f  "$workDir/$fileName");
    my $fileSize = -s "$workDir/$fileName";
    my $fileNode = XmlNode->new( 'file' );
    $fileNode->setAttribute( 'size', $fileSize );
	$fileNode->setAttribute( 'main' , $isMain ? 'true' : 'false' );
    $fileNode->setText( $fileName );
    $contentListNode->addChild( $fileNode );
    my $contentListSize = $contentListNode->getAttribute( 'size' ) || '0';
    my $contentListNewSize = int($contentListSize) + int($fileSize);
    $contentListNode->setAttribute( 'size', $contentListNewSize );
  };

  if ( DumpComposer::serializeXmlNode($migrationDumpNode,"$workDir/" . $DUMP_FILE_NAME) ) {
    $append2content->($DUMP_FILE_NAME, 1);
  }
  else {
    Logging::error("Dump file ".$DUMP_FILE_NAME." is not saved.");
  }

  $append2content->($MIGRATION_RESULT_FILE_NAME, 0);

  unless ( DumpComposer::serializeXmlNode($contentListNode,"$workDir/$CONTENT_LIST_FILE_NAME") ) {
    Logging::error("Content list file '$CONTENT_LIST_FILE_NAME' is not saved.");
  }

  return $migrationDumpNode;
}

sub getResellersNode {
  my ($resellers, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip) = @_;

  my @resellerIds = Agent::getResellers();
  unless ( @resellerIds ) {
    return;
  }

  my $resellersNode;
  if (scalar(@{$resellers})) {
    foreach my $reseller (@{$resellers}) {
      foreach my $resellerId (@resellerIds) {
        if ($reseller eq $resellerId) {
          my $resellerClientsNode = getClientsNode(
              $resellerId, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip
          );
          my $resellerDomainsNode = getDomainsNode($resellerId, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
          if (defined($resellerClientsNode) or defined($resellerDomainsNode)) {
            my $resellerNode = XmlNode->new( 'reseller' );
            $resellerNode->setAttribute( 'name', $resellerId );
            $resellerNode->setAttribute( 'guid', '' );
            $resellerNode->addChild( DumpComposer::getPreferencesNode($resellerId) );
            $resellerNode->addChild( DumpComposer::_getDefaultResellerPropertiesNode() );
            if (defined($resellerClientsNode)) {
              $resellerNode->addChild($resellerClientsNode);
            }
            if (defined($resellerDomainsNode)) {
              $resellerNode->addChild($resellerDomainsNode);
            }
            unless (defined($resellersNode)) {
              $resellersNode = XmlNode->new('resellers');
            }
            $resellersNode->addChild($resellerNode);
          }
        }
      }
    }
  }
  else {
    foreach my $resellerId (@resellerIds) {
      my $resellerClientsNode = getClientsNode(
          $resellerId, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip
      );
      my $resellerDomainsNode = getDomainsNode($resellerId, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
      if (defined($resellerClientsNode) or defined($resellerDomainsNode)) {
        my $resellerNode = XmlNode->new( 'reseller' );
        $resellerNode->setAttribute( 'name', $resellerId );
        $resellerNode->setAttribute( 'guid', '' );
        $resellerNode->addChild( DumpComposer::getPreferencesNode($resellerId) );
        $resellerNode->addChild( DumpComposer::_getDefaultResellerPropertiesNode() );
        if (defined($resellerClientsNode)) {
          $resellerNode->addChild($resellerClientsNode);
        }
        if (defined($resellerDomainsNode)) {
          $resellerNode->addChild($resellerDomainsNode);
        }
        unless (defined($resellersNode)) {
          $resellersNode = XmlNode->new('resellers');
        }
        $resellersNode->addChild($resellerNode);
      }
    }
  }

  return $resellersNode;
}

sub getClientsNode {
  my ( $owner, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip ) = @_;

  my @clientIds = Agent::getClients( $owner );
  unless ( @clientIds ) {
    return;
  }

  my $clientsNode;
  if (scalar(@{$clients})) {
    foreach my $client (@{$clients}) {
      foreach my $clientId (@clientIds) {
        if ($client eq $clientId) {
          my $clientDomainsNode = getDomainsNode($clientId, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
          if (defined($clientDomainsNode)) {
            my $clientNode = XmlNode->new( 'client' );
            $clientNode->setAttribute( 'name', $clientId );
            $clientNode->setAttribute( 'guid', '' );
            $clientNode->addChild( DumpComposer::getPreferencesNode($clientId) );
            $clientNode->addChild( DumpComposer::getPropertiesNode($clientId) );
            $clientNode->addChild($clientDomainsNode);
            unless (defined($clientsNode)) {
              $clientsNode = XmlNode->new('clients');
            }
            $clientsNode->addChild($clientNode);
          }
        }
      }
    }
  }
  else {
    foreach my $clientId (@clientIds) {
      my $clientDomainsNode = getDomainsNode($clientId, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip);
      if (defined($clientDomainsNode)) {
        my $clientNode = XmlNode->new( 'client' );
        $clientNode->setAttribute( 'name', $clientId );
        $clientNode->setAttribute( 'guid', '' );
        $clientNode->addChild( DumpComposer::getPreferencesNode($clientId) );
        $clientNode->addChild( DumpComposer::getPropertiesNode($clientId) );
        $clientNode->addChild($clientDomainsNode);
        unless (defined($clientsNode)) {
          $clientsNode = XmlNode->new('clients');
        }
        $clientsNode->addChild($clientNode);
      }
    }
  }

  return $clientsNode;
}

sub getDomainsNode {
  my ( $owner, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip ) = @_;

  my @domainIds = Agent::getDomains( $owner );
  unless ( @domainIds ) {
    return;
  }

  my $domainsNode;
  if (scalar(@{$domains})) {
    foreach my $domain (@{$domains}) {
      foreach my $domainId (@domainIds) {
        if ($domain eq $domainId) {
          my $domainNode = DumpComposer::makeDomainNode($domainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
          unless (defined($domainsNode)) {
            $domainsNode = XmlNode->new('domains');
          }
          $domainsNode->addChild($domainNode);
        }
      }
    }
  }
  else {
    foreach my $domainId (@domainIds) {
      my $domainNode = DumpComposer::makeDomainNode($domainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
      unless (defined($domainsNode)) {
        $domainsNode = XmlNode->new('domains');
      }
      $domainsNode->addChild($domainNode);
    }
  }
  return $domainsNode;
}


1;
