#!/usr/bin/perl -w
#

use strict;
use lib "/opt/zimbra/common/lib/perl5";
use Zimbra::Util::Common;
use File::Basename;
use File::Temp qw/ tempfile /;
use FileHandle;
use IPC::Open3;
use Mail::Mailer;

my %LC;
my @originalArgs;

sub getLocalConfig(;@) {
  my @vars = @_;
  my $dir = dirname($0);
  my $cmd = "$dir/zmlocalconfig -s -q -x";
  if (scalar(@vars) > 0) {
    $cmd .= ' ' . join(' ', @vars);
  }
  open(LCH, "$cmd |") or die "Unable to invoke $cmd: $!";
  my $line;
  while (defined($line = <LCH>)) {
    $line =~ s/[\r\n]*$//;  # Remove trailing CR/LFs.
    my @fields = split(/\s*=\s*/, $line, 2);
    $LC{$fields[0]} = $fields[1];
  }
  close(LCH);
}

sub userCheck() {
  my $loggedIn = qx(id -un);
  chomp($loggedIn) if (defined($loggedIn));
  my $expected = $LC{zimbra_user};
  if ($loggedIn ne $expected) {
    print STDERR "Must be user $expected to run this command\n";
    exit(1);
  }
}

sub getLdapConfigValue {
  my $attrib = shift;

  return (undef) unless ($attrib);

  my $val = getLdapServerConfigValue($attrib);
  $val = getLdapGlobalConfigValue($attrib) if ($val eq "");
  return $val;
}

sub getLdapServerConfigValue {
  my $attrib = shift;

  return (undef) unless ($attrib);
  getLocalConfig("zimbra_server_hostname");
  my $server = $LC{"zimbra_server_hostname"};
  # could consider redirecting STDERR to /dev/null
  open( CONF, "/opt/zimbra/bin/zmprov -l gs $server '$attrib' |" )
    or die("Open global config failed: $!");

  my ( $key, $val );
  while (<CONF>) {
    chomp;
    next if (/^#/);
    ( $key, $val ) = split( /:\s*/, $_, 2 );
    # for now just assume a single value/attribute is requested
    last if ($val);
  }
  return $val;
}

sub getLdapGlobalConfigValue {
  my $attrib = shift;
  my ($val,$err);
  my ($rfh,$wfh,$efh,$cmd,$rc);
  $rfh = new FileHandle;
  $wfh = new FileHandle;
  $efh = new FileHandle;
  $cmd = "/opt/zimbra/bin/zmprov -l gcf $attrib";
  my $pid = open3($wfh,$rfh,$efh, $cmd);
  unless(defined($pid)) {
    return undef;
  }
  close $wfh;
  chomp($val = (split(/\s+/, <$rfh>))[-1]);
  chomp($err = join "", <$efh>);
  waitpid($pid,0);
  if ($? == -1) {
    # failed to execute
    return undef;
  } elsif ($? & 127) {
    # died with signal
    return undef;
  } else {
    $rc = $? >> 8;
    return undef if ($rc != 0);
  }

  return $val;
}

sub sendEmailReport {
  my $msg = shift;

  getLocalConfig('smtp_source','smtp_destination','zimbra_server_hostname');
  my $from_address = $LC{"smtp_source"};
  my $to_address = $LC{"smtp_destination"};
  my $server = $LC{"zimbra_server_hostname"};
  my $smtphost = getLdapConfigValue("zimbraSmtpHostname");
  my $subject =  "zmbackup for $server failed";
  return if (scalar @$msg == 0);
  eval {
    my $mailer = Mail::Mailer->new("smtp", Server => $smtphost);
    $mailer->open( { From => $from_address,
                   To   => $to_address,
                   Subject => $subject,
                })
    or warn "ERROR: Can't open: $!\n";
    print $mailer $msg;
    $mailer->close();
  };
  if ($@) {
    logError("Failed to email report: $@\n");
  }
}

sub logError {
  my $msg = shift;
  print STDERR $msg;
  return;
}

sub backupMysql {
	my $session = shift;
	getLocalConfig('mysql_backup_retention', 'mysql_root_password', 'mysql_socket');

	if (! $LC{mysql_backup_retention} > 0) {
		return;
	}
	my $numArgs = scalar(@originalArgs);
	my $hasTarget = 0;
        my $target = getLdapConfigValue("zimbraBackupTarget");
	for (my $i = 0; $i < $numArgs && !$hasTarget; $i++) {
		my $arg = $originalArgs[$i];
		if ($arg eq '-t' || $arg eq '--target') {
			$hasTarget = 1;
			if ($i + 1 < $numArgs) {
				$target = $originalArgs[$i + 1];
			}
		}
	}

	my @args = (
		"/opt/zimbra/common/bin/mysqldump",
		"--user=root",
		"--password='$LC{mysql_root_password}'",
		"--socket='$LC{mysql_socket}'",
		"--all-databases",
		"--single-transaction",
		"--flush-logs",
		"| gzip -c > ${target}/mysql-backup.0.gz",
	);
	my $rc = system(join(" ", @args));
	$rc >>= 8;
	if (!$rc) {
		unlink "${target}/mysql-backup.$LC{mysql_backup_retention}";
		while ($LC{mysql_backup_retention}) {
			rename ("${target}/mysql-backup.".($LC{mysql_backup_retention}-1).".gz" , "${target}/mysql-backup.".$LC{mysql_backup_retention}.".gz");
			$LC{mysql_backup_retention}--;
		}
	} else {
		print STDERR "mysqldump failed with exit $rc\n\n";
	}
}

sub backupAntiSpamDB {
    my $session = shift;
    getLocalConfig('antispam_mysql_enabled', 'antispam_backup_retention', 'antispam_mysql_root_password');

    if (! $LC{antispam_backup_retention} > 0) {
        return;
    }
    my $numArgs = scalar(@originalArgs);
    my $hasTarget = 0;
    my $target = "/opt/zimbra/backup";
    for (my $i = 0; $i < $numArgs && !$hasTarget; $i++) {
        my $arg = $originalArgs[$i];
        if ($arg eq '-t' || $arg eq '--target') {
            $hasTarget = 1;
            if ($i + 1 < $numArgs) {
                $target = $originalArgs[$i + 1];
            }
        }
    }

    # if mysql_enabled
    if ( defined($LC{antispam_mysql_enabled}) && $LC{antispam_mysql_enabled} eq 'TRUE' ) {
        my @args = (
            "/opt/zimbra/mta/mysql/bin/mysqldump",
            "--user=root",
            "--password='$LC{antispam_mysql_root_password}'",
            "--socket=/opt/zimbra/data/amavisd/mysql/mysql.sock",
            "--all-databases",
            "--single-transaction",
            "--flush-logs",
            "| gzip -c > ${target}/antispam-mysql-backup.0.gz",
        );
        my $rc = system(join(" ", @args));
        $rc >>= 8;
        if (!$rc) {
            unlink "${target}/antispam-mysql-backup.$LC{antispam_backup_retention}";
            while ($LC{antispam_backup_retention}) {
                rename ("${target}/antispam-mysql-backup.".($LC{antispam_backup_retention}-1).".gz" , "${target}/antispam-mysql-backup.".$LC{antispam_backup_retention}.".gz");
                $LC{antispam_backup_retention}--;
            }
        } else {
            print STDERR "mysqldump of antispam database failed with exit $rc\n\n";
        }
    } else {
        # Backup Berkeley DB antispam database
        my @args = (
            "/opt/zimbra/libexec/sa-learn",
            "--dbpath /opt/zimbra/data/amavisd/.spamassassin",
            "--sync",
            "--backup",
            "| gzip -c > ${target}/antispam-bdb-backup.0.gz",
        );
        my $rc = system(join(" ", @args));
        $rc >>= 8;
        if (!$rc) {
            unlink "${target}/antispam-bdb-backup.$LC{antispam_backup_retention}";
            while ($LC{antispam_backup_retention}) {
                rename ("${target}/antispam-bdb-backup.".($LC{antispam_backup_retention}-1).".gz" , "${target}/antispam-bdb-backup.".$LC{antispam_backup_retention}.".gz");
                $LC{antispam_backup_retention}--;
            }
        } else {
            print STDERR "bdb dump of antispam database failed with exit $rc\n\n";
        }
    }
}

getLocalConfig('zimbra_user');
userCheck();

my $useReport=0;
foreach my $arg (@ARGV) {
  if ($arg eq "--mail-report") {
    $useReport = 1;
  } else {
    push(@originalArgs, $arg);
  }
}

my $BRJAR = "/opt/zimbra/lib/ext/zcsplus-backuprestore/zimbrabackup.jar";
my $report = [];

my $backupCmd;
my @args;

if (-e $BRJAR) {
  # This is a mailbox server.
  $ENV{'EXT_JAR_PATH'} = $BRJAR;
  @args = ('', "", 'com.zimbra.cs.backup.BackupUtil', @originalArgs);
  $backupCmd = "/opt/zimbra/bin/zmjava";
} elsif (-x "/opt/zimbra/common/sbin/slapcat") {
# This is a ldap-only server.
  my ($needsHelp, $hasTarget, $target, $hasDelete, $del,$hasZip) = (0, 0, undef, 0, undef, 0);
  my $i;
  my $numArgs = scalar(@originalArgs);
  for ($i = 0; $i < $numArgs; $i++) {
    my $arg = $originalArgs[$i];
    if ($arg eq '-t' || $arg eq '--target') {
      $hasTarget = 1;
      if ($i + 1 < $numArgs) {
        $target = $originalArgs[$i + 1];
      }
    } elsif ($arg eq '-d') {
      next;
    } elsif ($arg eq '--del' || $arg eq '-del') {
      $hasDelete = 1;
      if ($i + 1 < $numArgs) {
        $del = $originalArgs[$i + 1];
      }
    } elsif ($arg eq '-h' || $arg eq '--help' || $arg eq '-help') {
      $needsHelp = 1;
    } elsif ($arg eq '-z' || $arg eq '--zip') {
      $hasZip = 1;
    } elsif($arg eq "--zipStore") {
      $hasZip = 0;
      # zmschedulebackup is wierd.  It passes -z --zipStore to have uncompressed data.
      # So if both -z and --zipStore are present, unset $hasZip
      # Thankfully we can rely on --zipStore coming after -z in the argument list
    }
  }
  if ($hasTarget) {
    push(@args, '--target');
    if (defined($target)) {
      push(@args, $target);
    }
  }
  if ($hasDelete) {
    push(@args, '--del');
    if (defined($del)) {
      push(@args, $del);
    }
  }
  if ($needsHelp) {
    push (@args, '--help');
  }
  if ($hasZip) {
    push (@args, '--zip');
  }

  $backupCmd = "/opt/zimbra/libexec/zmbackupldap";
} else {
  foreach my $arg (@ARGV) {
    if ($arg eq '-h' || $arg eq '--help' || $arg eq '-help') {
      print "No backup configuration required for this installation node\n";
    }
  }
  exit 0;
}
my $rfh = new FileHandle;
my $wfh = new FileHandle;
my $efh = new FileHandle;
foreach my $arg (@args) {
  $backupCmd .=" $arg";
}
my $pid = open3($wfh,$rfh,$efh,"$backupCmd");
if (!defined($pid)) {
  push(@$report,"Error: Failed to execute $backupCmd\n");
  if ($useReport) {
    sendEmailReport($report);
  } else {
    print "@$report\n";
  }
  exit 1;
}
close $wfh;
my @output=grep((!/^\[\] WARN|INFO|ERROR/, <$rfh>));
my @error=<$efh>;
waitpid($pid,0);
my $rc;
$rc=$? >> 8;
if ($rc == 0 && !$useReport) {
  foreach my $data (@output) {
    chomp ($data);
    print "$data\n";
  }
} else {
  foreach my $data (@error) {
    push(@$report,$data);
  }
  if ($useReport) {
    sendEmailReport($report);
  } else {
    print "@$report\n";
  }
  if ($rc != 0) {
    exit $rc;
  }
}
if (-e $BRJAR) {
	backupMysql();
	backupAntiSpamDB();
}
