#!/usr/local/cpanel/3rdparty/bin/perl

###########
# mvdomain
# Used to move a single domain into a cPanel account from another cPanel account or from a backup file.
# http://confluence.endurance.com/display/HGS/Migrations%3A+mvdomain
# https://stash.endurance.com/projects/HGADMIN/repos/mvdomain/browse
# Please submit all bug reports at jira.endurance.com
#
# (C) 2011 - HostGator.com, LLC
###########

=pod

=head1 mvdomain version 1.1

Usage: mvdomain [--fromfile=filename] [--workdir=/path/to/dir] [--alldomains] [--exclude-domains=/path/to/file] [--noprompt] [--nolog] [--perms] <domain.tld> <user>
       <domain.tld> is the domain to transfer.
       <user> is a username to transfer to.  If it is hgnew, then a new username is picked based on the domain name.
       --fromfile        - Used when transferring a domain from a file such as a cPanel backup file.  Can be a backup file or a directory of a
                           previously untarred backup.
       --noprompt        - Don't prompt to confirm anything.
       --workdir         - specify a working directory that already contains database dumps.
       --alldomains      - Move all of the domains.
       --exclude-domains - A file of domains to exclude when using --alldomains.  File should contain one domain per line.
       --nolog           - Don't log the output.
       --perms           - Run perms after each domain transfer.
 
 The syntax is the same for most transfers.  mvdomain determines many of the details based on what account the domain
 belongs to, whether or not the destination username exists, etc.
 
 Examples:
 Split the domain "domaintosplit.com" to a new account with the username "newuser".
 Before running this, "domaintosplit.com" is an addon domain and newuser is a username that doesn't exist yet.
 > mvdomain domaintosplit.com newuser
 
 Split the domain "domaintosplit.com" to a new account with a newly generated username based on the domain name.
 Before running this, "domaintosplit.com" is an addon domain.  "hgnew" is a reserved word that tells it to make up a username:
 > mvdomain domaintosplit.com hgnew
 
 Split all addons from an account into separate accounts.
 Before running this, "domaintosplit.com" is an addon domain (pick any addon in the source account).  "hgnew" is a reserved word that tells it to automatically generate the usernames.
 > mvdomain domaintosplit.com hgnew --alldomains
 Notice this is just like the previous example except that the --alldomains switch causes all addon domains in the account to be split.
 
 Move an existing addon domain to a different account as an addon domain.
 Before running this, myaddon.com is an addon to an account other than someuser.  "someuser" is an existing cPanel account.
 > mvdomain myaddon.com someuser
 
 Swap an addon domain with a primary domain.
 Before running this, mydom1.com is an addon domain and mydom2.com is the primary domain.  "someuser" is the account that has mydom1.com as an addon and mydom2.com as a primary domain.
 > mvdomain mydom1.com someuser
 After running this, mydom2.com is an addon domain, and mydom1.com is the primary domain.
 
 Merge the primary domain of an account into another account as an addon domain.
 Before running this, mydom1.com is a primary domain and someuser is an existing username other than the one mydom1.com belongs to.
 > mvdomain mydom1.com someuser
 After running this, mydom1.com is added to someuser as an addon domain.  The account that mydom1.com belonged to is deleted along with any addons.
 
 Merge an entire account into another.  The only difference between this and the last example is that the first account's addon domains are also
 merged.
 > mvdomain mydom1.com someuser --alldomains
 
 Restore one domain from a cPanel backup into an account as an addon domain.
 Before running this, mydom1.com is one of the domains in the cpmove-userabc.tar.gz backup file.  someuser is an existing user.
 > mvdomain mydom1.com someuser --fromfile=/home/cpmove-userabc.tar.gz
 
 Restore all of the domains from a cPanel backup into an account as addon domains.
 > mvdomain mydom1.com someuser --fromfile=/home/cpmove-userabc.tar.gz --alldomains
 This is just like the previous example except that the --alldomains switch is used.

=cut

use strict;
use lib "/usr/local/cpanel/";
use Cpanel::PasswdStrength::Generate;
use Cpanel::PasswdStrength::Check;
use Getopt::Long;
use XML::Simple;
use Data::Dumper;
use File::Find ();
use File::Copy;
use File::Basename;
use Tie::File;
use Cwd;
use Tie::File;
use Term::ANSIColor qw(:constants);
use URI::Escape;
use Sys::Hostname;
use POSIX qw(strftime);
use Cpanel::Version;
use JSON;
use cPanel::PublicAPI;

if (!dependency_check()) { # If a dependency is out of date or missing, then exit.
     exit 1;
}

my %userhash; #Hash array of the first 8 characters of all usernames on this server.
my %from_language;  #Language settings of the account we are transferring from.
my %to_language;    #Language settings of the account we are transferring to.
my $result = 0;  #Result of each operation 0=OK, 1=Error.  This is used to tell whether or not to leave the workdir in place if there was a problem.
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $global_workdirname;
my $fromfile;
my @dirs;
my $nolog;     
my $noprompt;       #0=Prompt                              1=Don't prompt.
my $perms = 0;    #0=Run perms                           1=Don't Run perms
my $alldomains = 0; #0=Just the specified domain           1=Transfer all domains.
my $excluded_domains_file; #Specifies a file that has a list of domains to exclude.
my $excluded_domains = {}; #Hash reference with the excluded domains.
my $apicode;
my $clone_domain; #Domain name to clone to
GetOptions ('workdir=s'         => \$global_workdirname,
            'fromfile=s'        => \$fromfile,   #Grab command line options.
            'exclude-domains=s' => \$excluded_domains_file,
            'nolog'             => \$nolog,
            'noprompt'          => \$noprompt,
            'perms'             => \$perms,
            'apicode=i'         => \$apicode,
            'alldomains'        => \$alldomains,
            'clone=s'           => \$clone_domain);
my $prompt = 1-$noprompt; #Opposite of noprompt. (0=Don't prompt, 1=prompt).
### TOKEN SUPPORT ###
my $token_name;
my $token;
my $json;
my $apic;
my $currversion = Cpanel::Version::getversionnumber(); 
if ( $currversion >= 11.64 ){
        $token_name = "hgtool_".time();
        $json = `whmapi1 api_token_create token_name=$token_name --output=json`;
        $token = JSON::from_json($json)->{'data'}{'token'};
        ###Clean up token before end
        END{
                if($token_name){
                        `whmapi1 api_token_revoke token_name=$token_name`
                }
        }
        {
                my $revoked;
                $SIG{INT} = sub {
                        unless ($revoked) {
                                $revoked = 1;
                                `whmapi1 api_token_revoke token_name=$token_name`;
                        }
                        die "Interrupted";
                }
                        
        }
	###Remove stale tokens
	stale_tokens();
	$apic = cPanel::PublicAPI->new(ssl_verify_mode => '0', api_token => "$token");
} else{
	$apic = cPanel::PublicAPI->new(ssl_verify_mode => '0');
}
### END TOKEN SUPPORT ###

if ($excluded_domains_file) {
     $excluded_domains = read_excluded_domains($excluded_domains_file);
     if (!$excluded_domains) {
          exit 1;
     }
     # Now the presence of a domain in $excluded_domains indicates an excluded domain.  i.e.
     #if ($excluded_domains->{"mydomain2.org"}) {
          #mydomain2.org is excluded.
     #}
}

my $l = logg->new("mvdomain", $nolog);
show_command();           #Log the command that was used to start this transfer.

my $global_workdir;
if (length($global_workdirname) > 0) {
     $global_workdir = workdir->new($global_workdirname);
}else {
     $global_workdir = 0;
}
my $usage1 = <<END;
mvdomain version 1.1

Usage: mvdomain [--fromfile=filename] [--workdir=/path/to/dir] [--alldomains] [--exclude-domains=/path/to/file] [--noprompt] [--nolog] [--perms] <domain.tld> <user>
       <domain.tld> is the domain to transfer.
       <user> is a username to transfer to.  If it is hgnew, then a new username is picked based on the domain name.
       --fromfile        - Used when transferring a domain from a file such as a cPanel backup file.  Can be a backup file or a directory of a
                           previously untarred backup.
       --clone=dom.tld   - "clone" to a new domain name.
       --noprompt        - Don't prompt to confirm anything.
       --workdir         - specify a working directory that already contains database dumps.
       --alldomains      - Move all of the domains.
       --exclude-domains - A file of domains to exclude when using --alldomains.  File should contain one domain per line.
       --nolog           - Don't log the output.
       --perms           - Run perms after each domain transfer.

The syntax is the same for most transfers.  mvdomain determines many of the details based on what account the domain
belongs to, whether or not the destination username exists, etc.

Examples:
Split the domain "domaintosplit.com" to a new account with the username "newuser".
Before running this, "domaintosplit.com" is an addon domain and newuser is a username that doesn't exist yet.
> mvdomain domaintosplit.com newuser

Split the domain "domaintosplit.com" to a new account with a newly generated username based on the domain name.
Before running this, "domaintosplit.com" is an addon domain.  "hgnew" is a reserved word that tells it to make up a username:
> mvdomain domaintosplit.com hgnew

Split all addons from an account into separate accounts.
Before running this, "domaintosplit.com" is an addon domain (pick any addon in the source account).  "hgnew" is a reserved word that tells it to automatically generate the usernames.
> mvdomain domaintosplit.com hgnew --alldomains
Notice this is just like the previous example except that the --alldomains switch causes all addon domains in the account to be split.

Move an existing addon domain to a different account as an addon domain.
Before running this, myaddon.com is an addon to an account other than someuser.  "someuser" is an existing cPanel account.
> mvdomain myaddon.com someuser

END

my $usage2 = <<END;
Swap an addon domain with a primary domain.
Before running this, mydom1.com is an addon domain and mydom2.com is the primary domain.  "someuser" is the account that has mydom1.com as an addon and mydom2.com as a primary domain.
> mvdomain mydom1.com someuser
After running this, mydom2.com is an addon domain, and mydom1.com is the primary domain.

Merge the primary domain of an account into another account as an addon domain.
Before running this, mydom1.com is a primary domain and someuser is an existing username other than the one mydom1.com belongs to.
> mvdomain mydom1.com someuser
After running this, mydom1.com is added to someuser as an addon domain.  The account that mydom1.com belonged to is deleted along with any addons.

Merge an entire account into another.  The only difference between this and the last example is that the first account's addon domains are also
merged.
> mvdomain mydom1.com someuser --alldomains

Restore one domain from a cPanel backup into an account as an addon domain.
Before running this, mydom1.com is one of the domains in the cpmove-userabc.tar.gz backup file.  someuser is an existing user.
> mvdomain mydom1.com someuser --fromfile=/home/cpmove-userabc.tar.gz

Restore all of the domains from a cPanel backup into an account as addon domains.
> mvdomain mydom1.com someuser --fromfile=/home/cpmove-userabc.tar.gz --alldomains
This is just like the previous example except that the --alldomains switch is used.

Clone an addon domain to a new account:
> mvdomain mydom1.com hgnew clone=mycloneddom1.com

END


if ($#ARGV != 1) {
     print $usage1;
     print "----- Press <Enter> to continue -----\n";
     <STDIN>;
     print $usage2;
     exit 1;
}

my $domain = $ARGV[0];
my $user   = $ARGV[1];
my $dbc    = dbconnect->new($l);
$dbc->{token} = $token;
if (!$dbc) {
     exit 1;  #dbconnect failed to instantiate.  Fail now.
}

$domain =~ s/http:\/\///;             #Clean up the domain name a bit.
$domain =~ s/^www\.//;

#If $clone_domain is already on the server, then it is always considered a domain conflict
if (find_domain_owner($clone_domain)) {
     $l->logg(RED . "Domain conflict: $clone_domain already exists.\n" . RESET, 1);
     exit(1);
}

if (length($clone_domain)==0) {       #If --clone wasn't specified then use the original domain as the clone domain.
     $clone_domain = $domain;
}

if (length($user) > 16) {
     $l->logg(RED . "The username is too long.  Please choose a username that is 16 characters or less.\n" . RESET, 1);
     exit(1);
}
if ($user =~ /^[0-9]/) {
     $l->logg(RED . "Please choose a username that doesn't start with a number.\n" . RESET, 1);
     exit(1);
}

if (is_shared_server()) {
     my $cpucount = count_cpus();
     if ($cpucount < 8 && $cpucount > 0) {
          $l->logg(RED . "This server only has $cpucount cpus.  This transfer might be better suited on a newer server.\n" . RESET, 1);
          exit(1);
     }
}

if (check_tweaksettings($domain) > 0) {
     $l->logg(RED . "The \"allowremotedomains\" setting in WHM Tweak Settings will prevent $domain from being created." . RESET . "  It is recommended that you fix this before continuing.  ", 1);
     if (show_prompt("Continue? ") != 1) { #Fail unless the user was prompted and said yes.
          exit(1);
     }
}

$l->logg(YELLOW . "If possible, please back up the accounts involved in this transfer if you haven't already  ;-)\n" . RESET, 1);
my $original_user = $user;             #This is a way to remember if "hgnew" was specified after we dynamically pick a username.
if ($user eq "hgnew") {
     $user = pick_username($domain);
}


# --clone  --alldomains Primarytest  Usertest  fromfile  Domuser=user  casevalue    
#    0        0           0           0          0         0               0     ** split addon
#    0        0           0           0          1         0              10     Domain conflict (addon exists, but transferring from a file)
#    0        0           0           x          0         0             100     ** addon2addon
#    0        0           0           x          0         1             101     ** addon<->primary swap
#    0        0           0           x          1         0             110     Domain conflict because it's from a file. remove --fromfile if it's addon2addon or remove existing addon to transfer from file.
#    0        0           0           x          1         1             111     Domain confilct because it's from a file. remove --fromfile if it's addon<->primary swap or remove existing addon to transfer from file
#    0        0           1           0          0         0            1000     Trying to copy a primary domain to a new account. Can't be done.  Suggest a username change?
#    0        0           1           0          1         0            1010     Domain conflict because it's from a file.
#    0        0           1           x          0         0            1100     Trying to copy a primary domain to a different user. Suggest addon<->primary swap, then addon2addon.
#    0        0           1           x          0         1            1101     Trying to copy a primary domain to same user.  Suggest using the addon domain if an addon swap is wanted.
#    0        0           1           x          1         0            1110     Domain conflict because it's from a file.
#    0        0           1           x          1         1            1111     Domain conflict because it's from a file.
#    0        0           2           0          0         0            2000     Domain and user are not on the server.
#    0        0           2           0          1         0            2010     ** From file to a new account
#    0        0           2           x          0         0            2100     Domain is not on the server.
#    0        0           2           x          0         1            2101     Domain is not on the server.
#    0        0           2           x          1         0            2110     ** From file to an addon domain.

#    0        1           0           0          0         0           10000     ** split all addons (not primary since it's already a primary) from the user that owns the domain to new users.
#    0        1           0           0          1         0           10010     Asking to split all addons from the backup file to new users, but the specified domain exists as an addon.
#    0        1           0           x          0         0           10100     Merge all domains (primary and addons) from the user that owns the domain to the specified user.
#    0        1           0           x          0         1           10101     Swap all addons with the primary domain (huh?  Just say no.)
#    0        1           0           x          1         0           10110     Asking to merge all domains from a backup, but the specified domain exists as an addon.
#    0        1           0           x          1         1           10111     Asking to merge all domains from a backup, but the specified domain exists as an addon.
#    0        1           1           0          0         0           11000     Trying to copy all domains to a new account.  A username change would be better.
#    0        1           1           0          1         0           11010     The specified domain is already on the server.  Therefore all domains cannot be copied from the backup.
#    0        1           1           x          0         0           11100     Merge all domains (primary and addons) from the user that owns the domain to the specified user.
#    0        1           1           x          0         1           11101     Swap all addons with the primary domain (huh?  Just say no.)
#    0        1           1           x          1         0           11110     Asking to merge all domains from a backup, but the specified domain exists as a primary domain.
#    0        1           1           x          1         1           11111     Asking to merge all domains from a backup, but the specified domain exists as a primary domain.
#    0        1           2           0          0         0           12000     Domain and user are not on the server.
#    0        1           2           0          1         0           12010     split all addons from the backup file to new users.
#    0        1           2           x          0         0           12100     Domain is not on the server.  We can't tell which user to transfer all domains on.
#    0        1           2           x          0         1           12101     Domain is not on the server.   We can't tell which user to transfer all domains from.
#    0        1           2           x          1         0           12110     Merge all domains from a backup file into an account.

#    1        0           0           0          0         0          100000     Not supported - ** split addon
#    1        0           0           0          1         0          100010     ** From file to a new account with a new domain.
#    1        0           0           x          0         0          100100     ** addon2addon with clone to new domain.
#    1        0           0           x          0         1          100101     ** addon2addon with clone to new domain in same account.
#    1        0           0           x          1         0          100110     Clone from a backup file to an addon.
#    1        0           0           x          1         1          100111     Clone from a backup file to an addon.
#    1        0           1           0          0         0          101000     Clone primary domain to a new account.
#    1        0           1           0          1         0          101010     Clone from a backup file to a new account with a new domain.
#    1        0           1           x          0         0          101100     Not implemented - Clone primary domain to another existing cPanel account. (test addon2addon_clone)
#    1        0           1           x          0         1          101101     Not implemented - Clone primary domain to addon in the same cPanel. (test addon2addon_clone)
#    1        0           1           x          1         0          101110     Clone from a backup file to an addon with a new domain.
#    1        0           1           x          1         1          101111     Clone from a backup file to an addon with a new domain.
#    1        0           2           0          0         0          102000     Not supported - Domain and user are not on the server.
#    1        0           2           0          1         0          102010     ** From file to a new account with a new domain.
#    1        0           2           x          0         0          102100     Not supported - Domain is not on the server.
#    1        0           2           x          0         1          102101     Not supported - Domain is not on the server.
#    1        0           2           x          1         0          102110     Clone from a backup file to an addon.
#    1        0           2           x          1         1          102111     Clone from a backup file to an addon.
#    1        1           0           0          0         0          110000     Not supported - ** split all addons (not primary since it's already a primary) from the user that owns the domain to new users.
#    1        1           0           0          1         0          110010     Not supported - Asking to split all addons from the backup file to new users, but the specified domain exists as an addon.
#    1        1           0           x          0         0          110100     Not supported - Merge all domains (primary and addons) from the user that owns the domain to the specified user.
#    1        1           0           x          0         1          110101     Not supported - Swap all addons with the primary domain (huh?  Just say no.)
#    1        1           0           x          1         0          110110     Not supported - Asking to merge all domains from a backup, but the specified domain exists as an addon.
#    1        1           0           x          1         1          110111     Not supported - Asking to merge all domains from a backup, but the specified domain exists as an addon.
#    1        1           1           0          0         0          111000     Not supported - Trying to copy all domains to a new account.  A username change would be better.
#    1        1           1           0          1         0          111010     Not supported - The specified domain is already on the server.  Therefore all domains cannot be copied from the backup.
#    1        1           1           x          0         0          111100     Not supported - Merge all domains (primary and addons) from the user that owns the domain to the specified user.
#    1        1           1           x          0         1          111101     Not supported - Swap all addons with the primary domain (huh?  Just say no.)
#    1        1           1           x          1         0          111110     Not supported - Asking to merge all domains from a backup, but the specified domain exists as a primary domain.
#    1        1           1           x          1         1          111111     Not supported - Asking to merge all domains from a backup, but the specified domain exists as a primary domain.
#    1        1           2           0          0         0          112000     Not supported - Domain and user are not on the server.
#    1        1           2           0          1         0          112010     Not supported - split all addons from the backup file to new users.
#    1        1           2           x          0         0          112100     Not supported - Domain is not on the server.  We can't tell which user to transfer all domains on.
#    1        1           2           x          0         1          112101     Not supported - Domain is not on the server.   We can't tell which user to transfer all domains from.
#    1        1           2           x          1         0          112110     Not supported - Merge all domains from a backup file into an account.

#
# --clone:        A new domain name to clone was specified (1) or was not specified (0)
#
#
#
#$primarytest:                                               0 = Addon
#                                                            1 = primary
#                                                            2 = Domain not on server
#
#$usertest:                                                  0 = User doesn't exist
#                                                            x = User exists
#
#Fromfile (i.e. length($fromfile)>0) :                       0 = Not from file
#                                                            1 = From file
#
#Domuser=user (i.e. $usertest->{primarydomain} eq $domain) : 0 = No
#                                                            1 = Yes
#
#--alldomains switch                                         0 = No
#                                                            1 = Yes
#

my $primarytest = is_primary_domain($domain); # 0=no, 1=yes, 2=Domain is not on server.
my $usertest    = does_user_exist($user);     # 0=no, Any other value means the user exists and the value is the hash value of the user object.
my $domainowner = find_domain_owner($domain);
my $ud_domainowner;
if ($domainowner) { # Create a Userdata object from the domain owner only if there is a domain owner for the domain on this server.
     $ud_domainowner = Userdata->new("localhost", $domainowner, undef, $token);
}

#Build the case value.  It should match one of the values in the table, so we can make a decision about what type of transfer it is.
my $casevalue = $primarytest * 1000;          #
if ($usertest != 0) {                         #
     $casevalue = $casevalue + 100;           #
     if ($domainowner eq $user) {
          $casevalue = $casevalue + 1;        #
     }                                        #
}                                             #
if (length($fromfile) > 0) {                  #
     $casevalue = $casevalue + 10;            #
}                                             #

if ($alldomains == 1) {
     $casevalue = $casevalue + 10000;
}

if ($domain ne $clone_domain) {
     $casevalue = $casevalue + 100000;
}

# At this point we have a $casevalue that represents what type of move to perform.  But let's see if the --apicode value
# was given on the command line.  If so, we will use that as the $casevalue instead.
if ($apicode) {
     if (length($apicode) > 5) {
          $l->logg("--apicode is invalid.  Ignoring.\n",1);
     }else {
          if ($apicode != $casevalue) {
               $l->logg(RED . "Warning: The --apicode value ($apicode) does not match the code mvdomain would have used ($casevalue).  Using the --apicode value anyway.\n" . RESET,1);
          }
          $casevalue = $apicode;
     }
}

# At this point the final $casevalue is set, and it's time to decide which type of subroutine to call
# 
if ($casevalue == 0) {                        #Now that we have a case value, let's decide what kind of transfer to do.
     #** split addon
     ssl_confirm($ud_domainowner, $domain, 1); 
     if (show_prompt("This looks like a split-addon transfer of $domain to a new account with a username of $user.  Continue? ") > 0) {
         $l->logg("Split-addon transfer\n", 0);
         save_language_settings();
         if (split_addon($domain, $user, $global_workdir) eq "Error") {
              $result = 1;                    #Split_addon failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
         }                                    # in case the user didn't make a backup.
     }
}elsif ($casevalue == 100000) {
     #** split addon with --clone option
     if (show_prompt("This looks like a cloned split-addon of $domain to a new account with a domain of $clone_domain and a username of $user.  Continue? ") > 0) {
         $l->logg("Split-addon-clone transfer\n", 0);
         save_language_settings();
         if (split_addon_clone($domain, $clone_domain, $user, $global_workdir) eq "Error") {
              $result = 1;                    #Split_addon failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
         }                                    # in case the user didn't make a backup.
     }    
}elsif ($casevalue == 101000) {
     #** split addon with --clone option
     if (show_prompt("This looks like a clone of primary domain $domain to a new account with a domain of $clone_domain and a username of $user.  Continue? ") > 0) {
         $l->logg("Clone of primary domain $domain to new accunt $user with cloned domain $clone_domain\n", 0);
         save_language_settings();
         if (split_addon_clone($domain, $clone_domain, $user, $global_workdir) eq "Error") {
              $result = 1;                    #Split_addon failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
         }                                    # in case the user didn't make a backup.
     }    
}elsif ($casevalue == 10) {
     #Domain conflict (addon exists, but transferring from a file)
     $l->logg("Domain conflict: $domain exists.\n", 1);
     $result = 1;
}elsif ($casevalue == 100) {
     #** addon2addon
     ssl_confirm($ud_domainowner, $domain, 1);
     if (show_prompt("This looks like an addon to addon transfer of $domain from $domainowner to $user.  Continue? ") > 0) {
         $l->logg("Addon to addon transfer\n", 0);
         save_language_settings();
         $result = addon2addon($domain, $usertest, $global_workdir);
     }else{
         $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 101) {
     #** addon<->primary swap
     ssl_confirm($ud_domainowner, $domain, 1);
     if (show_prompt("This looks like a swap of the addon domain $domain with the primary domain $usertest->{primarydomain} in the $user account.  Continue? ") > 0) {
          save_language_settings();
          $l->logg("Addon to primary swap.\n", 1);
          $result = addon_primary_swap($usertest, $domain, $global_workdir);
     }else {
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 110) {
     $l->logg("Domain conflict: $domain is already on the server.  Remove the --fromfile switch if it's addon2addon or remove existing addon to transfer from a backup file.\n", 1);
     $result = 1;
}elsif ($casevalue == 111) {
     $l->logg("Domain confilct: $domain is already on the server.  Remove the --fromfile switch if it's addon<->primary swap or remove existing addon to transfer from a backup file\n", 1);
     $result = 1;
}elsif ($casevalue == 1000) {
     $l->logg("Trying to copy a primary domain to a new account. Can't be done.  Maybe a username change is needed?\n", 1);
     $result = 1;
}elsif ($casevalue == 1010) {
     $l->logg("Domain conflict.  $domain is already on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 1100) {
     trash_confirm ($domainowner);
     ssl_confirm($ud_domainowner, $domain, 0);
     if (show_prompt("This looks like a merge of the $domainowner account ($domain) into the $user account.  The $domainowner account will be deleted.  Continue? ") > 0) {
          save_language_settings();
          $l->logg("Merge of the $domainowner account ($domain) into the $user account.\n", 1);
          $result = merge_primary($ud_domainowner, $usertest, $global_workdir);
     }else {
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 1101) {
     $l->logg("Trying to copy the primary domain $domain to same user.  Use the addon domain you wish to swap with if an addon swap is wanted.\n", 1);
     $result = 1;
}elsif ($casevalue == 1110) {
     $l->logg("Domain conflict.  $domain is already on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 1111) {
     $l->logg("Domain conflict.  $domain is already on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 2000) {
     $l->logg("The domain $domain and user $user are not on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 2010) {
     $l->logg("This looks like a transfer of $domain from the backup $fromfile to a new account $user.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          my $ud_fromuser = userobj_fromfile($fromfile);
          if (cpbackup2primary ($domain, $clone_domain, $fromfile, $ud_fromuser, $user) eq "Error") {
               $result = 1;                    #cpbackup2primary failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
          }                                    # in case the user didn't make a backup.
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 2100) {
     $l->logg("The domain $domain is not on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 2101) {
     $l->logg("The domain $domain is not on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 2110) {
     if (show_prompt("This looks like a backup file to addon transfer of $domain from $fromfile to $user.  Continue? ") > 0) { 
     save_language_settings();
          $l->logg("** From file to an addon domain.\n", 1);
          my $ud_fromuser = userobj_fromfile($fromfile);
          $result = cpbackup2addon ($domain, $clone_domain, $fromfile, $ud_fromuser, $usertest); 
     }else{ 
          $l->logg("User cancelled the transfer.\n", 0); 
     } 
}elsif ($casevalue == 10000 || $casevalue == 11000) {
     ssl_confirm($ud_domainowner, $domain, 0);
     $l->logg("split all addons (not primary since it's already a primary) from the $domainowner user to new users.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          $result = split_all ($domainowner, $global_workdir);
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 10010) {
     $l->logg("Asking to split all addons from the backup file to new users, but $domain exists as an addon.\n", 1);
     $result = 1;
}elsif ($casevalue == 10101) {
     $l->logg("Swap all addons with the primary domain (huh?  Just say no.)\n", 1);
     $result = 1;
}elsif ($casevalue == 10110) {
     $l->logg("Asking to merge all domains from a backup, but $domain exists as an addon.\n", 1);
     $result = 1;
}elsif ($casevalue == 10111) {
     $l->logg("Asking to merge all domains from a backup, but $domain exists as an addon.\n", 1);
     $result = 1;
}elsif ($casevalue == 11010) {
     $l->logg("The specified domain is already on the server.  Therefore all domains cannot be copied from the backup.\n", 1);
     $result = 1;
}elsif ($casevalue == 10100 || $casevalue == 11100) {
     trash_confirm($domainowner);
     ssl_confirm($ud_domainowner, $domain, 0);
     $l->logg("Merge all domains (primary and addons) from the $domainowner user to the $user user.  The source account will be deleted.  ", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          $result = merge_all ($ud_domainowner, $usertest, $global_workdir);
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 11101) {
     $l->logg("Swap all addons with the primary domain (huh?  Just say no.)\n", 1);
     $result = 1;
}elsif ($casevalue == 11110) {
     $l->logg("Asking to merge all domains from a backup, but $domain exists as a primary domain.\n", 1);
     $result = 1;
}elsif ($casevalue == 11111) {
     $l->logg("Asking to merge all domains from a backup, but $domain exists as a primary domain.\n", 1);
     $result = 1;
}elsif ($casevalue == 12000) {
     $l->logg("Domain $domain and user $user are not on the server.\n", 1);
     $result = 1;
}elsif ($casevalue == 12010) {
     $l->logg("Split all domains from the backup file to new users.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          my $ud_fromuser = userobj_fromfile($fromfile);
          $result = cpbackup_all_2primary ($domain, $fromfile, $ud_fromuser);
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 12100) {
     $l->logg("The domain $domain is not on the server.  We can't tell which user to transfer all domains on.\n", 1);
     $result = 1;
}elsif ($casevalue == 12101) {
     $l->logg("The domain $domain is not on the server.   We can't tell which user to transfer all domains from.\n", 1);
     $result = 1;
}elsif ($casevalue == 12110) {
     $l->logg("Merge all domains from a backup file into an account.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          my $ud_fromuser = userobj_fromfile($fromfile);
          $result = cpbackup_all_2addon ($domain, $fromfile, $ud_fromuser, $usertest);
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 100010 || $casevalue == 101010 || $casevalue == 102010) {
     $l->logg("This looks like a transfer of $domain from the backup $fromfile to a new account $user with a new domain $clone_domain.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          my $ud_fromuser = userobj_fromfile($fromfile);
          if (cpbackup2primary ($domain, $clone_domain, $fromfile, $ud_fromuser, $user) eq "Error") {
               $result = 1;                    #cpbackup2primary failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
          }                                    # in case the user didn't make a backup.
     }else{
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 100110 || $casevalue == 101110 || $casevalue == 102110 ||
        $casevalue == 100111 || $casevalue == 101111 || $casevalue == 102111) {
     $l->logg("This looks like a clone of $domain from the backup $fromfile to an addon domain of the user $usertest->{username} with a new domain $clone_domain.\n", 1);
     if (show_prompt("Continue? ") > 0) {
          save_language_settings();
          $l->logg("** Clone of $domain from the backup $fromfile to an addon domain of the user $usertest->{username} with a new domain $clone_domain.\n", 1);
          my $ud_fromuser = userobj_fromfile($fromfile);
          $result = cpbackup2addon ($domain, $clone_domain, $fromfile, $ud_fromuser, $usertest);
               $result = 1;                    #cpbackup2primary failed.  Set $result to 1 so the workdir is not deleted.  Database dumps will remain
                                               # in case the user didn't make a backup.
     }else{ 
          $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 100100 || $casevalue == 100101) {
     #** addon2addon_clone
     if (show_prompt("This looks like a clone of an addon $domain in $domainowner to another addon $clone_domain in $user.  Continue? ") > 0) {
         $l->logg("Addon to addon transfer\n", 0);
         save_language_settings();
         $result = addon2addon_clone($domain, $clone_domain, $usertest, $global_workdir);
     }else{
         $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 101100) {
     #** Primary --> addon in a separate account
     if (show_prompt("This looks like a clone of a primary domain $domain in $domainowner to an addon domain $clone_domain in $user.  Continue? ") > 0) {
         $l->logg("Addon to addon transfer\n", 0);
         save_language_settings();
         $result = addon2addon_clone($domain, $clone_domain, $usertest, $global_workdir);
     }else{
         $l->logg("User cancelled the transfer.\n", 0);
     }
}elsif ($casevalue == 101101) {
     # Primary --> addon in same account
     if (show_prompt("This looks like a clone of a primary domain $domain in $domainowner to an addon domain $clone_domain in the same account.  Continue? ") > 0) {
         $l->logg("Addon to addon transfer\n", 0);
         save_language_settings();
         $result = addon2addon_clone($domain, $clone_domain, $usertest, $global_workdir);
     }else{
         $l->logg("User cancelled the transfer.\n", 0);
     }
}else {
     $l->logg("Critical Error - Unknown casevalue: $casevalue.\n", 1);
     $result = 1;
}

restore_language_settings();

if ($perms == 0) {
     $l->logg("Don't forget to run perms as needed.\n",1);
}

if ($result > 0 && $global_workdir > 0) {
     $global_workdir->{error} = "mverror";
}

$l->logg("Returning: $result to calling program\n",0);
exit($result);



#------------------------------------------------
#--------------- Subroutines --------------------
#------------------------------------------------

# Transfer all of the domains including the primary from a cPanel backup file to new accounts. (Split all domains from the backup file to new users)
# Input:  Domain
#         Filename of the backup file.
#         cpbdata  object reference of the backup.
# Output: 0=OK, 1=Error
sub cpbackup_all_2primary {
     my $domain        = $_[0];
     my $cp_backupfile = $_[1];
     my $ud_fromuser   = $_[2];
     my @newusers;
     my %domains;
     my %passwords;
     my $pass;
     my $touser;
     if (!$excluded_domains->{$ud_fromuser->{primarydomain}}) { #If the primary domain wasn't excluded, then move it.
          $touser = pick_username($ud_fromuser->{primarydomain});
          $pass = cpbackup2primary($ud_fromuser->{primarydomain}, $ud_fromuser->{primarydomain}, $cp_backupfile, $ud_fromuser, $touser);
          if ($pass eq "Error") {;   #Transfer the primary domain.
               $l->logg(RED . "Exiting because there was a problem restoring the $ud_fromuser->{primarydomain} domain.\n" . RESET, 1);
               return 1;
          }
          push (@newusers, $touser);
          $domains{$touser} = $ud_fromuser->{primarydomain};
          $passwords{$touser} = $pass;
     }else {
          print "Skipping excluded domain: " . $ud_fromuser->{primarydomain} . "\n";
     }
     foreach my $addon (@{$ud_fromuser->{addons}}) {                                                       #Transfer each of the addon domains.
          $addon =~ s/:.*//; # remove the part after the : because before this the addon is in the form "somedom123.com:somedom.mssdomain4.com"
          if (!$excluded_domains->{$addon}) { #If this addon domain wasn't excluded, then move it.
               $touser = pick_username($addon);
               $pass = cpbackup2primary($addon, $addon, $cp_backupfile, $ud_fromuser, $touser);
               if ($pass eq "Error") {
                    $l->logg(RED . "Exiting because there was a problem restoring the $addon domain.\n" . RESET, 1);
                    return 1;
               }
               push (@newusers, $touser);
               $domains{$touser} = $addon;
               $passwords{$touser} = $pass;
          }else {
               print "Skipping excluded domain: $addon\n";
          }
     }
     #Now that we've split all of these addons, let's summarize it.
     print "-----------------------------------\n";
     print "User   -   Domain   -   Password\n";
     foreach my $user (@newusers) {
          print "$user   -   $domains{$user}   -   $passwords{$user}\n";
     }
     $l->print_sorted_entries();
     return 0;
}


# Transfer all of the domains from a cPanel backup file to an existing account as addon domains.
# Input:  Domain
#         Filename of the backup file.
#         cpbdata  object reference of the backup.
#         Userdata object reference of the destination account.
# Output: 0=OK, 1=Error
sub cpbackup_all_2addon {
     my $domain          = $_[0];
     my $cp_backupfile   = $_[1];
     my $ud_fromuser     = $_[2];
     my $ud_touser       = $_[3];
     my $ex_domain_count = keys %{$excluded_domains}; #The number of domains that were excluded.
                                                      # We have to assume that all of the domains the user listed are actually in the backup.
     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     my $allowed_addons = $ud_touser->{maxaddons} - scalar(@{$ud_touser->{addons}}); #Number of additional addons that are allowed in the dest account.
     my $new_addons = scalar(@{$ud_fromuser->{addons}}) + 1 - $ex_domain_count;          #Number of addons we plan to transfer.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && $new_addons > $allowed_addons) {
          $l->logg(RED . "Not enough addon domains can be added to the destination account (" . $ud_touser->{username} . ").  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     if (!$excluded_domains->{$ud_fromuser->{primarydomain}}) { #If the primary domain wasn't excluded, then move it.
          if (cpbackup2addon($ud_fromuser->{primarydomain}, $ud_fromuser->{primarydomain}, $cp_backupfile, $ud_fromuser, $ud_touser) > 0) {;   #Transfer the primary domain.
               $l->logg(RED . "Exiting because there was a problem restoring the $ud_fromuser->{primarydomain} domain.\n" . RESET, 1);
               return 1;
          }
     }else {
          print "Skipping excluded domain: " . $ud_fromuser->{primarydomain} . "\n";
     }
     foreach my $addon (@{$ud_fromuser->{addons}}) {                                                       #Transfer each of the addon domains.
          $addon =~ s/:.*//; # remove the part after the : because before this the addon is in the form "somedom123.com:somedom.mssdomain4.com"
          if (!$excluded_domains->{$addon}) { #If this addon domain wasn't excluded, then move it.
               if (cpbackup2addon($addon, $addon, $cp_backupfile, $ud_fromuser, $ud_touser) > 0) {
                    $l->logg(RED . "Exiting because there was a problem restoring the $addon addon domain.\n" . RESET, 1);
                    return 1;
               }
          }else {
               print "Skipping excluded domain: $addon\n";
          }
     }
     print "-----------------------------------\n";
     $l->print_sorted_entries();
     return 0;
}

#Split all of the addon domains from an account into new accounts.
# Input:  Username of the account we are splitting.
# Output: 0=OK, 1=Error.
sub split_all {
     my $fromuser      = $_[0];
     my $workdir       = $_[1];
     my $ud_fromuser   = Userdata->new("localhost", $fromuser, undef, $token);
     my @newusers;
     my %domains;
     my %passwords;
     my @databases;
     if ($workdir == 0) {                                                         #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
          list_databases($fromuser, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.
          $workdir->{hasdatabases} = 1;                            #Set the workdir object flag that says it has databases.
     }
     foreach my $addonline (@{$ud_fromuser->{addons}}) {
          my $addon = $addonline;                                                 # i.e. addon.com:addon.parent.com
          $addon =~ s/:.*//;                                                      # i.e. addon.com
          if (!$excluded_domains->{$addon}) { #If this addon domain wasn't excluded, then move it.
               my $touser = pick_username($addon);
               my $pass = split_addon($addon, $touser, $workdir);
               if ($pass eq "Error") {
                    $l->logg(RED . "Exiting because there was a problem splitting the $addon addon domain\n" . RESET, 1);
                    foreach my $user (@newusers) {
                         print "$user   -   $domains{$user}   -   $passwords{$user}\n";
                    }
                    $workdir->{error} = "mverror";
                    return 1;
               }
               push (@newusers, $touser);
               $domains{$touser} = $addon;
               $passwords{$touser} = $pass;
          }else {
               print "Skipping excluded domain: $addon\n";
          }
     }
     print "-----------------------------------\n";
     #Now that we've split all of these addons, let's summarize it.
     print "User   -   Domain   -   Password\n";
     foreach my $user (@newusers) {
          print "$user   -   $domains{$user}   -   $passwords{$user}\n";
     }
     $l->print_sorted_entries();
     return 0;
}

#Merge an entire account into another account.
# Input:  A domain name of the account we are merging from.
#         Userdata reference of the account we are merging to.
# Output: 0=OK, 1=Error.
sub merge_all {
     my $ud_fromuser   = $_[0];
     my $ud_touser     = $_[1];
     my $workdir       = $_[2];
     my $primarydomain = $ud_fromuser->{primarydomain};
     my @databases;
     my $ex_domain_count = keys %{$excluded_domains}; #The number of domains that were excluded.
                                                      # We have to assume that all of the domains the user listed are actually in the backup.
     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     my $allowed_addons = $ud_touser->{maxaddons} - scalar(@{$ud_touser->{addons}}); #Number of additional addons that are allowed in the dest account.
     my $new_addons = scalar(@{$ud_fromuser->{addons}}) + 1 - $ex_domain_count;          #Number of addons we plan to transfer.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && $new_addons > $allowed_addons) {
          $l->logg(RED . "Not enough addon domains can be added to the destination account (" . $ud_touser->{username} . ").  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     if ($workdir == 0) {                                                         #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
          list_databases($ud_fromuser->{username}, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.
          $workdir->{hasdatabases} = 1;                            #Set the workdir object flag that says it has databases.
     }
     foreach my $addonline (@{$ud_fromuser->{addons}}) {
          my $addon = $addonline;                                                 # i.e. addon.com:addon.parent.com
          $addon =~ s/:.*//;                                                      # i.e. addon.com
          if (!$excluded_domains->{$addon}) { #If this addon domain wasn't excluded, then move it.
               if (addon2addon($addon, $ud_touser, $workdir) > 0) {
                    $l->logg(RED . "Exiting because there was a problem transferring an addon domain\n" . RESET, 1);
                    $workdir->{error} = "mverror";
                    return 1;
               }
          }else {
               print "Skipping excluded domain: $addon\n";
          }
     }
     if (!$excluded_domains->{$ud_fromuser->{primarydomain}}) { #If the primary domain wasn't excluded, then move it.
          if (merge_primary($ud_fromuser, $ud_touser, $workdir) > 0) {
               $l->logg(RED . "There was a problem transferring the primary domain " . $ud_fromuser->{primarydomain} . "\n" . RESET, 1);
               $workdir->{error} = "mverror";
               return 1;
          }
     }else {
          print "Skipping excluded domain: " . $ud_fromuser->{primarydomain} . "\n";
     }
     print "-----------------------------------\n";
     $l->print_sorted_entries();
     return 0;
}

# Merge an account into another account.
# Input:  Domain
#         Userdata object ref of the destination user.
# Output: 
sub merge_primary {
     my $ud_fromuser  = $_[0];
     my $ud_touser    = $_[1];
     my $workdir      = $_[2];
     my $fromuser     = find_domain_owner($domain);
     my $domain       = $ud_fromuser->{primarydomain};
     my $src_homedir  = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $dest_homedir = "/" . $ud_touser->{partition} . "/" . $ud_touser->{username} . "/";
     my $touser       = $ud_touser->{username};
     my $src_docroot  = $ud_fromuser->lookup_docroot($domain);
     my $dest_relative_docroot = "public_html/$domain";
     my $dest_docroot = "/$ud_touser->{partition}/$ud_touser->{username}/$dest_relative_docroot";        # the document root path.
     my @databases;
     my @undostack;
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.
     my $oldzup;           #Zup object representing the zone file in the backup.
     my $newzup;           #Zup object representing the zone file of the new account (to be created later).

     #---------- Prechecks -----------
     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && scalar(@{$ud_touser->{addons}}) >= $ud_touser->{maxaddons}) {
          $l->logg(RED . "An addon domain cannot be added to the destination account ($touser).  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     $oldzup = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return 1;
     }
     ssl_save($ud_fromuser, $domain, 1);
     if ($workdir == 0) {                                                                                #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
     }
     # Check for existing document roots in the destination account and warn the user if any are found.
     decide_new_subdomains($domain, $domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!check_for_docroots($domain, $dest_docroot, $ud_touser)) {
          $workdir->{error} = "mverror";
          return 1;
     }
     if (scalar(@{$ud_fromuser->{addons}}) > 0 && $alldomains == 0) {
          $l->logg(RED . "The source account ($fromuser) has one or more addon domains.\n" . RESET, 1);
          if (show_prompt("These will be lost if you continue.  If you don't wish to lose the addon domain(s), please cancel now.  Continue? ") != 1) {
               $workdir->{error} = "mverror";
               return 1;
          }else {
               $l->logg("User chose to continue.\n",0);
          }
     }
     #----- End of Prechecks ---------

     # Copy the files.
     copy_docroots($domain, $domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);
     $ud_fromuser->copy_mail($domain, $ud_touser);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($src_docroot, $dest_docroot, $dest_docroot);

     # Dump databases
     if ($workdir->{hasdatabases} == 0) {
          $l->logg("Dumping databases to $workdir->{workdir}\n",1);
          list_databases($fromuser, \@databases);                                                             #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);                                                   #Export the listed databases.      
     }else {
          $l->logg("Not dumping databases because I assume you've already dumped them since you specified $workdir->{workdir} as the workdir.\n",1);
     }

     $l->logg(GREEN . "Copying AWStats if available...\n" . RESET, 1);
     if (!$ud_fromuser->copy_awstats_pre_primary_addon($domain, $ud_touser)) {
          $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
     }

     # Save forwarders.
     if (copy ("/etc/valiases/$domain", "$workdir->{workdir}/valiases-$domain") == 0) {
          $l->logg(RED . "Error copying /etc/valiases/$domain to $workdir->{workdir}/valiases-$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }

     # Remove the old account.
     if (!remove_subdomains($domain, $ud_fromuser, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }
     $l->logg(GREEN . "Removing the $fromuser account.\n", 1);
     if (remove_account($fromuser) >0) {                                                                                 #Remove the source account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1;
     }

     # Add domain in the new account.
     my $op = cpopp->new("add-account", $fromuser, $domain);
     unshift (@undostack, $op);     #Account was removed, so put an add-account on the undo stack.

     $l->logg(GREEN . "Creating addon domain $domain for $touser in $dest_docroot.\n" . RESET, 1);
     if (create_addon($domain, $ud_touser, $dest_relative_docroot) > 0) {                                                   #Create the addon in the dest account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     # The destination user didn't contain the addon domain before we moved it, but the remove_addon sub expects a Userdata object with the addon's info.
     #  To solve this dilemma with the undo stack, let's create a new Userdata object with current info.
     my $ud_undo_touser = Userdata->new("localhost", $ud_touser->{username}, undef, $token);
     my $op = cpopp->new("rem-addon", $domain, $ud_undo_touser);
     unshift (@undostack, $op);

     # Re-create the subdomains in the destination account.
     if (!create_subdomains($domain, $ud_touser)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     $l->logg("Enabling forwarders.\n", 0);                                                                                 #Copy the forwarders file back into place.
     if (copy ("$workdir->{workdir}/valiases-$domain", "/etc/valiases/$domain") == 0) {
          $l->logg(RED . "Error copying $workdir->{workdir}/valiases-$domain to /etc/valiases/$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }
     $dbc->{prompt} = $prompt;
     my @summary = $dbc->dbclist ($workdir->{workdir}, \@dbclist_docroots);                                                            #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};
     $l->loggarray(\@summary, 2); # Save summary of connected databases to be printed later if --alldomains was used.

#---------------------- Merge DNS Zone files -------------------------
     $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
     $newzup = ZUP::Parser->new("/var/named/$domain.db");
     if (!$newzup) {
          $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($newzup->getzonefile())) {
          return 1;
     }
     if (!merge_mx_recs($oldzup, $newzup)) {
          return 1; # If the zone file merge failed, return 1 to indicate a problem.
     }

     $l->logg(GREEN . "Updating AWStats files...\n" . RESET, 1);
     $ud_touser->cpanel_setaddons(); #Update the addon info.
     if (!$ud_fromuser->copy_awstats_post_primary_addon($domain, $ud_touser)) {
          $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
     }

     run_perms($ud_touser);                                                                                              #Run perms on the new account.
     # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $src_docroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
          }
     }
     return 0;
} 

# Split an addon domain off into a new account.
# Input:  Domain to split into a new account.
#         Username of the new account (needs to be a username that doesn't already exist).
# Output: The domain is split into a new account.  Its files are moved to the public_html dir of the new account and email is moved.
# Returns: "Error" if there was a problem.
#          password of the new account if it was successful.
sub split_addon {
     my $domain          = $_[0];
     my $touser          = $_[1];
     my $workdir         = $_[2];
     my $fromuser        = find_domain_owner($domain);
     my $ud_fromuser     = Userdata->new("localhost", $fromuser, undef, $token);
     my $ud_touser;                                                    #Will be created after we create the new account.
     my $dest_pass;                                                    #Will hold the password to the destination account when it is created.
     my $dest_homedir;
     my $src_homedir   = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $dest_docroot;
     my @databases;
     my $fromdocroot     = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     my @undostack;                                                    #Stack of operations to go through if we have to undo partial changes to an account.
     my $oldzup;           #Zup object representing the zone file in the backup.
     my $newzup;           #Zup object representing the zone file of the new account (to be created later).
 
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.
     if ($workdir == 0) {                                                                                #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
     }

     if (my $longuser = lookup_long_username($touser)) {
          print "A user ($longuser) exists which conflicts with $touser.  ";
          print "Make sure the first 8 characters of the username are unique.\n";
          return "Error";
     }

     if (!validate_user($touser)) {
          print "User $touser is not a valid username.\n";
          return "Error";
     }

     if (copy ("/etc/valiases/$domain", "$workdir->{workdir}/valiases-$domain") == 0) {
          $l->logg(RED . "Error copying /etc/valiases/$domain to $workdir->{workdir}/valiases-$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return "Error";
     }

     $oldzup = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return "Error";
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return "Error";
     }
     ssl_save($ud_fromuser, $domain, 1);

     #Remove the subdomains from the addon domain for the source account.
     if (!remove_subdomains($domain, $ud_fromuser, \@undostack)) {
          $workdir->{error} = "mverror";
          return "Error";
     }

     $l->logg(GREEN . "Removing addon domain $domain from $ud_fromuser->{username} with document root $fromdocroot.\n" . RESET, 1);
     if (remove_addon($domain, $ud_fromuser) > 0) {                    #Remove the addon domain from the old account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return "Error"; #We had a problem.  Exit now.
     }
     my $relative_docroot =  $fromdocroot;
     $relative_docroot    =~ s/$src_homedir//;                         #i.e. public_html/addondir
     my $op = cpopp->new("add-addon", $domain,$ud_fromuser,$relative_docroot);
     unshift (@undostack, $op);

     $l->logg(GREEN . "Creating a new account for $domain with a username of $touser.\n" . RESET, 1);
     $dest_pass = create_account($touser, $domain);
     if ($dest_pass eq "Error") {                                      #Create the new account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return "Error"; #We had a problem.  Exit now.
     }
     my $op = cpopp->new("rem-account", $touser);
     unshift (@undostack, $op);

     $ud_touser = Userdata->new("localhost", $touser, undef, $token);                 #Gather / set some info. on the new account.
     $dest_homedir = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";
     $dest_docroot = $dest_homedir . "public_html";

     decide_new_subdomains($domain, $domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!create_subdomains($domain, $ud_touser)) {
          $workdir->{error} = "mverror";
          return "Error";
     }

     # Copy the files.
     copy_docroots($domain, $domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);
     
     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($fromdocroot, $dest_docroot, $dest_docroot);

     # Rsync Email
     if (-e "$src_homedir/etc/$domain") {                             #if the source addon has email, rsync the mail directories.
          $l->logg(GREEN . "Moving email for $domain...\n" . RESET, 1);
          $ud_fromuser->copy_mail($domain, $ud_touser);
     }else {
          $l->logg("No mail found for $domain.\n", 0);
     }

     $l->logg("Enabling forwarders.\n", 0);          #Copy the forwarders file back into place.
     if (copy ("$workdir->{workdir}/valiases-$domain", "/etc/valiases/$domain") == 0) {
          $l->logg(RED . "Error copying $workdir->{workdir}/valiases-$domain to /etc/valiases/$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return "Error";
     }

     $l->logg(GREEN . "Transferring databases...\n" . RESET, 1);
     if ($workdir->{hasdatabases} == 0) {
          $l->logg("Dumping databases to $workdir->{workdir}\n",1);
          list_databases($fromuser, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.
     }else {
          $l->logg("Not dumping databases because I assume you've already dumped them since you specified $workdir->{workdir} as the workdir.\n",1);
     }
     $dbc->{prompt} = $prompt;
     my @summary = $dbc->dbclist ($workdir->{workdir}, \@dbclist_docroots); #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};
     $l->loggarray(\@summary, 2); # Save summary of connected databases to be printed later if --alldomains was used.
#---------------------- Merge DNS Zone files -------------------------
     $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
     $newzup = ZUP::Parser->new("/var/named/$domain.db");
     if (!$newzup) {
          $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($newzup->getzonefile())) {
          return 1;
     }
     if (!merge_mx_recs($oldzup, $newzup)) {
          return 1; # If the zone file merge failed, return 1 to indicate a problem.
     }

     $l->logg(GREEN . "Copying AWStats if available...\n" . RESET, 1);
     if (!$ud_fromuser->copy_awstats($domain, $ud_touser)) {
          $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
     }

     run_perms($ud_touser);                        #Run perms on the new account.

     # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $fromdocroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
          }
     }

     print GREEN . "New account info:\nUser= $touser\nDomain= $domain\nPassword: $dest_pass\n" . RESET;
     return $dest_pass;
}


# Split a domain off into a new account with a new domain name.
# Works with a primary domain or an addon domain.  It is called split_addon_clone for historical reasons.
# Input:  Domain to split into a new account.
#         Username of the new account (needs to be a username that doesn't already exist).
# Output: The domain is split into a new account.  Its files are moved to the public_html dir of the new account and email is moved.
# Returns: "Error" if there was a problem.
#          password of the new account if it was successful.
sub split_addon_clone {
     my $domain          = shift;
     my $clone_domain    = shift;
     my $touser          = shift;
     my $workdir         = shift;
     my $fromuser        = find_domain_owner($domain);
     my $ud_fromuser     = Userdata->new("localhost", $fromuser);
     my $ud_touser;                                                    #Will be created after we create the new account.
     my $dest_pass;                                                    #Will hold the password to the destination account when it is created.
     my $dest_homedir;
     my $src_homedir   = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $dest_docroot;
     my @databases;
     my $fromdocroot     = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     my @undostack;                                                    #Stack of operations to go through if we have to undo partial changes to an account.
     my $oldzup;           #Zup object representing the zone file in the backup.
     my $newzup;           #Zup object representing the zone file of the new account (to be created later).
 
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.

     if (my $longuser = lookup_long_username($touser)) {
          print "A user ($longuser) exists which conflicts with $touser.  ";
          print "Make sure the first 8 characters of the username are unique.\n";
          return "Error";
     }

     if (!validate_user($touser)) {
          print "User $touser is not a valid username.\n";
          return "Error";
     }

     if ($workdir == 0) {                                                                                #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
     }

     $oldzup = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return "Error";
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return "Error";
     }

     $l->logg(GREEN . "Creating a new account for $clone_domain with a username of $touser.\n" . RESET, 1);
     $dest_pass = create_account($touser, $clone_domain);
     if ($dest_pass eq "Error") {                                      #Create the new account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return "Error"; #We had a problem.  Exit now.
     }
     my $op = cpopp->new("rem-account", $touser);
     unshift (@undostack, $op);

     $ud_touser = Userdata->new("localhost", $touser, undef, $token);                 #Gather / set some info. on the new account.
     $dest_homedir = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";
     $dest_docroot = $dest_homedir . "public_html";

     decide_new_subdomains($domain, $clone_domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!create_subdomains($clone_domain, $ud_touser)) {
          $workdir->{error} = "mverror";
          return "Error";
     }

     # Copy the files.
     copy_docroots($domain, $clone_domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($fromdocroot, $dest_docroot, $dest_docroot);

     # Rsync Email
     if (-e "$src_homedir/etc/$domain") {                             #if the source addon has email, rsync the mail directories.
          $l->logg(GREEN . "Cloning email for $domain to $clone_domain...\n" . RESET, 1);
          $ud_fromuser->clone_mail($domain, $clone_domain, $ud_touser);
     }else {
          $l->logg("No mail found for $domain.\n", 0);
     }

     $l->logg(GREEN . "Transferring databases...\n" . RESET, 1);
     if ($workdir->{hasdatabases} == 0) {
          $l->logg("Dumping databases to $workdir->{workdir}\n",1);
          list_databases($fromuser, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.
     }else {
          $l->logg("Not dumping databases because I assume you've already dumped them since you specified $workdir->{workdir} as the workdir.\n",1);
     }
     $dbc->{prompt} = $prompt;
     $dbc->dbclist ($workdir->{workdir}, \@dbclist_docroots);                                                                 #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};

     run_perms($ud_touser);                        #Run perms on the new account.

     # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $fromdocroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $domain . "' -r '" . $clone_domain . "'\n" . RESET, 3);
          }
     }

     print GREEN . "New account info:\nUser= $touser\nDomain= $domain\nPassword: $dest_pass\n" . RESET;
     return $dest_pass;
}


# Swap the passed addon domain with the primary domain.
# - Confirm that the new addon dir doesn't exist (i.e. public_html/primarydom.tld).
# - Move the public_html files into the directory with the new addon domain.
# - Move the files from the previous addon directory into public_html.
# - Remove the addon domain ($domain)
# - Rename the primary to    $domain (Its subdomains will change with the rename).
# - Create an addon with the previous primary domain name.
# Note that subdomains will remain as they were with the same names and same directories.  The only difference is that they will be subdomains of
#  a different kind of domain (i.e. subs of a primary when they used to be subs of an addon and vice versa).
sub addon_primary_swap {
     my $ud_user          = $_[0];
     my $domain           = $_[1];
     my $workdir          = $_[2];
     my $oldaddondir      = $ud_user->{addon_addondir}{$domain}; #Directory for the addon domain that is moving to the primary domain.
     my $homedir          = "/" . $ud_user->{partition} . "/" . $ud_user->{username};
     my $newaddondir      = $homedir . "/public_html/" . $ud_user->{primarydomain}; #Dir for what will be the addon domain (currently the primary).
     my @additional_excludes; #Passed to $ud_user->move_document_root to ensure that we don't copy from a directory we're copying to.
     my @undostack;
     my $zup_oldprimary;
     my $zup_oldaddon;
     my $zup_newprimary;      #Zup object representing the zone file of the newly added primary domain (what was the addon).
     my $zup_newaddon;

     my $zup_oldprimary = ZUP::Parser->new("/var/named/".$ud_user->{primarydomain}.".db"); #Zup object representing the zone file of the addon before any changes.
     if (!$zup_oldprimary) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($zup_oldprimary->getzonefile())) {
          return 1;
     }
     my $zup_oldaddon = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file of the addon before any changes.
     if (!$zup_oldaddon) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($zup_oldaddon->getzonefile())) {
          return 1;
     }
     ssl_save($ud_user, $domain, 1);
     ssl_save($ud_user, $ud_user->{primarydomain}, 1);
     #my @excludes;          #array of directories (1 in this case) to pass to the move_document_root method of $ud_user.

     #my @excludes;          #array of directories (1 in this case) to pass to the move_document_root method of $ud_user.
     #my $rel_exclude;       #Directory relative to the directory we are moving 
     if (length($oldaddondir) == 0) {
          $l->logg(RED . "The addon domain $domain does not appear to be in the $ud_user->{username} account.\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;                                   #Addon directory not found.
     }

     #Check to see if the touser is allowed to have any more addons.
     $ud_user->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     if ($ud_user->{maxaddons} !~ /unlimited/ && scalar(@{$ud_user->{addons}}) > $ud_user->{maxaddons}) {
          $l->logg(RED . "An addon domain cannot be added to the destination account (" . $ud_user->{username} . ").  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     if (-e $newaddondir) {
          $l->logg(RED . "The directory that we are moving the primary domain's files to ($newaddondir) already exists.\n" . RESET, 1);
          if (show_prompt("Continue anyway? ") != 1) {
               return 0;
          }else {
               $l->logg("User chose to continue.\n",0);
          }
     }

     if ($workdir == 0) {                                                                                #If a workdir object wasn't passed, then create one.
          $workdir = workdir->new("", $l);
     }

     if (copy ("/etc/valiases/$domain", "$workdir->{workdir}/valiases-$domain") == 0) {
          $l->logg(RED . "Error copying /etc/valiases/$domain to $workdir->{workdir}/valiases-$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }
     if (copy ("/etc/valiases/$ud_user->{primarydomain}", "$workdir->{workdir}/valiases-$ud_user->{primarydomain}") == 0) {
          $l->logg(RED . "Error copying /etc/valiases/$ud_user->{primarydomain} to $workdir->{workdir}/valiases-$ud_user->{primarydomain}: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }

     # Before moving the files, let's look for database configs in the old addon dir because it's isolated right now.
     push(my @oldaddondir_array, $oldaddondir);
     my $oldaddon_configs = $dbc->find_configs(\@oldaddondir_array);

     push (@additional_excludes, $newaddondir); #If we don't exclude this, then move_document_root will recurse into it, making a bit of a mess.
     $l->logg(GREEN . "Moving files for $ud_user->{primarydomain} from $homedir/public_html to $newaddondir.\n" . RESET, 1);
     
     $ud_user->move_document_root($ud_user->{primarydomain}, $newaddondir, \@additional_excludes);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace("$homedir/public_html", $newaddondir, $newaddondir);
     $replacer->replace($oldaddondir, "$homedir/public_html", $oldaddondir);

     $l->logg(GREEN . "Moving files for $domain from $oldaddondir to $homedir/public_html.\n" . RESET, 1);
     $ud_user->move_document_root($domain, "$homedir/public_html");

     if (!remove_subdomains($domain, $ud_user, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }
     if (!remove_subdomains($ud_user->{primarydomain}, $ud_user, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     $l->logg(GREEN . "Removing addon domain $domain from $ud_user->{username} with document root $oldaddondir.\n" . RESET, 1);
     if (remove_addon($domain, $ud_user) > 0) {            #Remove the addon domain.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     rmdir $oldaddondir; #The addon removal re-creates the directory.  So let's delete it again.
     my $relative_docroot =  $ud_user->lookup_docroot($domain);
     $relative_docroot    =~ s/$homedir//;                         #i.e. public_html/addondir
     my $op = cpopp->new("add-addon", $domain,$ud_user,$relative_docroot);
     unshift (@undostack, $op);

     $l->logg(GREEN . "Changing the primary domain of $ud_user->{username} from $ud_user->{primarydomain} to $domain.\n" . RESET, 1);
     if (change_primary_domain($ud_user, $domain) > 0) {
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     my $op = cpopp->new("ren-account", $ud_user, $ud_user->{primarydomain});
     unshift (@undostack, $op);

     $l->logg(GREEN . "Creating addon domain $ud_user->{primarydomain} for $ud_user->{username} with document root $newaddondir.\n" . RESET, 1);
     if (create_addon($ud_user->{primarydomain}, $ud_user, $newaddondir) > 0) { #Create the addon in the dest account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     # The user didn't contain the domain as an addon domain before we changed it, but the remove_addon sub expects a Userdata object with the addon's info.
     #  To solve this dilemma with the undo stack, let's create a new Userdata object with current info.
     my $ud_undo_touser = Userdata->new("localhost", $ud_user->{username}, undef, $token);
     my $op = cpopp->new("rem-addon", $ud_user->{primarydomain}, $ud_undo_touser);
     unshift (@undostack, $op);

     #Add the subdomains back to what used to be the primary domain (just as they were with the same domain names and directories).
     if (!create_subdomains($ud_user->{primarydomain}, $ud_user, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     #Add the subdomains back to what used to be the addon domain (just as they were)
     if (!create_subdomains($domain, $ud_user, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     $l->logg("Enabling forwarders.\n", 0);          #Copy the forwarders file back into place.
     if (copy ("$workdir->{workdir}/valiases-$domain", "/etc/valiases/$domain") == 0) {
          $l->logg(RED . "Error copying $workdir->{workdir}/valiases-$domain to /etc/valiases/$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }
     if (copy ("$workdir->{workdir}/valiases-$ud_user->{primarydomain}", "/etc/valiases/$ud_user->{primarydomain}") == 0) {
          $l->logg(RED . "Error copying $workdir->{workdir}/valiases-$ud_user->{primarydomain} to /etc/valiases/$ud_user->{primarydomain}: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }

#---------------------- Merge DNS Zone files -------------------------
     $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
     $zup_newprimary = ZUP::Parser->new("/var/named/$domain.db");
     if (!$zup_newprimary) {
          $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!merge_mx_recs($zup_oldaddon, $zup_newprimary)) {
          return 1; # If the zone file merge failed, return 1 to indicate a problem.
     }

     $zup_newaddon = ZUP::Parser->new("/var/named/".$ud_user->{primarydomain}.".db");
     if (!$zup_newaddon) {
          $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!merge_mx_recs($zup_oldprimary, $zup_newaddon)) {
          return 1; # If the zone file merge failed, return 1 to indicate a problem.
     }

#-------------------- Rename AWStats files -------------------------
     my $preaddonsub  = $ud_user->{addon_addonsubdomain}{$domain};
     $ud_user->cpanel_setaddons(); #Update the addon info.
     my $postaddonsub = $ud_user->{addon_addonsubdomain}{$ud_user->{primarydomain}};
     $ud_user->swap_awstats($domain, $preaddonsub, $postaddonsub);
          
#-------------------- Recommend srdbs updates ----------------------
     # Now that the files have been moved, we can look for database configs in the new addon dir.
     push(my @newaddondir_array, $newaddondir);
     my $newaddon_configs = $dbc->find_configs(\@newaddondir_array);


     if (scalar(@{$oldaddon_configs})>0 || scalar(@{$newaddon_configs})>0) { #If there are configs
          $l->logg("Databases were not touched.  However, it is recommended to update the database paths:\n", 1);
          foreach my $config (@{$oldaddon_configs}) {
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{olddb} . " " . $config->{dbuser} . " " . $config->{password} . "' -s '" . $oldaddondir . "' -r '" . $homedir . "/public_html'\n" . RESET, 3);
          }
          foreach my $config (@{$newaddon_configs}) {
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{olddb} . " " . $config->{dbuser} . " " . $config->{password} . "' -s '" . $homedir . "/public_html' -r '" . $newaddondir . "'\n" . RESET, 3);
          }
     }
     return 0;
}

# Restore a domain from a cPanel backup file to a new account.
# Input:  Domain
#         Clone domain (new domain if it's changing.  Otherwise should be same as Domain).
#         Filename of the cPanel backup file (can be relative or a full path)
#         cpbdata object reference to the backup file object.
#         Username of the new user.
# Output: If all is OK, then the password to the created account is returned.
#         Otherwise, "Error" is returned.
sub cpbackup2primary {
     my $domain        = shift;
     my $clone_domain  = shift;
     my $cp_backupfile = shift;
     my $ud_fromuser   = shift;
     my $touser        = shift;
     my $ud_touser;
     my $dest_homedir;
     my $dest_docroot;
     my $dest_pass;
     my $old_full_docroot;
     my $src_homedir   = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $archivedir    = $ud_fromuser->{archivedir};
     my @dbclist_docroots;
     my $oldzup;           #Zup object representing the zone file in the backup.
     my $newzup;           #Zup object representing the zone file of the new account (to be created later).

     #-------------------- Check for problems before changing anything.
     if (my $longuser = lookup_long_username($touser)) {
          print "A user ($longuser) exists which conflicts with $touser.  ";
          print "Make sure the first 8 characters of the username are unique.\n";
          return "Error";
     }

     if (!validate_user($touser)) {
          print "User $touser is not a valid username.\n";
          return "Error";
     }

     if ($ud_fromuser->{error} == 1) {return "Error";} #Exit if something failed with the cpbdata object creation.
     $old_full_docroot = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     if ($old_full_docroot eq "") {
          $l->logg(RED . "The domain $domain was not found in the backup file $cp_backupfile.\n" . RESET, 1);
          return "Error";
     }

     $oldzup = ZUP::Parser->new("$archivedir/dnszones/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return 1;
     }

     $l->logg(GREEN . "Creating a new account for $clone_domain with a username of $touser.\n" . RESET, 1);
     $dest_pass = create_account($touser, $clone_domain);
     if ($dest_pass eq "Error") {                       #Create the new account.
          return "Error"; #We had a problem.  Exit now.
     }
     $ud_touser = Userdata->new("localhost", $touser, undef, $token);                 #Gather / set some info. on the new account.
     $dest_homedir = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";
     $dest_docroot = $dest_homedir . "public_html";

     decide_new_subdomains($domain, $clone_domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!create_subdomains($clone_domain, $ud_touser)) {
          return 1;
     }

     # Copy the files.
     copy_docroots($domain, $clone_domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($old_full_docroot, $dest_docroot, $dest_docroot);

     if ($domain eq $clone_domain) {
          $l->logg(GREEN . "Extracting any email for $domain...\n" . RESET, 1);
          $ud_fromuser->copy_mail($domain, $ud_touser);
     }else{
          $l->logg(GREEN . "Extracting any email for $domain to $clone_domain...\n" . RESET, 1);
          $ud_fromuser->clone_mail($domain, $clone_domain, $ud_touser);
     }

     $dbc->{prompt} = $prompt;
     my @summary = $dbc->dbclist ("$archivedir/mysql", \@dbclist_docroots); #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};
     $l->loggarray(\@summary, 2); # Save summary of connected databases to be printed later if --alldomains was used.

     if ($domain eq $clone_domain) { # The following should only be done if we are going to the same domain.

          if (ref($ud_fromuser) eq "cpbdata") {               #If the --fromfile is a cPanel backup, then copy email forwarders too.
               $l->logg("Enabling forwarders.\n", 0);          #Copy the forwarders file back into place.
               if (copy ("$archivedir/va/$domain", "/etc/valiases/$clone_domain") == 0) {
                    $l->logg(RED . "Error copying $archivedir/va/$domain to /etc/valiases/$clone_domain: $!\n" . RESET, 1);
                    return "Error";
               }
          }
          #---------------------- Merge DNS Zone files -------------------------
          $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
          $newzup = ZUP::Parser->new("/var/named/$domain.db");
          if (!$newzup) {
               $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
               return 1;
          }
          if (!rcs_backup($newzup->getzonefile())) {
               return 1;
          }
          if (!merge_mx_recs($oldzup, $newzup)) {
               return 1; # If the zone file merge failed, return 1 to indicate a problem.
          }
     
          $l->logg(GREEN . "Copying AWStats if available...\n" . RESET, 1);
          if (!$ud_fromuser->copy_awstats($domain, $ud_touser)) {
               $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
          }
     }

     run_perms($ud_touser);                        #Run perms on the new account.

     # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, etc, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $old_full_docroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
               if ($domain ne $clone_domain) {
                    $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $domain . "' -r '" . $clone_domain . "'\n" . RESET, 3);
               }
          }
     }

     print GREEN . "New account info:\nUser= $touser\nDomain= $clone_domain\nPassword: $dest_pass\n" . RESET;
     return $dest_pass;
}


# Transfer a domain from a cPanel backup file to an addon domain of an existing account.
# Input:  Domain to transfer
#         Filename of the cPanel backup file.
#         Username to add the domain to.
# Output: 0=OK, 1=Error
sub cpbackup2addon {
     my $domain        = shift;
     my $clone_domain  = shift;
     my $cp_backupfile = shift;
     my $ud_fromuser   = shift;
     my $ud_touser     = shift;
     my $touser        = $ud_touser->{username};
     my $src_homedir   = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $dest_homedir  = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";     # the document root path.
     my $archivedir       = $ud_fromuser->{archivedir};
     my $old_full_docroot; #Full path of document root as it was in the old account.            i.e. /home/olduser/public_html/mydomain.com
     my $relative_docroot; #Document root of the domain relative to the user's home directory.  i.e. public_html/mydomain.com
     my $dest_docroot;
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.
     my $oldzup;           #Zup object representing the zone file in the backup.
     my $newzup;           #Zup object representing the zone file of the new account (to be created later).

     #-------------------- Check for problems before changing anything.
     if ($ud_fromuser->{error} == 1) {return 1;} #Exit if something failed with the cpbdata object creation.
     $old_full_docroot = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     if ($old_full_docroot eq "") {
          $l->logg(RED . "The domain $domain was not found in the backup file $cp_backupfile.\n" . RESET, 1);
          return 1;
     }

     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && scalar(@{$ud_touser->{addons}}) >= $ud_touser->{maxaddons}) {
          $l->logg(RED . "An addon domain cannot be added to the destination account ($touser).  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     $oldzup = ZUP::Parser->new("$archivedir/dnszones/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return 1;
     }
     $relative_docroot = $old_full_docroot;             #Find out the document root relative to the home user's home dir.
     $relative_docroot =~ s/\/.*?\/.*?\///;
     if ($relative_docroot =~ /^httpdocs/) {            #If the document root is within httpdocs (i.e. from a Plesk box), change it to public_html.
          $relative_docroot =~ s/httpdocs/public_html/;
     }
     if ($relative_docroot eq "public_html") { #If it's the root of public_html, then it must be the primary domain
          $relative_docroot = "public_html/$domain";    # in the archive, but since we are moving to an addon, change the dir to public_html/$domain.
     }
     if ($domain ne $clone_domain) {
          $relative_docroot =~ s/$domain/$clone_domain/;
     }
     $dest_docroot = $dest_homedir . $relative_docroot; #Find out the destination document root.
     decide_new_subdomains($domain, $clone_domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!check_for_docroots($clone_domain, $dest_docroot, $ud_touser)) {
          return 1;
     }

     $l->logg(GREEN . "Creating addon domain $clone_domain for $touser in $dest_docroot.\n" . RESET, 1);
     if (create_addon($clone_domain, $ud_touser, $relative_docroot) > 0) { #Create the addon in the dest account.
          return 1; #We had a problem.  Exit now.
     }

     if (!create_subdomains($clone_domain, $ud_touser)) {
          return 1;
     }

     # Copy the files.
     copy_docroots($domain, $clone_domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($old_full_docroot, $dest_docroot, $dest_docroot);

     if ($domain eq $clone_domain) {
          $l->logg(GREEN . "Extracting any email for $domain...\n" . RESET, 1);
          $ud_fromuser->copy_mail($domain, $ud_touser);
     }else{
          $l->logg(GREEN . "Extracting any email for $domain to $clone_domain...\n" . RESET, 1);
          $ud_fromuser->clone_mail($domain, $clone_domain, $ud_touser);
     }


     $dbc->{prompt} = $prompt;
     my @summary = $dbc->dbclist ("$archivedir/mysql", \@dbclist_docroots);                                                                 #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};
     $l->loggarray(\@summary, 2); # Save summary of connected databases to be printed later if --alldomains was used.

     if ($domain eq $clone_domain) { # The following should only be done if we are going to the same domain.

          if (ref($ud_fromuser) eq "cpbdata") {               #If the --fromfile is a cPanel backup, then copy email forwarders too.
               $l->logg("Enabling forwarders.\n", 0);          #Copy the forwarders file back into place.
               if (copy ("$archivedir/va/$domain", "/etc/valiases/$clone_domain") == 0) {
                    $l->logg(RED . "Error copying $archivedir/va/$domain to /etc/valiases/$clone_domain: $!\n" . RESET, 1);
                    return 1;
               }
          }
          #---------------------- Merge DNS Zone files -------------------------
          $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
          $newzup = ZUP::Parser->new("/var/named/$domain.db");
          if (!$newzup) {
               $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
               return 1;
          }
          if (!rcs_backup($newzup->getzonefile())) {
               return 1;
          }
          if (!merge_mx_recs($oldzup, $newzup)) {
               return 1; # If the zone file merge failed, return 1 to indicate a problem.
          }
     
          $l->logg(GREEN . "Copying AWStats if available...\n" . RESET, 1);
          $ud_touser->cpanel_setaddons(); #Update the addon info.
          if (!$ud_fromuser->copy_awstats($domain, $ud_touser)) {
               $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
          }
     }
 
     run_perms($ud_touser);                        #Run perms on the new account.

     # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $old_full_docroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
               if ($domain ne $clone_domain) {
                    $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $domain . "' -r '" . $clone_domain . "'\n" . RESET, 3);
               }
          }
     }

     return 0;
}

# Transfer an addon domain to an addon domain of another account.
# Input: Domain to transfer
#        To username
# Returns: 0=OK, 1=Problem.
sub addon2addon {
     my $domain      = $_[0];
     my $ud_touser   = $_[1];
     my $workdir     = $_[2];
     my $fromuser    = find_domain_owner($domain);
     my $ud_fromuser = Userdata->new("localhost", $fromuser, undef, $token);
     my $touser      = $ud_touser->{username};
     my $src_docroot;           #Source      document root (full path)
     my $src_homedir;
     my $dest_docroot;          #Destination document root (full path)
     my $dest_homedir;          #Destination home directory
     my $dest_relative_docroot; #Destination document root (relative to the destination home directory).
     my @subdomains;            #Array to keep track of any subdomains of the addon we're moving.
     my @subdomain_relative_dirs;
     my @databases;
     my @undostack;
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.
     my $newzup;                #Zup object representing the zone file of the new account (to be created later).
     my $oldzup = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return 1;
     }
     ssl_save($ud_fromuser, $domain, 1);
     # Set some variables
     $src_docroot  = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     $dest_docroot = $src_docroot;                             #Set the $dest_docroot to the $src_docroot so we can change it.
     $src_homedir  = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/"; #Set homedir variables for the source and dest so we can use them to convert
     $dest_homedir = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";     # the document root path.
     $dest_docroot =~ s/$src_homedir/$dest_homedir/;
     $dest_relative_docroot = $dest_docroot;                   #$dest_relative_docroot is needed by the create_addon subroutine.
     $dest_relative_docroot =~ s/$dest_homedir//;

     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && scalar(@{$ud_touser->{addons}}) >= $ud_touser->{maxaddons}) {
          $l->logg(RED . "An addon domain cannot be added to the destination account ($touser).  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     decide_new_subdomains($domain, $domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!check_for_docroots($domain, $dest_docroot, $ud_touser)) {
          return 1;
     }


     if ($workdir == 0) {                    #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
     }

     if (copy ("/etc/valiases/$domain", "$workdir->{workdir}/valiases-$domain") == 0) {
          $l->logg(RED . "Error copying /etc/valiases/$domain to $workdir->{workdir}/valiases-$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }

     remove_subdomains($domain, $ud_fromuser, \@undostack);

     $l->logg(GREEN . "Removing addon domain $domain from $fromuser with document root $src_docroot.\n" . RESET, 1);
     if (remove_addon($domain, $ud_fromuser) > 0) {            #Remove the addon from the source account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     my $relative_docroot =  $src_docroot;
     $relative_docroot    =~ s/$src_homedir//;                      #i.e. public_html/addondir/
     $relative_docroot    =~ s/\/$//;                                 #Remove the trailing slash if there is one.
     my $op = cpopp->new("add-addon", $domain,$ud_fromuser,$relative_docroot);
     unshift (@undostack, $op);

     $l->logg(GREEN . "Creating addon domain $domain for $touser in $dest_docroot.\n" . RESET, 1);
     if (create_addon($domain, $ud_touser, $dest_relative_docroot) > 0) { #Create the addon in the dest account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     # The destination user didn't contain the addon domain before we moved it, but the remove_addon sub expects a Userdata object with the addon's info.
     #  To solve this dilemma with the undo stack, let's create a new Userdata object with current info.
     my $ud_undo_touser = Userdata->new("localhost", $ud_touser->{username}, undef, $token);
     my $op = cpopp->new("rem-addon", $domain, $ud_undo_touser);
     unshift (@undostack, $op);

     if (!create_subdomains($domain, $ud_touser, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     # Copy the files.
     copy_docroots($domain, $domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($src_docroot, $dest_docroot, $dest_docroot);

     # Rsync Email
     if (-e "$src_homedir/etc/$domain") {                                 #if the source addon has email, rsync the mail directories.
          $l->logg(GREEN . "Moving email for $domain...\n" . RESET, 1); 
          $ud_fromuser->copy_mail($domain, $ud_touser);
     }else {
          $l->logg("No mail found for $domain.\n", 0);
     }

     $l->logg("Enabling forwarders.\n", 0);          #Copy the forwarders file back into place.
     if (copy ("$workdir->{workdir}/valiases-$domain", "/etc/valiases/$domain") == 0) {
          $l->logg(RED . "Error copying $workdir->{workdir}/valiases-$domain to /etc/valiases/$domain: $!\n" . RESET, 1);
          $workdir->{error} = "mverror";
          return 1;
     }


     #Transfer the databases.
     $l->logg(GREEN . "Transferring databases...\n" . RESET, 1);
     if ($workdir->{hasdatabases} == 0) {
          $l->logg("Dumping databases to $workdir->{workdir}\n",1);
          list_databases($fromuser, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.      
     }else {
          $l->logg("Not dumping databases because I assume you've already dumped them since you specified $workdir->{workdir} as the workdir.\n",1);
     }
     $dbc->{prompt} = $prompt;
     my @summary = $dbc->dbclist ($workdir->{workdir}, \@dbclist_docroots);                                                                 #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};
     $l->loggarray(\@summary, 2); # Save summary of connected databases to be printed later if --alldomains was used.
#---------------------- Merge DNS Zone files -------------------------
     $l->logg(GREEN . "Checking for any needed zone file updates...\n" . RESET, 1);
     $newzup = ZUP::Parser->new("/var/named/$domain.db");
     if (!$newzup) {
          $l->logg(RED . "Error: Cannot parse the newly created zone file. $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($newzup->getzonefile())) {
          return 1;
     }
     if (!merge_mx_recs($oldzup, $newzup)) {
          return 1; # If the zone file merge failed, return 1 to indicate a problem.
     }

     $l->logg(GREEN . "Copying AWStats if available...\n" . RESET, 1);
     $ud_touser->cpanel_setaddons(); #When $ud_touser object was created, the addon we're moving didn't exist.  Re-read its addon info.
     if (!$ud_fromuser->copy_awstats($domain, $ud_touser)) {
          $l->logg(RED . $ud_fromuser->{error_msg} . RESET);
     }

     run_perms($ud_touser);                        #Run perms on the destination account.
          # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $src_docroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
          }
     }
     return 0;
}


# Transfer an addon domain to a new addon domain with a new domain name in the same account or a different account.
# Input: Domain to transfer
#        New domain name.
#        To username
# Returns: 0=OK, 1=Problem.
sub addon2addon_clone {
     my $domain       = shift;
     my $clone_domain = shift;
     my $ud_touser    = shift;
     my $workdir      = shift;
     my $fromuser    = find_domain_owner($domain);
     my $ud_fromuser = Userdata->new("localhost", $fromuser, undef, $token);
     my $touser      = $ud_touser->{username};
     my $src_docroot;           #Source      document root (full path)
     my $src_homedir;
     my $dest_docroot;          #Destination document root (full path)
     my $dest_homedir;          #Destination home directory
     my $dest_relative_docroot; #Destination document root (relative to the destination home directory).
     my @subdomains;            #Array to keep track of any subdomains of the addon we're moving.
     my @subdomain_relative_dirs;
     my @databases;
     my @undostack;
     my @dbclist_docroots = ();    #Array of document roots including subdomains to pass to dbclist.
     my $newzup;                #Zup object representing the zone file of the new account (to be created later).
     my $oldzup = ZUP::Parser->new("/var/named/$domain.db"); #Zup object representing the zone file before any changes.
     if (!$oldzup) {
          $l->logg(RED . "Error parsing the source DNS zone file.  $ZUP::Parser::errstr\n" . RESET, 1);
          return 1;
     }
     if (!rcs_backup($oldzup->getzonefile())) {
          return 1;
     }
     # Set some variables
     $src_docroot  = $ud_fromuser->lookup_docroot($domain);  #Find the document root in the source account.
     $dest_docroot = $src_docroot;                             #Set the $dest_docroot to the $src_docroot so we can change it.
     $src_homedir  = "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/"; #Set homedir variables for the source and dest so we can use them to convert
     $dest_homedir = "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";     # the document root path.
     $dest_docroot =~ s/$src_homedir/$dest_homedir/;
     $dest_docroot =~ s/$domain/$clone_domain/; #Make sure the dest docroot has the new domain name.
     if ($dest_docroot =~ /public_html$/) {  #If it ends with public_html, then make it a subdir because we're going to an addon.
          $dest_docroot = $dest_docroot . "/$clone_domain";
     }
     $dest_relative_docroot = $dest_docroot;                   #$dest_relative_docroot is needed by the create_addon subroutine.
     $dest_relative_docroot =~ s/$dest_homedir//;

     #Check to see if the touser is allowed to have any more addons.
     $ud_touser->lookup_diskusage(); #This also populates $ud_touser->{maxaddons}.
     if ($ud_touser->{maxaddons} !~ /unlimited/ && scalar(@{$ud_touser->{addons}}) >= $ud_touser->{maxaddons}) {
          $l->logg(RED . "An addon domain cannot be added to the destination account ($touser).  Adjust it's maximum addon domains before proceeding\n" . RESET, 1);
          return 1;
     }

     decide_new_subdomains($domain, $clone_domain, $ud_fromuser, $ud_touser, $dest_docroot);
     if (!check_for_docroots($clone_domain, $dest_docroot, $ud_touser)) {
          return 1;
     }


     if ($workdir == 0) {                    #If a workdir object wasn't passed, then create one.
          $workdir      = workdir->new("", $l);
     }

     $l->logg(GREEN . "Creating addon domain $clone_domain for $touser in $dest_docroot.\n" . RESET, 1);
     if (create_addon($clone_domain, $ud_touser, $dest_relative_docroot) > 0) { #Create the addon in the dest account.
          undo_changes(\@undostack);
          $workdir->{error} = "mverror";
          return 1; #We had a problem.  Exit now.
     }
     # The destination user didn't contain the addon domain before we moved it, but the remove_addon sub expects a Userdata object with the addon's info.
     #  To solve this dilemma with the undo stack, let's create a new Userdata object with current info.
     my $ud_undo_touser = Userdata->new("localhost", $ud_touser->{username}, undef, $token);
     my $op = cpopp->new("rem-addon", $domain, $ud_undo_touser);
     unshift (@undostack, $op);

     if (!create_subdomains($clone_domain, $ud_touser, \@undostack)) {
          $workdir->{error} = "mverror";
          return 1;
     }

     # Copy the files.
     copy_docroots($domain, $clone_domain, $dest_docroot, $ud_fromuser, $ud_touser, \@dbclist_docroots);

     # Update the paths in any known files.
     my $replacer = replacer->new($l);
     $replacer->replace($src_docroot, $dest_docroot, $dest_docroot);

     # Rsync Email
     if (-e "$src_homedir/etc/$domain") {                                 #if the source addon has email, rsync the mail directories.
          $l->logg(GREEN . "Cloning email for $domain to $clone_domain...\n" . RESET, 1); 
          $ud_fromuser->clone_mail($domain, $clone_domain, $ud_touser);
     }else {
          $l->logg("No mail found for $domain.\n", 0);
     }

     #Transfer the databases.
     $l->logg(GREEN . "Transferring databases...\n" . RESET, 1);
     if ($workdir->{hasdatabases} == 0) {
          $l->logg("Dumping databases to $workdir->{workdir}\n",1);
          list_databases($fromuser, \@databases);   #List the databases in the from account.
          dump_databases($workdir->{workdir}, \@databases);        #Export the listed databases.      
     }else {
          $l->logg("Not dumping databases because I assume you've already dumped them since you specified $workdir->{workdir} as the workdir.\n",1);
     }
     $dbc->{prompt} = $prompt;
     $dbc->dbclist ($workdir->{workdir}, \@dbclist_docroots);                                                                 #Find and connect databases for any known apps.
     $prompt = $dbc->{prompt};

     run_perms($ud_touser);                        #Run perms on the destination account.
          # The $dbc object now contains info. about the connected databases that we can use to print srdbs suggestions.
     my $shown_flag = 0; #Indicates whether or not the SRDbs intro text has been shown yet.
     foreach my $config (@{$dbc->{configlist}}) {
          if (length($config->{newdb}) > 0) {
               if (!$shown_flag) {
                    $l->logg(YELLOW . "To update the file paths, run the following SRDbs line(s):\n" . RESET, 1);
               }
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $src_docroot . "' -r '" . $dest_docroot . "'\n" . RESET, 3);
               $l->logg(YELLOW . "srdbs --dbinfo '" . $config->{newdb} . "' '" . $config->{newdb} . "' '" . $config->{password} . "' -s '" . $domain . "' -r '" . $clone_domain . "'\n" . RESET, 3);
          }
     }
     return 0;
}

# -----------------------------------------------------------------------------
# --------------------- DNS related subroutines ---------------------------
# -----------------------------------------------------------------------------

# Merge a source zone file into the new zone file for the same domain.
# Input:  ZUP::Parser object for the source zone.
#         ZUP::Parser object for the destination zone.
# Output: 
sub merge_mx_recs {
     my $oldparser = shift;
     my $newparser = shift;
     
     # Take care of MX records.
     # If the old zone file has MX records for a known service such as Google Apps, then replace
     #  the MX record in the new zone file with the MX records for that service.
     my $live = has_live($oldparser);
     if ($live) {
          $newparser->set_winlive($live);
     }elsif (has_gapps($oldparser)) {
          $newparser->set_gapps();
     }elsif(has_godaddy($oldparser)) {
          $newparser->set_godaddy();
     }elsif(has_office365($oldparser)) {
          $newparser->set_office365();
     }

     my $result = zup_validate_and_save($newparser);
     return $result;
}

sub merge_a_recs {
     my $oldparser = shift;
     my $newparser = shift; 
     # Take care of A records.
     # Any A records in the old zone file that are not in the new zone file will be copied over.
     my $ip = find_zone_ip($newparser);
     my $missingrecs = list_missing_arecs($oldparser, $newparser);
     foreach my $rec (keys %{$missingrecs}) { #Update the IPs to the new account IP.
          $missingrecs->{$rec} = $ip;
     }
     $newparser->set_a_records($missingrecs);
}

# Validate and save a zup object.
# Input:  A zup object.
# Output: Zone file is saved and validated and loaded.
#         1=OK, 0=Error
sub zup_validate_and_save {
     my $parser = shift;
     chomp (my $named_check = `which named-checkzone`);
     if($parser->validatezone($named_check) == 0) {
          if ($parser->savezone()   == 0) {
               print "Error saving zone file.\n";
               return 0;
          }
          if ($parser->rndcreload() != 0) {
               print "Error reloading the zone.\n";
               return 0;
          }
     }else{
          print "Error validating the zone file.\n";
          return 0;
     }
     return 1;
}

# Determine whether or not a zone has Google Apps installed.
# Input:  A ZUP::Parser object
# Output: 0 = No Google Apps in the MX records.
#         1 = Google Apps is in the MX records.
sub has_gapps {
     my $zupobj = shift;
     my $mxrecs = $zupobj->get_mx_records();
     foreach my $mx (@{$mxrecs}) {
          if ($mx->{host} =~ /ASPMX.L.GOOGLE.COM/i) {
               return 1; #Found a Google Apps record.
          }
     }
     return 0; #No Google Apps records found.
}

# Determine whether or not a zone has Windows Live MX records installed.
# Input:  A ZUP::Parser object
# Output: 0 = No Windows Live in the MX records.
#         1 = Windows Live is in the MX records.
sub has_live {
     my $zupobj = shift;
     my $result;
     my $mxrecs = $zupobj->get_mx_records();
     foreach my $mx (@{$mxrecs}) {
          if ($mx->{host} =~ /PAMX1.HOTMAIL.COM/i) {
               $result = $mx->{host};
               $result =~ s/\.pamx1.*//i;
               return $result; # Return the host ID.  This can be used as a true test and can
                               # be passed to ->set_winlive method of a zup object.
          }
     }
     return 0; #No Windows Live records found.
}

# Determine whether or not a zone has Godaddy installed.
# Input:  A ZUP::Parser object
# Output: 0 = No Godaddy in the MX records.
#         1 = Godaddy is in the MX records.
sub has_godaddy {
     my $zupobj = shift;
     my $mxrecs = $zupobj->get_mx_records();
     foreach my $mx (@{$mxrecs}) {
          if ($mx->{host} =~ /secureserver.net/i) {
               return 1; #Found a Godaddy record.
          }
     }
     return 0; #No Godaddy records found.
}

# Determine whether or not a zone has Office365 installed.
# Input:  A ZUP::Parser object
# Output: 0 = No Office365 in the MX records.
#         1 = Office365 is in the MX records.
sub has_office365 {
     my $zupobj = shift;
     my $mxrecs = $zupobj->get_mx_records();
     foreach my $mx (@{$mxrecs}) {
          if ($mx->{host} =~ /outlook.com/i) {
               return 1; #Found a Office365 record.
          }
     }
     return 0; #No Office365 records found.
}

## List the A records from the source zone file that are not in the destination zone file.
## Input:  Array ref of A records for source zone (output from $oldparser->get_a_records()).
##         Array ref of A records for dest   zone (output from $newparser->get_a_records()).
## Output: Hash array of A records that are in the source zone but not in the dest zone.
##         Can be used as input for ->set_a_records() method of a ZUP::Parser object.
#sub list_missing_arecs {
#     my $oldzupobj = $_[0];
#     my $newzupobj = $_[1];
#     my $oldarecs  = $oldzupobj->get_a_records();
#     my $newarecs  = $newzupobj->get_a_records();
#     my $missingarecs = {};
#     foreach my $oldrec (@{$oldarecs}) {
#          my $found = 0;
#          #print "Old: " . $oldrec->{name} . "\n";
#          foreach my $newrec (@{$newarecs}) {
#               #print "New: " . $newrec->{name} . "\n";
#               if ($oldrec->{name} eq $newrec->{name}) {
#                    $found = 1;
#               }
#          }
#          if (!$found) {
#               #print $oldrec->{name} . " - " . $oldrec->{host} . "\n";
#               $missingarecs->{$oldrec->{name}} = $oldrec->{host};
#          }
#     }
#     return $missingarecs;
#}


# Find out the IP address of the domain in this zone file.
# Input:  Domain
#         reference to the array of A records. i.e. from ZUP::Parser->get_a_records()
# Output: IP address of the zone.
#         undef if the domain name is not found.
sub find_zone_ip {
     my $zupobj = shift;
     my $domain = $zupobj->{domain};
     my $recs   = $zupobj->get_a_records();
     foreach my $rec (@{$recs}) {
          if ($rec->{name} =~ m/^$domain\.?$/) {
               return $rec->{host};
          }
     }
     return undef;
}

# Back up the zone file.
# Input:  Filename of the zone file including full path.
# Output: Zone file is backed up to an RCS archive in /home/hgtransfer/zup-backups/RCS-archives.
sub rcs_backup {
     my $zonefile =  shift;
     my $zonepath =  $zonefile;
     $zonefile    =~ s/.*\///; #Chop the directory path.
     $zonepath    =~ s/[^\/]*$//;
     $zonepath    =~ s/\/$//;
     my $rcsobj = ZUP::RCS->new();
     my $rcsdir = "/home/hgtransfer/zup-backups/RCS-archives";
     $rcsobj->rcsdir ($rcsdir);
     $rcsobj->workdir($zonepath);
     $rcsobj->file($zonefile);
     my $rcsbin  = `which rcs`;
     chomp $rcsbin;
     $rcsobj->bindir(dirname($rcsbin));
     # dir check
     if (not -d $rcsdir) {
          if (!mkdirectory ($rcsdir)) {
               $l->logg(RED . "Failed to create '$rcsdir' to backup zone data.\n" . RESET,1);
               return 0;
          }
     }
     if (-s "$rcsdir/$zonefile,v") {
             $rcsobj->ci('-l', "-mBackup of $zonefile");
     } else {
             $rcsobj->ci('-l', "-t-ZUP Backup of $zonefile");
     }
}

# -----------------------------------------------------------------------------
# -------------------------- cPanel subroutines ------------------------------
# -----------------------------------------------------------------------------

##Remove stale tokens
sub stale_tokens {
        my $token_list = `whmapi1 api_token_list --output=json`;
        my $listjson = from_json($token_list);
        my $current_time = time();
        if ($currversion >= 11.68){
                foreach my $token_name (%{$listjson->{'data'}{'tokens'}}){
                        if( $token_name =~ /hgtool_/ ){
                                my $token_time = $token_name;
                                $token_time =~ s/\D//g;
                                if ( ($token_time + 432000) < $current_time ){
                                        print "Revoking token";
                                        #Token is 5 days older than now, removing.
                                        `whmapi1 api_token_revoke token_name=$token_name`;
                                }
                        }
                }
        } else {
                foreach my $token_data (@{$listjson->{'data'}{'tokens'}}){
                        if( $token_data->{'name'} =~ /hgtool_/ ){
                                my $token_time = $token_data->{'name'};
                                $token_time =~ s/\D//g;
                                if ( ($token_time + 432000) < $current_time ){
                                        #Token is 5 days older than now, removing.
                                        `whmapi1 api_token_revoke token_name=$token_data->{'name'}`;
                                }
                        }
                }
        }
}

## Migrate AWStats for a domain.
## Input:  Domain
##         Source homedir
##         Dest homedir
## Output: 1=OK, 0=Fail
#sub migrate_awstats {
#     my $domain      = shift;
#     my $src_homedir = shift;
#     my $dst_homedir = shift;
#     my $dh;                   #directory handle
#     $src_homedir =~ s/\/$//;  #Remove trailing / if there is one.
#     $dst_homedir =~ s/\/$//;  #Remove trailing / if there is one.
#
#     # List the files to copy
#     if (!opendir ($dh, "$src_homedir/tmp/awstats")) {
#          $l->logg(RED . "Failed to open directory $src_homedir/tmp/awstats.  $!\n" . RESET, 1);
#          return 0;
#     }
#     my @files = grep { /(awstats.$domain.conf|awstats[0-9]{6}.$domain.txt)/ && -f "$src_homedir/tmp/awstats/$_" } readdir($dh);
#     closedir $dh;
#     if (scalar(@files) == 0) {
#          $l->logg("No Awstats found for $domain\n", 1);
#          return 0;
#     }
#
#     # dir check
#     if (not -d "$dst_homedir/tmp/awstats") {
#          if (!make_path ("$dst_homedir/tmp/awstats", "root", "root")) {
#               $l->logg(RED . "Failed to create $dst_homedir/tmp/awstats to copy Awstats data.\n" . RESET,1);
#               return 0;
#          }
#     }
#
#     # Copy th files
#     foreach my $file (@files) {
#          if (!copy ("$src_homedir/tmp/awstats/$file", "$dst_homedir/tmp/awstats/$file")) {
#               $l->logg(RED . "File copy of $src_homedir/tmp/awstats/$file to $dst_homedir/tmp/awstats/$file failed: $!\n", 1);
#          }
#     }
#
#     #update the config file with the new Awstats path.
#     tie my @lines, 'Tie::File', "$dst_homedir/tmp/awstats/awstats.$domain.conf";
#     if (!scalar(@lines)) {
#          $l->logg(RED . "Failed to update the Awstas config file $dst_homedir/tmp/awstats/awstats.$domain.conf\n", 1);
#          return 0;
#     }
#     foreach my $line (@lines) {
#          $line =~ s/^DirData\s*=.*/DirData=\"$dst_homedir\/tmp\/awstats\"/;
#     }
#     untie @lines;
#     return 1;
#}

# Undo any changes that were made to an account.  This would be called to put a source account back together if there was a problem part way through a transfer.
# Input: Array reference to an array of cpopp objects.  A cpopp object represents one cPanel operation such as adding an addon domain, etc.
# cpopp->{op} types with args: add-account, 
#                              rem-account,
#                              add-addon,domain,user,dir
#                              rem-addon,
#                              add-sub,domain,user,dir,subdom
#                              rem-sub,
sub undo_changes {
     my $undostack = $_[0]; #Technically not a stack becasue we're not popping from it, but we are looping in a LIFO manner.
     my @args;
     my $ret;
     $l->logg(YELLOW . "Rolling back any cPanel changes since there was an issue.  Note that any other changes that were made such as moved files, etc. will not be undone.\nSee $l->{filename} for the log of changes that have been made.\n\n" . RESET, 1);
     foreach my $op (@{$undostack}) {     #Loop through the undo stack to perform each undo operation.
          if ($op->{op} eq "add-addon") {
               $l->logg(YELLOW . "Rollback: Creating addon domain $op->{arg0} in $op->{arg1}->{username} with document root $op->{arg2}\n" . RESET, 1);
               $ret = create_addon($op->{arg0}, $op->{arg1}, $op->{arg2});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to add the addon domain.\n" . RESET, 1);
               }
          }elsif ($op->{op} eq "rem-addon") {
               $l->logg(YELLOW . "Rollback: Removing addon domain $op->{arg0} from $op->{arg1}->{username}.\n" . RESET, 1);
               $ret = remove_addon($op->{arg0}, $op->{arg1});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to remove the addon domain.\n" . RESET, 1);
               }   
          }elsif ($op->{op} eq "add-sub") {
               $l->logg(YELLOW . "Rollback: Creating subdomain $op->{arg3}.$op->{arg0} in $op->{arg1}.\n" . RESET, 1);
               $ret = create_subdomain($op->{arg0}, $op->{arg1}, $op->{arg2}, $op->{arg3});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to add the subdomain.\n" . RESET, 1);
               }
          }elsif ($op->{op} eq "rem-sub") {
               $l->logg(YELLOW . "Rollback: Removing subdomain $op->{arg1} from $op->{arg0}.\n" . RESET, 1);
               $ret = remove_subdomain($op->{arg0}, $op->{arg1});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to remove the subdomain.\n" . RESET, 1);
               }
          }elsif ($op->{op} eq "add-account") {
               $l->logg(YELLOW . "Rollback: Adding an account $op->{arg0} with domain $op->{arg1}.\n" . RESET, 1);
               $ret = create_account($op->{arg0}, $op->{arg1});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to create the account.\n" . RESET, 1);
               }
          }elsif ($op->{op} eq "rem-account") {
               $l->logg(YELLOW . "Rollback: Removing the account $op->{arg0}.\n" . RESET, 1);
               $ret = remove_account($op->{arg0});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to remove the account.\n" . RESET, 1);
               }
          }elsif ($op->{op} eq "ren-account") {
               $l->logg(YELLOW . "Rollback: Renaming the primary domain on account $op->{arg0} to $op->{arg1}.\n" . RESET, 1);
               $ret = change_primary_domain($op->{arg0}, $op->{arg1});
               if ($ret != 0) {
                    $l->logg(RED . "Error: Unable to rename the account domain.\n" . RESET, 1);
               }
          }else {
               $l->logg(RED . "Error: Unable to undo partial changes to the source account.  The undo stack is corrupt.\n" . RESET, 1);
          }
     }
}

# Create a new cPanel account
# Input:  Username for the new account.
#         Domain   of  the new account.
# Output: "Error" if there was a problem.
#         If all is ok, the password is returned.
sub create_account {
     my $user    =  $_[0]; #Username of the account.
     my $domain  =  $_[1]; #New Domain name for the account.
     my $pass    =  makepassword(); #Make a random password.
     my $uripass =  uri_escape($pass);
     $l->logg("Creating a cPanel account with username $user and domain name $domain\n", 0);
     my $response = $apic->whm_api('createacct', {'username' => "$user", 'domain' => "$domain", 'password' => "$uripass", 'quota' => '1000', 'bwlimit' => "1000", 'maxftp' => "unlimited", "maxsql" => 'unlimited', 'maxpop' => 'unlimited', 'maxsub' => 'unlimited', 'maxpark' => 'unlimited', 'maxaddon' => 'unlimited', 'maxlst' => '25' }, 'json');
     my $json = from_json($response);
     if ($json->{'metadata'}->{'result'} != 1 ) {
          $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
          $l->logg(RED . $json->{'metadata'}->{'reason'} . "\n" . RESET, 1);
          return "Error";
     }
     return $pass;
}


# Remove a cPanel account
# Input:  Username for the account to remove.
# Output: 0=OK, 1=Error
sub remove_account {
     my $user   =  $_[0]; #Username of the account.
     $l->logg("Removing the cPanel account with username $user\n", 0);
     my $response = $apic->whm_api('removeacct', { 'user' => "$user" }, 'json');
     my $json = from_json($response);
     if ($json->{'metadata'}->{'result'} != 1 ) {
          $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
          $l->logg(RED . $json->{'metadata'}->{'reason'} . "\n" . RESET, 1);
          return "Error";
     }
     return 0;
}



# Change the primary domain name of a cPanel account.
# Input:  User object
#         Domain to change to.
# Output: 0=OK, 1=Error
sub change_primary_domain {
     my $ud_user = $_[0]; #Username of the account.
     my $domain  = $_[1]; #New Domain name for the account.
     $l->logg("Renaming the primary domain of $ud_user->{username} to $domain\n", 0);
     # If the new domain has no email, then cPanel will rename the old directory.  For our purposes we don't want that.
     # So create a directoy in etc and mail for the new domain which will prevent cPanel from renaming the old directory.
     if (!-d "/$ud_user->{partition}/$ud_user->{username}/etc/$domain") {
          if (!mkdirectory("/$ud_user->{partition}/$ud_user->{username}/etc/$domain")) {
               return 1;
          }
     }
     if (!-d "/$ud_user->{partition}/$ud_user->{username}/mail/$domain") {
          if (!mkdirectory("/$ud_user->{partition}/$ud_user->{username}/mail/$domain")) {
               return 1;
          }
     }
     my $response = $apic->whm_api('modifyacct', { 'user' => "$user", 'domain'=> "$domain" }, 'json');
     my $json = from_json($response);
     if ($json->{'metadata'}->{'result'} != 1 ) {
          $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
          $l->logg(RED . $json->{'metadata'}->{'reason'} . "\n" . RESET, 1);
          return "Error";
     }
     return 0;
}


#List the databases in the cpanel account of the passed user.
#Input: cPanel username
#       reference to an array.
#Output: Populate the array with the database list.
#        Return value: 0=OK, 1=Error
sub list_databases {
     my $user  = $_[0]; #Username of the cPanel account. i.e. myuser
     my $array = $_[1];
     my $response = $apic->cpanel_api2_request('whostmgr', {'module' => 'MysqlFE', 'func' => 'listdbs'}, { 'user' => "$user" }, 'json');
     my $json = from_json($response);
     if ($json->{'cpanelresult'}->{'error'}) {
          $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
          $l->logg(RED . $json->{'cpanelresult'}->{'error'} . "\n" . RESET, 1);
          return "Error";
     }
     foreach my $line (@{$json->{'cpanelresult'}{'data'}}) {
          my $db = $line->{"db"};
          $db =~ s/\@002d/-/; # Convert @002d to a -
          push (@{$array}, $line->{"db"});
     }
     return 0;
}

#List the database users in the cpanel account of the passed cpanel user.
#Input: cPanel username
#       reference to an array.
#Output: Populate the array with the database list.
#        Return value: 0=OK, 1=Error
#This sub is not in use as far as I can see
sub list_database_users {
     my $user  = $_[0]; #Username of the cPanel account. i.e. myuser
     my $array = $_[1];
     my $xml = new XML::Simple;
     my $url = "xml-api/cpanel?user=$user&cpanel_xmlapi_module=MysqlFE&cpanel_xmlapi_func=listusers&cpanel_xmlapi_apiversion=2";
     my @ret = gocpanel("localhost", $url);
     if ($ret[0] eq "Error") { return 1;}
     my $xmlstring = join("\n", @ret);  #Yes, gocpanel splits the xml to an array, which we undo here, but this way we can use gocpanel.
     $xmlstring = $xmlstring . "\n";    #Re-add a linefeed at the end to prevent intermittent errors with $xml->XMLin not being able to find end of xml doc.
     my $data = $xml->XMLin($xmlstring, ForceArray => 1, SuppressEmpty => 1);
     print Dumper($data);
     #print $data->{data}->[0]->{db}[0];
#     foreach my $line (@{$data->{data}}) {
#          #print $line->{"db"}[0] . "\n";
#          push (@{$array}, $line->{"db"}[0]);
#     }
     return 0;

}

# Remove the subdomains of a domain.
# Input:  A Userdata or cpbdata object.
#         The domain to remove subdomains for.
#         Reference to an undostack array.
# Output: 1=Success
#         0=Failure
sub remove_subdomains {
     my $domain    = $_[0];
     my $ud_user   = $_[1];
     my $undostack = $_[2];
     my $homedir  = "/" . $ud_user->{partition} . "/" . $ud_user->{username} . "/";
     foreach my $fullsub (@{$ud_user->{subdomains}}) {  #Remove the subdomains from the primary domain.
          if (issubdomain($fullsub, $domain)) {
               $l->logg(GREEN . "Removing subdomain $fullsub with directory " . $ud_user->{subdomain_subdomaindir}{$fullsub} . ".\n" . RESET, 1);
               if (remove_subdomain($ud_user->{username}, $fullsub) > 0) {
                    undo_changes($undostack);
                    return 0; #We had a problem.  Exit now.
               }
               my $sub             =  $fullsub;           #i.e. sub1.domain.com 
               $sub                =~ s/\.$domain$//;     #i.e. sub1 
               my $relative_subdir =  $ud_user->lookup_docroot($fullsub);
               $relative_subdir    =~ s/$homedir//; #i.e. public_html/sub1dir$relative_subdir    =~ s/$src_homedir//; #i.e. public_html/sub1dir
               my $op = cpopp->new("add-sub", $domain,$ud_user->{username},$relative_subdir,$sub);
               unshift (@{$undostack}, $op);     #Subdomain was removed, so put it on the undo stack.
          }
     }
     return 1;
}

#Remove a subdomain
sub remove_subdomain {
     my $user       =  $_[0]; #Username of the cPanel account. i.e. myuser
     my $fullsubdom =  $_[1]; #                                i.e. mysub1.mydomain.com
     $l->logg("Removing subdomain $fullsubdom from username $user\n", 0);
     my $underscoresub = $fullsubdom;
     $underscoresub =~ s/\.(?=.*?\.)/_/g;
     my $response = $apic->cpanel_api2_request('whostmgr', { 'module' => 'SubDomain', 'func' => 'delsubdomain', 'user' => "$user" }, { 'domain' => "$underscoresub" }, 'json');
     my $json = from_json($response);
     if (length($json->{'cpanelresult'}->{'data'}[0]) > 0) {
          $l->logg($json->{'cpanelresult'}->{data}[0]{result} . "\n", 0);
          $l->logg($json->{'cpanelresult'}->{data}[0]{reason} . "\n", 0);
          if ($json->{'cpanelresult'}->{'data'}[0]{reason} =~ /The subdomain.*has been removed/) {  #If this says that the subdomain has been removed, then consider it a success.
               return 0;
          }else {
               $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
               return 1;
          }
     }
     return 1;
}

# Populate the destination user's subdomain_subdomaindir hash array with the new document roots.
# Input:  Domain that we want to work with.
#         Userdata object of the from user.
#         Userdata object of the to   user.
# Output: The {subdomain_subdomaindir} hash of the $ud_touser will be populated with
#          the new subdomains as keys and corresponding document roots as the key values.
sub decide_new_subdomains {
     my $domain       = shift;
     my $clone_domain = shift;
     my $ud_fromuser  = shift;
     my $ud_touser    = shift;
     my $dest_docroot = shift;
     foreach my $fullsub (@{$ud_fromuser->{subdomains}}) { #Add the subdomains back to the domain in the new account.
          if (issubdomain($fullsub, $domain)) {
               my $subdir = decide_sub_full_docroot($domain, $clone_domain, $fullsub, $ud_fromuser, $ud_touser, $dest_docroot);
               my $newsub = $fullsub;
               $newsub =~ s/$domain/$clone_domain/;
               $ud_touser->{subdomain_subdomaindir}{$newsub} = $subdir; 
          }
     }
}

# Figure out what the full destination document root should be for a subdomain.
# Input:  Domain    # i.e.      mydomain.com
#         subdomain # i.e. sub1.mydomain.com
#         ud_src = Reference to the source userdata object.
#         ud_dst = Reference to the dest   userdata object.
# Output: Full path of the new document root
#         or "Error" if there was a problem.
sub decide_sub_full_docroot {
     my $domain                  =  shift;   # i.e.      mydomain.com
     my $clone_domain            =  shift;   # i.e.      mynewdomain.com
     my $subdomain               =  shift;   # i.e.      sub1.mydomain.com
     my $ud_fromuser             =  shift;
     my $ud_touser               =  shift;
     my $dest_full_docroot       =  shift;
     my $src_homedir             =  "/" . $ud_fromuser->{partition} . "/" . $ud_fromuser->{username} . "/";
     my $dest_homedir            =  "/" . $ud_touser->{partition}   . "/" . $ud_touser->{username}   . "/";
     my $src_sub_full_docroot    =  $ud_fromuser->lookup_docroot($subdomain);
     my $dest_sub_full_docroot   =  $src_sub_full_docroot; #Init with source path as a starting point.            #i.e. /home/srcuser/public_html/sub1
     if ($src_sub_full_docroot   =~ /public_html/) {
          $dest_sub_full_docroot =~ s/.*\///;  # Remove all but the lowest directory (i.e. sub1)
          $dest_sub_full_docroot =  $dest_full_docroot . "/" . $dest_sub_full_docroot;
     }else {
          $dest_sub_full_docroot =~ s/$src_homedir/$dest_homedir/;  # i.e. /home/dstuser/public/sub1
     }
     if ($dest_sub_full_docroot  =~ /httpdocs/) {            #If the document root is within httpdocs (i.e. from a Plesk box), change it to public_html.
          $dest_sub_full_docroot =~ s/httpdocs/public_html/;
     }
     $dest_sub_full_docroot      =~ s/$domain/$clone_domain/i; # If the path has the old domain name, update to the new domain name.
     return $dest_sub_full_docroot
}

# Check for existing document roots.  
# Input:  Domain to check document roots for.
#         Document root of the domain.  $ud_user doesn't necessarily have the document root info yet so we have to have it separately.
#         $ud_user (make sure decide_new_subdomains has been run against it if this is a destination object).
# Output: 1=OK, 0=Don't continue.
sub check_for_docroots {
     my $domain  = $_[0];
     my $docroot = $_[1];
     my $ud_user = $_[2];
     if (-d $docroot) { #If this docroot already exists, give the user a chance to quit.
          if (show_prompt("The document root we are transferring to ($docroot) already exists. Continue? ") != 1) {
               return 0;
          }
     }
     foreach my $fullsub (keys %{$ud_user->{subdomain_subdomaindir}}) { #Go through the subdomains checking to see if any document roots exist.
          if (issubdomain($fullsub, $domain)) {                         # If so, give user a chance to quit.
               my $subdir = $ud_user->{subdomain_subdomaindir}{$fullsub};
               if (-d $subdir) {
                    if (show_prompt("A subdomain document root we are transferring to ($subdir) already exists. Continue? ") != 1) {
                         return 0;
                    }
               }
          }
     }
     return 1;
}

# Copy the document roots of a domain and its subdomains.
# Input:  Domain to copy the document roots for.
#         Domain we are cloning to (if not cloning, then this should be the same as the above domain).
#         Document root of the domain.  $ud_user doesn't necessarily have the document root info yet so we have to have it separately.
#         $ud_user (make sure decide_new_subdomains has been run against it if this is a destination object).
#         Reference to an array to populate with the document roots that were copied.
# Output: No returned value.
sub copy_docroots {
     my $domain       = shift;
     my $clone_domain = shift;
     my $docroot      = shift;
     my $ud_fromuser  = shift;
     my $ud_touser    = shift;
     my $docroots     = shift;
     $l->logg(GREEN . "Copying document root for $domain: $docroot\n" . RESET, 1); 
     $ud_fromuser->copy_document_root($domain, $docroot);
     push (@{$docroots}, $docroot);

     foreach my $fullsub (keys %{$ud_fromuser->{subdomain_subdomaindir}}) {
          if (issubdomain($fullsub, $domain)) {
               my $newsub = $fullsub; #Found one of our subdomains, but now we want to find the directory
               $newsub =~ s/$domain/$clone_domain/; # of the cloned subdomain if we're cloning.
               my $subdir = $ud_touser->{subdomain_subdomaindir}{$newsub};
               $l->logg(GREEN . "Copying document root for $fullsub: $subdir\n" . RESET, 1);
               $ud_fromuser->copy_document_root($fullsub, $subdir);
               push (@{$docroots}, $subdir);
          }
     }
}

# Create subdomains
# Input:  Domain to create subdomains for
#         Userdata object that has the ->{subdomain_subdomaindir} hash that we want to create subs for.
#         Reference to an undstack. (optional.  i.e. cpbackup2addon doesn't need it).
# Output: 1=OK, 0=Error.
sub create_subdomains {
     my $domain    = $_[0];
     my $ud_user   = $_[1];
     my $undostack = $_[2];
     my $homedir   = "/" . $ud_user->{partition}   . "/" . $ud_user->{username}   . "/";
     foreach my $fullsub (keys %{$ud_user->{subdomain_subdomaindir}}) { #Add the subdomains back to the domain in the new account.
          if (issubdomain($fullsub, $domain)) {
               my $sub             =  $fullsub;          #i.e. sub1.domain.com
               $sub                =~ s/\.$domain$//;    #i.e. sub1
               my $relative_subdir =  $ud_user->{subdomain_subdomaindir}{$fullsub};
               $relative_subdir    =~ s/$homedir//; #i.e. public_html/sub1dir
               $l->logg(GREEN . "Adding subdomain $fullsub with directory " . $ud_user->{subdomain_subdomaindir}{$fullsub} . ".\n" . RESET, 1);
               miniperms($ud_user, $ud_user->{subdomain_subdomaindir}{$fullsub}); #Make sure the user owns the path so create_subdomain works.
               if ($undostack) { #If an undostack was passed then use it.
                    if (create_subdomain($domain, $ud_user->{username}, $relative_subdir, $sub) > 0) {
                         undo_changes($undostack);
                         return 0; #We had a problem.  Exit now.
                    }
                    my $op = cpopp->new("rem-sub", $ud_user->{username}, $fullsub);
                    unshift (@{$undostack}, $op);
               }else {
                    if (create_subdomain($domain, $ud_user->{username}, $relative_subdir, $sub) > 0) {
                         return 0; #We had a problem.  Exit now.
                    }
               }
          }
     }
     return 1;
}

#Create a subdomain
#Input: Domain under which to create the subdomain.
#       Username of the cPanel account we are creating the subdomain in.
#       Directory relative to the cPanel user's home directory.
#       subdomain to create (without the domain it's being created under.  i.e. mysub1, not mysub1.mydom.com).
sub create_subdomain {
     my $domain =  $_[0]; #Domain name under which to create the subdomain.              i.e. mydom.com
     my $user   =  $_[1]; #Username of the cPanel account.                               i.e. myuser
     my $dir    =  $_[2]; #Directory of sudomain, relative to the user's home directory  i.e. public_html/mysub1
     my $subdom =  $_[3]; #                                                              i.e. mysub1

     if ($subdom eq "mail" ||  $subdom eq "www"     || $subdom eq "ftp" || $subdom eq "cpanel" || #If we try to add one of the default subdomains,
         $subdom eq "whm"  || $subdom eq "webmail"  || $subdom eq "webdisk") {                    # it will fail, so skip it if it is a default subdomain.
          $l->logg(YELLOW . "Not adding subdomain $subdom.$domain because it is already a default cPanel subdomain.\n" . RESET, 1);
          return 0;
     }
     $l->logg("Creating subdomain in domain $domain in the $dir directory of username $user\n", 0);
     my $response = $apic->cpanel_api2_request('whostmgr', { 'module' => 'SubDomain', 'func' => 'addsubdomain', 'user' => "$user" }, { 'domain' => "$subdom", 'rootdomain' =>  "$domain" , 'dir' => "$dir" }, 'json');
     my $json = from_json($response);
     if (length($json->{'cpanelresult'}->{'data'}[0]) > 0) {
          $l->logg($json->{'cpanelresult'}->{data}[0]{result} . "\n", 0);
          $l->logg($json->{'cpanelresult'}->{data}[0]{reason} . "\n", 0);
          if ($json->{'cpanelresult'}->{'data'}[0]{result} != 1 ) {
               $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
               return 1;
          }else {
               return 0;
          }
     }
     return 1;
}


sub create_addon {
     my $domain  =  $_[0]; #Domain name to create as an addon.
     my $ud_user =  $_[1]; #Username of the destination account.
     my $dir     =  $_[2]; #Directory of addon domain, relative to the user's home directory.
     my $user    =  $ud_user->{username};
     my $pass    =  makepassword(); #Make a random password.
     $pass       =  uri_escape($pass);
     my $sub     =  $domain;
     $sub        =~ s/\..*//;
     my $primdom =  $ud_user->{primarydomain};
     # Let's make sure we have a non-conflicting subdomain.     
     if (length(find_domain_owner("$sub.$primdom"))>0) {
          $sub = substr($sub, 0, 7);
          $sub =~ s/[\W]//g;
          if ($sub !~ /^[a-z]/ ) { $sub =~ s/^./a/; }
          
          my $fullsub = "$sub.$primdom";
          my $i = length($sub) - 1;
          while (length(find_domain_owner("$sub.$primdom"))>0) {
               if ($i > 0){
                    my $x = randoms(1);
                    substr($sub, $i, 1, $x);
                    $fullsub = "$sub.$primdom";
                    $i--;
                    next;
               }else {
                    $i = length($sub) - 1;
                    next;
               }
          }
     }
     # Now that we know we have a non-conflicting
     $l->logg("Creating addon domain $domain in the $dir directory of username $user\n", 0);
     my $response = $apic->cpanel_api2_request('whostmgr', { 'module' => 'AddonDomain', 'func' => 'addaddondomain', 'user' => "$user" }, { 'newdomain' => "$domain", 'pass' => "$pass", 'subdomain' => "$sub", 'dir' => "$dir" }, 'json');
     my $json = from_json($response);
     if (length($json->{'cpanelresult'}->{'data'}[0]) > 0) {
          $l->logg($json->{'cpanelresult'}->{data}[0]{result} . "\n", 0);
          $l->logg($json->{'cpanelresult'}->{data}[0]{reason} . "\n", 0);
          if ($json->{'cpanelresult'}->{'data'}[0]{result} != 1) {
               $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
               return 1;
          }else {
               return 0;
          }
     }
     return 1;
}

#Remove an addon domain
#Input: Domain
#       Userdata object.
#Return: 0=ok, 1=Error.
sub remove_addon {
     my $domain = $_[0];
     my $ud     = $_[1];
     my $domainkey;
     $l->logg("Removing addon domain $domain from username " . $ud->{'username'} . "\n", 0);
     my $counter = 0;
     foreach my $line (@{$ud->{addons}}) { #Search through the addon domains.
          $line =~ s/:.*//;
          if ($line eq $domain) {           #Is this the addon we're looking for?
               $domainkey = $ud->{domainkeys}[$counter]; #Yes - Set the corresponding domain key.
               last;
          }
          $counter++;
     }
     my $response = $apic->cpanel_api2_request('whostmgr', { 'module' => 'AddonDomain', 'func' => 'deladdondomain', 'user' => $ud->{'username'} }, { 'domain' => "$domain", 'subdomain' => "$domainkey" }, 'json');
     my $json = from_json($response);
     if (length($json->{'cpanelresult'}->{'data'}[0]) > 0) {
          $l->logg($json->{'cpanelresult'}->{data}[0]{result} . "\n", 0);
          $l->logg($json->{'cpanelresult'}->{data}[0]{reason} . "\n", 0);
          if ($json->{'cpanelresult'}->{'data'}[0]{result} != 1) {
               $l->logg(RED . "There appears to be a problem.  Please see /usr/local/cpanel/logs/error_log for more detail.\n" . RESET, 1); #Otherwise, ask user to check the log.
               return 1;
          }else {
               return 0;
          }
     }
     return 1;
}

# Make a cPanel API call locally.
# Input: cpanel API request (i.e. "accountsummary?user=$cpuser")
# Output: Array showing the XML output of the API call.
sub gocpanel {
     my ($server, $url) = @_;
     my $apistring = $_[0];
     my @result;
     my $auth;
     my $command;
     my $hash;
     my $ua;
     my $response;
     if ( $currversion >= 11.64 ){
             $auth = "whm root:". $token;
     } else {
                if ( ! -s '/root/.accesshash' ) {
                        system('export REMOTE_USER="root"; /usr/local/cpanel/bin/realmkaccesshash');
                }
                local( $/ ) ;
                open( my $fh, "/root/.accesshash" ) or die "Failed to open /root/.accesshash\n";
                $hash = <$fh>;
                $hash =~ s/\n//g;
                $auth = "WHM root:" . $hash;
     }
     my $browser = LWP::UserAgent->new;
     $browser->ssl_opts( 'verify_hostname' => 0);
     $browser->ssl_opts( 'SSL_verify_mode' => 'SSL_VERIFY_NONE');
     my $request = HTTP::Request->new( GET => "https://127.0.0.1:2087/$url" );
     $request->header( Authorization => $auth );
     my $response = $browser->request( $request );
     #$l->logg($response->content . "\n", 1);
     if ($response->is_error()) {
          $l->logg(RED . "Error: " . $response->status_line() . RESET . "\n", 1);
          if ($response->status_line() =~ /403 Forbidden/) {
               $l->logg("This error is often caused by the WHM cPHulk Brute Force Protection. Log into WHM to flush the cPHulk database or to temporarily disable cPHulk Brute Force Protection.\n", 1);
          }
          if ($response->status_line() =~ /500/) {
               $l->logg("This error is often caused by an improperly licensed cPanel installation.  Please try logging into WHM to confirm this.\n", 1);
          }
         push (@result, "Error");
         return @result;
     }
     @result = split(/\n/, $response->content);
     return @result;
}

# Save the language settings of the from and to accounts.
#Input:  %from_language  global variable
#        %to_language    global variable
#        $usertest       global variable
#        $ud_domainowner global variable
#        $domainowner    global variable
#Output: nothing.
sub save_language_settings {
     if ($usertest) {
          $l->logg("Temporarily setting language to English for $usertest->{username}\n", 1);
          set_english_language($usertest, \%to_language);
     }
     if (length($domainowner)>0) {
          $l->logg("Temporarily setting language to English for $ud_domainowner->{username}\n", 1);
          set_english_language($ud_domainowner, \%from_language);
     }
}

# Restore language settings of the from and to accounts.
#Input:  %from_language  global variable
#        %to_language    global variable
#        $usertest       global variable
#        $ud_domainowner global variable
#        $domainowner    global variable
sub restore_language_settings {
     if ($usertest && scalar(keys %to_language) == 2) {
          $l->logg("Restoring language settings for $usertest->{username}\n", 1);
          restore_language($usertest, \%to_language);
     }
     if (length($domainowner)>0 && scalar(keys %from_language) == 2) {
          $l->logg("Restoring language settings for $ud_domainowner->{username}\n", 1);
          restore_language($ud_domainowner, \%from_language);
     }
}

# Set the language of a cPanel account to English.
# Input:  User object
#         Reference to a hash array
# Output: The array populated with the language settings before the change.  i.e.:
#             LANG=portuguese
#             LOCALE=pt
#         Returns 0=OK, 1=Error
sub set_english_language {
     my $ud_user           = $_[0];  #User object
     my $lang_info         = $_[1];  #Array reference
     my $userfile          = "/var/cpanel/users/$ud_user->{username}";
     ${$lang_info}{LANG}   = "";     # Clear the hash keys.
     ${$lang_info}{LOCALE} = "";
     tie my @lines, 'Tie::File', $userfile;
     if (scalar(@lines) == 0) { #If the tied array is empty, then there was a problem reading the file.
          $l->logg(RED . "Error reading $userfile\n" . RESET, 1);
          return 1;
     }
     foreach my $line (@lines) {
          if ($line =~ /^LANG=/) {
               ${$lang_info}{LANG} = $line;
               $line = "LANG=english";
          }
          if ($line =~ /^LOCALE=/) {
               ${$lang_info}{LOCALE} = $line;
               $line = "LOCALE=en";
          }
     }
     untie @lines;
     if (${$lang_info}{LOCALE} eq "" || ${$lang_info}{LANG} eq "") {
          $l->logg(YELLOW . "Not all of the language settings were found in $userfile\n" . RESET, 1);
     }
     return 0;
}

# Restore the language to the settings in @{lang_info}
# Input:  User object
#         Reference to a hash array.
# Output: Returns 0=OK, 1=Error.
sub restore_language {
     my $ud_user           = $_[0];  #User object
     my $lang_info         = $_[1];  #Array reference
     my $userfile          = "/var/cpanel/users/$ud_user->{username}";
     tie my @lines, 'Tie::File', $userfile;
     if (scalar(@lines) == 0) { #If the tied array is empty, then there was a problem reading the file.
          $l->logg("Error reading $userfile\n", 0);
          return 1;
     }
     foreach my $line (@lines) {   
          if ($line =~ /^LANG=/) {
               $line = ${$lang_info}{LANG};
          }  
          if ($line =~ /^LOCALE=/) {
               $line = ${$lang_info}{LOCALE};
          }
     }
     untie @lines;
     return 0;
}

#Pick a username that doesn't already exist.
#Input:  A domain name to base the username from.
#Output: New username that is not yet on the server.
sub pick_username {
     my $domain = $_[0];
     my $user;
     my $counter;
     $domain =~ s/\..*//;                             #Chop off the first dot from the domain and all to the right of the first dot.
     $domain =~ s/-//g;                               #Remove any dashes because we don't want dashes in our usernames.
     $user = $domain;
     if (length($user) < 1) {
          return "Error";
     }
     if ($user =~ /^[0-9]/ || $user =~ /^test/ || $user =~ /^cpanel/          #If the domain name started with something not allowed,
                           || $user =~ /^cpmydns/ || $user =~ /^admin/) {                          # insert a character to make it a valud username.
          my $alphachars = "abcdefghijklmnopqrstuvwxyz";
          $user = substr($alphachars,rand(length($alphachars)),1) . $user;
     }
     if (length($user) > 7) {                       #Truncate to a maximum of 7 characters.
          $user = substr($user, 0, 7);
     }
     if (! lookup_long_username($user) && validate_user($user) && !does_user_exist($user)) {                      #If the username chopped down to 7 characters max is available, return that.
          return $user;
     }
     for ($counter = 1; $counter < 10; $counter++) {  #If not, then try adding numbers 1-10 after it.
          if (! lookup_long_username("$user$counter") && validate_user("$user$counter") && !does_user_exist("$user$counter")) {
               return "$user$counter";
          }
     }
     if (length($domain) > 6) {                       #Truncate to a maximum of 6 characters.
          $user = substr($domain, 0, 6);
     }
     if (! lookup_long_username($user) && validate_user($user) && !does_user_exist($user)) {                      #If the username chopped down to 6 characters max is available, return that.
          return $user;
     }
     for ($counter = 1; $counter < 100; $counter++) { #If not, then try adding numbers 1-10 after it.
          if (! lookup_long_username("$user$counter") && validate_user("$user$counter")  && !does_user_exist("$user$counter")) {
               return "$user$counter";
          }
     }
     if (length($domain) > 5) {                       #Truncate to a maximum of 6 characters.
          $user = substr($domain, 0, 5);
     }
     if (! lookup_long_username($user) && validate_user($user) && !does_user_exist($user)) {                      #If the username chopped down to 6 characters max is available, return that.
          return $user;
     }
     for ($counter = 1; $counter < 1000; $counter++) {#If not, then try adding numbers 1-10 after it.
          if (! lookup_long_username("$user$counter") && validate_user("$user$counter") && !does_user_exist("$user$counter")) {
               return "$user$counter";
          }
     }
     return "Error";                                  #If we haven't found an available username by now, give up.
}

# Determine whether or not the given cPanel user is valid.
#  (It does not check to see if the user already exists)
# Input: cPanel user
# Output (return value): 1=Valid, 0=Invalid
sub validate_user {
	my $user = shift;
	# Check length (2-16 characters)
        if (length($user) < 2 || length($user) > 16) {
		#$l->logg("[!] Please make the new username 2-16 characters long.\n", 1);
		return undef;
        }
	# Cannot start with a number.
	if ($user =~ /^[0-9]/) {
		#$l->logg("[!] The new username cannot start with a number.\n", 1);
		return undef;
	}
	# No special characters allowed.
	if ($user =~ /[^a-z0-9]/) {
		#$l->logg("[!] The new username cannot contain special characters.\n", 1);
		return undef;
	}
	# Cannat start with "test"
	if ($user =~ /^test/) {
		#$l->logg("[!] The new username cannot start with \"test\".\n", 1);
		return undef;
	}
	# Cannat start with "cpmydns"
	if ($user =~ /^cpmydns/) {
		#$l->logg("[!] The new username cannot start with \"cpmydns\".\n", 1);
		return undef;
	}
	# Cannat start with "cpanel"
	if ($user =~ /^cpanel/) {
		#$l->logg("[!] The new username cannot start with \"cpanel\".\n", 1);
		return undef;
	}
	# Cannat end with "assword"
	if ($user =~ /assword$/) {
		#$l->logg("[!] The new username cannot end with \"assword\".\n", 1);
		return undef;
	}
	if ($user =~ /(^all$|^rvadmin|^cpbackup$|^cphulkd$|^cpses$|^dirs$|^dovecot$|^eximstats$|^files$|^horde$|
                       ^logaholic$|^mailman$|^modsec$|^munin$|^mydns$|^postgres$|^proftpd$|^root$|
                       ^roundcube$|^spamassassin$|^system$|^tmp$|^tomcat$|^toor$|^virtfs$|^shadow$)/) {
		#$l->logg("[!] The username $user is reserved\n", 1);
		return undef;	
	}
	return 1;
}


#Find out if an exact username exists on the server.
#Input: username
#Output: 0 = User does not exist.
#        Any other value = User does exist.  If the user exists, then return value is the object hash of the Userdata object.
sub does_user_exist {
     my $user = $_[0];
     my $ud = Userdata->new("localhost", $user, undef, $token);
     if (length($ud->{'primarydomain'}) > 0) {
          return $ud;
     }else {
          return 0;
     }
}

# Look up the long username.
# This can be used to either check for a username confilict of find out the
# long username from the short username.
# Input:  Username (normally short version but can be long version)
# Output: Long username if user exists.
#         undef = No conflicting user exists.
sub lookup_long_username {
     my $user = shift;
     if (!keys %userhash) {
          populate_userhash();
     }
     my $chopped; #Username chopped down to 8 characters max.
     if (length($user)>8) {
          $chopped = substr($user,0,8);
     }else{
          $chopped = $user;
     }
     return $userhash{$chopped};
}

# Populate a hash of usernames.  Only the first 8 characters are used
#  because that is what determines username conflicts when database prefixing is on.
# Input:  Nothing (except /var/cpanel/users)
# Output: Global variable %userhash is populated.
sub populate_userhash {
     my $dh;
     if (!opendir($dh, "/var/cpanel/users")) {
          print "Error listing users while checking for a conflicting user.\n";
          return;
     }
     while ($user = readdir($dh)) {
          my $chopped = $user;
          if (length($chopped)>8) {
               $chopped = substr($user,0,8);
          }
          $userhash{$chopped} = $user;
     }
     closedir $dh;
}

# Answers whether or not a domain name is a primary domain name.
# Input: domain name
# Output: 0=no, 1=yes, 2=Domain is not on server.
sub is_primary_domain {
     my $domain = $_[0];
     my $user   = find_domain_owner($domain);
     if (length($user) == 0) {
          return 2;                                   #Username is empty, therefore the doman must not be on the server.
     }
     my $ud     = Userdata->new("localhost", $user, undef, $token);
     if ($domain eq $ud->{primarydomain}) {
          return 1;                                   #The domain in question is a primary domain.
     }else {
          return 0;                                   #The domain in question is not a primary domain.
     }
}

#Input:  Domain
#Output: Username of account that owns the domain.
sub find_domain_owner {
     my $domain  = $_[0];
     my $user;
     open (my $INFILE, "/etc/userdomains"); #Read /etc/userdomains which contains all domains and their associated usernames.
     my @userdomains = <$INFILE>;
     close $INFILE;
     foreach my $line (@userdomains) {      #Find the matching domain
          chomp($line);
          my $trydom = $line;
          $trydom =~ s/:.*//;
          if ($domain eq $trydom) {
               $user = $line;               #When we find the matching domain, set $user = the associated username.
               $user =~ s/.*: //;
               last;
          }
     }
     return $user;
}

# See if a path ends with public_html.
# Input:  A path such as /home/user/public_html/
# Output: 1 = Yes
#         0 = No
sub ends_with_public_html {
     my $path = $_[0];        # i.e. /home/user/public_html/
     $path =~ s/\/$//;     # i.e. /home/user/public_html     - remove trailing slash if there is one.
     $path =~ s/.*\///;    # i.e. public_html                - remove everything before the last part of the path.
     if ($path eq "public_html") {
          return 1;        # Yes, it ends       with public_html.      
     }
     return 0;             # No, it doesn't end with public_html.
}

# Check the Tweak Settings file to see if allowremotedomains is off.  If so, check DNS for the domain being moved.
# Input:  Domain to check
#         /var/cpanel/cpanel.config
# Output: 0=OK
#         1=Error
#         2=cPanel will fail to create the domain due to the allowremotedomains setting.
sub check_tweaksettings {
     my $domain = $_[0];
     my @lines;
     my $found  = 0;        #Flag that says whether or not we found a line in /var/cpanel/cpanel.config that says, "allowremotedomains=0".
     eval {
          open (my $INFILE, "/var/cpanel/cpanel.config"); #Slurp the primary domain file so we can set multiple class members at the same time.
          @lines = <$INFILE>;
          close $INFILE;
     } or do {
          return 1;
     };
     foreach my $line (@lines) {      #Search the config file for allowremotedomains=0.
          if ($line =~ /allowremotedomains.*=.*0/) {
              my $lookup_result = check_for_domain($domain);
              if ($lookup_result == 0) {
                   return 0;               #Remote domains aren't allowed, but this domain doesn't resolve, so all is well.
              }elsif ($lookup_result == 1) {
                   return 2;               #Remote domains aren't allowed, and the domain resolves.  This is the problem we're looking for. Warn by returning 2.
              }else {
                   return 1;               #Remote domains aren't allowed, but check_for_domain must have failed.  Return an error.
              }
          }
     }
     return 0;   #If we've made it this far, then "allowremotedomains=0" wasn't found.  Assume that it's 1 which means all is well.
}

# Check to see if a domain resolves.
# Input:  domain
# Output: 0=No, it doesn't resolve.
#         1=Yes, it does resolve.
#         2=Error.
sub check_for_domain {
     my $domain = $_[0];
     my $hostoutput = `host -t a $domain`; #Look up the NS records.
     if ($hostoutput =~ /NXDOMAIN/) {
          return 0;
     }elsif ($hostoutput =~ /has address/) {
          return 1;
     }else {
          $l->logg(RED . "Error looking up $domain.\n" . RESET, 1);
          print $hostoutput;
          return 2;                  #If we've made it this far, then there's a problem.
     }
}


# -----------------------------------------------------------------------------
# -------------------------- General subroutines ------------------------------
# -----------------------------------------------------------------------------

# Make a directory including its path as needed.
# Input:  Directory to make
# Output: 1=OK, 0=Error
sub mkdirectory {
     my $dir  = shift;
     my @output = `mkdir -p $dir 2>&1`;
     if (($? >> 8) > 0) {        #Check the return code, and exit if the external call failed.
          foreach my $out (@output) {
               print $out;
          }
          return 0;
     }
     return 1;
}

# Generate a random string
# Input:  Length of desired string
# Output: A random string of that length.
sub randoms {
	my $limit = shift;
	my $possible = 'abcdefghijkmnpqrstuvwxyz';
	my $string;
	while (length($string) < $limit) {
		$string .= substr( $possible, ( int( rand( length($possible) ) ) ), 1 );
	}
	return $string;
}

#Determine whether or not this is a shared server.
# Input: nothing
# Output: 0=no  1=yes it is a shared server.
sub is_shared_server {
     my $hostname = hostname;
     chomp($hostname);
     if ($hostname =~ /\.(hostgator\.(com(\.(tr|br))?|in)|(websitewelcome|webhostsunucusu|ehost(s)?|ideahost|hostclear)\.com|websitedns\.in|prodns\.com\.br)$/) {
          return 1;
     }
     return 0;
}

# Read the domains and put them into a hash.
sub read_excluded_domains {
     my $excluded_domains_file = $_[0];
     my $excluded_domains = {};
     eval {
          open (my $INFILE, $excluded_domains_file);
          while (my $line = <$INFILE>) {
               chomp($line);
               $line =~ s/\s+//g;
               if ($line !~ /^([a-z0-9\-]+(\.|\-*\.))+[a-z]{2,6}$/) {
                    print "The line $line from the exclude-domains file $excluded_domains_file does not appear to be a valid domain.\n";
                    return 0;
               }
               $excluded_domains->{$line} = 1;
          }
          close $INFILE;
     } or do {
          print "Error reading the exclude-domains file $excluded_domains_file.\n";
          return 0;
     };
     return $excluded_domains;
}

# Instantiate a User object from a backup file.
# Input:  Filename
# Output: Reference to a newly created user object.
sub userobj_fromfile {
     my $filename = $_[0];
     my $ud_user;
     if ($filename =~ /backup.*\.xml/) {
          $ud_user = plbackup->new($filename);
     }else {
          $ud_user = cpbdata->new($filename, "1");
     }
     return $ud_user;
}


# Log the command that was used to start this transfer.
sub show_command {
     $l->logg("Command executed: mvdomain");
     if (length($global_workdirname) > 0) {
           $l->logg(" --workdir=$global_workdirname");
     }
     if (length($fromfile) > 0) {
          $l->logg(" --fromfile=$fromfile");
     }
     if ($nolog > 0) {
          $l->logg(" --nolog");
     }
     if ($noprompt > 0) {
          $l->logg(" --noprompt");
     }
     if ($perms > 0) {
          $l->logg(" --perms");
     }
     if ($alldomains > 0) {
          $l->logg(" --alldomains");
     }
     foreach my $line (@ARGV) {
          $l->logg(" " . $line);
     }
     $l->logg("\n");
}

sub trash_confirm {
     my $user = $_[0];
     $l->logg(YELLOW . "\n!!!!!!!!!!  News Bulletin  !!!!!!!!!!\n", 1);
     $l->logg(BLUE . "   /\n", 1);
     $l->logg("  /\n", 1);
     $l->logg(" /   " . GREEN . "<---- Bye Bye $user account\n", 1);
     $l->logg(BLUE . "/_______          ____\n", 1);
     $l->logg(" |||||||         / o o\\\n", 1);
     $l->logg(" |" . WHITE . "Trash" . BLUE . "|        |   <  |\n", 1);
     $l->logg(" |||||||        |   O  |\n", 1);
     $l->logg(" |||||||         \\____/\n" . RESET, 1);
     $l->logg("\nSome data will be permanently discarded in the merge.\nIt is very important to make a backup before continuing.\nPlease confirm that you have a backup and wish to proceed.\n", 1);
     return 0;
}

# Called when an SSL is about to be deleted as part of the mvdomain work.
# Input: Userdata object
#        Domain being moved.
#        Flag to indicate whether to warn about all certs, or just the one that matches the domain name.
#          1=match doman,  0=any certs
#Output: Warnings printed as needed.  No return value.
sub ssl_confirm {
     my $user         = shift;
     my $domain       = shift;
     my $match_domain = shift;
     my $index = 0;
     foreach my $ssl (@{$user->{sslcerts}}) {
          if ($ssl =~ /$domain/ || $match_domain == 0) {
               print YELLOW . "************ Warning *****************************\n";
               print "This SSL certificate will be deleted: $ssl\n";
               print          "**************************************************\n" . RESET;
          }
          $index = $index + 1;
     }
}

# Save SSL certificate(s)
# Input: Userdata object
#        Domain being moved.
#        Flag to indicate whether to warn about all certs, or just the one that matches the domain name.
#          1=match doman,  0=any certs
#Output: Warnings printed as needed.  No return value.
sub ssl_save {
     my $user         = shift;
     my $domain       = shift;
     my $match_domain = shift;
     my $index = 0;
     foreach my $ssl (@{$user->{sslcerts}}) {
          if ($ssl =~ /$domain/ || $match_domain == 0) {
               ssl_save_single($user, $domain, @{$user->{sslcertsfile}}[$index]);
          }
          $index = $index + 1;
     }
}

# Input:  Userdata object
#         domain
#         The filename (not full path) that lists the SSL files for this certificate.
# Output: The SSL files are copied into a file in /home/hgtransfer/ssltools-backups/
sub ssl_save_single {
     my $user         = shift;
     my $domain       = shift;
     my $sslcertsfile = shift;
     my @file;
     my $crtfile;
     my $cafile;
     my $keyfile;
     my $timestamp = POSIX::strftime("%m-%d-%Y-%H-%M-%S", localtime);
     my $dir       = "/home/hgtransfer/ssltools-backups/";
     my $file      = $dir . "ssldata-$domain-$timestamp";
     $l->logg("Saving SSL certificate to $file\n", 1);
     eval {
          open (my $INFILE, "/var/cpanel/userdata/$user->{username}/$sslcertsfile");
          @file = <$INFILE>;
          close $INFILE;
     } or do {
          $l->logg( RED . "Error opening /var/cpanel/userdata/$user->{username}/$sslcertsfile\n" . RESET, 1);
          return 0;
     };
     foreach my $line (@file) {
          chomp($line);
          if ($line =~ /^sslcertificatefile: /) {
              $crtfile = $line;
              $crtfile =~ s/^sslcertificatefile: //;
          }elsif ($line =~ /^sslcacertificatefile: /) {
              $cafile = $line;
              $cafile =~ s/^sslcacertificatefile: //;
          }elsif ($line =~ /^sslcertificatekeyfile: /) {
              $keyfile = $line;
              $keyfile =~ s/^sslcertificatekeyfile: //;
          }
     }

     # $crtfile, $cafile and $keyfile have path/filenames, so now we can copy them.
     if (!mkdirectory ($dir)) {
         $l->logg(RED . "Failed to create '$file' to backup SSL for $domain.\n" . RESET,1);
         return 0;
     }
     eval {
          open my $backupfile_fh, ">", $file;
          print $backupfile_fh "SSL DATA FOR '$domain'\n";
          print $backupfile_fh "Backed up by mvdomain\n";
          print $backupfile_fh "Current IP: $user->{ip} - cPanel User: $user->{username}\n\n";
          my $crt = readfile($crtfile);
          if ($crt) {
               print $backupfile_fh "CRT:\n";
               print $backupfile_fh $crt;
          }
          my $key = readfile($keyfile);
          if ($key) {
               print $backupfile_fh "\nRSA KEY:\n";
               print $backupfile_fh $key;
          }
          my $ca = readfile($cafile);
          if ($ca) {
               print $backupfile_fh "\nCA Bundle:\n";
               print $backupfile_fh $ca;
          }
          close $backupfile_fh;
     } or do {
          $l->logg(RED . "Error copying the certificate files\n" . RESET);
     }
}

# Read the contents of a file.
# Input:  Filename including full path.
# Output: If successful, the content of the file is returned.
#         If failed, undef is returned.
sub readfile {
     my $filename = shift;
     my @array_filecontent;
     my $filecontent;
     eval {
          open (my $INFILE, $filename);
          @array_filecontent = <$INFILE>;                     #Sluuuuurp
          close $INFILE;
     } or do {
          $l->logg(RED . "Error reading the file $filename.\n" . RESET);
          return undef;
     };
     $filecontent = join('', @array_filecontent);
     return $filecontent;
}

# Make sure the ownership is set for the directories under the home directory in the given path.
# The purpose of this sub is to ensure that cPanel has the needed file permission when create_subdomain is called.
# Input:  A Userdata object ref.
#         The file path to fix.    i.e. /home/user1/public_html/myaddon.com/sub1
# Output: 0=OK, 1=Error.
sub miniperms {
     my $ud_user = $_[0];
     my $path    = $_[1];
     my @pwinfo  = getpwnam($ud_user->{username});
     my $uid     = $pwinfo[2];
     my $gid     = $pwinfo[3];
     my $homedir = "/" . $ud_user->{partition} . "/" . $ud_user->{username};
     #Sanity checks
     if ($path !~ /$homedir/) {
          $l->logg (RED . "While making sure the ownership in $path is right, a problem was found.  The path $path is not within the home directory $homedir\n" . RESET, 1);
          return 1;
     }
     if (count_slashes($path) < 2) {
          $l->logg (RED . "While making sure the ownership in $path is right, a problem was found.  The home directory $homedir is too close to the root path.\n" . RESET, 1);
          return 1;
     }
     while (count_slashes($path) > 3) {
          chown ($uid, $gid, $path);
          $path = chopbottomdir($path);
     }
     return 0;
}

# Chop off the bottom part of a path.  (i.e. chop of the file or directory to the right of the last "/".
# Input: Full directory path (i.e. /var/www/vhosts/mydomain.tld/httpdocs)
# Output: Rightmost directory chopped (i.e. /var/www/vhosts/mydomain.tld)
sub chopbottomdir {
     my $fullpath = $_[0];
     #Find last "/" in the fullpath and put its index into $lastloc.
     my $loc = 0;
     my $lastloc = 0; #Init the variable.
     while ($loc >= 0) {
          $lastloc = $loc;
          $loc = index($fullpath, "/", $loc+1);
     }
     my $dirpath = substr $fullpath,0,$lastloc;
     return $dirpath;
}


#Count the / characters in a passed string.
#Used for a sort such as: my @sorteddocroots = sort {count_slashes($b) <=> count_slashes($a)} (@docroots);
#Input:  A string
#Output: The number of / characters in that string.
sub count_slashes {
     my $dirpath = $_[0];
     my $count = ($dirpath =~ tr/\///);
     return $count
}


#Determine whether of not a subdomain is a subdomain of the corresponding domain.
# Input:  Subdomain
#         Domain
# Output: 0 = No
#         1 = Yes
sub issubdomain {
     my $sub     = $_[0];
     my $domain  = $_[1];
     my $testsub = $sub;
     if ($testsub =~ /\*/) { #If it contains a wildcard, consider it not a subdomain for our purposes.  mvdomain doesn't support wildcard subdomains.
          return 0;
     }
     $testsub =~ s/$domain$//;
     if ($testsub =~ /\.$/) {
          return 1;
     }else {
          return 0;
     }
}

#Prompt to conform whether or not the user wants to continue.
#Input:  Message to show as prompt (or blank to show "Continue?")
#Output: 0=User was prompted and answered no.
#        1=User was prompted and answered yes.
#        2=Indicate that the --noprompt switch was used.
sub show_prompt {
     my $promptmessage = $_[0];
     if (length($promptmessage) == 0) {
          $promptmessage = "Continue? ";
     }
     $l->logg($promptmessage, 1);
     if ($prompt == 0) {
          return 2;         #Indicate that the --noprompt switch was used.
     }else {
          my $in = <STDIN>;
          chomp($in);
          $in = lc($in);    #Make sure input is lower-case.
          if ($in eq "y" || $in eq "yes" || length($in) == 0) {
               return 1;    #User was prompted and answered yes.
          }
          return 0;         #User was prompted and answered no.
     }
}

# Count the number of cpus on the local machine.
# Input:  Nothing
# Output: # of cpus or -1 if there's an error.
sub count_cpus {
     my $filename = "/proc/cpuinfo";
     my @cpuinfo;
     eval {
          open (my $INFILE, $filename);
          @cpuinfo = <$INFILE>;                     #Sluuuuurp
          close $INFILE;
     } or do {
          print "Error reading the file $filename while counting processors.\n";
          return -1;
     };
     if (@cpuinfo) {
          @cpuinfo = grep(/processor/, @cpuinfo);
          if (scalar(@cpuinfo)<1) {
               print "Error 1 while extracting the number of cpus out of $filename.\n";
               return -1;
          }else {
               return scalar(@cpuinfo);
          }
     }else{
          print "Error 2 while extracting the number of cpus out of $filename.\n";
          return -1;
     }
}

#Run the perms for an account.
#Input: a userdata/cpbdata object
#
sub run_perms {
     my $ud_user    = $_[0];
     my $homedir    = "/" . $ud_user->{partition} . "/" . $ud_user->{username};
     my $currentdir = getcwd();
     if ($perms == 1) {
          $l->logg("Running perms...\n", 1);
          if (! -e "/root/bin/perms") {
               $l->logg(RED . "The perms script is not on this server.  Run \"yum install ESO-utils\".\n" . RESET, 1);
               return 0;
          }else {
               chdir $homedir;
               my @output = `/root/bin/perms`;
               #if (($? >> 8) > 0) {     #Check the return code, and exit if the external call failed.
               #     return ($? >> 8);
               #}
               foreach my $line (@output) { #Put the addon domains that are in @addons into the object's addon array.
                    chomp ($line);
                    $l->logg($line . "\n", 0);
               }
               chdir $currentdir;
          }
     }
     return 0;
}

#Input: Directory to dump in
#       Array of databases, passed by reference. i.e. \@mydatabases
sub dump_databases {
     my $workdir       = $_[0];
     my $databases = $_[1];
     if (! -e $workdir) {
          mkdir $workdir;
     }
     foreach my $db (@{$databases}) {
          print "Database: $db\n";
          $l->logg("mysqldump --add-drop-table $db > $workdir/$db.sql\n", 0);
          my @output = `mysqldump --add-drop-table $db > $workdir/$db.sql`;
          if (($? >> 8) > 0) {     #Check the return code, and exit if the external call failed.
               $l->logg(RED . "Warning" . RESET . " - Failed to dump database $db\n",1);
               #return ($? >> 8);
          }
          foreach my $line (@output) { #Put the addon domains that are in @addons into the object's addon array.
               chomp ($line);
               $l->logg($line . "\n", 0);
          }
     }
}

# Make a 15 character random password starting with a letter.
# Input:  Nothing
# Output: The password string in the return value.
sub makepassword {
     my $password;
     do {
          $password = Cpanel::PasswdStrength::Generate::generate_password(15);
     } while (!Cpanel::PasswdStrength::Check::check_password_strength('pw' => $password, 'app' => "passwd"));
     return $password;
}

# Check for dependencies.
# Input:  $plesk global variable
# Output: 1=Success, 0=Fail
sub dependency_check {
     #************** Check for xfermodules ****************
     if (eval {require "/root/bin/xfermodules.pm";}) {  #Is xfermodules available?
          require "/root/bin/xfermodules.pm";           #Yes, load it.
          if ($xfermodules::VERSION lt "1.20") {
               print "This server appears to have an outdated version of the transfers modules.\n";
               print "Update with: ".YELLOW."yum update ESO-utils\n".RESET;
               return 0;
          }
     }else{
          if (! -s "/root/bin/xfermodules.pm") {
               print "The transfers modules do not appear to be on this server.\n";
               print "Install with: ".YELLOW."yum install ESO-utils\n".RESET;
               return 0;
          }else{
               $|++;
               print RED . "Error while trying to load xfermodules.pm\n".RESET;
               delete $INC{'/root/bin/xfermodules.pm'};
               require "/root/bin/xfermodules.pm";
               return 0;
          }
     }
     #************** Check for File::Path (Required for Zup) *********
     my $pathtest = 1;
     eval {
          require File::Path;
          File::Path->import(qw/make_path/);
          1;
     } or $pathtest = undef;
     if (!$pathtest) {
          print "This server does not appear to have an updated version of the File::Path module.\n";
          print "Install with: ".YELLOW."/scripts/perlinstaller File::Path\n".RESET;
          return 0;
     }
     #************** Check for zup ****************
     if (eval {require "/root/bin/zup";}) {  #Is xfermodules available?
          require "/root/bin/zup";           #Yes, load it.
          if ($ZUP::Parser::VERSION lt "1.1.0") {
               print "This server appears to have an outdated version of Zup.  Please install it or have a level 2 admin install it if this is a shared/reseller server.\n";
               print "Install with: ".YELLOW."yum update ESO-utils\n".RESET;
               return 0;
          }
     }else{
          if (! -s "/root/bin/zup") {
               print "The zup module does not appear to be on the server.  Please install it or have a level 2 admin install it if this is a shared/reseller server.\n";
               print "Install with: ".YELLOW."yum install ESO-utils\n".RESET;
               return 0;
          }else{
               $|++;
               print RED . "Error while trying to load Zup\n".RESET;
               delete $INC{'/root/bin/zup'};
               require "/root/bin/zup";
               return 0;
          }
     }
     #************** Check for JSON ****************
     if (!eval {require JSON;}) { #JSON is needed by the Userdata module within xfermodules.pm.
          print "the JSON perl module does not appear to be on this server.  Please install it or have a level 2 admin install it if this is a shared/reseller server.\nInstall with: ".YELLOW."/scripts/perlinstaller JSON\n".RESET;
          return 0;
     }
     return 1;
}

# cpopp objects represent cPanel operations such as creating an addon domain, etc.
# They are added to an undo stack and used if something goes wrong mid-way.
package cpopp;
use strict;
sub new {                          #Constructor
     my $class = shift;
     my $self  = {};
     $self->{class}         = $class;
     $self->{op}            = $_[0];
     $self->{arg0}          = $_[1];
     $self->{arg1}          = $_[2];
     $self->{arg2}          = $_[3];
     $self->{arg3}          = $_[4];
     bless($self, $class);
     return $self;
}

#--------------------------------------------------------------------------------------------------
# Class to create a working directory.  This is needed instead of /tmp in order to add support for
#  a working directory that is pre-populated with database dump files.
#--------------------------------------------------------------------------------------------------
package workdir;
use strict;

sub new {                          #Constructor
     my $class = shift;
     my $self  = {};
     $self->{class}         = $class;
     $self->{dirname}       = $_[0];  #Directory name passed when the object is instantiated.  Will be part of the workdir name.
     $self->{loggobj}       = $_[1];  #Logg object
     $self->{workdir}       = undef;  #This is the final work directory name created.
     $self->{sec}           = undef;  #--- Time related members
     $self->{min}           = undef;  #-
     $self->{hour}          = undef;  #-
     $self->{mday}          = undef;  #-
     $self->{mon}           = undef;  #-
     $self->{year}          = undef;  #-
     $self->{wday}          = undef;  #-
     $self->{yday}          = undef;  #-
     $self->{isdst}         = undef;  #-
     $self->{hasdatabases}  = 0;        #0=no, 1=yes it has databases.  Using this flag we can tell whether or not to dump databases.
     $self->{selfcreated}   = 0;        #0=We did not create the work dir (so don't delete it). 1=We did create the work dir, so ok to delete it.
     $self->{error}         = "";
     bless($self, $class);

     ($self->{sec}, $self->{min}, $self->{hour}, $self->{mday}, $self->{mon}, $self->{year},
      $self->{wday}, $self->{yday}, $self->{isdst}) = localtime(time);
     
     if (length($self->{dirname}) > 0) {
          $self->{workdir}      = $self->{dirname}; #Make a dir name.
          $self->{hasdatabases} = 1;                #Assume that we the existing work dir has databases already dumped.
     }else {
          $self->{workdir} = "/home/hgtransfer/" . "work-" . sprintf("%04d%02d%02d%02d%02d%02d", $self->{year}+1900, $self->{mon}+1, $self->{mday}, $self->{hour}, $self->{min}, $self->{sec});
     }

     if (! -d $self->{workdir}) {       #If the directory doesn't exist, then create it.
          my @output = `mkdir -p $self->{workdir} 2>&1`;  # Make the directory.
          if (($? >> 8) > 0) { #Check the return code, and exit if the external call failed.
               $self->{error} = "Could not make the directory $self->{workdir}\n";
               foreach my $out (@output) {
                    $self->{error} = $self->{error} . $out;
               }
          }else {
               $self->{selfcreated} = 1;
          }
     }
     $self->logg ("Work directory is $self->{workdir}\n", 1);
     return $self;
}

#Print or log the passed argument.
# Input:  A text string
# Output: If $self->{loggobj} is defined then log to the logg object.  Otherwise, print the info.
sub logg {
     my $self = shift;
     my $info_to_log = $_[0];
     my $loglevel    = $_[1];
     if ($self->{loggobj}) {
          $self->{loggobj}->logg($info_to_log, $loglevel);
     }else {
          print $info_to_log;
     }
     return 0;
}

sub DESTROY {
     my $self = shift;
     my $workdir = $self->{workdir};
     if ($self->{selfcreated} == 1 && $self->{error} ne "mverror") { #Since we're doing an rm -rf, let's at least do a little bit of sanity checking.
          if (-d "$workdir") {                        #Let's also make sure the directory exists.
               my $temp = `rm -rf $workdir`;
          }
     }
     if ($self->{error} eq "mverror") {
          $self->logg("Not deleting work work directory $self->{workdir} because there was a problem with the transfer.\n", 1);
     }
}

1;

#*******************************************************************
#************************ replacer package *************************
#*******************************************************************
#
# Searches for files and does search/replace within the files.
# Example usage of replacer:
# my $replacer = replacer->new();
# $replacer->replace($search, $replace, $dirlist);
package replacer;

use strict;
use Tie::File;
use Data::Dumper;
use File::Find;

sub new {
     my $class = shift;
     my $self  = {};
     bless($self, $class);
     $self->{logg}                       = shift;
     if (ref $self->{logg} ne "logg") {
          print "Invalid logger object passed when creating replacer object.\n";
          return undef;
     }
     $self->{filenames}                  = {};    #Hash array of filenames to look for.
     $self->{filenames}{"wp-config.php"} = 1;
#     $self->{filenames}{"someconfigfile"} = 1;   #Uncomment and set filename to add a file to search for.
     $self->{found_files}                = [];    #Array of files found.
     $self->{search_string}              = undef; #/home/mstreete/public_html
     $self->{replace_string}             = undef; #/home2/someuser/public_html/test
     return $self;
}

# Uncomment this sub and the "__PACKAGE__->main unless caller;" at the end of the package to use as standalone script.
#sub main {
#     my $replacer = replacer->new();
#     my $search  = $ARGV[0];
#     my $replace = $ARGV[1];
#     my $dir     = ".";
#     $replacer->replace($search, $replace, $dir);
#}

# Input: search_string
#        replace_string
#        directory to search.
sub replace {
     my $self    = shift;
     my $search  = shift;
     my $replace = shift;
     my $dir     = shift;
     my $dirlist = [];
     push (@{$dirlist}, $dir);
     $self->find_files($dirlist); # Populate the $self->{found_files} array ref.
     foreach my $file (@{$self->{found_files}}) {
          $self->{logg}->logg("Replacing $search with $replace in $file\n", 1);
          $self->replace_text($search, $replace, $file);
     }
}

#Replace text in a file
#Input:  Text pattern to search for
#        Text pattern to replace the first pattern with.
#        Filename to update
#Output: Updates the file.  If there's a serious error: crash and burn!
sub replace_text {
     my $self            = shift;
     my $search_pattern  = shift;
     my $replace_pattern = shift;
     my $filenam         = shift;
     # Tie the config file to the @lines array so we can modify the file the same way we mod an array.
     tie my @lines, 'Tie::File', $filenam or die "Error updating file $filenam\n";
     # Loop through each line of the file, looking for the lines to change.
     foreach my $line (@lines) {
         if ($line =~ /\Q$search_pattern\E/) {
              $line =~ s/\Q$search_pattern\E/$replace_pattern/g;
         }
     }
     untie @lines;
}

# Find all of the known config files in a list of directories.
# Input:  Ref to an array of directories.
# Output: Ref to an array of ConfigInfo objects.
sub find_files {
     my $self         = shift;
     my $finaldirlist = shift;
     # Traverse the user home directories, looking for config files.
     #http://stackoverflow.com/questions/5502218/how-do-i-insert-new-fields-into-self-in-perl-from-a-filefind-callback
     # "wanted" is a closure referencing the address of the wanted sub because this is the only way you can pass additional
     # arguments to the wanted sub.  Since the wanted sub is in an object, we have to pass the object hash.
     $self->{found_files} = [];
     File::Find::find(sub {$self->wanted()}, @{$finaldirlist});
     return $self->{found_files}; # <-- Populated by the wanted sub.
}



sub wanted {
     my $self = shift;
     my $filename = $File::Find::name;
     $filename =~ s/.*\///;
     if ($self->is_supported_filename($filename) && -f $filename) {
         push (@{$self->{found_files}}, $File::Find::name);
     }
}


sub is_supported_filename {
     my $self     = shift;
     my $filename = shift;
     if ($self->{filenames}{$filename}) {
          return 1;
     }
     return 0;
}

#Add a file to the list of filenames to look for.
sub add_file {
}

# Uncomment this and the main function to use this package as a standalone script.
#__PACKAGE__->main unless caller; # call main function unless we were included as module

1;

