#!/usr/bin/perl
#
# BEGIN COPYRIGHT BLOCK
# 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, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA.
# 
# In addition, as a special exception, Red Hat, Inc. gives You the additional
# right to link the code of this Program with code not covered under the GNU
# General Public License ("Non-GPL Code") and to distribute linked combinations
# including the two, subject to the limitations in this paragraph. Non-GPL Code
# permitted under this exception must only link to the code of this Program
# through those well defined interfaces identified in the file named EXCEPTION
# found in the source code files (the "Approved Interfaces"). The files of
# Non-GPL Code may instantiate templates or use macros or inline functions from
# the Approved Interfaces without causing the resulting work to be covered by
# the GNU General Public License. Only Red Hat, Inc. may make changes or
# additions to the list of Approved Interfaces. You must obey the GNU General
# Public License in all respects for all of the Program code and other code used
# in conjunction with the Program except the Non-GPL Code covered by this
# exception. If you modify this file, you may extend this exception to your
# version of the file, but you are not obligated to do so. If you do not wish to
# provide this exception without modification, you must delete this exception
# statement from your version and license this file solely under the GPL without
# exception. 
# 
# 
# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
# Copyright (C) 2005 Red Hat, Inc.
# All rights reserved.
# END COPYRIGHT BLOCK
#
use Time::localtime;

BEGIN {
	require 'uname.lib';
	$| = 1;
	$isNT = -d '\\';
	$PATHSEP = $isNT ? "\\" : "/";
        $SEP = $isNT ? ";" : ":" ;
	$exitCode = 0;
	@INC = ( '.', '../../../admin/admin/bin');
	grep { s@/@\\@g } @INC if $isNT;
	$script_suffix = $isNT ? ".bat" : "";
	$exe_suffix = $isNT ? ".exe" : "";
	# NT needs quotes around some things unix doesn't
	$quote = $isNT ? "\"" : "";
	if ($isNT) {
		# we have to pass batch files directly to the NT command interpreter
		$com_spec = $ENV{ComSpec};
		if (!$com_spec) {
			$com_spec = $ENV{COMSPEC};
		}
		if (!$com_spec || ! -f $com_spec) {
			# find the first available command interpreter
			foreach $drive (c..z) {
				$com_spec = "$drive:\\winnt\\system32\\cmd.exe";
				last if (-f $com_spec);
				$com_spec = undef;
			}
			if (! $com_spec) {
				# punt and pray
				$com_spec = 'c:\winnt\system32\cmd.exe';
			}
		}
		$os = "WINNT";
	} else {
		$os = &uname("-s");
		if ($os eq "SunOS") {
			$isSolaris9 = ( &uname("-r") eq "5.9" );
		}
	}
 
	if ( ($os eq "AIX") || ($os eq "HP-UX") ) {
		$sigChildHandler = 'sigChildHandler';
	}
	SWITCH: {
	    if ($os eq "AIX") {
		$LIB_PATH = "LIBPATH" ;
		last SWITCH ;
	    }
	    if ($os eq "HP-UX") {
		$LIB_PATH = "SHLIB_PATH" ;
		last SWITCH ;
	    }
	    if ($isNT) {
                $LIB_PATH = "PATH" ;
	        last SWITCH ;
            }
            else {
                $LIB_PATH = "LD_LIBRARY_PATH" ;
	        last SWITCH ;
            }
       }
	$slapdExecName = $isNT ? 'slapd.exe' : 'ns-slapd';
	select STDERR;
	$| = 1;
	select STDOUT;
	$| = 1;
}



$TRACELEVEL = 0;
${root}           = "{{DS-ROOT}}" ;   
${type}           = "" ;               
${newname}        = "" ; 
${newport}        = "" ;                                     
${rootDN}         = "" ;
${rootpwd}        = "" ;
${localhost}      = "" ;
${LogFileReport}  = "" ;

# get input users
&getParameters() ;  


${oldHome}         = "${oldDir}${PATHSEP}$type-$oldname" ;    
${oldConfDir}      = "${oldHome}${PATHSEP}config${PATHSEP}" ; 
${oldSlapdConf}    = "${oldConfDir}slapd.conf" ;              
${serverHome}      = "${root}${PATHSEP}$type-$newname" ;      
${ldif_rep}        = "${oldConfDir}ldif${PATHSEP}" ;
${curdir}          = getCwd();


if (!(-d $serverHome)) {
    print("\n$serverHome doesn't exist\n");
    exit(1);
  }
  if (!(-d $oldHome)) {
    print("\n$oldHome doesn't exist\n");
    exit(1);
  }
if ($olddatadir && !(-d $olddatadir)) {
    print("\n$olddatadir doesn't exist\n");
    exit(1);
  }
$ENV{"$LIB_PATH"} = "$root${PATHSEP}lib${SEP}".$ENV{"$LIB_PATH"} ;

# get the version of the DS to migrate
($oldVersion, $oldMinor) = &getVersion($oldDir, $oldversionstr);
# get the version of the new DS
($Version, $Minor) = &getVersion($root);

if ($Version >= 7) {
  if ($oldVersion == 4) {
	$myscript = "migrateTo7" ;
	printMsg("\n******* Migration from $oldVersion.$oldMinor to $Version.$Minor Directory Server *********\n");
  }
  elsif ($oldVersion == 5 ) {
	printMsg("\nWarning. You may experience some problems if the version of directory server you want to migrate is not a 5.0 or 5.1") if ($oldMinor > 1);
	$myscript = "migrate5to7" ;
	printMsg("\n******* Migration from $oldVersion.$oldMinor to $Version.$Minor Directory Server *********\n");
  } 
  elsif ($oldVersion == 6 ) {
    $myscript = "migrate6to7" ;
	printMsg("\n******* Migration from $oldVersion.$oldMinor to $Version.$Minor Directory Server *********\n");
  }
  else {

	die "We don't support the version of directory server you want to migrate";
  }
}
else {
  die "\n\nThe version of directory you want to upgrade is not a 7.x product\nMigration aborted\n";
}

my $start_time = gmtime ;
@args = ($^X, $myscript, @ARGV, '-L', $LogFileReport);
$exitCode = &mySystem(@args);
#die "Error: @args: $!" if ($exitCode != 0);

open(LOGFILE,">> $LogFileReport") or die "\nCan't write to $LogFileReport\n$!\n";
if (! $exitCode) {
  my $end_time = gmtime ;
  printMsg("->  Migration started at $start_time\n");
  printMsg("->  Migration ended at $end_time\n\n");
}
printMsg("***********************************************\n\n");
print("-> The migration report file is available at: $LogFileReport\n\n");

close(LOGFILE);

#######################################################################################################################
sub usage {
        print(STDERR "\nUsage: $0 -D rootdn { -w password | -w - | -j filename } -p port \n");
	print(STDERR "        -o oldInstancePath -n newInstancePath [-t tracelevel] \n");
	print(STDERR "        [-L logfile] [-noinput]\n");
        print(STDERR "************** parameters in brackets are optionals, others are required **************\n");
        print(STDERR " Opts: -D rootdn              - new Directory Manager\n");
        print(STDERR "     : -w password            - new Directory Manager's password\n");
        print(STDERR "     : -w -                   - Prompt for new Directory Manager's password\n");
        print(STDERR "     : -j filename            - Read new Directory Manager's password from file\n");
        print(STDERR "     : -p port                - new Directory Server port\n");
        print(STDERR "     : -o oldInstancePath     - Path of the old instance to migrate \n");
        print(STDERR "     : -n newInstancePath     - Path of the new instance\n");
        print(STDERR "     : [-d dataPath]          - Path to directory containing data files to import into new instance\n");
        print(STDERR "     : [-v oldVersion]        - Version of old instance (obtained by running $slapdExecName -v\n");
        print(STDERR "     : [-t tracelevel]        - specify the level of trace (0..3) by default setup to 1\n");
        print(STDERR "     : [-L logfile]           - specify the file to log the migration report \n");
        print(STDERR "     : [-noinput]             - no user interventions during migration processing to solve conflicts\n");
       
      }


#######################################################################################################################
# get input users

sub getParameters {
  my $exit = 0 ;
  my $i    = 0;
  my $pwdfile= "";

  while ($i <= $#ARGV) {
    if ( "$ARGV[$i]" eq "-D" ) { # directory manager
      if (! $rootDN) {
	$rootDN = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-w") { # password
      if (! $rootpwd) {
	$rootpwd = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-j") { # password file 
      if (! $pwdfile) {
	$pwdfile = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-o") { # old instance path
       if (! $oldHome ) {
	 $oldHome = $ARGV[++$i] ;
	 grep { s@\\@/@g } $oldHome if $isNT ;
	 if ($oldHome =~ /[\"]?(.*)?[\"]?/) { $oldHome = $1 ; }
	 if ($oldHome =~ m@^(.*)/([^-/]*)-([^/]*)[/]?$@) {
	   $oldDir  = $1 ;
	   $type    = $2 ;
	   $oldname = $3 ;
	   if ($isNT) {
	     $oldDir  = lc($oldDir)     ;
	     $type    = lc($type)       ;
	     $oldname = lc($oldname)    ;
	     $oldHome = lc($oldHome)    ;
	     grep { s@/@\\@g } $oldDir  ;
	     grep { s@/@\\@g } $oldHome ;
	   }
	 }
	 else {
	   print("\nThe old instance path is not correct. It must be like slapd-instancename");
	   &usage();
	   exit(1);
	 }
       }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-n") { # 5.x instance path
       if (! $serverHome ) {
	 $serverHome = $ARGV[++$i] ;
	 grep { s@\\@/@g } $root if $isNT ;
	 grep { s@\\@/@g } $serverHome if $isNT ;
	 if ($serverHome =~ /[\"]?(.*)?[\"]?/) { $serverHome = $1 ; }
	 if ($serverHome =~ m@^(.*?)/?([^/-]*)-([^/]*)[/]?$@) {
	   $root    = $1 if ($1);
	   $type    = $2 ;
	   $newname = $3 ;
	   if ($isNT) {
	     $root       = lc($root)       ;
	     $type       = lc($type)       ;
	     $newname    = lc($newname)    ;
	     $serverHome = lc($serverHome) ;
	     grep { s@/@\\@g } $root       ;
	     grep { s@/@\\@g } $serverHome ;
	   }
	 }
	 else {
	   print("\nThe new instance path is not correct. It must be like slapd-instancename");
	   &usage();
	   exit(1);
	 }
       }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-p") { # 5.x DS port 
      if (! $newport ) {
	$newport = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-d") { # old instance LDIF data dir
      if (! $olddatadir ) {
	$olddatadir = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-v") { # old version
      if (! $oldversionstr ) {
	$oldversionstr = $ARGV[++$i] ;
      }
      else {	
	&usage() ;
	exit(1);
      }
    } elsif ("$ARGV[$i]" eq "-t") { # TRACELEVEL
      my $value = $ARGV[++$i] ;	
      if ($value =~ /[0-3]/) {
	$TRACELEVEL = $value ;
      }
      else {
	print("\nThe tracelevel must belong to 0..3 interval");
        &usage();
	exit();
      }	
    } elsif ("$ARGV[$i]" eq "-noinput") { # no user interventions during processing
    } elsif ("$ARGV[$i]" eq "-L") { # user defined logfile for the migration
	$LogFileReport = $ARGV[++$i];
    }
    else {
      &usage() ;
      exit(1);
    }
    $i++;
  }

  if (! $rootDN) {
    print("\nThe rootDN is missing");
    $exit = 1;
  }
  if ($pwdfile ne "") {
    # Open file and get the password
    unless (open (RPASS, $pwfile)) {
      die "Error, cannot open password file $passwdfile\n";
    }
    $rootpwd = <RPASS>;
    chomp($rootpwd);
    close(RPASS);
  } elsif ($rootpwd eq "-"){
    # Read the password from terminal
    die "The '-w -' option requires an extension library (Term::ReadKey) which is not\n",
    "part of the standard perl distribution. If you want to use it, you must\n",
    "download and install the module. You can find it at\n",
    "http://www.perl.com/CPAN/CPAN.html\n";
    # Remove the previous line and uncomment the following 6 lines once you have installed Term::ReadKey module.
# use Term::ReadKey; 
#	print "Bind Password: ";
#	ReadMode('noecho'); 
#	$rootpwd = ReadLine(0); 
#	chomp($rootpwd);
#	ReadMode('normal');
  }
  if (! $rootpwd) {
    print("\nThe rootpwd is missing");
    $exit = 1 ;
  }
  if (! $newport) {
    print("\nThe port is missing");
    $exit = 1;
  }
  if (! $serverHome) {
    print("\nThe new instance path is missing");
    $exit = 1;
  }
  if (! $oldHome) {
    print("\nThe old instance path is missing");
    $exit = 1;
  }
  if ((! $LogFileReport) && $serverHome) {
    ($sec, $min, $hour, $dd, $mm, $yy) = &GetTime();
    $LogFileReport  = "${serverHome}${PATHSEP}logs${PATHSEP}Migration_${dd}${mm}${yy}_${hour}${min}${sec}.log";
  }
  if ($exit) {	
    &usage() ;
    exit(1);
  }
  
}

#############################################################################
# printMsg print message to the user standard output.  

sub printMsg {

  my $TypeMsg = shift ;
  my $Msg     = shift ;
  my $LineNb  = shift ;
  if ($LineNb) {
      printTrace("Line: $LineNb, $TypeMsg, $Msg");
  }
  else {
      printTrace("$TypeMsg $Msg");
  }
}

#############################################################################
#  print message error to the user standard output.  

sub printTrace {

  my $Msg = shift ;
  my $level     = shift ;
  if ($level <= $TRACELEVEL) {
      print($Msg);
      print LOGFILE $Msg;
  }

}  

#############################################################################
sub mySystem {
	my $cmd = $_[0];
	# the system {$cmd} avoids some NT shell quoting problems if the $cmd
	# needs to be quoted e.g. contains spaces; the map puts double quotes
	# around the arguments on NT which are stripped by the command
	# interpreter cmd.exe; but don't quote things which are already quoted
	my @fixargs = map { /^[\"].*[\"]$/ ? $_ : $quote . $_ . $quote } @_;
	my $rc = 0;
	if ($cmd =~ /[.](bat|cmd)$/) {
		# we have to pass batch files directly to the NT command interpreter
		$cmd = $com_spec;
#		print "system $cmd /c \"@fixargs\"\n";
		$rc = system {$cmd} '/c', "\"@fixargs\"";
	} else {
#		print "system $cmd \"@fixargs\"\n";
		if($isNT) {
			$rc = system "\"@fixargs\"";
		} else {
			$rc = system @fixargs;
		}
	}

	return $rc;
}

#############################################################################

sub GetTime {
    my $tm = localtime;
    (my $sec, my $min, my $hour, my $dd, my $mm, my $yy) = ($tm->sec, $tm->min, $tm->hour, $tm->mday, ($tm->mon)+1, ($tm->year)+1900);
    $sec  = "0$sec"  unless $sec > 9  ;
    $min  = "0$min"  unless $min > 9  ;
    $hour = "0$hour" unless $hour > 9 ;
    $dd   = "0$dd"   unless $dd > 9   ;
    $mm   = "0$mm"   unless $mm > 9   ;
    return ($sec, $min, $hour, $dd, $mm, $yy);
}

#############################################################################

sub getVersion {
	my $dir         = shift;
	my $versionstr  = shift;
	my $version     = 0;
	my $minor       = 0;
	my $buildNumber = 0;
	my $progDir     = "${PATHSEP}bin${PATHSEP}slapd${PATHSEP}server${PATHSEP}";
	my $progDir2    = "${PATHSEP}bin${PATHSEP}slapd${PATHSEP}";

	# find the slapd executable
	if (!$versionstr) { # version not specified on cmd line - find it
		$prog = $dir . $progDir . $slapdExecName;
		if (! -f $prog) {
			$prog = $dir . $progDir2 . $slapdExecName;
			if (-f $prog && $isNT) {
				# if slapd is in bin/slapd and we're on NT, just assume version 1;
				# apparently, slapd.exe doesn't like the -v argument . . .
				return ( '1', $minor );
			}
			else{
				die "Could not run slapd program $prog: $!";
			}
		}
		else {
			chdir($dir . $progDir);
		}
		$preserve_lib_path = $ENV{"$LIB_PATH"};
 		$ENV{"$LIB_PATH"}=
 			"$dir${PATHSEP}lib${SEP}".
 			"$dir${PATHSEP}bin${PATHSEP}slapd${PATHSEP}lib${SEP}".
 			"$dir${PATHSEP}bin${PATHSEP}slapd${PATHSEP}server${SEP}".
 			$ENV{"$LIB_PATH"};
		# read the old version from the old slapd program

		open(F, "${quote}${quote}$prog${quote} -v${quote} 2>&1 |") or
			die "Could not run slapd program $prog: $!";
		sleep(1); # allow some data to accumulate in the pipe
#	print "Output from $prog -v:\n";
		while (<F>) {
			if (/^Red Hat-Directory/ || /^Netscape-Directory/ || /^iPlanet-Directory/i) {
				$versionstr = $_;
				last;
			}
		}
		$code = close(F);
		#	print "$prog returned code=$code status=$?\n";
		$ENV{"$LIB_PATH"}="$root${PATHSEP}lib${SEP}".$ENV{"$LIB_PATH"};
	}

	if ($versionstr =~ /^Red Hat-Directory\/(\d+)\.(\d+)(?:b\d)*\s+(\S+)/) {
		$version     = $1;
		$minor       = $2;
		$buildNumber = $3;
	}
	elsif ($versionstr =~ /^Netscape-Directory\/(\d+)\.(\d+)(?:b\d)*\s+(\S+)/) {
		$version     = $1;
		$minor       = $2;
		$buildNumber = $3;
	}
	elsif ($versionstr =~ /^Netscape-Directory\(restrict?ed-mode\)\/(\d+)\.(\d+)\s+(\S+)/) { # we can have restricted-mode or restriced-mode ...
        $version     = $1;
		$minor       = $2;
		$buildNumber = $3;
	}
	elsif ($versionstr =~ /^iPlanet-Directory\/(\d+)\.(\d+)\s+(\S+)/i) {
		$version     = $1;
		$minor       = $2;
		$buildNumber = $3;
	} elsif ($versionstr =~ /(\d+)\.(\d+)/) {
		$version     = $1;
		$minor       = $2;
	}

	if ($version == 0) {
	    die "\nCould not determine version of the directory server in $dir: \n";
	}

	# distinguish the 4.1 and the 4.11 thanks to the buildNumber
	if (($version == 4) && ($minor == 1)){
	    if (! ($buildNumber =~ /^B99\.16/)) {
		# it's not a 4.1 Netscape Directory Server => it's a 4.11
		$minor = 11 ;
	    }
	}
	# Restore the original library path
	$ENV{"$LIB_PATH"} = $preserve_lib_path;
	chdir($curdir) or die "Could not change directory to $curdir: $!" ;
	return ( $version, $minor );
}

###############################################################################################
# get current directory

sub getCwd {
        my $command = $isNT ? "cd" : "/bin/pwd";
        open(PWDCMD, "$command 2>&1 |") or
                die "Error: could not execute $command: $!";
        # without the following sleep, reading from the pipe will
        # return nothing; I guess it gives the pwd command time
        # to get some data to read . . .
        sleep(1);
        my $currentdir;
        while (<PWDCMD>) {
                if (!$currentdir) {
                        chomp($currentdir = $_);
                }
        }
        my $code = close(PWDCMD);
#       if ($code || $?) {
#               print "$command returned code=$code status=$? dir=$curdir\n";
#       }
#       print "getCwd curdir=\[$curdir\]\n";
        return $currentdir;
}
