#!/usr/bin/perl -w
#
# Rough tool to migrate ACLs from a server you've already done a file
# clone on.
#
use File::Find;
use bytes;
use strict;

use Getopt::Long;

my ( $UNIX_ROOT, $NT_ROOT );

GetOptions( "uroot=s" => \$UNIX_ROOT,
		  "nroot=s" => \$NT_ROOT ) or usage();

usage() unless $UNIX_ROOT;
usage() unless $NT_ROOT;

$NT_ROOT =~ s/\$/\\\$/g;

# Mappings for things we can't find in passwd/group
my %usermap = (
			   "BUILTIN\\Administrators" => "root",
			   "NT AUTHORITY\\SYSTEM" => "root",
			  );
my %groupmap = (
				"BUILTIN\\Administrators" => "Domain Admins",
				"NT AUTHORITY\\SYSTEM" => "Domain Admins",
				"BUILTIN\\Server Operators" => "Domain Admins",
			   );

my %groups;
my %users;
my $verbose = 1;

print "Cloning $UNIX_ROOT to $NT_ROOT\n";

find( { wanted => \&acl_frob,
		no_chdir => 1 }, $UNIX_ROOT );

sub acl_frob {
  # Fetch ACL for file
  my $file = $File::Find::name;
  $file =~ s@$UNIX_ROOT@@;
  $file =~ s@^/@@;
  return unless $file;

  # make filename safe
  $file =~ s/"/\\"/g;
  $file =~ s/`/\\`/g;

  # use double instead of single quotes around the filename because
  # for some reason single quotes don't seem to get escaped properly?
  my $cmd = "smbcacls -A /root/cred $NT_ROOT \"$file\"";
  my @bits = qx($cmd 2>&1) or warn "$cmd failed\n";

  # back to what it used be
  $file = $File::Find::name;

  my ( $chuid, $chgid, @acls ) = ( -1, -1 );
  for my $bit ( @bits ) {
	chomp( $bit );
	my ( $acl, $setting ) = split( ':', $bit, 2 );

	if ( $acl eq "REVISION" ) {
	  next;						# discard
	} elsif ( $acl eq "OWNER" ) {
	  if ( defined( $usermap{$setting} )) {
		$setting = $usermap{$setting};
	  }

	  $chuid = cached_getpwnam( $setting );
	} elsif ( $acl eq "GROUP" ) {
	  if ( defined( $groupmap{$setting} )) {
		$setting = $groupmap{$setting};
	  }

	  $chgid = cached_getgrnam( $setting );
	} elsif ( $acl eq "ACL" ) {
	  # NT AUTHORITY\SYSTEM:ALLOWED/0/FULL

	  my ( $user, $access ) = split( ':', $setting );

	  if ( defined( $usermap{$user} )) {
		$user = $usermap{$user};
	  } elsif (defined( $groupmap{$user})) {
		$user = $groupmap{$user};
	  }

	  # parse out the access
	  my ( $perm, $flags, $acls ) = split( '/', $access );
	  my $mode = 'r';
	  my $aclcmd = "";
	  if ( $acls eq 'FULL' ) {
		$mode = 'rwx';
	  }

	  # now see if it's a user, a group, or some Damned Thing
	  if ( defined( getpwnam( $user ))) {
		#print "   $user is a user\n";
		$aclcmd = 'setfacl -m u:\'' . $user . '\':' . $mode . " \"" . $file . "\"";
	  } elsif ( defined( getgrnam( $user ))) {
		#print "   $user is a group\n";
		$aclcmd = 'setfacl -m g:\'' . $user . '\':' . $mode . " \"" . $file . "\"";
	  } elsif ( $user eq "\\Everyone" ) {
		#print "   $user is EVERYONE\n";
		$aclcmd = 'setfacl -m o::' . $mode . " \"" . $file . "\"";
	  } elsif ( $user eq "\\CREATOR OWNER" ) {
		#print "   $user is file's owner\n";
		$aclcmd = 'setfacl -m u::' . $mode . " \"" . $file . "\"";
	  } else {
		print "$file\n";
		print " $acl => $setting\n";
#		die "What is $user, exactly?";
		warn "Can't identify $user\n";
	  }
	  my $code = system( $aclcmd );
	  warn "ACL command $aclcmd failed with $code\n" if $code;
	} else {
	  if ( $bit =~ /Failed to open/ ) {
		print "$file appears to have vanished\n";
		unlink( $file );
		return;
	  }
	  print "Command was >$cmd<\n";
	  die "Can't parse output from command '$bit'\n";
	}
  }
  chown( $chuid, $chgid, $file );
}

sub cached_getpwnam {
  my $user = shift;

  if ( !defined( $users{$user})) {
	my ( $login, $pass, $uid, $gid ) = getpwnam( $user )
		or warn "$user is not a valid username ($File::Find::name)";
	$uid ||= 0;
	$users{$user} = $uid;
  }
  $users{$user};
}

sub cached_getgrnam {
  my $group = shift;

  if ( !defined( $groups{$group})) {
	my ( $name,$passwd,$gid,$members) = getgrnam( $group )
		or warn "$group is not a valid group ($File::Find::name)";
	$gid ||= 0;
	$groups{$group} = $gid;
  }
  $groups{$group};
}

sub usage {
  print STDERR "Usage: $0 --uroot UNIX_ROOT --nroot NT_ROOT\n";
  exit(1);
}
