#!/usr/bin/perl
# 
# ***** BEGIN LICENSE BLOCK *****
# Zimbra Collaboration Suite Server
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software Foundation,
# version 2 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
# ***** END LICENSE BLOCK *****
# 

# Add entries to or update master LDAP server
#
# Exit values
#
# 0 - success
# 1 - ldap start failed
# 2 - ldapmodify failed

use strict;
use lib "/opt/zimbra/common/lib/perl5";
use Zimbra::Util::Common;
use Getopt::Std;
use Net::LDAP;
use Net::LDAP::LDIF;
use Net::LDAP::Entry;
use Crypt::SaltedHash;
use MIME::Base64;
use File::Grep qw (fgrep);

our %options=();
our %loaded=();
our %saved=();

my $zimbra_user=getLocalConfig("zimbra_user");
my $zimbra_tmp_directory=getLocalConfig("zimbra_tmp_directory");
my $ldap_master_url=getLocalConfig("ldap_master_url");
my $ldap_starttls_supported=getLocalConfig("ldap_starttls_supported");
my $source_config_dir="/opt/zimbra/common/etc/openldap";
my $config_dir="/opt/zimbra/conf";
my $ldap_config_dir="/opt/zimbra/data/ldap/config/cn\=config";

if ($#ARGV== -1) {
	setLocalConfigRandom("ldap_root_password");
	setLocalConfigRandom("zimbra_ldap_password");
} elsif ($#ARGV == 1) {
	my $ldap_root_pw = shift;
	my $ldap_admin_pw = shift;
	setLocalConfig("ldap_root_password", "$ldap_root_pw");
	setLocalConfig("zimbra_ldap_password", "$ldap_admin_pw");
} else {
	print "Wrong number of arguments, exiting\n";
	exit;
}

setLocalConfig("ldap_is_master","true");

my $zimbra_ldap_userdn=getLocalConfig("zimbra_ldap_userdn");
my $zimbra_ldap_password=getLocalConfig("zimbra_ldap_password");
my $ldap_root_password=getLocalConfig("ldap_root_password");
my $zimbra_server_hostname=getLocalConfig("zimbra_server_hostname");

# Get the SHA password.
my $ctx = Crypt::SaltedHash->new(algorithm => 'SHA-512', salt_len => '8');
$ctx->add("$ldap_root_password");
my $root_ssha_password = $ctx->generate;

$ctx = Crypt::SaltedHash->new(algorithm => 'SHA-512', salt_len => '8');
$ctx->add("$zimbra_ldap_password");
my $zimbra_ssha_password = $ctx->generate;

# Update config database
#
my $infile="$ldap_config_dir/olcDatabase\=\{0\}config.ldif";
my $outfile="$zimbra_tmp_directory/olcDatabase\=\{0\}config.ldif.$$";
my $mime_root_passwd=MIME::Base64::encode($root_ssha_password,"");
chomp ($mime_root_passwd);
open(IN,"<$infile");
open(OUT,">$outfile");
while (<IN>) {
  if ($_ =~ /^olcRootPW/) {
    $_ =~ s|^olcRootPW.*|olcRootPW:: $mime_root_passwd|;
    print OUT $_;
  } else {
    print OUT $_;
  }
}
close IN;
close OUT;
if ( -s $outfile ) {
  my $rc=0xffff & system("/bin/mv -f $outfile $infile");
  if ($rc != 0) {
    print "Warning: failed to write $infile\n";
  }
  qx(chown $zimbra_user:$zimbra_user $infile);
  qx(chmod 600 $infile);
} else {
  print "Warning: Failed to update root password.\n";
}

# Update zimbra.ldif
$infile = "$source_config_dir/zimbra/zimbra.ldif";
$outfile = "$config_dir/zimbra.ldif";
my $ldifin = Net::LDAP::LDIF->new( "$infile", "r", onerror => 'undef' );
my $ldifout = Net::LDAP::LDIF->new("$outfile", "w", onerror => 'undef' );
while( not $ldifin->eof()) {
  my $entry = $ldifin->read_entry ( );
  if ( $ldifin->error ( ) ) {
    print "Error msg: ", $ldifin->error ( ), "\n";
    print "Error lines:\n", $ldifin->error_lines ( ), "\n";
  } elsif ( $entry ) {
      if ($entry->dn() eq "uid=zimbra,cn=admins,cn=zimbra") {
        $entry->replace (
          userPassword => "$zimbra_ssha_password",
        );
      }
    $ldifout->write($entry);
  }
}
$ldifin->done ( );
$ldifout->done ( );

# Start ldap

my $rc=qx(/opt/zimbra/bin/ldap start);

if ( $rc eq 0 ) {
	exit 1;
}

my $ldap = Net::LDAP->new("$ldap_master_url")  or  die "$@";

# startTLS Operation
my $mesg;
if ($ldap_master_url !~ /^ldaps/i) {
  if ($ldap_starttls_supported) {
    $mesg = $ldap->start_tls(
         verify => 'none',
         capath => "/opt/zimbra/conf/ca",
         ) or die "start_tls: $@";
    $mesg->code && die "TLS: " . $mesg->error . "\n";
  }
}

$mesg = $ldap->bind("cn=config", password=>"$ldap_root_password");

$infile = "$config_dir/zimbra.ldif";
$ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
while ( not $ldifin->eof() ) {
    my $entry = $ldifin->read_entry();
    if ( $ldifin->error() ) {
      print "Error msg: ", $ldifin->error ( ), "\n";
      print "Error lines:\n", $ldifin->error_lines ( ), "\n";
    } elsif ( $entry ) {
      $entry->changetype("add");
      $entry->update($ldap);
    }
}

$infile = "$source_config_dir/zimbra/zimbra_globalconfig.ldif";
$ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
while ( not $ldifin->eof() ) {
    my $entry = $ldifin->read_entry();
    if ( $ldifin->error() ) {
      print "Error msg: ", $ldifin->error ( ), "\n";
      print "Error lines:\n", $ldifin->error_lines ( ), "\n";
    } elsif ( $entry ) {
      $entry->changetype("add");
      $entry->update($ldap);
    }
}

$infile = "$source_config_dir/zimbra/zimbra_defaultcos.ldif";
$ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
while ( not $ldifin->eof() ) {
    my $entry = $ldifin->read_entry();
    if ( $ldifin->error() ) {
      print "Error msg: ", $ldifin->error ( ), "\n";
      print "Error lines:\n", $ldifin->error_lines ( ), "\n";
    } elsif ( $entry ) {
      $entry->changetype("add");
      $entry->update($ldap);
    }
}

$infile = "$source_config_dir/zimbra/zimbra_defaultexternalcos.ldif";
$ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
while ( not $ldifin->eof() ) {
    my $entry = $ldifin->read_entry();
    if ( $ldifin->error() ) {
      print "Error msg: ", $ldifin->error ( ), "\n";
      print "Error lines:\n", $ldifin->error_lines ( ), "\n";
    } elsif ( $entry ) {
      $entry->changetype("add");
      $entry->update($ldap);
    }
}

if (-f "/opt/zimbra/conf/ldap/zimbra_mimehandlers.ldif") {
  $infile = "/opt/zimbra/conf/ldap/zimbra_mimehandlers.ldif";
} else {
  $infile = "$source_config_dir/zimbra/zimbra_mimehandlers.ldif";
}
$ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
while ( not $ldifin->eof() ) {
  my $entry = $ldifin->read_entry();
  if ( $ldifin->error() ) {
    print "Error msg: ", $ldifin->error ( ), "\n";
    print "Error lines:\n", $ldifin->error_lines ( ), "\n";
  } elsif ( $entry ) {
    $entry->changetype("add");
    $entry->update($ldap);
  }
}

if (-f "$source_config_dir/zimbra/convertd_mimehandlers.ldif") {
  $infile = "$source_config_dir/zimbra/convertd_mimehandlers.ldif";
  $ldifin = Net::LDAP::LDIF->new("$infile", "r", onerror => 'undef' );
  while ( not $ldifin->eof() ) {
    my $entry = $ldifin->read_entry();
    if ( $ldifin->error() ) {
      print "Error msg: ", $ldifin->error ( ), "\n";
      print "Error lines:\n", $ldifin->error_lines ( ), "\n";
    } elsif ( $entry ) {
      $entry->changetype("add");
      $entry->update($ldap);
    }
  }
}

exit 0;

sub setLocalConfig {
  my $key = shift;
  my $val = shift;

  if (exists $main::saved{lc}{$key} && $main::saved{lc}{$key} eq $val) {
    return;
  }
  $main::saved{lc}{$key} = $val;
  qx(/opt/zimbra/bin/zmlocalconfig -f -e ${key}=\'${val}\' 2> /dev/null);
}

sub setLocalConfigRandom {
  my $key = shift;
  qx(/opt/zimbra/bin/zmlocalconfig -f -e -r ${key} 2> /dev/null);
}

sub getLocalConfig {
  my $key = shift;

  return $main::loaded{lc}{$key}
    if (exists $main::loaded{lc}{$key});

  my $val = qx(/opt/zimbra/bin/zmlocalconfig -x -s -m nokey ${key} 2> /dev/null);
  chomp $val;
  $main::loaded{lc}{$key} = $val;
  return $val;
}
