#!<#PERLEXE#> -- # -*-Perl-*-

#=====================================================================
#                                ntSync
#
# This script is responsible for handling the external build activity
# after the NTSambaBuilderTask in the Ganymede server's userKit
# writes out its data files.  ntSync synchronizes user and
# group accounts to a Windows NT Primary Domain Controller and
# to a Samba server, if needed.
#
# Released under GPL as part of the Ganymede network directory
# management system, http://www.arlut.utexas.edu/gash2
#
# Release: $Name: userKit_103 $
# Version: $Revision: 1.1 $
# Date: $Date: 2001/06/01 04:13:37 $
#
#=====================================================================

$root = "<#SCHEMAROOT#>";

#  ------------------------------------------------------------

# You may need to edit any or all of these variables to establish
# synchronization to your Windows NT PDC.

$ntRSHAccountName = "ganymede";
$ntPDCName = "";		# should be the PDC's DNS name or IP address
$rshCommand = "/usr/bin/rsh";
$perlLoc = "c:\\perl\\bin\\perl.exe";
$scriptLoc = "c:\\ganymede\\ganymederemote.pl";

$updateEverything = 1;
$reallyDoRSH = 0;

# You probably won't have to change these..

$passwdFile = "$root/output/ntsamba/passwd";
$oldPasswdFile = "$root/output/ntsamba/passwd.last";
$groupFile = "$root/output/ntsamba/group";
$oldGroupFile = "$root/output/ntsamba/group.last";
$iniFile = "$root/output/ntsamba/NTsync.ini";

#
# ** You will definitely have to change the next couple of lines!!!! **
#

# make the adopter have to edit this script by hand to set the variables
# if they want to sync with a remote NT PDC

print "ntSync: script not customized, skipping sync to remote PDC\n";
exit 0;

#  ------------------------------------------------------------

# First, let's make sure that we keep the permissions
# tight, since the server's Java code has no means
# to set the permissions on files it generates

chmod 0600, $passwdFile;
chmod 0600, $groupFile;

#
# Our purpose in this script is to transform the passwd and group file
# written out by the NTSambaBuilderTask into a file that is, at
# least in rudimentary fashion, styled after a Windows .ini file, and
# which then can be fed to a Perl script on an NT Primary Domain
# Controller (PDC)
#
# Our two input data files are formatted as follows:
#
# The passwdFile should be formatted as a series of lines formatted
# as follows, with all colons within the labeled fields back-slashed:
#
# username:invid:Status:plaintextpassword:Full Name:Location:Gecos Info
#
# The groupFile likewise a series of lines with intra-field colons
# back-slash escaped:
#
# groupname:invid:Status:username,username,username,username:Comment
#
# And we want to generate a file that looks like this:
#
# [Create/Update]
# broccol:oldname:dwEsx8zlWOM/PA:Jonathan Abbey:S321 CSD,3199,3357681
# amy::dwEsx8zlWOM/PA:Amy Bush:S222 CSD,3028
# [Inactivate]
# oldaccount
# [Delete]
# deaduser
# [Create/Update Groups]
# omssys:oldname:broccol,amy,omara,mulvaney:Ganymede [owners]
# omsovr::abc,gomod,gojo,kneuper,cb,luna:Ganymede [owners]
# [Inactivate Groups]
# oldgroup
# [Delete Groups]
# removed
#
# To achieve this, we need to scan in the passwd and group files that
# were just written by the NTSambaBuilderTask, and the old copies
# from the last run as well, so we can compare them to identify
# changes, including user and group renames and deleted users and groups.

# first, try and read the passwd file that was just generated by
# the Ganymede server's NTSambaBuilderTask

if (!open (INPUT, "<$passwdFile")) {
  print "ntSync passing, no $passwdFile generated\n";
  exit 0;
}

while (<INPUT>) {

  # in the context of a while (<FILEHANDLE>)
  # loop, $_ is set to each line of the text,
  # including trailing newline

  chop $_;  # cut off trailing newline from $_

  if ($_ =~ /^\s*$/) {
    next;
  }

  # Cut the line up into pieces

  ($username, $invid, $status, $passwd,
   $fullname, $location) = &colonsplitter($_);

  # Actually fill the hash with the information gathered

  $InvidToUser{$invid} = {
			  name     => $username,
			  status   => $status,
			  password => $passwd,
			  invid    => $invid,
			  fullname => $fullname,
			  location => $location
			 };
}

close (INPUT);

# now, if we have an older copy of the password file, open and read it
# to do a before and after comparison, to identify any deleted or renamed
# users

if (open INPUT, "<$oldPasswdFile") {

  while (<INPUT>) {

    # in the context of a while (<FILEHANDLE>)
    # loop, $_ is set to each line of the text,
    # including trailing newline
    
    chop $_;  # cut off trailing newline from $_
    
    if ($_ =~ /^\s*$/) {
      next;
    }
    
    # Cut the line up into pieces
    
    ($username, $invid, $status, $passwd,
     $fullname, $location) = &colonsplitter($_);

    if (!(defined $InvidToUser{$invid})) {

      # deleted the old account

      push @deletedUsers, $username;
    } else {
      $currentname = $InvidToUser{$invid}{name};

      if ($username ne $currentname) {
	$InvidToUser{$invid}{oldname} = $username;
      }
    }
  }
  
  close (INPUT);
}

#
# Now we scan in group information
#

open (INPUT, "<$groupFile") || die "Error!  Couldn't open $groupFile!";

while (<INPUT>) {

  # in the context of a while (<FILEHANDLE>)
  # loop, $_ is set to each line of the text,
  # including trailing newline

  chop $_;  # cut off trailing newline from $_

  if ($_ =~ /^\s*$/) {
    next;
  }

  # Cut the line up into pieces

  ($groupname, $invid, $status, $users, $comment) = &colonsplitter($_);

  # Actually fill the hash with the information gathered

  $InvidToGroup{$invid} = {
			  name     => $groupname,
			  invid    => $invid,
			  status   => $status,
			  users    => $users,
			  comment  => $comment,
			 };
}

close (INPUT);

# now, if we have an older copy of the group file, open and read it
# to do a before and after comparison, to identify any deleted or renamed
# groups

if (open INPUT, "<$oldGroupFile") {

  while (<INPUT>) {

    # in the context of a while (<FILEHANDLE>)
    # loop, $_ is set to each line of the text,
    # including trailing newline
    
    chop $_;  # cut off trailing newline from $_
    
    if ($_ =~ /^\s*$/) {
      next;
    }
    
    # Cut the line up into pieces
    
    ($groupname, $invid, $status, $users) = &colonsplitter($_);

    if (!(defined $InvidToGroup{$invid})) {

      # deleted the old account

      push @deletedGroups, $groupname;
    } else {
      $currentname = $InvidToGroup{$invid}{name};

      if ($groupname ne $currentname) {
	$InvidToGroup{$invid}{oldname} = $groupname;
      }
    }
  }
  
  close (INPUT);
}

#=============================================================================
# and we are done with our data gathering.. write out our ini file.
#=============================================================================

open(INIOUT, ">$iniFile") || die "Couldn't create $iniFile";

#
# First we write out any currently defined user records.. we don't
# care if they are newly created or not, the NT-side code will
# take care of this as needed.
#

print INIOUT "[Create/Update]\n";

foreach $invid (keys %InvidToUser) {
  
  $status = $InvidToUser{$invid}{status};

  if ($status eq "inactive") {
    next;
  }

  $username = $InvidToUser{$invid}{name};  
  $oldname = $InvidToUser{$invid}{oldname};
  $passwd = $InvidToUser{$invid}{password};
  $fullname = $InvidToUser{$invid}{fullname};
  $location = $InvidToUser{$invid}{location};

  if (($passwd ne "") || $updateEverything) {
    print INIOUT "$username:$oldname:$passwd:$fullname:$location\n";
  }
}

#
# Now we write out the names of inactivated users, one per line
#

print INIOUT "[Inactivate]\n";

foreach $invid (keys %InvidToUser) {
  
  $status = $InvidToUser{$invid}{status};

  if ($status ne "inactive") {
    next;
  }
  
  print INIOUT $InvidToUser{$invid}{name}."\n";
}

#
# And deleted users
#

print INIOUT "[Delete]\n";

foreach $username (@deletedUsers) {
  print INIOUT "$username\n";
}

#
# Then we write out all of the groups that we are sync'ing over to
# the NT side
#

print INIOUT "[Create/Update Groups]\n";

foreach $invid (keys %InvidToGroup) {
  
  $status = $InvidToGroup{$invid}{status};

  if ($status eq "inactive") {
    next;
  }

  $groupname = $InvidToGroup{$invid}{name};  
  $oldname = $InvidToGroup{$invid}{oldname};
  $users = $InvidToGroup{$invid}{users};
  $comment = $InvidToGroup{$invid}{comment};

  print INIOUT "$groupname:$oldname:$users:$comment\n";
}

#
# Now we write out the names of inactivated groups, one per line
#

print INIOUT "[Inactivate Groups]\n";

foreach $invid (keys %InvidToGroup) {
  
  $status = $InvidToGroup{$invid}{status};

  if ($status ne "inactive") {
    next;
  }
  
  print INIOUT $InvidToGroup{$invid}{name}."\n";
}

#
# And deleted groups
#

print INIOUT "[Delete Groups]\n";

foreach $groupname (@deletedGroups) {
  print INIOUT "$groupname\n";
}

#
# And wa-la.
#

print INIOUT "[EOF]\n";

close(INIOUT);

chmod 0600, $iniFile;

#
# Do the actual build stuff here
#

if ($reallyDoRSH) {
  $result = system("$rshCommand -l $ntRSHAccountName $ntPDCName \"$perlLoc $scriptLoc\" < $iniFile");

  $resultcode = 0xffff & $result;
  
  if ($resultcode == 0) {
    # Move the newly generated files to backup positions
    # so that our next run can do the compare
    #
    # We'll only want to do this if the build process
    # succeeded, since our saved copies should accurately
    # reflect the state of these accounts on the remote NT server
    #
    # Note that by renaming these files, we circumvent the automatic
    # backups made by the Ganymede builder task system, since the
    # builder task will never encounter the old versions of the files
    # when it goes to write out the new ones (they will have been
    # moved out of the way first).  This is probably not a bad thing
    # to do, since the NT files will generally contain plaintext
    # passwords and since we are expecting the NT side to
    # retain state for us.
    #
    # If you *do* want to have a complete history of NT group and
    # password files kept in the server's backup directory, replace
    # these next two lines with code to copy, not move, the
    # files.
    
    rename($passwdFile, $oldPasswdFile);
    rename($groupFile, $oldGroupFile);
    
  } else {
    print "Couldn't successfully execute rsh synchronization.. resultcode = $resultcode\n";
  }
} else {
  print "Skipped rsh synchronization, \$reallyDoRSH = $reallyDoRSH\n";
}

######################################################################
#
# Subroutine to break a colon-separated line into fields. Supports
# backslashed-colons within fields.
#
######################################################################

sub colonsplitter {
    my ($string) = @_;
    my ($temp, @results, $i, $char);

    for ($i = 0; $i < length $string; $i++) {
      $char = substr($string, $i, 1);

      if ($char eq "\\") {
	$i++;
	$char = substr($string, $i, 1);
	
	$temp = $temp . $char;
      } elsif ($char eq ":") {
	push @results, $temp;
	$temp = "";
      } else {
	$temp = $temp . $char;
      }
    }

    chomp $temp;

    if ($temp ne "") {
      push @results, $temp;
    }
    
    return @results;
}
