#!/usr/bin/perl # cpanel - pkgacct Copyright(c) 2009 cPanel, Inc. # All Rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited package Script::Pkgacct; use strict; require 5.006; BEGIN { unshift @INC, '/scripts', '/usr/local/cpanel'; if ( $ARGV[0] eq '--allow-override' ) { shift(@ARGV); if ( -e '/var/cpanel/lib/Whostmgr/Pkgacct/pkgacct' && -x _ ) { exec( '/var/cpanel/lib/Whostmgr/Pkgacct/pkgacct', @ARGV ); } } } use strict; use bytes; #required for mysqldumpdb use Cwd (); use Fcntl (); use IPC::Open3 (); use cPScript::Tar (); use cPScript::OSSys (); use cPScript::AcctUtils::GetHomeDir (); use cPScript::SafeSync (); use cPScript::Config::LoadCpUserFile (); use cPScript::Config::LoadCpConf (); use cPScript::DiskLib (); use cPScript::PwCache (); use cPScript::AccessIds::SetUids (); use cPScript::Encoder::URI (); use cPScript::HttpUtils::SSL (); use cPScript::ApacheConf (); use cPScript::Filesys (); use cPScript::Limits (); use Cpanel::Locale (); #issafe #nomunge use Cpanel::Locale::Utils::3rdparty (); #issafe #nomunge use Cpanel::Locale::Utils::Display (); #issafe #nomunge use cPScript::DomainIp (); use cPScript::SSLPath (); use cPScript::DnsUtils::AskDnsAdmin (); use cPScript::SafeRun::Simple (); use cPScript::SafeRun::Errors (); use cPScript::StringFunc::Match (); use cPScript::MysqlUtils (); use cPScript::PwDiskCache (); use cPScript::Config::userdata (); use cPScript::BWFiles (); eval 'local $SIG{__DIE__}; require Lchown;'; my $has_lchown = exists $INC{'Lchown.pm'} ? 1 : 0; __PACKAGE__->script(@ARGV) unless caller(); sub script { my($class, @argv) = @_; my %SECURE_PWCACHE; tie %SECURE_PWCACHE, 'cPScript::PwDiskCache', 'load_callback' => \&cPScript::PwCache::load, 'validate_callback' => \&cPScript::PwCache::validate; cPScript::PwCache::init( \%SECURE_PWCACHE ); my $tarcfg = cPScript::Tar::load_tarcfg(); my ($status, $message) = cPScript::Tar::checkperm(); exit 1 if !$status; # No need to give message. Handled in checkperm routine my $loaded_signals; #this is just a fallback in case get_signal_num_from_name fails; my %signo = ( 'USR1' => 10, 'USR2' => 12 ); my $ppid = getppid(); my $httpuser = 'nobody'; my $httpgid = ( getgrnam($httpuser) )[2]; #recusive, copy symlinks as symlinks, preserve permissions, #preserve times, preserve devices $| = 1; delete $ENV{'LD_LIBRARY_PATH'}; my $is_incremental_backup = $ENV{'INCBACKUP'}; ########## @argv processing # We always do archive version 3 now unless we are doing an incremental backup my $archive_version = 3; if ( $argv[0] eq '--version' ) { shift(@argv); shift(@argv); } my $skiphomedir = 0; if ( $argv[0] eq '--skiphomedir' ) { $skiphomedir = 1; shift(@argv); } my $user = $argv[0]; my $tarroot = $argv[1]; ## from scripts/cpbackup my $opt_backup = ( $argv[2] eq "backup" ); ## from bin/backupadmin.pl my $opt_userbackup = ( $argv[2] eq "userbackup" ); ## set when called from whm5 my $opt_split = ( $argv[2] =~ /split/i ); my $opt_nocompress = ( $argv[3] =~ /nocompress/i ); my($opt_mysql, $new_mysql_version); if ( $argv[4] =~ /mysql/i ) { $opt_mysql = 1; $new_mysql_version = $argv[5]; } ########## /@argv processing my $syshomedir = cPScript::AcctUtils::GetHomeDir::gethomedir($user); my ( $uid, $gid ) = ( cPScript::PwCache::getpwnam($user) )[ 2, 3 ]; if ( !$uid ) { die "Unable to get user id for user $user"; } my $system = ( cPScript::OSSys::uname() )[0]; my $usedomainlookup = 0; if ( $> == 0 ) { $ENV{'USER'} = 'root'; $ENV{'HOME'} = '/root'; } else { require cPScript::DomainLookup; $usedomainlookup = 1; } if ( $user eq "root" ) { print "You cannot copy the root user.\n"; exit; } if ( substr( $tarroot, 0, 1 ) eq "~" ) { my $tuser = substr( $tarroot, 1 ); $tarroot = ( cPScript::PwCache::getpwnam($tuser) )[7]; } my $isuserbackup = 0; my $isbackup = 0; my $prefix = ''; if ( $opt_backup ) { $isbackup = 1; $prefix = ''; } elsif ( $opt_userbackup ) { $isuserbackup = 1; $isbackup = 1; my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time); $mon++; $year += 1900; $sec = sprintf( "%02d", $sec ); $min = sprintf( "%02d", $min ); $hour = sprintf( "%02d", $hour ); $prefix = "backup-${mon}.${mday}.${year}_${hour}-${min}-${sec}_"; } else { $prefix = 'cpmove-'; } my $compressflag = 'z'; my $archiveext = 'tar.gz'; my $compress = 1; if ( $opt_nocompress ) { $compress = 0; $compressflag = ''; $archiveext = 'tar'; } my $pkg_version = 8.3; print "pkgacct started.\n"; print "pkgacct version $pkg_version - user : $user - archive version: $archive_version - running with uid $<\n"; my $split = 0; if ( $opt_split ) { print "We will be splitting the archive!!\n"; $split = 1; } $prefix =~ s/\s//g; $prefix =~ s/\n//g; if ( $tarroot eq "" || !-d "$tarroot" ) { if ( $opt_backup ) { print "Bailing out.. you must set a valid destination for backups\n"; exit; } $tarroot = cPScript::Filesys::getmntpoint(); } if ( $> == 0 && ( !($isbackup) ) ) { my $output = cPScript::SafeRun::Errors::saferunallerrors("rdate", "-s", "rdate.cpanel.net"); if ($output =~ /Could not read data/) { print "Rdate bug detected. Please update to rdate-1.1\n"; } } $0 = "pkgacct - ${user} - av: $archive_version"; if ( !cPScript::PwCache::getpwnam($user) ) { print "Invalid Account\n"; exit; } my $homedir = ( cPScript::PwCache::getpwnam($user) )[7]; my $abshomedir = $homedir; #reversed if ( -l $homedir ) { $homedir = readlink($homedir); } my @DNS; my $cpuser_ref = cPScript::Config::LoadCpUserFile::loadcpuserfile($user); my $dns = $cpuser_ref->{'DOMAIN'}; push( @DNS, $dns, 'www.' . $dns ); my $suspended = ( $cpuser_ref->{'SUSPENDED'} ? 1 : 0 ); if ( ref $cpuser_ref->{'DOMAINS'} ) { foreach my $domain ( @{ $cpuser_ref->{'DOMAINS'} } ) { push @DNS, $domain, 'www.' . $domain; } } if ( !$dns ) { print "Unable to find domain name for $user\n"; exit; } my $ip; if ($usedomainlookup) { require cPScript::UserDomainIp; $ip = cPScript::UserDomainIp::getdomainip($dns); } else { $ip = cPScript::DomainIp::getdomainip($dns); } my $ssldomain = cPScript::HttpUtils::SSL::getsslhostbyip($ip); if ( !grep { /\A$ssldomain\z/ } @DNS ) { $ssldomain = ''; } if ( !$prefix && ( $tarroot eq '/' || $tarroot eq '/home' || $tarroot eq cPScript::Filesys::getmntpoint() ) ) { print "Bailing out .. no prefix set and tarroot is / or /home\n"; exit; } my $work_dir = ( $is_incremental_backup && ( $user eq 'files' || $user eq 'dirs' ) ) ? "${tarroot}/${prefix}user_${user}" : "${tarroot}/${prefix}${user}"; if ($prefix) { if ( -d $work_dir && !-l $work_dir ) { if ( !$is_incremental_backup ) { system( "rm", "-rf", $work_dir ); } } if ( -d "${work_dir}-split" && !-l "${work_dir}-split" ) { if ( !$is_incremental_backup ) { system( "rm", "-rf", "${work_dir}-split" ); } } if ( -f "${work_dir}.${archiveext}" && !-l "${work_dir}.${archiveext}" ) { system( "rm", "-rf", "${work_dir}.${archiveext}" ); } } if ($isuserbackup) { my $now = time(); if ( my $xpid = fork() ) { waitpid( $xpid, 0 ); } else { cPScript::AccessIds::SetUids::setuids($user); open( TMPF, ">", "$homedir/$prefix$user" ); print TMPF "s ${now}\n"; open( TMPF, ">", "$homedir/$prefix$user.$archiveext" ); print TMPF "s ${now}\n"; close(TMPF); exit(); } } if ( !$is_incremental_backup && !$split ) { open( CPM, ">$work_dir.$archiveext" ); close(CPM); chmod( 0600, "$work_dir.$archiveext" ); } if ( !-e $work_dir ) { build_pkgtree($work_dir); } else { if ( !$is_incremental_backup ) { my $part = 0; while ( $part != 1024 ) { if ( !-d "$work_dir.$part" ) { system( 'mv', $work_dir, "$work_dir.$part" ); build_pkgtree($work_dir); last; } $part++; } } } # Write version of pkgacct open( my $ver_h, '>', "$work_dir/version" ); print {$ver_h} "pkgacct version: $pkg_version\n"; print {$ver_h} "archive version: $archive_version\n"; close($ver_h); # "$work_dir/homedir_paths" is to be deprecated in favor of "$work_dir/meta/homedir_paths" foreach my $file ( "$work_dir/homedir_paths", "$work_dir/meta/homedir_paths" ) { sysopen( my $home_fh, $file, &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); print {$home_fh} $homedir . "\n"; if ( $abshomedir ne $homedir ) { print {$home_fh} $abshomedir . "\n"; } close($home_fh); } open( my $mailserver_fh, '>', "$work_dir/meta/mailserver" ); my $cpconf = cPScript::Config::LoadCpConf::loadcpconf(); print {$mailserver_fh} $cpconf->{'mailserver'} . "\n"; close($mailserver_fh); my $ssldir = cPScript::SSLPath::getsslroot(); print "Copying Reseller Config..."; if ( $> == 0 ) { cPScript::Limits::backup_reseller_config( $user, "$work_dir/resellerconfig" ); cPScript::Limits::backup_reseller_limits( $user, "$work_dir/resellerconfig" ); } print "Done\n"; print "Copying Suspension Info (if needed)..."; syncfile( "/var/cpanel/suspended/$user", "$work_dir/suspended" ); syncfile( "/var/cpanel/suspended/$user.lock", "$work_dir/suspended" ); syncfile( "/var/cpanel/suspendinfo/$user", "$work_dir/suspendinfo" ); print "Done\n"; print "Copying SSL Certificates, CSRS, and Keys..."; if ( $> == 0 ) { foreach my $domain ( @DNS, $ssldomain ) { if ( -e "${ssldir}/certs/${domain}.crt" ) { print "..${domain}.crt.."; syncfile( "${ssldir}/certs/${domain}.crt", "$work_dir/sslcerts" ); } if ( -e "${ssldir}/certs/${domain}.cabundle" ) { print "..${domain}.cabundle.."; syncfile( "${ssldir}/certs/${domain}.cabundle", "$work_dir/sslcerts" ); } if ( -e "${ssldir}/certs/${domain}.csr" ) { print "..${domain}.csr.."; syncfile( "${ssldir}/certs/${domain}.csr", "$work_dir/sslcerts" ); } if ( -e "${ssldir}/private/${domain}.key" ) { print "..${domain}.key.."; syncfile( "${ssldir}/private/${domain}.key", "$work_dir/sslkeys" ); } } } else { my @SSLTYPES = ( "csr", "key", "crt", "cabundle" ); foreach my $ssltype (@SSLTYPES) { my $items = `/usr/local/cpanel/bin/sslwrap LIST ${dns} $ssltype`; my (@SSLFILES) = split( /\n/, $items ); foreach my $host (@SSLFILES) { my $ssldata = `/usr/local/cpanel/bin/sslwrap FETCH $host $ssltype`; if ( $ssltype eq "csr" ) { print "..${host}.csr.."; open( CSR, ">", "$work_dir/sslcerts/${host}.csr" ); print CSR $ssldata; close(CSR); } elsif ( $ssltype eq "crt" ) { print "..${host}.crt.."; open( CRT, ">", "$work_dir/sslcerts/${host}.crt" ); print CRT $ssldata; close(CRT); } elsif ( $ssltype eq "cabundle" ) { print "..${host}.cabundle.."; open( CAB, ">", "$work_dir/sslcerts/${host}.cabundle" ); print CAB $ssldata; close(CAB); } elsif ( $ssltype eq "key" ) { print "..${host}.key.."; open( KEY, ">", "$work_dir/sslkeys/${host}.key" ); print KEY $ssldata; close(KEY); } } } } print "Done\n"; print "Copying Domain Keys...."; my $domainkeys_dir = '/var/cpanel/domain_keys'; foreach my $domain ($dns, @{ $cpuser_ref->{'DOMAINS'} }) { if (-e "$domainkeys_dir/public/$domain") { syncfile("$domainkeys_dir/public/$domain", "$work_dir/domainkeys/public/"); } if (-e "$domainkeys_dir/private/$domain") { syncfile("$domainkeys_dir/private/$domain", "$work_dir/domainkeys/private/"); } } print "Done\n"; print "Copying Counter Data...."; opendir( my $counters, '/var/cpanel/Counters' ); while ( my $file = readdir($counters) ) { next if ( $file =~ /^\./ ); if ( ( stat( '/var/cpanel/Counters/' . $file ) )[4] == $uid ) { syncfile( "/var/cpanel/Counters/$file", "$work_dir/counters" ); } } closedir($counters); print "Done\n"; print "Copying Bandwidth Data...."; my $bw_dir = cPScript::BWFiles::default_dir(); ## Case 17778: copies the $user-all, -ftp, -http, -imap, -pop3, and -smtp files. opendir my $bwd_h, $bw_dir; ## FIXME: similar to case 9010, we need to consolidate the places where .rrd files are ## defined. Otherwise every time they change, we eventually need to open a few cases ## (upon account termination, transfers, backups/restores maybe). Seems we are missing ## the files denoted by domain name below... my @nondots = grep { !/^\.\.?$/ } readdir($bwd_h); my @userfiles = grep { ($_ eq $user) || ($_ =~ /^$user-/) } @nondots; my @user_bandwithfiles = map { "$bw_dir/$_" } @userfiles; closedir $bwd_h; foreach my $rrd (@user_bandwithfiles) { my $xml = $rrd; if ($rrd =~ /\.rrd$/) { $xml =~ s{\Q$bw_dir\E/}{}; $xml =~ s/\.rrd$/.xml/; } else { syncfile( $rrd, "$work_dir/bandwidth" ); next; } my $pid = IPC::Open3::open3(my $w, my $r, '', '/usr/local/cpanel/3rdparty/bin/rrdtool', 'dump', $rrd); close $w; if ( sysopen my $to, "$work_dir/bandwidth/$xml", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW, 0644 ) { my $buffer; while ( sysread( $r, $buffer, 4096 ) ) { if ( !syswrite( $to, $buffer, length $buffer ) ) { last; } } close $to; } close $r; waitpid $pid, 0; } foreach my $bdomain (@DNS) { next if ( $bdomain =~ /^www\./ ); print "...$bdomain..."; next if ( !-e "$bw_dir/$bdomain" ); opendir my $bwd_h, $bw_dir; ## FIXME: see note above re: case 9010 my @nondots = grep { !/^\.\.?$/ } readdir($bwd_h); my @userfiles = grep { $_ eq $bdomain || $_ =~ /^$bdomain-/ } @nondots; my @domain_bandwithfiles = map { "$bw_dir/$_" } @userfiles; closedir $bwd_h; foreach my $rrd (@domain_bandwithfiles) { my $xml = $rrd; if ($rrd =~ /\.rrd$/) { $xml =~ s{\Q$bw_dir\E/}{}; $xml =~ s/\.rrd$/.xml/; } else { syncfile( $rrd, "$work_dir/bandwidth" ); next; } my $pid = IPC::Open3::open3(my $w, my $r, '', '/usr/local/cpanel/3rdparty/bin/rrdtool', 'dump', $rrd); close $w; if ( sysopen my $to, "$work_dir/bandwidth/$xml", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW, 0644 ) { my $buffer; while ( sysread( $r, $buffer, 4096 ) ) { if ( !syswrite( $to, $buffer, length $buffer ) ) { last; } } close $to; } close $r; waitpid $pid, 0; } } print "Done\n"; print "Copying Dns Zones...."; if ( $> == 0 ) { my %ZONES; my $encoded_zones = cPScript::DnsUtils::AskDnsAdmin::askdnsadmin( 'GETZONES', ( $isbackup ? 1 : 0 ), join( ',', @DNS ) ); foreach my $zonedata ( split( /\&/, $encoded_zones ) ) { my ( $name, $value ) = split( /=/, $zonedata ); next if ( !$name || $name eq '' ); $name =~ s/^cpdnszone-//g; $name = cPScript::Encoder::URI::uri_decode_str($name); print "...$name..."; if ( length($value) > 10 ) { sysopen( ZONE, "$work_dir/dnszones/$name.db", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); my $temp = cPScript::Encoder::URI::uri_decode_str($value); syswrite( ZONE, $temp, length $temp ); close(ZONE); } } } print "Done\n"; print "Copying Mail files...."; foreach my $domain (@DNS) { if ( -e "/etc/valiases/${domain}" ) { syncfile( "/etc/valiases/${domain}", "$work_dir/va" ); } if ( -e "/etc/vdomainaliases/${domain}" ) { syncfile( "/etc/vdomainaliases/${domain}", "$work_dir/vad" ); } if ( -e "/etc/vfilters/${domain}" ) { syncfile( "/etc/vfilters/${domain}", "$work_dir/vf" ); } } print "Done\n"; print "Copying frontpage files...."; { my $site_dir = '/usr/local/apache/conf/sites'; if (-e $site_dir) { opendir my $dir_h, $site_dir; my @sites = grep { /$dns/ } grep { !/^\.\.?$/ } readdir($dir_h); closedir($dir_h); foreach my $file (@sites) { syncfile($site_dir . '/' . $file, "$work_dir/fp/sites"); } } foreach my $domain (@DNS) { foreach my $port ( '80', '443' ) { syncfile( "/usr/local/frontpage/www.$domain:$port.cnf", "$work_dir/fp" ); syncfile( "/usr/local/frontpage/$domain:$port.cnf", "$work_dir/fp" ); } } print "Done\n"; } print "Copying proftpd file...."; if ( $> == 0 ) { syncfile( "/etc/proftpd/${user}", "$work_dir/proftpdpasswd" ); } else { system("/usr/local/cpanel/bin/ftpwrap DUMP 0 0 >> $work_dir/proftpdpasswd"); } chmod( 0600, "$work_dir/proftpdpasswd" ); print "Done\n"; if ( $ENV{'pkgacct-logs'} eq "yes" || $ENV{'pkgacct-cpbackup'} eq "" ) { print "Copying www logs...."; if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { foreach my $domain (@DNS) { $0 = "pkgacct - ${user} - log copy child"; globsyncfile( "/usr/local/apache/domlogs/${domain}*", "$work_dir/logs" ); globsyncfile( "/usr/local/apache/domlogs/www.${domain}*", "$work_dir/logs" ); globsyncfile( "/usr/local/apache/domlogs/ftp.${domain}*", "$work_dir/logs" ); } exit; } print "Done\n"; } print 'Copy userdata...'; if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { $0 = "pkgacct - ${user} - userdata"; my $userdata = "/var/cpanel/userdata/$user"; opendir(my $dir_h, $userdata); my @userdatafiles = grep { !/cache$/ } grep { !/^\.\.?$/ } readdir $dir_h; close $dir_h; foreach my $userdatafile (@userdatafiles) { my $config = cPScript::Config::userdata::load_userdata($user, $userdatafile); foreach my $key (qw/custom_vhost_template_ap1 custom_vhost_template_ap2/) { if (exists $config->{$key}) { syncfile($config->{$key}, "$work_dir/userdata"); } } syncfile($userdata . '/' . $userdatafile, "$work_dir/userdata"); } exit; } print 'Copy custom virtualhost templates...'; if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { $0 = "pkgacct - ${user} - custom virtualhost templates copy child"; my $main_userdata = cPScript::Config::userdata::load_userdata( $user, 'main' ); foreach my $domain ( $main_userdata->{main_domain}, @{ $main_userdata->{sub_domains} }, keys %{$main_userdata->{addon_domains}}) { my $base = '/usr/local/apache/conf/userdata/'; foreach my $path ( "$base/ssl/1/$user/$domain/", "$base/ssl/2/$user/$domain/", "$base/std/1/$user/$domain/", "$base/std/2/$user/$domain/" ) { if ( -e $path ) { if ( $path =~ m{(s(?:(?:td)|(?:sl)))/([12])} ) { my $proto = $1; my $ver = $2; mkdir( "$work_dir/httpfiles/$proto/", 0700 ); mkdir( "$work_dir/httpfiles/$proto/$ver/", 0700 ); mkdir( "$work_dir/httpfiles/$proto/$ver/$domain/", 0700 ); globsyncfile( $path . '/*', "$work_dir/httpfiles/$proto/$ver/$domain" ); } } } } exit; } print "Done\n"; if ( $ENV{'pkgacct-psql'} eq "yes" || $ENV{'pkgacct-cpbackup'} eq "" ) { my $postgresadmin = -e '/usr/local/cpanel/bin/postgresadmin.pl' ? '/usr/local/cpanel/bin/postgresadmin.pl' : '/usr/local/cpanel/bin/postgresadmin'; my $pg_active; if ( $> == 0 ) { $pg_active = quickrun_stdin($postgresadmin, "$uid PING" ); } else { $pg_active = quickrun_stdin('/usr/local/cpanel/bin/postgreswrap', 'PING'); } my $postgresuser = &getpostgresuser(); if ($postgresuser && $pg_active && $pg_active eq 'PONG') { print "Grabbing PostgreSQL databases..."; if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { my $psqluser = $user; $psqluser =~ s/-//g; my $users; my @DBS; if ( $> == 0 ) { @DBS = split( /\n/, quickrun_stdin( $postgresadmin, "$uid LISTDBS 0 0" ) ); } else { @DBS = split( /\n/, `/usr/local/cpanel/bin/postgreswrap LISTDBS 0 0` ); } foreach my $db (@DBS) { $db =~ s/\n//g; if ( $> == 0 || $ENV{'REMOTE_PASSWORD'} eq "" ) { open( DUMPFILE, ">", "$work_dir/psql/${db}.tar" ) || warn "Unable to write archive: $!"; open( PGDUMP, "-|" ) || exec( $postgresadmin, $uid, "PGDUMP", $db, "0" ); while () { print DUMPFILE; } close(PGDUMP); } else { open( DUMPFILE, ">", "$work_dir/psql/${db}.tar" ) || warn "Unable to write archive: $!"; open( PGDUMP, "-|" ) || exec( "/usr/local/cpanel/bin/postgreswrap", "PGDUMP", $db, "0" ); while () { print DUMPFILE; } close(PGDUMP); } } exit(); } print "Done\n"; print "Grabbing PostgreSQL privileges..."; if ( $> == 0 ) { sysopen( my $pg_users_fh, "$work_dir/psql_users.sql", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); print {$pg_users_fh} quickrun_stdin( $postgresadmin, "$uid DUMPSQL_USERS 0 0" ); close($pg_users_fh); sysopen( my $pg_grants_fh, "$work_dir/psql_grants.sql", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); print {$pg_grants_fh} quickrun_stdin( $postgresadmin, "$uid DUMPSQL_GRANTS 0 0" ); close($pg_grants_fh); } else { system("/usr/local/cpanel/bin/postgreswrap DUMPSQL_USERS 0 0 >> $work_dir/psql_users.sql"); system("/usr/local/cpanel/bin/postgreswrap DUMPSQL_GRANTS 0 0 >> $work_dir/psql_grants.sql"); } print "Done\n"; } } if ( $ENV{'CPUWATCH'} eq "1" ) { print "Leaving timeout safety mode\n"; #allow our parent to suspend us again ## $loaded_signals and %signo passed in by reference, as they were formerly both global, and assigned ## to in the subroutine... my $signal = get_signal_num_from_name('USR2', \$loaded_signals, \%signo); kill $signal, $ppid; } print "Copying mailman lists...."; my $cpargs = '-a'; if ( $system =~ /freebsd/i ) { $cpargs = '-Rpf'; } foreach my $domain (@DNS) { nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/lists/*_${domain} $work_dir/mm"); nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/suspended.lists/*_${domain} $work_dir/mms"); } print "Done\n"; print "Copying mailman archives...."; foreach my $domain (@DNS) { nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/archives/public/*_${domain} $work_dir/mma/pub"); nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/archives/public/*_${domain}.mbox $work_dir/mma/pub"); nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/archives/private/*_${domain} $work_dir/mma/priv"); nooutputsystemsh("cp ${cpargs} /usr/local/cpanel/3rdparty/mailman/archives/private/*_${domain}.mbox $work_dir/mma/priv"); } print "Done\n"; if ( !$skiphomedir ) { homedir_block($work_dir, $gid, $isbackup, $isuserbackup, $homedir, $user, $is_incremental_backup, $tarcfg); } else { my $nfl_ref = cPScript::SafeSync::find_uid_files( $homedir, [ 'nobody' ] ); sysopen( my $nf_fh, "$work_dir/nobodyfiles", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $file ( keys %$nfl_ref ) { chomp($file); $file =~ s/^\Q$homedir\E\/?//g; print {$nf_fh} $file . "\n"; } close($nf_fh); } #mysql block requires loading Config so we do it after we fork { my $mysqladmin = cPScript::MysqlUtils::find_mysqladmin(); my $mysqldump = cPScript::MysqlUtils::find_mysqldump(); if ( $ENV{'CPUWATCH'} eq "1" ) { print "Entering timeout safety mode\n"; #prevent our parent from suspending us to prevent a mysql timeout my $signal = get_signal_num_from_name('USR1', \$loaded_signals, \%signo); kill $signal, $ppid; } my ($mysqldatadir) = '/var/lib/mysql'; open( MADMIN, "$mysqladmin variables|" ); while () { s/\|//g; if (/[\s\t]+datadir[\s\t]+(\S+)/) { $mysqldatadir = $1; } } close(MADMIN); while ( -l $mysqldatadir ) { $mysqldatadir = readlink($mysqldatadir); } if ( $ENV{'pkgacct-mysql'} eq 'yes' || !$ENV{'pkgacct-cpbackup'} ) { print 'Grabbing mysql dbs...'; if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print '.....'; } dotsleep(); $dotcount++; } } else { my $mysqluser = $user; $mysqluser =~ s/-//g; my $users; my @DBS; if ( $> == 0 ) { @DBS = split( /\n/, quickrun_stdin( '/usr/local/cpanel/bin/mysqladmin', "$uid LISTDBS 0 0" ) ); } else { @DBS = split( /\n/, `/usr/local/cpanel/bin/mysqlwrap LISTDBS 0 0` ); } my $old_mysql_version = getoldmysqlversion(); my @downgrade_options = downgrade_mysql( $old_mysql_version, $new_mysql_version ); if ( mysqldump_ver() >= 5.0 && $old_mysql_version >= 5.0 ) { push @downgrade_options, '-R'; } elsif ( mysqldump_ver() < 5.0 && $old_mysql_version >= 5.0 ) { print "\nLocal mysql tools are version 4.x and remote mysql is 5.x. Unable to backup stored procedures.\n"; } foreach my $db (@DBS) { $db =~ s/\n//g; if ( $> == 0 || !$ENV{'REMOTE_PASSWORD'} ) { #mysqldumpdb( { 'options' => [ @downgrade_options, '-c', '-Q', '-q' ], 'db' => $db, 'file' => "$work_dir/mysql/${db}.sql" } ); } else { #mysqldumpdb( { 'options' => [ @downgrade_options, '-c', '-Q', '-q', '-u' . $user, '-p' . $ENV{'REMOTE_PASSWORD'} ], 'db' => $db, 'file' => "$work_dir/mysql/${db}.sql" } ); } } # Horde if ( $> == 0 ) { my $horde_db = "$work_dir/mysql/horde.sql"; my @dns_regex_list = grep( !/(?:^www\.|\*)/i, @DNS ); my $dnslist = join( '|', @dns_regex_list ); $dnslist =~ s/\./\\./g; my @options = ( @downgrade_options, '-c', '-Q', '-q', '-t', '-w' ); #FIXME: make this one dump call mysqldumpdb( { 'options' => [ @options, qq{owner_id='$user' or owner_id REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'turba_objects', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{pref_uid='$user' or pref_uid REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'horde_prefs', 'append' => 1, } ); ## case 28546: change from 'event_uid' column to 'calendar_id' mysqldumpdb( { 'options' => [ @options, qq{calendar_id='$user' or calendar_id REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'kronolith_events', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{vfb_owner='$user' or vfb_owner REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'kronolith_storage', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{memo_owner='$user' or memo_owner REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'mnemo_memos', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{task_owner='$user' or task_owner REGEXP '@(${dnslist})\$'} ], 'db' => 'horde', 'file' => $horde_db, 'table' => 'nag_tasks', 'append' => 1, } ); } # RoundCube if ( $> == 0 && exists $cpconf->{'roundcube_db'} and ($cpconf->{'roundcube_db'} eq 'sqlite') ) { ## pass: roundcube.db is in homedir.tar. The logic reads better if this is blank block. } elsif ( $> == 0 && cPScript::MysqlUtils::db_exists('roundcube') ) { my $round_db = "$work_dir/mysql/roundcube.sql"; my @dns_regex_list = grep( !/(?:^www\.|\*)/i, @DNS ); my $dnslist = join( '|', @dns_regex_list ); $dnslist =~ s/\./\\./g; #FIXME: make this one dump call ## case 16846: adding "username = '$user'" to ensure the system users that use webmail are converted my $ids = qx{ mysql roundcube -B -ss -e "SELECT user_id FROM users WHERE username REGEXP '@(${dnslist})\$' or username = '$user'" }; if ($ids) { $ids = join ',', split /\n/, $ids; ## -c:complete insert; -Q:quote names; -q:quick; -w:where clause; (REMOVED -t:no create info) ## NOTE: the src server will no longer suppress the 'CREATE TABLE' statements. This accounts for the case where ## the dest server has performed the conversion (which archives and deletes the database). ???Would it be better ## to re-create the database with the shipped 'mysql.initial.sql'? my @options = ( @downgrade_options, '--skip-add-drop-table', '-c', '-Q', '-q', '-w' ); mysqldumpdb( { 'options' => [ @options, qq{user_id IN ($ids)} ], 'db' => 'roundcube', 'file' => $round_db, 'table' => 'users', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{user_id IN ($ids)} ], 'db' => 'roundcube', 'file' => $round_db, 'table' => 'messages', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{user_id IN ($ids)} ], 'db' => 'roundcube', 'file' => $round_db, 'table' => 'identities', 'append' => 1, } ); mysqldumpdb( { 'options' => [ @options, qq{user_id IN ($ids)} ], 'db' => 'roundcube', 'file' => $round_db, 'table' => 'contacts', 'append' => 1, } ); } } exit; } print "...Done\n"; #print "Grabbing mysql privs..."; #if ($suspended) { #system( '/scripts/unsuspendmysqlusers', $user ); #} #if ( $> == 0 ) { # sysopen( my $mysql_sql_fh, "$work_dir/mysql.sql", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); # print {$mysql_sql_fh} quickrun_stdin( '/usr/local/cpanel/bin/mysqladmin', "$uid DUMPSQL 0 0" ); # close($mysql_sql_fh); # } # else { # system("/usr/local/cpanel/bin/mysqlwrap DUMPSQL 0 0 >> $work_dir/"); # } # if ($suspended) { # system( '/scripts/suspendmysqlusers', $user ); # } # print "Done\n"; } } print "Copying cpuser file......."; syncfile( "/var/cpanel/users/${user}", "$work_dir/cp" ); print "Done\n"; print "Copying crontab file......."; if ( -r "/var/cron/tabs/${user}" ) { syncfile( "/var/cron/tabs/${user}", "$work_dir/cron" ); } if ( -r "/var/spool/cron/${user}" ) { syncfile( "/var/spool/cron/${user}", "$work_dir/cron" ); } elsif ( -r "/var/spool/fcron/${user}" ) { syncfile( "/var/spool/fcron/${user}", "$work_dir/cron" ); } else { open( CO, '>', "$work_dir/cron" ); open( CT, '-|' ) || exec( 'crontab', '-l' ) or die "Failed to execute crontab: $!"; while () { print CO; } close(CT); close(CO); } print "Done\n"; my $quota; print "Copying quota info......."; if ( open( my $quota_fh, '<', '/etc/quota.conf' ) ) { while ( readline($quota_fh) ) { if (/^\Q$user\E=(\d+)/) { $quota = $1; } } close($quota_fh); } sysopen( my $qout_fh, "$work_dir/quota", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); print {$qout_fh} $quota; close($qout_fh); print "Done\n"; print "Storing Subdomains....\n"; my %SUBS; if ($usedomainlookup) { %SUBS = cPScript::DomainLookup::listsubdomains( $homedir, @DNS ); } else { #yes abshomedir and homedir are reversed here. %SUBS = cPScript::ApacheConf::listsubdomains( $user, $abshomedir, $homedir, \@DNS ); } sysopen( SH, "$work_dir/sds", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $sd ( keys %SUBS ) { syswrite( SH, "$sd\n", length "$sd\n" ); } close(SH); sysopen( SH, "$work_dir/sds2", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $sd ( keys %SUBS ) { my $basedir = $SUBS{$sd}; $basedir =~ s/^$homedir\/?//g; $basedir =~ s/^$syshomedir\/?//g; my $temp = "$sd=$basedir\n"; syswrite( SH, $temp, length $temp ); } close(SH); print "Done\n"; print "Storing Parked Domains....\n"; my %SDS; if ($usedomainlookup) { %SDS = cPScript::DomainLookup::getparked( $dns ); } else { %SDS = cPScript::ApacheConf::getparked( $dns ); } sysopen( SH, "$work_dir/pds", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $sd ( keys %SDS ) { my $temp = "$sd\n"; syswrite( SH, $temp, length $temp ); } close(SH); print "Done\n"; print "Storing Addon Domains....\n"; my (@PSUBS); my ( %PN, %FN, $pname, $fname ); foreach ( keys %SUBS ) { $fname = $_; s/_/\./g; $FN{$_} = $fname; push( @PSUBS, $_ ); } my %PARKED; if ($usedomainlookup) { %PARKED = cPScript::DomainLookup::getmultiparked(@PSUBS); } else { %PARKED = cPScript::ApacheConf::getmultiparked(@PSUBS); } sysopen( SH, "$work_dir/addons", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $subdomain ( keys %PARKED ) { foreach my $parked ( keys %{ $PARKED{$subdomain} } ) { my $temp = "$parked=$FN{$subdomain}\n"; syswrite( SH, $temp, length $temp ); } } close(SH); print "Done\n"; print "Storing ssl domain......"; sysopen( SH, "$work_dir/ssldomain", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW |&Fcntl::O_TRUNC, 0600 ); syswrite( SH, $ssldomain, length $ssldomain ); close(SH); print "Done\n"; print "Copying password......."; sysopen( SH, "$work_dir/shadow", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); chmod( 0600, "$work_dir/shadow" ); my $pass = ( cPScript::PwCache::getpwnam($user) )[1]; syswrite( SH, $pass, length $pass ); close(SH); print "Done\n"; print "Copying shell......."; sysopen( SH, "$work_dir/shell", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); my $shell = ( cPScript::PwCache::getpwnam($user) )[8]; syswrite( SH, $shell, length $shell ); close(SH); print "Done\n"; if ( $> == 0 ) { export_non_cpanel_locale($user, $work_dir); } else { print "Exporting of the user's locale must be done as root.\n"; } chdir($tarroot); print "Creating Archive ...."; my $prefix_user = "${prefix}${user}"; if ( !$is_incremental_backup ) { ## e.g. invoked as './scripts/pkgacct $user "" userbackup' ## - or - './scripts/pkgacct $user /tmp backup' if ($isbackup) { my $destfile = "$prefix_user.${archiveext}"; my $rv = system( $tarcfg->{'bin'}, "pc${compressflag}f", $destfile, $prefix_user ); if ($rv) { ## case 20142: out of disk space on tar system call print "\nERROR: tar of backup archive returned error $rv\n"; } } else { ## e.g. invoked as './scripts/pkgacct $user "" --split' if ($split) { mkdir( "${work_dir}-split", 0700 ); rename( $work_dir, "${work_dir}-split/$prefix_user" ); chdir("${work_dir}-split"); opendir( SPD, "${work_dir}-split" ); my @FILES = readdir(SPD); closedir(SPD); foreach my $file (@FILES) { if ( -f "${work_dir}-split/${file}" ) { unlink("${work_dir}-split/${file}"); } } my $dotpid = create_antitimeout_process(); if ( $system =~ /freebsd/i ) { open( TR, "-|" ) || exec( $tarcfg->{'bin'}, "pc${compressflag}f", "-", $prefix_user ); } else { open( TR, "-|" ) || exec( $tarcfg->{'bin'}, "pc${compressflag}", $prefix_user ); } my $bf; my $bytes = 0; ## starts at 0, to detect the first time through the loop (immediately incr'ed) my $part = 0; while ( my $bytes_read = sysread( TR, $bf, 65535 ) ) { $bytes += $bytes_read; ## do this the first time through and when buffer is full if ( !$part || ($bytes > 100_000_000) ) { ## only close when not the first time through if ($part) { close(PART); } $bytes = 0; $part++; my $fname = "$prefix_user.${archiveext}.part${part}"; open( PART, '>', $fname ); chmod( 0600, $fname ); } my $bytes_write = syswrite( PART, $bf ); if ($bytes_write == 0) { ## case 20142: out of disk space on syswrite print "\nERROR: tar of split archive ran out of space\n"; last; } } close(TR); close(PART); print "\n"; opendir( SPD, "${work_dir}-split" ); @FILES = (); @FILES = readdir(SPD); closedir(SPD); for ( 0 .. $#FILES ) { my $file = $FILES[$_]; next if ( $file !~ /^\Q$prefix_user\E/ ); #in case of cruft files my $splitfile = "${work_dir}-split/$file"; if ( -f $splitfile ) { print "splitpkgacctfile is: $splitfile\n"; my $md5sum = getmd5sum($splitfile); if ( $dotpid && $dotpid > 0 ) { kill( 8, $dotpid ); kill( 9, $dotpid ); } print "\nsplitmd5sum is: $md5sum\n"; my $splitsize = (stat($splitfile))[7]; print "\nsplitsize is: $splitsize\n"; if ( $_ != $#FILES ) { $dotpid = create_antitimeout_process(); } } } if ( $dotpid && $dotpid > 0 ) { kill( 8, $dotpid ); kill( 9, $dotpid ); } $dotpid = create_antitimeout_process(); if ( -d "${work_dir}-split/$prefix_user" && !-l "${work_dir}-split/$prefix_user" ) { system( "rm", "-rf", "${work_dir}-split/$prefix_user" ); } if ( $dotpid && $dotpid > 0 ) { kill( 8, $dotpid ); kill( 9, $dotpid ); } } else { ## e.g. invoked as './scripts/pkgacct $user' if ( my $tpid = fork() ) { my $dotcount = 5; while ( waitpid( $tpid, 1 ) != -1 ) { if ( $dotcount % 15 == 0 || $dotcount == 5 ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { my $destfile = "$prefix_user.${archiveext}"; my $rv = system( $tarcfg->{'bin'}, "pc${compressflag}f", $destfile, $prefix_user ); if ($rv) { ## case 20142: out of disk space on tar system call print "\nERROR: tar of archive returned error $rv\n"; } exit(); } } } if ( -d $work_dir && !-l $work_dir ) { system( "rm", "-rf", $work_dir ); } } print "Done\n"; if ($is_incremental_backup) { print "pkgacct target is: $work_dir\n"; } elsif ( !$split && !$is_incremental_backup ) { print "pkgacctfile is: $work_dir.${archiveext}\n"; } if ( !$is_incremental_backup && !$split ) { my $md5sum = getmd5sum("$work_dir.$archiveext"); print "md5sum is: $md5sum\n"; my $size = (stat("$work_dir.$archiveext"))[7]; print "\nsize is: $size\n"; } if ($skiphomedir) { my $du = qx( du -s $homedir ); my($homesize_kb) = ($du =~ m/^(\d+)/); ## FreeBSD does not have the -b option; performing the calculation manually. my $homesize = $homesize_kb * 1024; print "\nhomesize is: $homesize\n"; } } sub getmd5sum { my $file = shift; my $mbuf; my $bintouse; my @md5sums = qw(/bin/md5sum /usr/bin/md5sum /usr/local/bin/md5sum); foreach my $md5sumbin (@md5sums) { if ( -e $md5sumbin ) { if ( !-x _ ) { chmod 0755, $md5sumbin; } $bintouse = $md5sumbin; last; } } if ($bintouse) { $mbuf = cPScript::SafeRun::Simple::saferun( $bintouse, $file ); } else { $mbuf = cPScript::SafeRun::Simple::saferun( 'md5', '-r', $file ); } chomp($mbuf); my $md5 = ( split( /\s+/, $mbuf ) )[0]; return $md5; } # syncfile - source, destination [, no_symlinks] # Given source and dest, if source's mtime is greater than that # of dest's, source will be copied over dest. # Return Values: 0 - Problem copying. # -1 - File not copied, # 1 - File copied. sub syncfile { my ( $source, $dest, $no_sym, $no_chown ) = @_; my $s_handle; my $d_handle; my $buff; return 0 if ( !-e $source ); my ( $mode, $uid, $gid, $s_size, $s_mod ) = ( stat(_) )[ 2, 4, 5, 7, 9 ]; if ( !defined($no_sym) ) { $no_sym = 0; } if ( !defined($no_chown) ) { $no_chown = 0; } if ( -d $dest ) { $dest =~ s{/$}{}g; my @SRC = split( /\//, $source ); $dest .= '/' . $SRC[$#SRC]; undef @SRC; stat($dest); #if we change files we need to stat it again as we use the stat cache below } my ( $d_mod, $d_size, $d_mode, $d_uid, $d_gid ); if ( !-e _ ) { $d_mode = $d_mod = $d_size = 0; $d_uid = $d_gid = -1; } else { ( $d_mode, $d_uid, $d_gid, $d_size, $d_mod ) = ( stat(_) )[ 2, 4, 5, 7, 9 ]; } if ( ( $s_mod != $d_mod ) || ( $d_size != $s_size ) ) { copy( $source, $dest, $mode & 07777, $no_sym ); if ( !$no_chown ) { if ($has_lchown) { Lchown::lchown( $uid, $gid, $dest ); } else { chown( $uid, $gid, $dest ); } } utime( time, $s_mod, $dest ); } else { if ( ( ( $d_uid != $uid ) || ( $d_gid != $gid ) ) && ( !$no_chown ) ) { if ($has_lchown) { Lchown::lchown( $uid, $gid, $dest ); } else { chown( $uid, $gid, $dest ); } } return -1; # Not Copied, but successful. } return 1; } # globsyncfile - # Params: # source : csh style glob expression for file matching. # dest : Directory to copy the files into. # no_sym : 1 to not follow symlinks, 0 to follow. # no_chown : 1 to disable chowning, 0 to chown. sub globsyncfile { my ( $source, $dest, $no_sym, $no_chown ) = @_; my @files = glob $source; foreach my $file (@files) { syncfile( $file, $dest, $no_sym, $no_chown ); } } # copy - # Params: Source file, Destination file. # Copies source to destination. sub copy { my ( $source, $dest, $mode, $nofollow ) = @_; $mode ||= 0600; my $errno = 0; if ( sysopen my $from, $source, ( $nofollow ? ( &Fcntl::O_RDONLY | &Fcntl::O_NOFOLLOW ) : (&Fcntl::O_RDONLY) ) ) { unlink($dest); if ( sysopen my $to, $dest, &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_EXCL, $mode ) { my $buffer; while ( sysread( $from, $buffer, 16384 ) ) { if ( !syswrite( $to, $buffer, length $buffer ) ) { last; } } close $to; } else { if ( -e $dest ) { unlink $dest; } } close $from; } } sub getpostgresuser { if ( cPScript::PwCache::getpwnam("postgres") ) { return ("postgres"); } elsif ( cPScript::PwCache::getpwnam("pgsql") ) { return ("pgsql"); } else { return (""); } } sub dotsleep { select( undef, undef, undef, 0.10 ); } sub getoldmysqlversion { my $mysql = 'mysql'; my $version; my $select_version = `$mysql -u root mysql -e 'SELECT VERSION()'`; my @version = split /\n/, $select_version; foreach (@version) { if (m/^\s*(\d[.]\d)/) { $version = $1; } } return $version; } sub downgrade_mysql { my $old_server = shift; my $new_server = shift; return if ( !$new_server || $new_server >= $old_server ); my $downgrade_table = { '5.1' => { 'options' => ['--skip-events'] }, '5.0' => { 'options' => [ '--skip-routines', '--skip-triggers' ] }, '4.1' => { 'options' => ['--compatible=mysql40'] }, '4.0' => { 'options' => [] }, }; my @downgrade_order = sort { $b <=> $a } keys %{$downgrade_table}; my @options = (); foreach my $version (@downgrade_order) { if ( ( $old_server >= $version ) && ( $new_server != $version ) ) { @options = $downgrade_table->{$version}{'munge'}->(@options) if exists $downgrade_table->{$version}{'munge'}; push @options, @{ $downgrade_table->{$version}{'options'} }; } last if $new_server == $version; } return @options; } sub mysqldumpdb { my ($args) = @_; my @options = @{ $args->{'options'} }; my $db = $args->{'db'}; my $table = $args->{'table'}; my $file = $args->{'file'}; my $file_write_mode = $args->{'append'} ? '>>' : '>'; my $mysqldump = cPScript::MysqlUtils::find_mysqldump(); my @db = ($db); if ($table) { push @db, $table; } print join( '.', @db ) . ' '; my $pid = IPC::Open3::open3( my $w, my $r, '', $mysqldump, @options, @db ); my $first_line = 1; if ( open( my $fh, $file_write_mode, $file ) ) { while (<$r>) { if ( $first_line && ( !$_ || m/^mysqldump:/ ) ) { warn join( '.', @db ) . ': ' . $_; close $w; close $r; waitpid( $pid, 0 ); $first_line = 0; my $mysqlcheck = cPScript::MysqlUtils::find_mysqlcheck(); system( $mysqlcheck, '--repair', @db ); $pid = IPC::Open3::open3( $w, $r, '', $mysqldump, @options, @db ); } else { print {$fh} $_; } } } close $w; close $r; waitpid( $pid, 0 ); } ## e.g. invoked as './scripts/pkgacct $user' sub homedir_block { my ( $work_dir, $gid, $isbackup, $isuserbackup, $homedir, $user, $is_incremental_backup, $tarcfg ) = @_; print "Copying homedir...."; if ( -d $work_dir && !-l $work_dir ) { if ($has_lchown) { Lchown::lchown( 0, $gid, $work_dir ); } else { chown( 0, $gid, $work_dir ); } chmod( 0750, $work_dir ); } if ( -d "$work_dir/homedir" && !-l "$work_dir/homedir" ) { if ($has_lchown) { Lchown::lchown( 0, 0, "$work_dir/homedir" ); } else { chown( 0, 0, "$work_dir/homedir" ); } } mkdir( "$work_dir/homedir", 0700 ); chmod( 0700, "$work_dir/homedir" ); if ( my $pid = fork() ) { my $dotcount = 5; while ( waitpid( $pid, 1 ) != -1 ) { if ( !$isbackup && ( $dotcount % 15 == 0 || $dotcount == 5 ) ) { print ".........\n"; } dotsleep(); $dotcount++; } } else { if ( $isbackup || $isuserbackup ) { cPScript::SafeSync::build_cpbackup_exclude_conf( $homedir, $user ); } my $nfl_ref = {}; if ( !$is_incremental_backup ) { open( REALSTDOUT, ">&STDOUT" ); my $dest_tar_fname = "$work_dir/homedir.tar"; sysopen( my $dest_tar_fh, $dest_tar_fname, &Fcntl::O_WRONLY | &Fcntl::O_TRUNC | &Fcntl::O_CREAT, 0600 ); my $tar_reader_child_pid = open( my $tar_fh, '-|' ); if ($tar_reader_child_pid) { close(REALSTDOUT); waitpid( $tar_reader_child_pid, 0 ); ## this is the exit code from the tar child process in the else block my $exit_code = $? >> 8; my $out_of_disk_space = 0; my $permission_problem = 0; if ($exit_code) { my $partition_map = {}; my @partitions = (); my $filesys = cPScript::DiskLib::get_disk_used_percentage(); foreach my $key (keys %{$filesys}) { $partition_map->{$filesys->{$key}{'mount'}} = $filesys->{$key}{'available'}; push @partitions, { mount => $filesys->{$key}{'mount'}, key => $key }; } my $pwd = Cwd::getcwd(); my $mount_point = find_mount(\@partitions, $pwd); my $available = $partition_map->{$mount_point}; if ($available <= 10 || (!-e $dest_tar_fname || -z _)) { $out_of_disk_space = 1; } else { $permission_problem = 1; } } if ($permission_problem) { print "\nOne or more files in the home directory were not readable and were not copied. Please review the home directory upon completion of transfer\n\n"; } if ($out_of_disk_space) { ## case 20142: out of disk space on tar exec call print "\nERROR: tar of homedir returned error $exit_code\n"; ## env variable set when pkgacct is called from context of scripts/cpbackup ## (consider renaming ..._NOTIFY_ON_FAIL) if ( $ENV{'CPBACKUP_NOTIFY_FAIL'} ) { if ( my $pid = fork() ) { waitpid( $pid, 0 ); } else { require cPScript::Notify; require cPScript::Hostname; my $host = cPScript::Hostname::gethostname(); cPScript::Notify::notification ( 'application' => 'cpbackup', 'status' => 'account backup failure', 'priority' => 2, # Once per day 'interval' => 60 * 60 * 24, 'subject' => sprintf( "The backup of %s's account encountered errors on $host", $user ), 'message' => sprintf( "The backup of %s's account encountered errors\n" . "Please check /usr/local/cpanel/logs/error_log and %s for more information.", $ENV{'CPBACKUP_LOGFILE'}, $user ), 'msgtype' => '' ); exit; } } } } else { ## note: this is the block that performs the 'tar' of homedir # we need to do this before we setuid to ensure we are writing to the error log # so Cannot savedir: Permission denied # does not get into the pkgacct transfer screen and cause the transfer to abort if ( $ENV{'CPBACKUP'} ) { open( STDERR, '>&REALSTDOUT' ); } else { open( STDERR, '>>', '/usr/local/cpanel/logs/error_log' ); } if ( $> == 0 ) { cPScript::AccessIds::SetUids::setuids($user); } chdir($homedir) || exit; my @tarargs = ( '-c', '-f', '-' ); if ( $isbackup || $isuserbackup ) { if ( -r $homedir . '/cpbackup-exclude.conf' && -s _ ) { push @tarargs, '-X', $homedir . '/cpbackup-exclude.conf'; } if ( -r $cPScript::SafeSync::global_exclude && -s _ ) { push @tarargs, '-X', $cPScript::SafeSync::global_exclude; } } if ($isuserbackup) { push @tarargs, '--exclude', 'backup-[!_]*_[!-]*-[!-]*-[!_]*_' . $user . '*'; } open( STDOUT, ">&=" . fileno($dest_tar_fh) ); my $cmdstr = join( ' ', $tarcfg->{'bin'}, @tarargs, '.' ); exec( $tarcfg->{'bin'}, @tarargs, '.' ); die "Failed to execute: $cmdstr: $!"; } close($dest_tar_fh); my $parse_ok = 0; ( $parse_ok, $nfl_ref ) = cPScript::SafeSync::find_uid_files_from_tarball( "$work_dir/homedir.tar", [ 'cpanel', 'nobody' ] ); if ( !$parse_ok ) { #if we could not parse the tar file -t (--list) output then manually stat each file $nfl_ref = cPScript::SafeSync::find_uid_files( $homedir, [ 'cpanel', 'nobody' ] ); } } else { $nfl_ref = cPScript::SafeSync::safesync( 'pkgacct' => 1, #ignore ftp quota files 'user' => $user, 'gidlist' => [ 'cpanel', 'nobody' ], 'source' => $homedir, 'dest' => "$work_dir/homedir", 'chown' => 0, 'isbackup' => ( $isbackup || $isuserbackup ), 'delete' => 1, 'verbose' => 0 ); } chmod( 0700, "$work_dir/homedir" ); sysopen( my $nf_fh, "$work_dir/nobodyfiles", &Fcntl::O_WRONLY | &Fcntl::O_CREAT | &Fcntl::O_NOFOLLOW | &Fcntl::O_TRUNC, 0600 ); foreach my $file ( keys %$nfl_ref ) { next if ( $nfl_ref->{$file} ne 'nobody' ); chomp($file); $file =~ s/^\Q$homedir\E\/?//g; print {$nf_fh} $file . "\n"; } close($nf_fh); exit(); } print "Done\n"; } sub mysqldump_ver { my $mysqldump = cPScript::MysqlUtils::find_mysqldump(); if ( open( my $ver_h, "-|", "$mysqldump -V" ) ) { my $version_line = <$ver_h>; close($ver_h); if ( $version_line =~ /(\d+\.\d+)\.\d+/ ) { return $1; } else { return; } } } sub build_pkgtree { my($work_dir) = @_; mkdir( $work_dir, 0700 ); foreach my $dir ( 'cp', 'resellerconfig', 'logs', 'mysql', 'psql', 'mm', 'mms', 'mma', 'mma/pub', 'mma/priv', 'va', 'vad', 'fp', 'fp/sites','interchange', 'httpfiles', 'vf', 'cron', 'sslcerts', 'sslkeys', 'dnszones', 'counters', 'bandwidth', 'suspended', 'suspendinfo', 'userdata', 'meta', 'domainkeys', 'domainkeys/private', 'domainkeys/public', 'locale') { mkdir( "$work_dir/$dir", 0700 ); } } sub get_signal_num_from_name { ## $sr_loaded_signals and %signo passed in by reference, as they were formerly both global, and assigned ## to below... my($signame, $sr_loaded_signals, $hr_signo) = @_; if ( ! $$sr_loaded_signals ) { require Config; my $i = 0; foreach my $name ( split( ' ', $Config::Config{'sig_name'} ) ) { if ( $name =~ /USR/ ) { $hr_signo->{$name} = $i; } $i++; } $$sr_loaded_signals = 1; } return $hr_signo->{$signame}; } sub quickrun_stdin { my $bin = shift; my $stdin = shift; my $output; my $stdinpid = IPC::Open3::open3( my $wtrstdin, my $rdrstdin, ">&STDERR", $bin ); print {$wtrstdin} $stdin . "\n"; close($wtrstdin); while ( readline($rdrstdin) ) { $output .= $_; } close($rdrstdin); waitpid( $stdinpid, 0 ); return $output; } sub create_antitimeout_process { my $dotpid; if ( $dotpid = fork() ) { } else { my $dotcount = 5; while (1) { if ( $dotcount % 15 == 0 ) { print ".........\n"; } dotsleep(); $dotcount++; } } return $dotpid; } sub nooutputsystemsh { my (@cmd) = @_; my $pid; if ( $pid = fork() ) { #master } else { open( STDIN, "<", "/dev/null" ); open( STDOUT, ">", "/dev/null" ); open( STDERR, ">", "/dev/null" ); exec(@cmd); } waitpid( $pid, 0 ); } sub find_mount { my ( $partitions, $directory ) = @_; $directory =~ s/\/+/\//g; foreach my $mount ( sort { length $b->{'mount'} <=> length $a->{'mount'} } @{$partitions} ) { if ( cPScript::StringFunc::Match::beginmatch( $directory, $mount->{'mount'} ) ) { return $mount->{'mount'}; } } return '/'; } sub export_non_cpanel_locale { my ( $user, $dest ) = @_; my $user_file = cPScript::Config::LoadCpUserFile::loadcpuserfile($user); my $current_locale = $user_file->{'LOCALE'}; my $locale = Cpanel::Locale->get_handle(); #issafe #nomunge my $is_installed_locale = grep { $current_locale eq $_ } Cpanel::Locale::Utils::Display::get_locale_list($locale); #issafe #nomunge if ( !exists $Cpanel::Locale::Utils::3rdparty::cpanel_provided{$current_locale} && $is_installed_locale ) { #issafe #nomunge print "Copying locale ..."; system( '/scripts/locale_export', '--quiet', "--locale=$current_locale", "--export-${current_locale}=$dest/locale/${current_locale}.xml"); print "Done\n"; } } 1;