#!/usr/bin/perl
# cpanel12 - scripts                              Copyright(c) 2008 cPanel, Inc.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cpanel license. Unauthorized copying is prohibited
# $Desc: v3.0.0-274-g545221d$

use strict;
use warnings;

use DynaLoader;
use File::Glob;
use IPC::Open2;
use IPC::Open3;

$| = 1;

my $user = $ARGV[0];
if ( $user eq "root" ) {
    print "You cannot copy the root user.\n";
    exit;
}

my $tarroot = $ARGV[1] || '/home';
my $skipacctdb = pop(@ARGV) eq '--skipacctdb';
my $prefix = 'cpmove-';

if ( !$tarroot || !-d $tarroot ) {
    $tarroot = '/home';
}

if ( !getpwnam($user) ) {
    print "Invalid Account\n";
    exit;
}

my $homedir = ( getpwnam($user) )[7];

my $uid = ( getpwnam($user) )[2];
my $gid = ( getpwnam($user) )[3];

my %USERS;
open( MT, "/etc/mail/mailertable" );
while (<MT>) {
    s/\n//g;
    my ( $domain, $user ) = split( /\t/, $_ );
    $user =~ s/^procmail://g;
    if ( !defined $USERS{$user} ) {
        $USERS{$user} = $domain;
    }
}
close(MT);

my $dns = $USERS{$user};

if ( $dns eq "" ) {
    print "Unable to find domain name for $user\n";
    exit;
}

print "Found domain to be $dns\n";
my ( $leader, undef ) = split( /\./, $dns, 2 );
print "Found leader to be: $leader\n";

if ( -d "${tarroot}/${prefix}${user}" && !-l "${tarroot}/${prefix}${user}" ) {
    system( "rm", "-rf", "${tarroot}/${prefix}${user}" );
}
if ( -f "${tarroot}/${prefix}${user}.tar.gz" && !-l "${tarroot}/${prefix}${user}.tar.gz" ) {
    system( "rm", "-rf", "${tarroot}/${prefix}${user}.tar.gz" );
}

open( CPM, ">${tarroot}/${prefix}${user}.tar.gz" );
close(CPM);
chmod( 0600, "${tarroot}/${prefix}${user}.tar.gz" );

if ( !-e "${tarroot}/${prefix}${user}" ) {
    mkdir( "${tarroot}/${prefix}${user}",          0700 );
    mkdir( "${tarroot}/${prefix}${user}/cp",       0700 );
    mkdir( "${tarroot}/${prefix}${user}/logs",     0700 );
    mkdir( "${tarroot}/${prefix}${user}/mysql",    0700 );
    mkdir( "${tarroot}/${prefix}${user}/mm",       0700 );
    mkdir( "${tarroot}/${prefix}${user}/mma",      0700 );
    mkdir( "${tarroot}/${prefix}${user}/mma/pub",  0700 );
    mkdir( "${tarroot}/${prefix}${user}/mma/priv", 0700 );
    mkdir( "${tarroot}/${prefix}${user}/va",       0700 );
    mkdir( "${tarroot}/${prefix}${user}/vf",       0700 );
    mkdir( "${tarroot}/${prefix}${user}/homedir",  0755 );
    mkdir( "${tarroot}/${prefix}${user}/vad",      0700 );
    mkdir( "${tarroot}/${prefix}${user}/cron",     0755 );
    mkdir( "${tarroot}/${prefix}${user}/sslcerts", 0755 );
    mkdir( "${tarroot}/${prefix}${user}/sslkeys",  0755 );
    mkdir( "${tarroot}/${prefix}${user}/dnszones", 0755 );

}
else {
    print "${tarroot}/${prefix}${user} exists, please remove it and try again\n";
    exit;
}

# Copying SSL certs

my $ssldir;
if ( $> == 0 and -e '/usr/local/apache-ssl/conf' ) {
    $ssldir = '/usr/local/apache-ssl/conf';
    print "Copying SSL Certificates, CSRS, and Keys...";
    system( 'cp', "$ssldir/ssl.crt/server.crt",    "${tarroot}/${prefix}${user}/sslcerts" );
    system( 'cp', "$ssldir/ssl.crt/ca-bundle.crt", "${tarroot}/${prefix}${user}/sslcerts" );
    system( 'cp', "$ssldir/ssl.csr/server.csr",    "${tarroot}/${prefix}${user}/sslcerts" );
    system( 'cp', "$ssldir/ssl.key/server.key",    "${tarroot}/${prefix}${user}/sslkeys" );
    print 'Done...' . "\n";
}
elsif ( -e '/usr/local/ssl' ) {
    $ssldir = '/usr/local/ssl';
    print "Copying SSL Certificates, CSRS, and Keys...";

    # Find certs and private directory
    opendir( SSL, "${ssldir}" ) || die "Can't open ssl directory: $!";
    my @FILES = readdir(SSL);
    closedir(SSL);
    @FILES = grep ( !/^\./, @FILES );

    if ( -e "$ssldir" ) {
        foreach my $sslfile (@FILES) {
            if ( $sslfile eq "certs" ) {

                # Open certs directory
                opendir( CERTS, "${ssldir}/certs" );
                my @FILES = readdir(CERTS);
                closedir(CERTS);
                @FILES = grep ( !/^\./, @FILES );

                # Find files contain the domain name, should include www in front, crt, cabundle, csr in end
                @FILES = grep ( /.*${dns}./, @FILES );
                foreach my $certs (@FILES) {
                    print "\$certs is $certs\n";
                    system("cp -a ${ssldir}/certs/${certs} ${tarroot}/${prefix}${user}/sslcerts");
                }
            }

            if ( $sslfile eq "private" ) {

                # Open private directory
                opendir( PRIVATE, "${ssldir}/private" );
                my @FILES = readdir(PRIVATE);
                closedir(PRIVATE);
                @FILES = grep ( !/^\./, @FILES );

                # Find files contain the domain name, should include www in front, crt, cabundle, csr in end
                @FILES = grep ( /.*${dns}./, @FILES );
                foreach my $private (@FILES) {
                    print "\$private is $private\n";
                    system("cp -a ${ssldir}/private/${private} ${tarroot}/${prefix}${user}/sslkeys");
                }
            }
        }
    }
    print 'Done...' . "\n";
}
print "Converting mail passwords....\n";

my $now = time();
open( CPU, ">${tarroot}/${prefix}${user}/cp/${user}" );
print CPU "DNS=$dns\n";
print CPU "STARTDATE=$now\n";
print CPU "PLAN=alabanza\n";
close(CPU);

mkdir( "${tarroot}/${prefix}${user}/homedir/etc/",     0770 );
mkdir( "${tarroot}/${prefix}${user}/homedir/etc/$dns", 0770 );

open( PASSWD, ">${tarroot}/${prefix}${user}/homedir/etc/$dns/passwd" );
open( SHADOW, ">${tarroot}/${prefix}${user}/homedir/etc/$dns/shadow" );
open( MM,     "${homedir}/.mm/${dns}.passwd" );

my @MBOXS;
while (<MM>) {
    s/\n//g;
    my ( $mailuser, $pass ) = split( /:/, $_ );
    next if ( $mailuser eq "default" );
    print "\tFound a user $mailuser\n";
    push( @MBOXS, $mailuser );
    print PASSWD "${mailuser}:x:-1:-1::/home/${user}/mail/${dns}/${mailuser}/:/usr/local/cpanel/bin/noshell\n";
    print SHADOW "${mailuser}:${pass}:::::::\n";
}
close(MM);
close(PASSWD);
close(SHADOW);

print "Done\n";
print "Setting passwd permissions...";
chmod( 0600, "${tarroot}/${prefix}${user}/homedir/etc/passwd" );
chmod( 0600, "${tarroot}/${prefix}${user}/homedir/etc/shadow" );
print "Done\n";

print "Checking autoresponders....";

my @FILES = glob("${homedir}/.*.response");
my %ARS;
foreach my $res (@FILES) {
    my $file = $res;
    my @RES = split( /\//, $res );
    $res = $RES[$#RES];
    $res =~ s/^\.//g;
    $res =~ s/\.response$//g;

    my ( $resuser, $domain ) = split( /\./, $res, 2 );
    mkdir( "${tarroot}/${prefix}${user}/homedir/.autorespond", 0770 );
    system("cp -f $file ${tarroot}/${prefix}${user}/homedir/.autorespond/${user}\@${domain}");
    system("chown ${user} ${tarroot}/${prefix}${user}/homedir/.autorespond/${user}\@${domain}");
    $ARS{"${resuser}\@${domain}"} = 1;
}
print "Done\n";

print "Building valiases file....";
open( VF, ">${tarroot}/${prefix}${user}/vf/${dns}" );
close(VF);
open( VL, ">${tarroot}/${prefix}${user}/va/${dns}" );
open( MM, "${homedir}/.mm/${dns}.mailaddr" );

my %DIR;
my $defaultr;
while (<MM>) {
    s/\n//g;
    my ( $muser, $pass ) = split( /:/, $_ );
    $DIR{$muser} = ${pass};
    if ( $muser eq "default" ) {
        $defaultr = $pass;
    }
}
close(MM);
foreach my $muser ( sort keys %DIR ) {
    my @USERS = split( /\,/, $DIR{$muser} );
    if ( $#USERS > -1 ) {
        my $line  = "${muser}\@${dns}: ";
        my $count = 0;
        foreach my $suser (@USERS) {
            if ( $suser eq $user ) { next; }
            if ( $suser !~ /\@/ ) {
                $suser = $suser . '@' . ${dns};
            }
            $count++;
            $line .= "${suser},";
        }
        if ( exists $ARS{"${muser}\@${dns}"} ) {
            $line .= "|/usr/local/cpanel/bin/autorespond ${muser}\@${dns} ${homedir}/.autorespond,";
            $count++;
        }
        $line =~ s/\,$//g;
        next if ( $count == 0 );
        print VL "$line\n";
    }
}
my @DFS = split( /\,/, $defaultr );
my @ACS;
foreach my $df (@DFS) {
    if ( $df !~ /\@/ ) {
        $df = $df . '@' . ${dns};
    }
    push( @ACS, $df );
}

my $dfs;
foreach my $df (@ACS) {
    $dfs .= $df . ',';
}

$dfs =~ s/\,$//g;
print VL "*: $dfs\n";

close(VL);

print "*:$dfs ....Done\n";

print "Copying log files....";
system("cp -a ${homedir}/${leader}-logs/access-log ${tarroot}/${prefix}${user}/logs/${dns}");
system("cp -a ${homedir}/${leader}-logs/old ${tarroot}/${prefix}${user}/homedir/old-logfiles");
print "Done\n";

print "Searching for non-public files....\n";
opendir( FF, "${homedir}" );
@FILES = readdir(FF);
closedir(FF);

@FILES = grep ( !/^\./,         @FILES );
@FILES = grep ( !/^${leader}-/, @FILES );
foreach my $file (@FILES) {
    print "\tCopying $file\n";
    next if ( -l "${homedir}/$file" );
    next if ( $file eq "${prefix}${user}" );
    system( "cp", "-a", "${homedir}/$file", "${tarroot}/${prefix}${user}/homedir" );
}
print "Done\n";

print "Converting mailboxes....";
mkdir( "${tarroot}/${prefix}${user}/homedir/mail", 0770 );
foreach my $mbox (@MBOXS) {
    print "..${mbox}..";
    mkdir( "${tarroot}/${prefix}${user}/homedir/mail/$dns",       0770 );
    mkdir( "${tarroot}/${prefix}${user}/homedir/mail/$dns/$mbox", 0770 );
    mkdir( "${tarroot}/${prefix}${user}/homedir/webmail/",        0770 );
    mkdir( "${tarroot}/${prefix}${user}/homedir/webmail/$mbox",   0770 );
    if ( -f "${homedir}/${leader}-mail/${mbox}" ) {
        system( "cp", "-a", "${homedir}/${leader}-mail/${mbox}", "${tarroot}/${prefix}${user}/homedir/mail/$dns/$mbox/inbox" );
        chmod( 0660, "${tarroot}/${prefix}${user}/homedir/mail/$mbox/inbox" );
    }

    if ( -d "${homedir}/${leader}-webmail/${mbox}" ) {
        opendir my $webmail_h, "${homedir}/${leader}-webmail/${mbox}";
        my @webmail_files = readdir $webmail_h;
        @webmail_files = grep !/^\./, @webmail_files;

        foreach my $webmail_file (@webmail_files) {
            if ( -f "${homedir}/${leader}-webmail/${mbox}/${webmail_file}" ) {
                system( 'cp', "${homedir}/${leader}-webmail/${mbox}/${webmail_file}", "${tarroot}/${prefix}${user}/homedir/mail/$dns/$mbox/" );
            }

            if ( -d "${homedir}/${leader}-webmail/${mbox}/${webmail_file}" and $webmail_file =~ /^web/ ) {
                system( 'cp', '-r', "${homedir}/${leader}-webmail/${mbox}/${webmail_file}", "${tarroot}/${prefix}${user}/homedir/webmail/$mbox" );
            }
        }
    }
}
print "Done\n";

# Copying crontab for user
my $crondir = "/var/spool/cron";
if ( -e "{$crondir}/${user}" ) {
    print "Copying crontab for user ${user}\n";
    system( "cp", "-a", "${crondir}/${user}", "${tarroot}/${prefix}${user}/cron" );
}

# Adding parked domain
if ( -e "${homedir}/.mm/${dns}.parkingdomain" ) {
    print "Adding parked domain\n";
    system("cp -a ${homedir}/.mm/${dns}.parkingdomain ${tarroot}/${prefix}${user}/pds");
}

# Keeping old zone files
if ( -e "/var/named/${dns}.b" ) {
    print "Found zone file for ${dns}, copying...\n";
    system("cp -a /var/named/${dns}.b ${tarroot}/${prefix}${user}/dnszones/");
}

print "Copying webfiles...";
system( "cp", "-a", "${homedir}/${leader}-www", "${tarroot}/${prefix}${user}/homedir/public_html" );
print "Done\n";
mkdir( "${tarroot}/${prefix}${user}/homedir/public_html", 0755 );
chmod( 0755, "${tarroot}/${prefix}${user}/homedir/public_html" );

if ( -e "${homedir}/${leader}-anonftp" ) {
    print "Copying anonftp area....";
    system( "cp", "-a", "${homedir}/${leader}-anonftp", "${tarroot}/${prefix}${user}/homedir/public_ftp" );
    print "Done\n";
}
mkdir( "${tarroot}/${prefix}${user}/homedir/public_ftp", 0755 );
chmod( 0755, "${tarroot}/${prefix}${user}/homedir/public_ftp" );

print "Grabbing password.....";
open( SHADOW, "/etc/shadow" );
my $cpass;
while (<SHADOW>) {
    if (/^${user}:/) {
        ( undef, $cpass, undef ) = split( /:/, $_, 3 );
    }
}
close(SHADOW);
open( SH, ">${tarroot}/${prefix}${user}/shadow" );
print SH "$cpass";
close(SH);
print "Done\n";

print "Getting quota info...";

my $quota = `quota -v ${user}`;
my @QUOTA = split( /\n/, $quota );
my $LIMIT;
foreach my $line (@QUOTA) {
    $line =~ s/^\s*//g;
    if ( $line =~ /^\// ) {
        $line =~ /^(\S+)\s*(\d+)\s*(\d+)/;
        my $dsk = $1;
        if ( getmnt($dsk) =~ /backup/ ) {
            next;
        }
        my $USED += $2;
        if ( !$LIMIT ) { $LIMIT = $3; }
    }
}

open( QUOTA, ">${tarroot}/${prefix}${user}/quota" );
$LIMIT = ( $LIMIT / 1000 );    #should be 1024 but this is ala math
print QUOTA "$LIMIT\n";
close(QUOTA);
print "Done\n";

unless ($skipacctdb) {
    print 'Grabbing mysql grants...' . "\n";
    print "\t" . 'Stopping mysql.' . "\n";
    fork_code( { 'code' => \&stop_mysql, 'wait' => 1 } );
    print "\t" . 'Done.' . "\n\n";

    print "\t" . 'Starting mysql without grants.' . "\n";
    fork_code( { 'code' => \&start_grantless_mysql, 'wait' => 0 } );
    sleep(5);
    print "\t" . 'Done.' . "\n\n";

    print "\t" . 'Setting cpanel mysql user.' . "\n";
    set_cpanel_user();
    print "\t" . 'Done.' . "\n\n";

    print "\t" . 'Stopping mysql' . "\n";
    fork_code( { 'code' => \&stop_mysql, 'wait' => 1 } );
    print "\t" . 'Done.' . "\n\n";

    print "\t" . 'Starting mysql with grants.' . "\n";
    fork_code( { code => \&start_mysql, 'wait' => 0 } );
    sleep(5);
    print "\t" . 'Done.' . "\n\n";

    my $mysql_info = mysql_info($user);
    my $grants     = get_grants( $user, @{ $mysql_info->{'host'} } );
    my @options    = ( '-c', '-Q', '-q', '-u', 'cpanel', "--password=cpanel1" );

    if ( scalar @{$grants} ) {
        if ( open( my $grants_h, '>', "$tarroot/${prefix}$user/mysql.sql" ) ) {
            print "Getting mysql informaation.\n";
            foreach my $grant ( @{$grants} ) {
                my $grant_obj = Mysql::Grants->new($grant);
                my $db        = $grant_obj->db_name();

                print {$grants_h} $grant_obj->to_string(), "\n";
                if ( $db ne '*' ) {
                    mysqldumpdb( { 'options' => [@options], 'db' => $db, 'file' => "${tarroot}/${prefix}${user}/mysql/${db}.sql" } );
                }
            }
            close $grants_h;
            print 'Done.' . "\n";
        }
        else {
            print 'Unable to create mysql.sql' . "\n";
        }
    }

    print "\t" . 'Deleting cpanel mysql user' . "\n";
    delete_cpanel_user();
    print "\t" . 'Done.' . "\n\n";
}

sub getmnt {
    my ($dsk) = @_;

    open( MTAB, "/etc/mtab" );
    while (<MTAB>) {
        if (/^${dsk}\s(\S+)/) {
            return $1;
        }
    }
    close(MTAB);
    return "";
}

chdir("${tarroot}");
print "Creating Archive ....";

if ( my $pid = fork() ) {
    my $dotcount = 5;
    while ( waitpid( $pid, 1 ) != -1 ) {
        if ( $dotcount % 5 == 0 ) {
            print ".........\n";
        }
        sleep(1);
        $dotcount++;
    }
}
else {
    system( "tar", "czfp", "${prefix}${user}.tar.gz", "${prefix}${user}" );
    exit();
}

if ( -d "${tarroot}/${prefix}${user}" && !-l "${tarroot}/${prefix}${user}" ) {
    system( "rm", "-rf", "${tarroot}/${prefix}${user}" );
}
print "Done\n";

my $md5sum = getmd5sum("${tarroot}/${prefix}${user}.tar.gz");
print "md5sum is: $md5sum\n";
print "pkgacctfile is: ${tarroot}/${prefix}${user}.tar.gz\n";

sub getmd5sum {
    my ($file) = @_;
    my ($md5);

    if ( -e "/bin/md5sum" || -e "/usr/bin/md5sum" || -e "/usr/local/bin/md5sum" ) {
        open( MD5, "-|" ) || exec 'md5sum', $file;
    }
    else {
        open( MD5, "-|" ) || exec 'md5', "-r", $file;
    }
    while (<MD5>) {
        chomp();
        ( $md5, undef ) = split( /\s+/, $_ );
        last();
    }
    close(MD5);
    return ($md5);
}

sub mysql_version {
    my $mysqlconfig = find_mysql_config();
    my $version     = `$mysqlconfig --version`;
    chomp($version);
    return ( split( '-', $version ) )[0];
}

sub find_mysqld {
    return _find_bin( 'mysqld', [ '/usr/local/mysql/libexec', '/usr/local/mysql/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin', '/usr/libexec', '/usr/local/libexec' ] );
}

sub find_mysql {
    return _find_bin( 'mysql', [ '/usr/local/mysql/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin', '/usr/libexec', '/usr/local/libexec' ] );
}

sub find_mysql_config {
    return _find_bin( 'mysql_config', [ '/usr/local/mysql/bin', '/usr/bin', '/usr/local/bin', '/usr/local/mysql/bin' ] );
}

sub _find_bin {
    my $bin     = shift;
    my $pathref = shift;

    foreach my $path ( @{$pathref} ) {
        if ( -e $path . '/' . $bin ) {
            return $path . '/' . $bin;
        }
    }
}

sub start_grantless_mysql {
    my $mysqld = find_mysqld();
    chmod( oct(775), '/var/lib/mysql/' );

    system( $mysqld, '-u', 'mysql', '--skip-grant-tables', '--socket=/tmp/mysql.sock' );
}

sub start_mysql {
    my $initscript = find_mysql_init_script();
    system("$initscript start >/dev/null 2>&1");
}

sub stop_mysql {
    my $initscript = find_mysql_init_script();
    system("$initscript stop >/dev/null 2>&1");
    sleep(1);
    system("killall -TERM mysqld_safe safe_mysqld mysqld >/dev/null 2>&1");
    sleep(1);
    system("killall -9 mysqld_safe safe_mysqld mysqld >/dev/null 2>&1");
    sleep(1);
    system("killall -9 mysqld_safe safe_mysqld mysqld >/dev/null 2>&1");
    sleep(1);
}

sub fork_code {
    my ($args) = @_;
    if ( ref( $args->{'code'} ) ne 'CODE' ) {
        print 'Argument not a code ref' . "\n";
        exit(1);
    }

    if ( ( exists $args->{'args'} ) && ( ref $args->{'args'} ne 'ARRAY' ) ) {
        print 'args must be an array ref.' . "\n";
        exit(1);
    }

    my $code = $args->{'code'};
    my $wait = $args->{'wait'} == 1 ? 1 : 0;
    my @args = exists $args->{'args'} ? @{ $args->{'args'} } : ();

    my $pid;
    if ( $pid = fork() ) {
        my $dotcount = 5;
        if ($wait) {
            while ( waitpid( $pid, 1 ) != -1 ) {
                if ( $dotcount % 5 == 0 ) {
                    print "\t" . ".........\n";
                }
                sleep(1);
                $dotcount++;
            }
        }
    }
    else {
        open( STDIN,  '<', '/dev/null' );
        open( STDOUT, '>', '/dev/null' );
        open( STDERR, '>', '/dev/null' );

        $code->(@args);
        exit;
    }
}

sub set_cpanel_user {

    my $mysql = find_mysql();

    my $stmt = "INSERT INTO user
                (Host,User,Password,Select_priv,Delete_priv,Reload_priv,Lock_tables_priv)
                 VALUES ('localhost', 'cpanel', PASSWORD('cpanel1'), 'Y','Y','Y','Y')";
    my $user = 'mysql';
    my $pass = '';
    my $db   = 'mysql';
    qx{$mysql $db -u $user --password=$pass -e "$stmt"};
}

sub delete_cpanel_user {
    my $mysql = find_mysql();

    my $stmt1 = "DELETE FROM user WHERE user='cpanel' AND host='localhost'";
    my $stmt2 = 'FLUSH PRIVILEGES';

    my $user = 'cpanel';
    my $pass = 'cpanel1';
    my $db   = 'mysql';

    foreach my $stmt ( $stmt1, $stmt2 ) {
        qx{$mysql $db -u $user --password=$pass -e "$stmt"};
    }
}

sub get_grants {
    my ( $ala_user, @host ) = @_;

    my $mysql  = find_mysql();
    my $result = [];

    # -B option for the mysql binary causes mysql to seperate values with tabs.
    # -ss option for the mysql binary causes mysql to not send the header row.
    my $db   = 'mysql';
    my $user = 'cpanel';
    my $pass = 'cpanel1';

    foreach my $host (@host) {
        next if $host eq '%';
        my $stmt = "SHOW GRANTS FOR $ala_user\@'$host'";
        open2( my $mysql_res, my $mysql_h, $mysql . qq{ -u $user $db -B -ss --password=$pass} );

        print {$mysql_h} $stmt;
        close $mysql_h;

        while ( my $res = <$mysql_res> ) {
            chomp $res;
            push @{$result}, $res . ';';
        }
    }
    return $result;
}

sub find_mysql_init_script {
    my $base = '/etc/rc.d/init.d/';
    foreach my $mysql (qw{mysql.server mysqld mysql}) {
        if ( -e $base . $mysql ) {
            return $base . $mysql;
        }
    }
}

sub mysql_info {
    my ($user) = @_;
    my $info = mysql_hashref( "select host, db, user from db where user = '$user'", { user => 'cpanel', pass => 'cpanel1', db => 'mysql' } );
    my ( %db, %host );
    foreach my $row (@$info) {
        $db{ $row->{'db'} }     = 1;
        $host{ $row->{'host'} } = 1;
    }
    return { db => [ keys %db ], host => [ keys %host ] };
}

sub mysql_hashref {
    my ( $stmt, $info ) = @_;

    my $mysql      = find_mysql();
    my $result_ref = [];

    IPC::Open3::open3( my $mysql_h, my $mysql_res, '', $mysql, $info->{'db'}, '-u', $info->{'user'}, "--password=$info->{'pass'}" );

    print {$mysql_h} $stmt;
    close $mysql_h;

    my $data = '';
    while ( my $res = <$mysql_res> ) {
        $data .= $res;
    }

    my @mysql_data = split /\n/, $data;
    my @field_names = map lc, split( /\s+|\t+/, shift(@mysql_data) );
    my @result_set;
    foreach my $row (@mysql_data) {
        my @row = _lexer($row);

        my $results = {};
        my $count   = 0;
        foreach my $field (@field_names) {
            $results->{$field} = $row[$count];
            $count++;
        }

        push @result_set, $results;
    }

    return \@result_set;
}

sub _lexer {
    my ($line) = @_;
    $line =~ s/\s{2,}/ /;
    my $buffer;
    my @stash;
    my $in_double_quote = 0;
    my $in_single_quote = 0;
    foreach my $chr ( split //, $line ) {
        if ( $chr =~ /\"/ ) {
            $in_double_quote = !$in_double_quote;
        }
        if ( $chr =~ /\'/ ) {
            $in_single_quote = !$in_single_quote;
        }
        if ( $chr =~ /\s/ && ( !$in_double_quote && !$in_single_quote ) ) {
            push @stash, $buffer;
            $buffer = '';
        }
        else {
            $buffer .= $chr;
        }
    }
    return @stash, $buffer;
}

sub mysqldumpdb {
    my ($args) = @_;

    my @options   = @{ $args->{'options'} };
    my $db        = $args->{'db'};
    my $table     = $args->{'table'};
    my $file      = $args->{'file'};
    my $file_mode = $args->{'append'} ? '>>' : '>';

    my $mysqldump = _find_mysqldump();
    my @db        = ($db);
    if ($table) {
        push @db, $table;
    }

    my $pid = IPC::Open3::open3( my $w, my $r, my $e, $mysqldump, @options, @db );

    my $first_line = 1;
    if ( open( my $fh, $file_mode, $file ) ) {
        use bytes;
        while (<$r>) {
            if ( $first_line && ( !$_ || m/^mysqldump:/ ) ) {
                warn join( '.', @db ) . ': ' . $_;
                close $w;
                close $r;
                waitpid( $pid, 0 );
                $first_line = 0;
                $pid = IPC::Open3::open3( $w, $r, '', $mysqldump, @options, @db );
            }
            else {
                print {$fh} $_;
            }
        }
        no bytes;
    }
    close $w;
    close $r;
    waitpid( $pid, 0 );
}

sub _find_mysqldump {
    return _find_bin( 'mysqldump', [ '/usr/local/mysql/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin', '/usr/libexec', '/usr/local/libexec' ] );
}

sub _find_mysqlcheck {
    return _find_bin( 'mysqlcheck', [ '/usr/local/mysql/bin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin', '/usr/libexec', '/usr/local/libexec' ] );
}

################################################################################
package Mysql::Grants;

sub new {
    my ( $class, $grant ) = @_;
    my $self = $class->init($grant);

    foreach my $method ( keys %$self ) {
        no strict 'refs';
        no warnings 'redefine';
        *{$method} = sub {
            my ( $self, $input ) = @_;

            if ($input) {
                $self->{$method} = $input;
            }
            return $self->{$method};
        };
    }

    return bless $self, $class;
}

sub init {
    my ( $class, $grant ) = @_;
    my ( $db_privs, $db_name, $db_obj, $db_user, $db_host, $db_rest ) = $grant =~ /
               GRANT\s        #
               (.+?)          # Privileges
               \sON\s         #
               `?([^`.]+)`?   # Database name
               \.             #
               (.+?)          # Database object
               \sTO\s         #
               '(.+?)'        # Database user
               \@             #
               '(.+?)'        # Database host
               \s*            #
               (.*)           # The rest of the line
            /xms;

    return {
        db_privs => $db_privs,
        db_name  => $db_name,
        db_obj   => $db_obj,
        db_user  => $db_user,
        db_host  => $db_host,
        db_rest  => $db_rest || '',
    };
}

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

    my $db_name = $self->db_name();
    if ( $self->db_name() ne '*' ) {
        $self->db_name( '`' . $self->db_name() . '`' );
    }

    my $string = sprintf "GRANT %s ON %s.%s TO '%s'\@'%s' %s", $self->db_privs(), $self->db_name(), $self->db_obj(), $self->db_user(), $self->db_host(), $self->db_rest();
    $self->db_name($db_name);

    return $string;
}
