#!/usr/bin/perl -w
#

use strict;
use Getopt::Long;
use File::Basename;
use IO::Uncompress::Gunzip qw (gunzip $GunzipError);

my %LC;

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);
		if ('zimbra_ldap_password' eq $fields[0]) {
			$fields[1] =~ s/([;<>\*\|\$!#\(\)\[\]\{\}:'"])/\\$1/g;
		}
		$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);
	}
}

getLocalConfig('zimbra_user', 'zimbra_ldap_userdn', 'zimbra_ldap_password');
getLocalConfig('ldap_master_url', 'ldap_starttls_supported', 'zimbra_require_interprocess_security');
getLocalConfig('ldap_is_master');
userCheck();

my %opts = ();

my $target = "/opt/zimbra/backup";

my ($configfile,$backupfile);

&GetOptions(\%opts, "h", "lbs", "lb=s", "t=s", "a", "l", "o=s") or usage();

sub usage {

	my ($msg) = (@_);

	$msg && print STDERR "\nERROR: $msg\n";
	print STDERR <<USAGE;

Restore accounts from ldap backup

Usage: zmrestoreldap -lb session -t target <options>
       zmrestoreldap -o <directory> <options>

	-lb:  Session label (e.g. full-20080519.212856.319) to restore from.
	-t:   Backup target (defaults to /opt/zimbra/backup)
<options> are:
	-lbs: List all session labels in backup (Not compatible work with -o)
	-l:   List accounts in file
	-a:   Restore named account(s).
	      List account names separated by white space.

USAGE
	exit (1);
}

sub listAccounts {

	open BACKUP, $backupfile or die "Can't open $backupfile: $!";
	my @lines = <BACKUP>;
	close BACKUP;

	for (my $i = 0; $i <= $#lines; $i++) {
		if ($lines[$i] =~ /zimbraMailDeliveryAddress:/) {
			my $isAccount = (split ' ', $lines[$i], 2)[1];
			chomp $isAccount;
			print "$isAccount\n";
		}
	}
}

sub restoreAccounts {

	open BACKUP, $backupfile or die "Can't open $backupfile: $!";
	my @lines = <BACKUP>;
	close BACKUP;

	open LDAP, ">/tmp/zmrestoreldap.$$" or
		die "Can't open /tmp/zmrestoreldap.$$: $!";

	my @curobject = ();
	my $isAccount = 0;
	my $isAccountString = "";
	for (my $i = 0; $i <= $#lines; $i++) {
		if ($lines[$i] =~ /^$/) {
			if ($isAccount) {
				if ($opts{a} && grep (/$isAccountString/, @ARGV)) {
					print "Restoring $isAccount\n";
					print LDAP @curobject;
					print LDAP "\n";
				}
			}
			$isAccount = 0;
			@curobject = ();
			next;
		}
		if ($lines[$i] =~ /zimbraMailDeliveryAddress:/) {
			$isAccount = (split ' ', $lines[$i], 2)[1];
			chomp $isAccount;
			$isAccountString = quotemeta $isAccount;
			# print "Found $isAccount\n";
		}
		push @curobject, $lines[$i];
	}

	close LDAP;
	restoreLdapAccount("/tmp/zmrestoreldap.$$");
	unlink "/tmp/zmrestoreldap.$$";
}

sub fullLdapRestore {
  my ($configdb,$maindb) = @_;
  print "Stopping ldap...";
  my $rc = 0xffff & system("ldap stop");
  if ($rc != 0) {
    print "ERROR: failed to stop ldap.\n";
    exit 1;
  }
  print "done.\n";


  system("mv /opt/zimbra/data/ldap/.db.prev /opt/zimbra/data/ldap/.db.prev.$$")
    if (-e "/opt/zimbra/data/ldap/.db.prev");
  $rc = 0xffff & system("mkdir -p /opt/zimbra/data/ldap/.db.prev");
  if ($rc != 0) {
    print "ERROR: Failed to create /opt/zimbra/data/ldap/.db.prev\n";
    exit 1;
  }

  $rc = 0xffff & system("mv /opt/zimbra/data/ldap/config /opt/zimbra/data/ldap/.db.prev/ 2> /dev/null");
   if ($rc > 1) {
    print "ERROR: Failed to move existing ldap config data: $rc\n";
    exit 1;
  }

  $rc = 0xffff & system("mv /opt/zimbra/data/ldap/mdb /opt/zimbra/data/ldap/.db.prev/ 2> /dev/null");
  if ($rc > 1) {
    print "ERROR: Failed to move existing ldap data: $rc\n";
    exit 1;
  }

  if ($LC{ldap_is_master} && -d "/opt/zimbra/data/ldap/accesslog") {
    $rc = 0xffff & system("mv /opt/zimbra/data/ldap/accesslog /opt/zimbra/data/ldap/.db.prev/ 2> /dev/null");
    if ($rc > 1) {
      print "ERROR: Failed to move existing ldap data: $rc\n";
      exit 1;
    }
  }

  $rc = 0xffff & system("mkdir -p /opt/zimbra/data/ldap/config");
  if ($rc != 0) {
    print "ERROR: Failed to create /opt/zimbra/data/ldap/config\n";
    exit 1;
  }

  $rc = 0xffff & system("mkdir -p /opt/zimbra/data/ldap/mdb/db");
  if ($rc != 0) {
    print "ERROR: Failed to create /opt/zimbra/data/ldap/mdb/db\n";
    exit 1;
  }

  if ($LC{ldap_is_master}) {
    $rc = 0xffff & system("mkdir -p /opt/zimbra/data/ldap/accesslog/db");
    if ($rc != 0) {
      print "ERROR: Failed to create /opt/zimbra/data/ldap/accesslog/db\n";
      exit 1;
    }
  }

  restoreLdapConfig($configdb);
  restoreLdap($maindb);

  print "Starting ldap...";
  $rc = 0xffff & system("ldap start");
  if ($rc != 0) {
    print "ERROR: failed to start ldap.\n";
    exit 1;
  }
  print "done.\n";
}

sub restoreLdapConfig {
	my $file = shift;

	if (! -r $file) {
		die "Can't read $file: $!";
	}

	qx(/opt/zimbra/libexec/zmslapadd -c $file);
}

sub restoreLdap {
	my $file = shift;

	if (! -r $file) {
		die "Can't read $file: $!";
	}

	qx(/opt/zimbra/libexec/zmslapadd $file);
}

sub restoreLdapAccount {
	my $file = shift;
        my $startTLS;

        if ($LC{ldap_starttls_supported} && $LC{zimbra_require_interprocess_security}) {
            $startTLS="-ZZ";
        }  else {
            $startTLS="";
        }

	if (! -r $file) {
		die "Can't read $file: $!";
	}

	qx(cat $file | egrep -v '^structuralObjectClass:' | egrep -v '^entryUUID:' | \
          egrep -v '^entryCSN:' | egrep -v '^creatorsName:' | egrep -v 'createTimestamp:' | egrep -v '^modifiersName:' | \
          egrep -v '^modifyTimestamp:' | /opt/zimbra/common/bin/ldapadd -x $startTLS -H "$LC{ldap_master_url}" -cv -D "$LC{zimbra_ldap_userdn}" -w $LC{zimbra_ldap_password});
}

$opts{h} && usage();

$opts{t} && ( $target = $opts{t} );

if ($opts{o} && $opts{t} ) {
  usage();
}

if ($opts{o} && $opts{lbs}) {
  usage();
}

if ($opts{o} && $opts{lb}) {
  usage();
}

$opts{o} && ($target = $opts{o} );

if ($opts{o}) {
  $backupfile="$target/ldap.bak";
  $configfile="$target/ldap-config.bak";
} else {
  if ($opts{lbs}) {
      print qx(/bin/ls -t "$target/sessions");
      exit 0;
  } elsif ($opts{lb}) {
      $configfile = "$target/sessions/$opts{lb}/ldap/ldap-config.bak";
      $backupfile = "$target/sessions/$opts{lb}/ldap/ldap.bak";
  } else {
  	print "backup session label not specified in -lb.\n";
  	usage();
  }
}

if ( !-e "$backupfile.gz" && !-e "$backupfile") {
   die "Error: No backup file found for main database: $backupfile\n";
}

if ( !-e "$configfile.gz" && !-e "$configfile") {
   die "Error: No backup file found for configuration database: $configfile\n";
}

if ( -e "$backupfile.gz" && !-e "$backupfile") {
  my $status = gunzip "$backupfile.gz" => $backupfile
    or die "gunzip failed: $GunzipError\n";
}

if ( -e "$configfile.gz" && !-e "$configfile") {
  my $status = gunzip "$configfile.gz" => $configfile
    or die "gunzip failed: $GunzipError\n";
}

if ($opts{l}) {
	listAccounts();
} elsif ($opts{a}) {
	if (defined($ARGV[0])) {
		restoreAccounts();
	} else {
		print "Account(s) to be restored must be specified when using -a.\n";
		usage();
	}
} else {
	fullLdapRestore($configfile,$backupfile);
}

if ( -e "$backupfile.gz" && -e "$backupfile") {
  unlink($backupfile);
}

if ( -e "$configfile.gz" && -e "$configfile") {
  unlink($configfile);
}
