package Dumper; use strict; use XmlNode; use AgentConfig; use Mailman; use Parser; use Packer; use SpamAssassinCfg; use Logging; use HelpFuncs; use Db::DbConnect; use Db::MysqlUtils; use Db::Connection; use EncodeBase64; my $DEFAULT_RESELLER = 'root'; my %mapLimits = ( 'max_addon' => 'MAXADDON', 'max_parked' => 'MAXPARK', 'max_ftpusers' => 'MAXFTP', 'max_maillists' => 'MAXLST', 'max_box' => 'MAXPOP', 'max_db' => 'MAXSQL', 'max_subdom' => 'MAXSUB', ); # List of cPanel features. # Features marked as 2 are included to the dump. # Do not forget change cPanel.xsd, if change flags. my %mapFeatures = ( 'addoncgi' => 1, #Addon Cgi Scripts 'addondomains' => 2, #Addon Domain Manager 'advguest' => 1, #Advanced Guestbook 'agora' => 1, #Agora Shopping Cart 'analog' => 1, #Analog Stats 'emailauth' => 1, #Email Authentication 'autoresponders' => 1, #Autoresponder Manager 'awstats' => 1, #Awstats Stats 'backup' => 2, #Backup Manager 'bandwidth' => 1, #Bandwidth Stats 'bbs' => 1, #PHPBB2 'blockers' => 1, #Email Filtering Manager 'boxtrapper' => 1, #BoxTrapper Spam Trap 'cgi' => 1, #CGI Center 'changemx' => 1, #Ability to Change MX 'chat' => 1, #Chat Rooms 'clamavconnector_scan' => 2, #Virus Scanner 'clock' => 1, # Java Clock 'countdown' => 1, #Java Countdown 'counter' => 1, #Counter 'cpanelpro_support' => 1, #Support System Submission 'cpanelpro_images' => 1, #Image Manager 'cron' => 2, #Crontab 'defaultaddress' => 1, #Default Address Manager 'diskusageviewer' => 1, #Disk Usage Viewer 'emaildomainfwd' => 1, #Email Domain Forwarding 'emailscripts' => 1, #Email Scripts (cgiemail,formmail) 'entropybanner' => 1, #Entropy Banner 'entropysearch' => 1, #Entropy Search 'errlog' => 1, #Error Log 'errpgs' => 1, #Customer Error Pages 'fantastico' => 1, # 'filemanager' => 1, #File Manager 'forwarders' => 1, #Forwarder Manager 'frontpage' => 1, #Frontpage 'ftpaccts' => 1, #Ftp Account Manager 'ftpsetup' => 2, #Ftp Settings 'getstart' => 1, #Getting Started Wizard 'guest' => 1, #Simple Guestbook 'handlers' => 1, #Apache Handlers Manager 'hotlink' => 1, #Hotlink 'ipdeny' => 1, #IP Deny Manager 'indexmanager' => 1, #File Manager Directory Selection 'interchange' => 1, #Interchange Shopping Cart 'lastvisits' => 1, #Latest Visitors 'cpanelpro_leechprotect' => 1, #Leech Protect 'lists' => 2, #Mailman List Manager 'mime' => 1, #Mime Types Manager 'modules-perl' => 1, #Install Perl Modules 'modules-php-pear' => 1, #Install PHP Pear Modules 'modules-ruby' => 1, #Install Ruby Modules 'mysql' => 1, #Mysql 'nettools' => 1, #Network Tools 'parkeddomains' => 2, #Parked Domain Manager 'password' => 2, #Password Change 'pgp' => 1, #PGP/GPG 'php-config' => 1, #See PHP Configuration 'phpmyadmin' => 1, #PhpMyAdmin 'phppgadmin' => 1, #PhpPgAdmin 'popaccts' => 1, #Email Account Manager 'postgres' => 1, #PostgresSQL 'randhtml' => 1, #Random Html Generator 'rawlog' => 2, #Raw Access Logs 'redirects' => 1, #Redirect Manage 'ror' => 1, #Ruby on Rails 'scgiwrap' => 1, #Simple Cgi Wrapper 'searchsubmit' => 1, #Search Engine Submit Tool 'serverstatus' => 1, #Server Status Viewer 'setlang' => 1, #Change Language 'spamassassin' => 2, #SpamAssassin 'spambox' => 1, #SpamAssassin Spam Box 'ssh' => 1, #SSH Connection Window 'sslinstall' => 1, #SSL Host Installer 'sslmanager' => 1, #SSL Manager 'statmanager' => 2, #Statistics Program Manager 'statselect' => 1, #Choose Log Programs 'style' => 1, #Change Style 'subdomains' => 2, #Subdomain Manager 'subdomainstats' => 1, #Subdomain Stats 'traceaddy' => 1, #Ability to Trace an email address 'updatecontact' => 1, #Update Contact Information 'videotut' => 1, #Video Tutorials 'webalizer' => 1, #Webalizer Stats 'webdisk' => 1, #Web Disk 'webmail' => 1, #Webmail 'webprotect' => 1, #Webprotect 'backupwizard' => 1, #Backup Wizard ); # # global variables # my $ptrQuota; my $ptrUserdomainsHash; my %trueUserDomains; my %anonftps; my %accountNodes; my $_parsedHttpdConf; my %_serversIps; my %_apacheAliases; my %_docRoots; my %_scriptAliases; my (%cPanelConfig); my (%featureList); my $installDir = '/var/cpanel'; # # end global variables # my $wrapUserMysql; # # Initialization routine # sub initialize { loadMainConfig(); my @all_accounts = getAllAccounts(); %featureList = loadFeatureList(); loadQuota(); } # # Parse main config file, # Initialize $installDir and cPanelConfig{'engineroot'} # sub loadMainConfig { my $pathToConf = shift || ''; my $ptrHash = shift || \%cPanelConfig; unless ( -T $pathToConf ) { foreach my $prefix ( './', '/var/cpanel/' ) { $pathToConf = $prefix . 'cpanel.config'; if ( -T $pathToConf ) { last; } } } if ( -T $pathToConf ) { if ( ref($ptrHash) =~ /HASH/ ) { if ( $pathToConf =~ /(.+)\/[^\/]+/ ) { $ptrHash->{'installDir'} = $1; } if ( open( CF, "<$pathToConf" ) ) { binmode CF; my ( $key, $value ); while ( my $line = ) { chomp $line; next unless $line; next if ( $line =~ /^#/ ); ( $key, $value ) = split( /\s*=\s*/, $line, 2 ); $key =~ s/^\s+//; $ptrHash->{$key} = $value; } close(CF); } } } else { $pathToConf = ''; } $installDir = $cPanelConfig{'installDir'} if defined ( $cPanelConfig{'installDir'} ); return $pathToConf; } # Returns password for system account $user sub getSystemPassword { my ($user) = @_; my $password; my @pw = getpwnam($user); if (-1 != $#pw) { $password = $pw[1]; } unless ($password) { $password = ''; } if (substr($password,0,8) eq '*LOCKED*') { $password = substr($password,8); } elsif (substr($password,0,2) eq '!!') { $password = substr($password,2); } return $password; } sub isUserLocked { my ($user) = @_; my @pw = getpwnam($user); if (-1 != $#pw) { if (my $passwd = $pw[1]) { if ( (substr($passwd,0,8) eq '*LOCKED*') || (substr($passwd,0,2) eq '!!') ) { return 1; } } } return; } sub getSuspendedShell { my ($account) = @_; my $fileParser = Parser::makeSafeFileParser("$installDir/suspendinfo/$account"); if ( defined $fileParser ) { my $ptrUserHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' ); if ( ref($ptrUserHash) =~ /HASH/ ) { return $ptrUserHash->{'shell'}; } } return; } # Returns home directory for the system account $user sub getHomeDir { my ($user) = @_; my $home; my @pw = getpwnam($user); if (-1 != $#pw) { $home = $pw[7]; } unless ($home) { # how it is by default $home = '/home/$account'; } return $home; } ####################################################### # # Get all client accounts, registered in the system # ####################################################### sub getAllAccounts { my @all_accounts; my $users_dir = "$installDir/users"; opendir( USRS, $users_dir ) || die "Can't open directory $users_dir: $!"; my @accts = readdir(USRS); closedir(USRS); chomp(@accts); # remove duplicate and possible wrong entries foreach my $account (@accts) { next if ( $account =~ /\./ || grep( /^$account$/, @all_accounts ) ); push @all_accounts, $account; } return @all_accounts; } sub getServersIps { parseHttpdConf(); return \%_serversIps; } sub getApacheAliases { parseHttpdConf(); return \%_apacheAliases; } sub getDocRoots { parseHttpdConf(); return \%_docRoots; } sub getScriptAliases { parseHttpdConf(); return \%_scriptAliases; } sub parseHttpdConf { if ( $_parsedHttpdConf ) { return 1; } my $httpdconf_file; if (-e "/etc/httpd/conf/httpd.conf") { $httpdconf_file = "/etc/httpd/conf/httpd.conf"; }elsif (-e "/usr/local/apache/conf/httpd.conf") { $httpdconf_file = "/usr/local/apache/conf/httpd.conf"; } unless ( open( INPUT, "<$httpdconf_file" ) ) { Logging::error("Error: unable to open '$httpdconf_file': $!"); return; } binmode INPUT; my $inVirtualHost; #for %_docRoots my ( $docroot ); #for %_serversIps my ( $ip, @vDomains ); #for %_apacheAliases my ( $vserver, %valiases ); #for %_scriptAliases my ( $scriptalias ); $inVirtualHost = 0; while () { chomp; next unless $_; next if (/^#/); if ( $inVirtualHost == 1 ) { if (/<\/VirtualHost>/) { foreach my $vDomain (@vDomains) { $vDomain =~ s/^www\.//; $_serversIps{$vDomain} = $ip; $_docRoots{$vDomain} = $docroot if $docroot; $_scriptAliases{$vDomain} = $scriptalias if $scriptalias; } $docroot = ''; $scriptalias = ''; @vDomains = (); if ( $vserver && ( keys %valiases ) ) { push @{ $_apacheAliases{$vserver} }, keys %valiases; } $vserver = ''; %valiases = (); $inVirtualHost = 0; } elsif (/^\s*ServerName\s+(\S+)/) { $vserver = $1; $vserver =~ s/^www\.//; push @vDomains, split( ' ', $1 ); } elsif (/^\s*ServerAlias\s+(.*)/) { map { s/^www\.//; $valiases{$_} = 1 } split( ' ', $1 ); push @vDomains, split( ' ', $1 ); } elsif (/^\s*DocumentRoot\s+(.*)/) { $docroot = $1; if ( substr($docroot,-1,1 ) eq '/' ) { chop $docroot; } } elsif (/^\s*ScriptAlias\s+\/cgi-bin\/\s+(.*)/) { $scriptalias = $1; if ( substr($scriptalias,-1,1 ) eq '/' ) { chop $scriptalias; } } } else { if (//) { $ip = $1; $docroot = ''; $scriptalias = ''; @vDomains = (); $vserver = ''; %valiases = (); $inVirtualHost = 1; } } } close(INPUT); $_parsedHttpdConf = 1; } my $_parsedErrdocConf; my %_errdocPaths; sub getErrdocPaths { parseErrdocConf(); return \%_errdocPaths; } sub parseErrdocConf { if ( $_parsedErrdocConf ) { return 1; } my $errdocconf_file = "/etc/httpd/conf/includes/errordocument.conf"; unless ( -e $errdocconf_file ) { $errdocconf_file = "/usr/local/apache/conf/includes/errordocument.conf"; } unless ( open( INPUT, "<$errdocconf_file" ) ) { Logging::error("Error: unable to open '$errdocconf_file': $!"); return; } binmode INPUT; while () { chomp; next unless $_; next if (/^#/); if (/^\s*ErrorDocument\s+(\d{3})\s+(.*)/) { my $errcode = $1; my $docpath = $2; $_errdocPaths{$errcode} = $docpath; } } close(INPUT); $_parsedErrdocConf = 1; } sub loadQuota { # Get disk quotas my $fileUser = Parser::makeFileParser("/etc/quota.conf"); $ptrQuota = $fileUser->{'PARSE'}->('KEYSEPARATOR' => '=','VALUESEPARATOR' => ''); unless ( ref($ptrQuota) =~ /HASH/ ) { $ptrQuota = {}; } return $ptrQuota; } sub loadFeatureList { my %list; my $features_dir = "$installDir/features"; opendir( FLST, $features_dir ) || die "Can't open directory $features_dir: $!"; my @lsts = readdir(FLST); closedir(FLST); foreach my $lst (@lsts) { my $cfg = "$features_dir/$lst"; if ( -T $cfg ) { Logging::info("Reading feature list: $lst"); my $cfgParser = Parser::makeFileParser($cfg); my $values = $cfgParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' ); $list{$lst} = $values; Logging::info("Feature list '$lst' is read"); } } #convert disabled options if ( exists $list{'disabled'} ) { Logging::info("Apply disabled feature list"); foreach my $key ( keys %list ) { if ( not $key eq 'disabled' ) { my $vals = $list{$key}; foreach my $diskey ( keys %{ $list{'disabled'} } ) { ${$vals}{$diskey} = 0 if ${ $list{'disabled'} }{$diskey} == 0 and exists ${$vals}{$diskey}; } } } } #create default if ( exists $list{'default'} ) { Logging::info("Apply default feature list"); foreach my $mapkey ( keys %mapFeatures ) { ${ $list{'default'} }{$mapkey} = 1 if not exists ${ $list{'default'} }{$mapkey}; } } return %list; } sub getUserDomains { unless ( keys %trueUserDomains ) { my $fileParser = Parser::makeFileParser('/etc/trueuserdomains'); my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' ); if ( ref($ptrHash) =~ /HASH/ ) { %trueUserDomains = ( %{$ptrHash} ); } } return \%trueUserDomains; } sub getDomainName { my $account = shift; my $userDomains = getUserDomains(); if ( ref($userDomains) =~ /HASH/ ) { my ( $domain, $owner ); while ( ( $domain, $owner ) = each( %{$userDomains} ) ) { if ( $owner eq $account ) { return $domain; } } } return; } sub getDomainOwner { my $domain = shift; my $userDomains = getUserDomains(); if ( ref($userDomains) =~ /HASH/ ) { return $userDomains->{$domain}; } return; } sub getAccountEmail { my $account = shift; my $home = getHomeDir($account); my $email = ''; if ( -T "$home/.contactemail" ) { my $fileParser = Parser::makeFileParser("$home/.contactemail"); my $ptrLines = $fileParser->{'PARSE'}->(); if ( ref($ptrLines) =~ /ARRAY/ ) { $email = $ptrLines->[0] || ''; } } return $email; } sub makeAccountNode { my ( $account, $ptrUserHash, $isReseller, $shallowMode ) = @_; my ( $ptrLines, $ptrHash, $xmlKey, $userKey, $value, $key, $domain, $item ); unless ($shallowMode) { if ( exists $accountNodes{ $account } ) { return $accountNodes{ $account }; } } my $root = XmlNode->new('account'); $root->setAttribute( 'name', $account ); $root->setAttribute( 'home', getHomeDir($account) ); $root->setAttribute( 'email', getAccountEmail($account) ) unless ($shallowMode); unless ( ref($ptrUserHash) =~ /HASH/ ) { my $fileParser = Parser::makeFileParser("$installDir/users/$account"); $ptrUserHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => '=' ); } unless ($shallowMode) { if ( ref($ptrUserHash) =~ /HASH/ ) { $root->setAttribute( 'date', HelpFuncs::epoch2CrDate( $ptrUserHash->{'STARTDATE'} ) ); } if ( isUserLocked( $account ) ) { $root->addChild(XmlNode->new('suspended')); } unless ($isReseller) { my $diskSpace = $ptrQuota->{$account}; if ( $diskSpace =~ /(\d+)/ ) { $diskSpace = $1 * 1024 * 1024; $root->addChild(Packer::makeLimitNode('disk_space', $diskSpace)) if $diskSpace; } if ( ref($ptrUserHash) =~ /HASH/ ) { # handle limits $mapLimits{'max_traffic'} = 'BWLIMIT'; while ( ( $xmlKey, $userKey ) = each(%mapLimits) ) { $value = $ptrUserHash->{$userKey}; if ( $value =~ /\d+/ ) { $root->addChild(Packer::makeLimitNode($xmlKey, $value)); } else { $root->addChild(Packer::makeLimitNode($xmlKey)); } } delete $mapLimits{'max_traffic'}; ## clean map } } if ( ref($ptrUserHash) =~ /HASH/ ) { makeFeaturesNode( $root, $ptrUserHash->{'FEATURELIST'} ) if exists $ptrUserHash->{'FEATURELIST'}; } # SpamAssssin migration makeSpamassassinNode( $root, $account ); } # # Handle domain # $domain = getDomainName($account); if (defined $domain) { Logging::info("Domain '$domain' is started"); my $ip = getIp($domain, $ptrUserHash) || getMainIP(); $item = Packer::makeIpNode($ip); $root->addChild($item); $item = makeDomainNode( $domain, $account, $shallowMode ); $root->addChild($item); Logging::info("Domain '$domain' is dumped"); } unless ($shallowMode) { $accountNodes{$account} = $root; } return $root; } sub makeFeaturesNode { my ( $root, $feature ) = @_; if ( exists $featureList{$feature} ) { my $fvalues = $featureList{$feature}; foreach my $fkey ( keys %mapFeatures ) { if ( 2 == $mapFeatures{$fkey} and exists ${$fvalues}{$fkey} ) { my $fnode = XmlNode->new( 'feature' ); $fnode->setAttribute( 'name', $fkey ); if ( ${$fvalues}{$fkey} ) { $fnode->setAttribute( 'allowed', 'true' ); } else { $fnode->setAttribute( 'allowed', 'false' ); } $root->addChild($fnode); } } return $root; } else { Logging::info("Cannot get featureList '$feature'"); } return; } sub getIp { my ( $domain, $ptrUserHash ) = @_; my $serversIps = getServersIps(); my ( $ptrRows, $ip, $item, $dmn ); unless ( $ip = $serversIps->{$domain} ) { my $fileParser = Parser::makeFileParser("$installDir/accts.db"); $ptrRows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ',' ); foreach my $ptrRow ( @{$ptrRows} ) { $dmn = $ptrRow->[0]; $dmn =~ s/^\s+//; $dmn =~ s/\s+$//; if ( $ptrRow->[2] =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ ) { $ip = $1; unless ( exists( $serversIps->{$domain} ) ) { $serversIps->{$domain} = $ip; } } } unless ( $ip = $serversIps->{$domain} ) { unless ( $ip = $ptrUserHash->{'IP'} ) { my $zone = "/var/named/$domain.db"; my $pattern = qr/^\Q$domain\E\.\s+(?:\d+\w?\s+)IN\s+A\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/; if ( -T $zone ) { if ( open( INPUT, "<$zone" ) ) { binmode INPUT; while () { if (/$pattern/) { $ip = $1; last; } } close(INPUT); if ($ip) { $serversIps->{$domain} = $ip; } } } } } } return $ip; } my %_accountSubdomains; sub getSubdomains { my ( $account ) = @_; if ( exists $_accountSubdomains{ $account } ) { return $_accountSubdomains{ $account }; } my $domain = getDomainName( $account ); my @subdomains = (); unless ( ref($ptrUserdomainsHash) =~ /HASH/ ) { my $fileParser = Parser::makeFileParser('/etc/userdomains'); $ptrUserdomainsHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' ); } if ( ref($ptrUserdomainsHash) =~ /HASH/ ) { my $apacheAliases = getApacheAliases(); my $pattern = qr/^(.+)\.\Q$domain\E$/; while ( ( my $subdomain, my $acc ) = each %{$ptrUserdomainsHash} ) { next unless ( $acc eq $account ); # Parked domain names could be like subdomains, # To drop them out let's look into apacheAliases next unless ( exists $apacheAliases->{ $subdomain } ); if ( $subdomain =~ /$pattern/ ) { push @subdomains, $1; } } } $_accountSubdomains{ $account } = \@subdomains; return $_accountSubdomains{ $account }; } my %_proftpdAccountRows; sub getProftpdAccountRows { my ( $account ) = @_; if ( exists $_proftpdAccountRows{ $account } ) { return $_proftpdAccountRows{ $account }; } my $rows = _getProftpdAccountRows("/etc/proftpd/$account.suspended"); $rows = _getProftpdAccountRows("/etc/proftpd/$account") unless defined $rows; if ( defined $rows ) { $_proftpdAccountRows{ $account } = $rows; return $_proftpdAccountRows{ $account }; } return; } sub _getProftpdAccountRows { my ( $file ) = @_; if ( -T $file ) { my $fileParser = Parser::makeFileParser($file); my $rows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':', 'KEYSEPARATOR' => '', ); if ( ref($rows)=~/ARRAY/ ) { return $rows; } } return; } my %_accountFtpusers; sub getFtpUsers { my ( $account ) = @_; if ( exists $_accountFtpusers{ $account } ) { return $_accountFtpusers{ $account }; } my $home = getHomeDir( $account ); my $ftpquota; my $ftpquota_file = "$home/etc/ftpquota"; if ( -e $ftpquota_file ) { Logging::info("Parse ftp quota '$ftpquota_file'"); my $ftpquotaParser = Parser::makeFileParser($ftpquota_file); $ftpquota = $ftpquotaParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' ); } my %ftpUsers; my $ptrProftpdRows = getProftpdAccountRows( $account ); if ( defined $ptrProftpdRows ) { foreach my $ptrRow ( @{$ptrProftpdRows} ) { my $user = $ptrRow->[0]; if ( $user eq $account ) { my $password = $ptrRow->[1]; if ( '*' eq $password ) { $password = getSystemPassword($account); if ( '*' eq $password ) { $password = ''; } } my $shell = getSuspendedShell($account) || $ptrRow->[6]; $ftpUsers{ $account } = [ $password, undef, $shell ]; } elsif ( $ptrRow->[4] eq $account ) { my $pattern = qr/\Q$home\E\/(.*)/; my $path = $ptrRow->[5]; if ( $path =~ /$pattern/ ) { my $password = ( $ptrRow->[1] ne '*' )? $ptrRow->[1] : ''; my $directory = $1; my $shell = $ptrRow->[6]; my $quota; if ( ref( $ftpquota ) =~ /HASH/ ) { $quota = $ftpquota->{$user}; # undef is possible } $ftpUsers{ $user } = [ $password, $directory, $shell, $quota ]; } } } } $_accountFtpusers{ $account } = \%ftpUsers; return $_accountFtpusers{ $account }; } sub makeDomainNode { my ( $domain, $account, $shallowMode ) = @_; my $root = XmlNode->new('domain'); $root->setAttribute( 'name', $domain ); my $docRoots = getDocRoots(); $root->setAttribute( 'www_root', $docRoots->{$domain} ) if $docRoots->{$domain}; my $cgiRoots = getScriptAliases(); $root->setAttribute( 'cgi_root', $cgiRoots->{$domain} ) if $cgiRoots->{$domain}; my ( @ftpusersdirs, @subdompatterns, ); my $home = getHomeDir($account); my $subdomains = getSubdomains( $account ); my $ftpusers = getFtpUsers( $account ); unless ($shallowMode) { my $anonftpNode = makeAnonFtpdNode($account); $root->addChild($anonftpNode) if $anonftpNode; } addFtpUserNodes($root, $domain, $account, $ftpusers, $subdomains); unless ($shallowMode) { my @pdirs = getProtectedDirs($root, $domain, $account, $subdomains); foreach my $pdir ( @pdirs ) { my $title = getProtectedDirTitle($account, $pdir); my $users = getProtectedDirUsers($account, $pdir); $root->addChild( Packer::makeProtectedDirNode($pdir, $title, $users) ); } } addSubDomains($root, $domain, $account, $subdomains, $shallowMode); my $addondomains = getAddOnDomains( $domain ); addAddOnDomains($root, $account, $addondomains); unless ($shallowMode) { addDefaultMailname($root, $domain, $account); addMailnames($root, $domain, $account); addMailAliases($root, $domain, $account); addDatabases($root, $account); addMailLists($root, $domain, $account); } return $root; } sub addMailLists { my ( $root, $domain, $account) = @_; unless ( defined Mailman::version() ) { Logging::info("Unable to found Mailman installation. Skip dumping maillists."); return; } Logging::info("Get maillists of '$domain'"); my $maillists = XmlNode->new( 'maillists' ); my $handle = unsuspendDomainMaillists($domain); my @lists = Mailman::getMailLists($domain); foreach my $listname (@lists) { Logging::info("Found maillist '$listname'"); my @owners = Mailman::getListOwners($listname); if (@owners) { if ($listname =~ m/(.*)(_$domain)/) { my $maillist = XmlNode->new( 'maillist' ); $maillist->setAttribute( 'name', $1); for my $listOwner (@owners) { my $owner_node = XmlNode->new( 'owner' ); $owner_node->setText( $listOwner ); $maillist->addChild($owner_node); } $maillist->addChild( Packer::makePasswordNode( Mailman::getListPassword($listname) , 'encrypted' )); my %listMembers = Mailman::getListMembers($listname); for my $memberEmail ( keys %listMembers ) { my $member = XmlNode->new( 'recipient' ); $member->setText( $memberEmail ); if ( $listMembers{$memberEmail} ne "" ) { $member->setAttribute( 'fullname', $listMembers{$memberEmail} ); } $maillist->addChild($member); } $maillists->addChild($maillist); } } else { Logging::info("Bad maillist '$listname', cannot find owner, skipped"); } } suspendDomainMaillists($handle); $root->addChild($maillists); return $maillists; } sub addDatabases { my ( $root, $account ) = @_; addMySQLDatabases( $root, $account ); addPgSQLDatabases( $root, $account ); return $root; } sub addMySQLDatabases { my ( $root, $account ) = @_; my ( $ptrRow, $item, $dumpFile, $sql, @dbNames ); unless ( ( ref($wrapUserMysql) =~ /HASH/ ) && ( ref( $wrapUserMysql->{'EXECUTE'} ) =~ /CODE/ ) ) { $wrapUserMysql = Db::DbConnect::getDbConnect( 'mysql', 'root', undef, 'mysql', 'localhost' ); } my $datadir; $sql = "SHOW variables LIKE 'datadir'"; if ( $wrapUserMysql->{'EXECUTE'}->($sql) and $ptrRow = $wrapUserMysql->{'FETCHROW'}->() ) { $datadir = $ptrRow->[1]; } $wrapUserMysql->{'FINISH'}->(); $sql = "SELECT DISTINCT db FROM db WHERE db like '" . $account . "\\\\\\\_%'"; if ( $wrapUserMysql->{'EXECUTE'}->($sql) ) { while ( $ptrRow = $wrapUserMysql->{'FETCHROW'}->() ) { push @dbNames, $ptrRow->[0]; } } $wrapUserMysql->{'FINISH'}->(); foreach my $database (@dbNames) { $database =~ s/\\_/_/g; next unless -d $datadir . $database; $item = XmlNode->new('database'); $item->setAttribute( 'name', $database ); $item->setAttribute( 'type', 'mysql' ); $item->setAttribute( 'version', Db::MysqlUtils::getVersion() ); Logging::info("Started $database dumping..."); Db::DbConnect::addDbUsers( $item, $database, $wrapUserMysql ); _unsuspendMysqlUsersPasswords($item); $root->addChild($item); Logging::info("done"); } return; } sub _unsuspendMysqlUsersPasswords { my ( $root ) = @_; XPath::Select $root, 'dbuser/password', sub { my $passwordNode = shift; my $password = $passwordNode->getText('password'); $password = '*' x 41 unless $password; if ( length( $password ) == 41 ) { if ( substr($password,0,1) eq '-' ) { $password =~ s/\-//; if ( $password =~ m/^\*+$/ ) { $password = ''; } else { $password = reverse $password; $password = '*' . $password; } } else { if ( $password =~ m/^\+/ ) { $password =~ s/\+/\*/; } } } elsif ( length( $password ) == 40 ) { $password = '*' . $password; if ( $password =~ m/^\*+$/ ) { $password = ''; } } $passwordNode->setText($password); }; } sub addPgSQLDatabases { my ( $root, $account ) = @_; my ( $ptrRow, $item, $dumpFile, $sql, @dbNames, %dbUsers ); if ( !AgentConfig::psqlBin() ) { return; } $Db::Connection::UsePostgreShellBackendAsSudo = 1; my $wrapUserPgsql = getDbConnect( 'postgresql', 'postgres', undef, 'template1', 'localhost' ); if ( ref($wrapUserPgsql) eq 'HASH' ) { if ( ref( $wrapUserPgsql->{'EXECUTE'} ) eq 'CODE' and ref( $wrapUserPgsql->{'FETCHROW'} ) eq 'CODE' and ref( $wrapUserPgsql->{'FINISH'} ) eq 'CODE' ) { if ( $wrapUserPgsql->{'EXECUTE'}->( "select datname from pg_database where datname like '$account\_\_%'" ) ) { while ( $ptrRow = $wrapUserPgsql->{'FETCHROW'}->() ) { Logging::info( "Found database: " . $ptrRow->[0] ); push @dbNames, $ptrRow->[0]; } $wrapUserPgsql->{'FINISH'}->(); if ( $wrapUserPgsql->{'EXECUTE'}->( "select usename, passwd from pg_shadow where usename like '$account\_\_%'" ) ) { while ( $ptrRow = $wrapUserPgsql->{'FETCHROW'}->() ) { Logging::info( "Found database user: " . $ptrRow->[0] ); $dbUsers{ $ptrRow->[0] } = $ptrRow->[1]; } $wrapUserPgsql->{'FINISH'}->(); } } } else { Logging::info("Connection to postgresql is not valid"); } } else { Logging::info("Cannot connect to postgresql"); } my $psql = AgentConfig::psqlBin(); my @out = `$psql --version | awk '{print \$3}'`; my $version = ''; chomp $out[0]; if ( $out[0] =~ /(\d+\.\d+\.\d+).*/ ) { $version = $1; } foreach my $database (@dbNames) { $item = XmlNode->new('database'); $item->setAttribute( 'name', $database ); $item->setAttribute( 'type', 'postgresql' ); $item->setAttribute( 'version', $version ); Logging::info("Started $database dumping..."); Logging::info("Started $database user dumping..."); if ( $wrapUserPgsql->{'EXECUTE'} ->("select datacl from pg_database where datname='$database'") ) { while ( $ptrRow = $wrapUserPgsql->{'FETCHROW'}->() ) { Logging::info( "Parse DACL: " . $ptrRow->[0] ); my @usernames = keys(%dbUsers); foreach my $uname (@usernames) { if ( $ptrRow->[0] =~ m/.*$uname\=.*/m ) { Logging::info("User '$uname' granted to '$database"); my $useritem = XmlNode->new( 'dbuser' ); $useritem->setAttribute( 'name', $uname ); my $pwditem = Packer::makePasswordNode( $dbUsers{$uname}, 'encrypted' ); if ($pwditem) { $useritem->addChild($pwditem); } $item->addChild($useritem); } } } $wrapUserPgsql->{'FINISH'}->(); } Logging::info("$database user dumping done"); $root->addChild($item); Logging::info("done"); } return; } sub addSubDomains { my ( $root, $domain, $account, $ptrSubDomains, $shallowMode ) = @_; my $docRoots = getDocRoots(); my $cgiRoots = getScriptAliases(); foreach my $subdomain ( @{$ptrSubDomains} ) { Logging::info( "Subdomain '$subdomain.$domain' ..." ); my $subdomainRoot; $subdomainRoot = $docRoots->{"$subdomain.$domain"} if $docRoots->{"$subdomain.$domain"}; my $subdomainCgiRoot; $subdomainCgiRoot = $cgiRoots->{"$subdomain.$domain"} if $cgiRoots->{"$subdomain.$domain"}; my $nodeSubDomain = XmlNode->new('subdomain'); $nodeSubDomain->setAttribute( 'name', $subdomain, 'noencode' ); $nodeSubDomain->setAttribute( 'www_root', $subdomainRoot) if $subdomainRoot; $nodeSubDomain->setAttribute( 'cgi_root', $subdomainCgiRoot) if $subdomainCgiRoot; my $ftpusers = getFtpUsers( $account ); if ( exists $ftpusers->{$subdomain} ) { my ( $password, $directory, $shell, $quota ) = @{$ftpusers->{$subdomain}}; my $ftpUserNode = Packer::makeFtpUserNode( $subdomain, $password, $directory, $shell, $quota); $nodeSubDomain->addChild($ftpUserNode); } # Dump mail system and addon domains: if there is 1 addon domain for # given subdomain - dump mail system of ADDON DOMAIN, not subdomain. # This case reflects common usage of addon domains in cPanel... my $addondomains = getAddOnDomains( "$subdomain.$domain" ); addAddOnDomains( $nodeSubDomain, $account, $addondomains ); unless ($shallowMode) { addMailnames( $nodeSubDomain, "$subdomain.$domain", $account ); addMailAliases( $nodeSubDomain, "$subdomain.$domain", $account ); } $root->addChild($nodeSubDomain); Logging::info(' OK'); } return $root; } sub getAddOnDomains { my ( $domain ) = @_; my $apacheAliases = getApacheAliases(); my @found; if ( exists $apacheAliases->{$domain} ) { my %aliases = map { $_ => 1 } @{ $apacheAliases->{$domain} }; foreach my $alias ( keys %aliases ) { next if exists $apacheAliases->{$alias}; push @found, $alias; } } return \@found; } sub addAddOnDomains { my ( $root, $account, $aliases ) = @_; if ( ref($aliases) =~ /ARRAY/ ) { foreach my $alias ( @{$aliases} ) { my $addonDomainNode = XmlNode->new( 'addondomain' ); $addonDomainNode->setAttribute( 'name', $alias ); addMailnames($addonDomainNode, $alias, $account); addMailAliases($addonDomainNode, $alias, $account ); $root->addChild($addonDomainNode); } } return $root; } my %_userParams; sub getUserParams { my $user = shift; unless ( keys %_userParams ) { my $passwdReader = Parser::makeFileParser("/etc/passwd"); if (defined($passwdReader)) { %_userParams = %{$passwdReader->{'PARSE'}->('KEYSEPARATOR' => ':', 'VALUESEPARATOR' => ':')}; } } return $_userParams{$user}; } sub addDefaultMailname { my ( $parentNode, $domain, $account ) = @_; my $mailNode = $parentNode->getChild( 'mail', 1 ); my $home = getHomeDir($account); Logging::info("Dumping default mailname for account '$account'"); my $mailnameNode = XmlNode->new('mailname'); $mailnameNode->setAttribute( 'name', $account ); $mailnameNode->setAttribute( 'domainname', $domain ); Logging::info( "migrating " . $account . "@" . "$domain" ); my $password = getSystemPassword($account); $mailnameNode->setAttribute( 'password', $password ); $mailNode->addChild($mailnameNode); } sub addMailnames { my ( $parentNode, $domain, $account ) = @_; my $mailNode = $parentNode->getChild( 'mail', 1 ); my $home = getHomeDir($account); Logging::info("Examining $home/etc/$domain"); if ( -d "$home/etc/$domain" && -T "$home/etc/$domain/passwd" ) { my $fileParser = Parser::makeFileParser("$home/etc/$domain/passwd"); my $ptrRows = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':' ); if ( ref($ptrRows) =~ /ARRAY/ ) { foreach my $ptrRow ( @{$ptrRows} ) { my $box = $ptrRow->[0]; next if ( $box eq $account); my $mailnameNode = XmlNode->new('mailname'); $mailnameNode->setAttribute( 'name', $box ); $mailnameNode->setAttribute( 'domainname', $domain ); Logging::info( "migrating " . $box . "@" . "$domain" ); my $password = ''; my $shadowParser = Parser::makeFileParser("$home/etc/$domain/shadow"); my $ptrHash = $shadowParser->{'PARSE'}->( 'KEYSEPARATOR' => ':', 'VALUESEPARATOR' => '' ); if ( ref($ptrHash) =~ /HASH/ ) { if ( my $value = $ptrHash->{$box} ) { ($password) = split( ':', $value ); } } if (substr($password,0,8) eq '*LOCKED*') { $password = substr($password,8); } elsif (substr($password,0,2) eq '!!') { $password = substr($password,2); } $mailnameNode->setAttribute( 'password', $password ); if ( -T "$home/etc/$domain/quota" ) { my $quotaParser = Parser::makeFileParser("$home/etc/$domain/quota"); $ptrHash = $quotaParser->{'PARSE'}->( 'KEYSEPARATOR' => ':', 'VALUESEPARATOR' => '' ); if ( ref($ptrHash) =~ /HASH/ && ( my $value = $ptrHash->{$box} ) ) { $mailnameNode->setAttribute( 'quota', sprintf( "%.0f", $value ) ); } } $mailNode->addChild($mailnameNode); } } } } sub addMailAliases { my ( $parentNode, $domain, $account ) = @_; my $mailNode = $parentNode->getChild( 'mail', 1 ); my ( $ptrRows, $default, $item, $box, $ptrHash, $value, $password, $dumpFile, $ptrAliases, $src, $dst ); my $home = getHomeDir($account); my $mainDomain = getDomainName($account); Logging::info("Mail for $domain migration started..."); $ptrAliases = []; if ( -T "/etc/valiases/$domain" ) { my $fileParser = Parser::makeFileParser("/etc/valiases/$domain"); $ptrAliases = $fileParser->{'PARSE'}->( 'VALUESEPARATOR' => ':' ); if ( ref($ptrAliases) =~ /ARRAY/ ) { foreach my $ptrRow ( @{$ptrAliases} ) { if ( $ptrRow->[0] eq '*' ) { my $value = $ptrRow->[1]; if ( $value =~ /\s*$account\s*/) { #Default domain mail account $default = "$account@".$mainDomain; } elsif ( $ptrRow->[1] =~ /\s*([^@]+\@[^@+]+)\s*/ ) { $default = $1; last; } elsif ( $ptrRow->[2] =~ /blackhole/ ) { $default = ':blackhole:'; last; } elsif ( $ptrRow->[2] =~ /fail/ ) { $default = ':fail:' . $ptrRow->[3]; last; } } } } } if ($default) { $item = XmlNode->new('default'); if ( $default eq ':blackhole:' ) { $item->setAttribute( 'target', 'ignore' ); } elsif ( $default =~ /:fail:(.*)/ ) { $item->setAttribute( 'target', 'fail' ); $item->setText($1); } else { $item->setAttribute( 'target', 'email' ); $item->setText($default); } $mailNode->addChild($item); } if ( @{$ptrAliases} ) { Packer::addForwarders( $mailNode, $ptrAliases ); addAutoresponders( $mailNode, $account, $ptrAliases ); } Logging::info("Finish dumping mail configuration for $domain."); } sub makeSpamassassinNode { my ( $root, $account ) = @_; my $home = getHomeDir($account); return unless -e "$home/.spamassassin"; my $saEnabled = 'off'; $saEnabled = 'on' if -e "$home/.spamassassinenable"; my $sanode = XmlNode->new('spamassassin'); $sanode->setAttribute( 'status', $saEnabled ); $root->addChild($sanode); if ( -e "$home/.spamassassin/user_prefs" ) { my $saConfig = SpamAssassinCfg::parseConfig( "$home/.spamassassin/user_prefs", $home ); my $cfgvalues = SpamAssassinCfg::getConfigRequireScore($saConfig); $sanode->setAttribute( 'hits', $cfgvalues ) if $cfgvalues; $cfgvalues = SpamAssassinCfg::getConfigRewriteHeadr($saConfig); if ($cfgvalues) { if ( scalar( @{$cfgvalues} ) == 2 ) { if (${$cfgvalues}[0] eq 'subject') { $sanode->setAttribute( 'subj-text', ${$cfgvalues}[1] ); } } $sanode->setAttribute( 'action', 'mark' ); } $cfgvalues = SpamAssassinCfg::getConfigBlackList($saConfig); Packer::addSpamassassinLists( $sanode, $cfgvalues, 'blacklist-member' ); $cfgvalues = SpamAssassinCfg::getConfigWhiteList($saConfig); Packer::addSpamassassinLists( $sanode, $cfgvalues, 'whitelist-member' ); $cfgvalues = SpamAssassinCfg::getConfigUnBlackList($saConfig); Packer::addSpamassassinLists( $sanode, $cfgvalues, 'unblacklist-member' ); $cfgvalues = SpamAssassinCfg::getConfigUnWhiteList($saConfig); Packer::addSpamassassinLists( $sanode, $cfgvalues, 'unwhitelist-member' ); } return $sanode; } sub addAutoresponders { my ( $mailNode, $account, $ptrAliases ) = @_; my $home = getHomeDir($account); foreach my $ptrRow ( @{$ptrAliases} ) { my $src = $ptrRow->[0]; my $dst = $ptrRow->[1]; next unless ( $src =~ /^([^@]+)@[^@]+$/ ); my $mailname = $1; $dst =~ s/^\s+//; $dst =~ s/\s+$//; foreach my $email ( split /,/, $dst ) { next unless ( $email =~ /^['"]?\s*\|.*\/cpanel\/bin\/autorespond/ ); next unless ( open( INPUT, "<$home/.autorespond/$src" ) ); binmode INPUT; my $autoresponderNode = XmlNode->new('autoresponder'); $autoresponderNode->setAttribute( 'mailname', $mailname ); while () { chomp; last unless $_; if (/^From:.+<(.+)>/) { $autoresponderNode->setAttribute( 'from', $1 ) if ( $src ne $1 ); } elsif (/^Content-type:\s+text\/([^;]+);\s+charset=(.+)/) { $autoresponderNode->setAttribute( 'type', $1 ); $autoresponderNode->setAttribute( 'charset', $2 ); } elsif (/^Subject:\s*(.*)/) { $autoresponderNode->setAttribute( 'subject', EncodeBase64::encode($1) ); } } $autoresponderNode->setText( EncodeBase64::encode( join( '', () ) ) ); close(INPUT); $mailNode->addChild($autoresponderNode); } } return $mailNode; } sub getProtectedDirs { my ( $root, $domain, $account, $ptrSubDomains ) = @_; my ( $cmd, $exclude, $find, $ptrHash); my $home = getHomeDir($account); my $rootDir = "$home/.htpasswds"; unless ( -d $rootDir ) { return; } $find = AgentConfig::findBin(); $cmd = " cd $rootDir; $find . "; if ( @{$ptrSubDomains} ) { $exclude = join( ' -o ', map {"-path './$_' -prune"} @{$ptrSubDomains} ); $cmd .= "\\( $exclude \\) -o"; } $cmd .= " \\( -type f -name passwd -printf '\%h\\n' \\)"; my @pdirs; foreach my $pdir (`$cmd`) { chomp $pdir; $pdir =~ s/^\.\///; push @pdirs, $pdir; } return @pdirs; } sub getProtectedDirTitle { my ( $account, $pdir ) = @_; my $home = getHomeDir($account); my $title = _getAuthName("$home/$pdir/.htaccess.suspend"); $title = _getAuthName("$home/$pdir/.htaccess") unless defined $title; $title = '' unless defined $title; return $title; } sub _getAuthName { my ( $htaccess_file ) = @_; if ( -T $htaccess_file ) { my $fileParser = Parser::makeFileParser($htaccess_file); my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ' ' ); if ( ref($ptrHash) =~ /HASH/ ) { if ( exists( $ptrHash->{'AuthName'} ) ) { if ( $ptrHash->{'AuthName'} =~ /"([^"]+)"/ ) { return $1; } } } } return; } sub getProtectedDirUsers { my ( $account, $pdir ) = @_; my $home = getHomeDir($account); my $rootDir = "$home/.htpasswds"; unless ( -d $rootDir ) { return; } my %users; my $fileParser = Parser::makeFileParser("$rootDir/$pdir/passwd"); my $ptrHash = $fileParser->{'PARSE'}->( 'KEYSEPARATOR' => ':' ); if ( ref($ptrHash) =~ /HASH/ ) { while ( ( my $name, my $password ) = each( %{$ptrHash} ) ) { $name =~ s/ /_/; if ( $password !~ /^[\x20-\x7f]*$/ ) { $users{$name} = {'password' => EncodeBase64::encode($password), 'encoding' => 'base64' }; } else { $users{$name} = {'password' => $password }; } } } return \%users; } sub addFtpUserNodes { my ( $root, $domain, $account, $ftpUsers, $subdomains ) = @_; my $home = getHomeDir($account); # ftp users of domain while ( ( my $user, my $userData ) = each( %{$ftpUsers} ) ) { my ( $password, $directory, $shell, $quota ) = @{$userData}; if ( $user eq $account ) { # makes main domains's ftp user node $shell = getSuspendedShell($account); if( !defined $shell ) { my $userParams = getUserParams($account); if ( ( defined $userParams ) && ( @{$userParams} ) ) { $shell = $userParams->[5]; } } my $ftpUserNode = Packer::makeFtpUserNode( $account, $password, undef, $shell || '' ); $root->addChild( $ftpUserNode ); } elsif ( ! grep ( /$user/, @{$subdomains} ) ) { my $ftpUserNode = Packer::makeFtpUserNode( $user, $password, $directory, $shell, $quota); $root->addChild($ftpUserNode); } } return $root; } sub makeAnonFtpdNode { my $account = shift; my ( $node, $publicFtp, $pureFtpd, $dumpFile ); my $home = getHomeDir($account); $publicFtp = "$home/public_ftp"; unless ( -d $publicFtp ) { return; } $node = XmlNode->new('anonftp'); # # prepare anonftps # $pureFtpd = '/etc/pure-ftpd'; if ( -d $pureFtpd ) { unless ( keys %anonftps ) { my ( $linkTo, $fullPath ); if ( opendir( FTPD, "$pureFtpd" ) ) { foreach my $link ( readdir(FTPD) ) { $fullPath = "$pureFtpd/$link"; next unless ( -l $fullPath ); $linkTo = readlink($fullPath); if ( $linkTo =~ /\Qhome\E\/([^\/]+)\/public_ftp/ ) { $anonftps{$1} = $linkTo; } } closedir(FTPD); } } } # # end prepare anonftps # # # check permissions # # check for read permission if ( exists( $anonftps{$account} ) && ( ( stat($publicFtp) )[2] & 05 ) ) { $node->setAttribute( 'pub', 'true' ); # check for write permission if ( ( stat("$publicFtp/incoming") )[2] & 03 ) { $node->setAttribute( 'incoming', 'true' ); } } # # end check permissions # return $node; } sub getCpanelVersion { my ( $engineRoot, $util ); loadMainConfig(); $engineRoot = $cPanelConfig{'engineroot'}; if ( defined $engineRoot) { unless ( -d $engineRoot ) { return; } $util = "$engineRoot/cpanel"; unless ( -x $util ) { return; } my @outs = `$util`; foreach my $line (@outs) { chomp $line; if ( $line =~ /cPanel\s+\[(\d+)\.(\d+).\d+/ ) { if ( $1 ne '9' && $1 ne '10' && $1 ne '11' ) { return; } return "$1.$2"; } } } elsif ( -f "$installDir/cpanel.config" ) { return '9'; } return; } sub getMainIP { my $mainip_file = "$installDir/mainip"; my $ip; if ( open( INPUT, "<$mainip_file" ) ) { binmode INPUT; my $pattern = qr/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/; while () { if (/$pattern/) { $ip = $1; last; } } close(INPUT); if ($ip) { return $ip; } } return; } sub unsuspendDomainMaillists { my ( $domain ) = @_; my @handledLists; my $listsDir = AgentConfig::mailmanRoot()."/lists/"; my $suspendedListsDir = AgentConfig::mailmanRoot()."/suspended.lists/"; my @suspendedLists = <$suspendedListsDir*_$domain>; if ( @suspendedLists ) { foreach my $list ( @suspendedLists ) { system("mv $list $listsDir 2>/dev/null"); push @handledLists, substr($list,length($suspendedListsDir)); } } return \@handledLists; } sub suspendDomainMaillists { my ( $handle ) = @_; my @handledLists = @{$handle}; my $listsDir = AgentConfig::mailmanRoot()."/lists/"; my $suspendedListsDir = AgentConfig::mailmanRoot()."/suspended.lists/"; if ( @handledLists ) { foreach my $list ( @handledLists ) { system("mv $listsDir$list $suspendedListsDir 2>/dev/null"); } } } 1;