#=======================================================================
# Require additional modules required
use File::Compare;
use Win32::File;
use Win32::FileSecurity;
use Win32::FileSecurity qw(Get EnumerateRights);
use Win32::NetResource;
use File::Basename;
use File::Copy;
use File::Find;
use DirHandle;
use Socket;
{
#=======================================================================
  $SIG{'INT'}   = 'main::cleanup';
  $SIG{'HUP'}   = 'main::cleanup';
  $SIG{'QUIT'}  = 'main::cleanup';
  $SIG{'PIPE'}  = 'main::cleanup';
# $SIG{'ALARM'} = 'main::cleanup';
  
#=======================================================================
# Set default values....
  $VERSION    = 0.16;           # Script version number
  $VDATE      = "29 July 1999"; # Script release date
  $binary     = 0;
  $debug      = 0;
  $noexe      = 0;
  $quiet      = 0;
  $verify     = 0;
  $datecheck  = 0;
  $rmextra    = 0;
  $stats      = 0;    # Print host update statistics
  $ntfsupdate = 0;    # Do not update NTFS permissions when set to 1
  $ntfsignore = 0;    # Do not read NTFS permissions when set to 1
  $tolerance  = 3;    # Tolerance for file date/time checks for FAT file
# systems - in seconds
  $dotposn    = 0;    # Dot Position for screen feedback
  undef $targmap ;    # Memory for mapped drive to target host
  undef $srcmap  ;    # Memory for mapped drive to target host
  my(@os) = qw(Win32s, Win95, WinNT);
  my($update, $target_drive);
  
  &args(@ARGV);  # Process command line arguments
  
#=======================================================================
# Open log file
#
  unless ($log) {
    $tmp = $ENV{'TMP'};                    # Identify TMP environment variable -
    $tmp = $ENV{'TEMP'} unless $tmp;       # - or failing that TEMP
    $tmp = $ENV{'SystemDrive'} unless $tmp;# - or failing that TEMP
    $log = $tmp . '/rdist.log' ;           # Create the &log file name
  }
  StartLog($log);                 # Open the log file
  
  &log( "win32rdist.pl version $VERSION   - dated $VDATE\n");
  $name = Win32::LoginName;
  
  $machine = Win32::NodeName;   # Determine Machine (Computer) name
  $domain  = Win32::DomainName; # Determine Domain Name (for &logon)
  &log( "Machine $machine - domain $domain\n");
  
# Determine the OS type and version details
  my($string,$major,$minor,$build,$id) = Win32::GetOSVersion();
  &log( "      $os[$id] $major\.$minor $string (Build $build)\n");
  &log( "   Logon Server $ENV{'LOGONSERVER'}\n");
  if ( $os[$id] eq "WinNT" ) {
    use Win32::NetAdmin;
    Win32::NetAdmin::UserGetAttributes($ENV{'LOGONSERVER'},$name,$password,
    $passwordage,$privilege,$homedir,$comment,$flags,$scriptpath);
    }else{
    $comment = ''
  }
  &log( "   User $name ($comment)\n\n");
  
  &log("log file : $log \n\n") if $debug;
  &log("    started at " . &datetime . "\n\n");
  $start_time = time;
  
#   Test for incompatible switches.!!!!!!!!!!!!!!!!!
  if ( $noexe && $quiet ) {
    &log( "   quiet option is invalid with no exe option\n");
    undef $quiet;
  }
  
  &ReadDistfile($distfile) if $distfile;
  
#=======================================================================
# Print out configuration settings
  if ($debug & 2) {
    &log( "   -b (binary test)  : $binary\n");
    
    &log( "   -D (debug)        : $debug\n");
    &log( "   -f distfile       : $distfile\n");
    &log( "   -l logfile        : $log\n") if $logset;
    &log( "   -n (no exe)       : $noexe\n");
    &log( "   -q (quiet)        : $quiet\n");
    &log( "   -R (rm extra)     : $rmextra\n");
    
    &log( "   -v (verify)       : $verify\n");
    &log( "   Append Path  : $appendpath\n");
    &log( "   -y (date check)   : $datecheck\n");
    
    foreach (@hostlimit) {
      &log( "   hostlimit    : $_\n");
    }
    if ( @exceptions ) {
      &log("===========================Exceptions================================\n");
      foreach (@exceptions){
        &log("   $_\n");
      }
    }
    if ( @except_pats ) {
      &log("========================Exception Patterns===========================\n");
      foreach (@except_pats){
        &log("   $_\n");
      }
    }
    if ( @notify ) {
      &log("====================Notification Addresses===========================\n");
      foreach (@notify){
        &log("   $_\n");
      }
      &log("=====================================================================\n");
    }
  }
  
  if ($debug & 128) {
    &log("rdist configuration file variables\n");
    foreach $key ( keys %variable ) {
      &log("\n   name   : $key\n");
      &log("   values : ");
      foreach $value ( split('\|',$variable{$key} ) ) {
        &log("$value\n            ");
      }
    }
    &log("\n");
  }
  
#=======================================================================
  
  $list = sub{
    my($file) = $File::Find::name;
    my($mask)=$cwd;
    $mask =~ s/\\/\\\\/g;
    $mask =~ s/\//\\\//g;
    $file =~ s/$mask//g;
    push(@srcfiles, $cwd . ' ! .' . $file);
  };
    
#=======================================================================
# Process each Destination
#----------------------------------------------------------------------
  
  &log("===============================TARGET================================\n") if ($debug>0);
  TARGET:
  foreach $target ( split('\|',$variable{$targets}) ) {
    &log("updating $target (at " . &datetime . ")\n" );
    $targ_start_time = time;
    $kbupdate   = 0;    # Total size of files transferred
    $kbtotal    = 0;    # Total size of files maintained
    $ftotal     = 0;    # Total number of files
    $fupdate    = 0;    # Total number of files updated
    $fremove    = 0;    # Total number of files deleted
    $dtotal     = 0;    # Total number of directories
    $dupdate    = 0;    # Total number of directories created
    $dremove    = 0;    # Total number of files deleted
    
#   If Share is specified
    if ( $target =~ /^([A-Z]:)(\S*)/i ) {
      $hostdrive = $1;
      $hostpath  = $2;
      if ( $debug > 0 ) {
        &log("Logical drive connection\n");
        &log("  Drive : $hostdrive\n");
        &log("  Path  : $hostpath\n");
      }
      
      }elsif( $target =~ /(\\\\|\/\/)([a-zA-Z0-9]+)(\\|\/)([a-zA-Z0-9]+[\$]?)(.*)$/ ) {
      &log(" Mapping target UNC - $target \n") if $debug;
      ($hostdrive,$hostpath,$hostshare) = &mapdrive($target);
      if ( $hostdrive =~ /[a-z]:/i ) {
        &log("  - mapped $target to use drive $hostdrive$hostpath\n") if $debug;
        $targmap = 1;
        }else{
        &gripe(" failed to connect to $target\n");
        next TARGET;
      }
    }
    
    
    
    
#   Check $hostpath exists - if not create it
#        - Return to TARGET on failure
    unless (Win32::SetCwd($hostdrive)) {
      &gripe("Unable to find $hostdrive\n");
      next TARGET;
    }
    
    
# Read Source Files/Directories
    
    if ( $debug & 64 ) {
      &log("\nstarting find process - looking for directories under $variable{$files}\n") ;
      &log("   Source directories are contained in variable : $files\n");
      &log("   (use -D128 to list source directories)\n");
    }
    
    $variable{$files} =~ s/\s+$//g;
    SRCDIR:  foreach $cwd ( split('\|',$variable{$files} ) ) {
      
      $cwd =~ s/^"(.*)"$/$1/;  # Remove any surrounding quotation marks
      
      &log (" CWD - initially set to \"$cwd\" \n") if $debug;
#  Call mapdrive routine here - but only if a UNC mapping is specified
      undef $cwddrive;
      undef $cwdpath;
      undef $cwdshare;
      if ( ! ( $cwd =~ /^([A-Z]:)/i ) ) {
        &log(" Mapping drive to cwd - $cwd \n") if $debug;
        ($cwddrive,$cwdpath,$cwdshare) = &mapdrive($cwd);
        if ( $cwddrive =~ /[a-z]:/i ) {
          &log("  - mapped $cwd to use drive") if $debug;
          $cwd = $cwddrive . '/' . $cwdpath;
          &log(" $hostdrive$hostpath\n") if $debug;
          $srcmap = 1;
          }else{
          &gripe(" failed to connect to $cwd\n");
          next SRCDIR;
        }
        }else{
        &log(" No need to add drive mapping for CWD\n") if $debug;
      }
      
      if ( Win32::SetCwd($cwd) ){
        undef @srcfiles;
        find($list,$cwd);
        
        if ( $debug & 64 ) {
          foreach (@srcfiles){
            my ($path,$base) = split(/ ! \./,$dir);
            &log("$path$base\n");
          }
        }
        
        
#   Process each directory in turn
        HOSTDIR:  foreach $dir ( @srcfiles ) {
          &log("\n***********************************************************************\n") if $debug;
          ($path,$base) = split(/ ! \./,$dir);
          undef %f_ref;
          undef %f_test;
          $f_ref{name} = $path . $base;
          $f_ref{name} =~ s/\/\//\//g;
          if ( $appendpath ) {
            $f_test{name} = $hostdrive . $hostpath . $base;
            &log("   Append path option has changed destination from\n") if $debug;
            &log("      $f_test{name} to\n") if $debug;
            $f_test{name} = $path;
            $f_test{name} =~ s/^([a-zA-Z]:)/$hostdrive$hostpath/;  # Strip Source
#$f_test{name} = $hostdrive . $hostpath;
            $f_test{name} .= $base;
            &log("      $f_test{name}\n") if $debug;
            }else{
            $f_test{name} = $hostdrive . $hostpath . $base;
          }
          $f_test{name} =~ s/\/\//\//g;  # Remove and double forward slashes...
          
          $f_test{display_name} = $f_test{name};
          if ( $targmap ) {
            $f_test{display_name} =~ s/$hostdrive/$hostshare/;
            $f_test{display_name} =~ s/\\/\//g;          # Change \ for /
            $f_test{display_name} =~ s/(\S)\/\//$1\//g;  # Sub any // for /
          }
          
          $f_ref{display_name} = $f_ref{name};
          if ( $srcmap ) {
            $f_ref{display_name} =~ s/$cwddrive/$cwdshare/;
            $f_ref{display_name} =~ s/\\/\//g;
            $f_ref{display_name} =~ s/(\S)\/\//$1\//g;
          }
          &log(" $f_ref{display_name}  --> $f_test{display_name}\n") if $debug;
          
# Finish loop here if $f_ref{name} matches an exception entry
          if ( @exceptions ) {
            foreach (@exceptions){
              if ( $_ eq $f_ref{name} ) {
                &log("   ignoring $_ (exception match)\n") if ($debug & 256) ;
                next HOSTDIR;
              }
            }
          }
# Finish loop here if $f_ref{name} matches an except_pat entry
          
          if ( @except_pats ) {
            foreach (@except_pats){
              if ( $f_ref{name} =~ /$_/ ) {
                &log("   ignoring $f_ref{display_name} (except_pat match with /$_/ )\n") if ($debug & 256) ;
                next HOSTDIR;
              }
            }
          }
          
          undef $update;
          undef $create;
          undef $ntfschange;
          
          
#==============================================================
# Check File Type (Dir or File)
          if ( -f $f_ref{name} ) {
            $f_ref{type} = "FILE";
            $ftotal ++;
          }
          
          if ( -d $f_ref{name} ) {
            $f_ref{type} = "DIR" ;
            $dtotal ++;
          }
          if ( -f $f_test{name} ) { $f_test{type} = "FILE";};
          if ( -d $f_test{name} ) { $f_test{type} = "DIR" ;};
#==============================================================
# Check files exist
          unless ( $f_ref{type} ) {
            &log( "$f_ref{display_name} does not exist\n");
            next HOSTDIR;
          }
          
#==============================================================
          if (( $f_ref{type} eq "DIR" ) & ( $f_test{type} eq "FILE" ) ) {
# Remove redundant file
            if ($noexe) {
              &log( "   delete file $f_test{display_name}\n");
            }
            elsif ( $verify )	{
              &log( "   delete file $f_test{display_name}\n");
              }elsif ( &_dfile($f_test{name},$f_test{display_name})  ) {
              undef $f_test{type};
            }
#==============================================================
            }elsif(( $f_ref{type} eq "FILE" ) & ( $f_test{type} eq "DIR" ) ) {
# Remove redundant directory
# Remove all files in directory tree
            &deltree($f_test{type});
            if ( -d $f_test{name} ) {
              $f_test{type} = "DIR" ;
              }else{
              undef $f_test{type};
            }
#==============================================================
            }elsif (( $f_ref{type} eq "DIR" ) & ( $f_test{type} eq "DIR" ) & ( $rmextra == 1 ) ) {
            
            $d = new DirHandle $f_test{name};
            if (defined $d) {
              @dir = $d->read();
              foreach (@dir){
                next if ( $_ eq '.' );
                next if ( $_ eq '..' );
                my($t) = $f_test{name} . '/' . $_;
                my($s) = $f_ref{name} . '/' . $_;
                my($display_t) = $f_test{display_name} . '/' . $_;
                if ( ( -d $t) && ( ! -d $s ) ){
                  &log ("   removing directory tree $display_t\n");
                  &deltree($t);
                  }elsif ( -f $t ){
                  next if ( -f $s );
                  if ($verify) {
                    &log("   remove $display_t\n");
                    }elsif ($noexe){
                    &log("   remove $display_t\n");
                    }else{
                    &_dfile($t,$display_t);
                  }
                }
              }
            }
#==============================================================
            }elsif (( $f_ref{type} eq "FILE" ) & ( $f_test{type} eq "FILE" ) ) {
# Binary compare of files
            if ( $binary ){
              if  ( compare($f_ref{name},$f_test{name}) != 0) {
                if ( $verify ) {
                  &log ("   update $f_test{display_name} \n");
                  }else{
                  $update = 1;
                }
              }
            }
          }
#==============================================================
          unless ( $f_test{type} ) {
            if ( $f_ref{type} eq "DIR" ) {
              if ( $verify ) {
                &log ("   need to create $f_test{display_name} \n");
                next HOSTDIR;
                }elsif ( $noexe ) {
                &log ("   need to create $f_test{display_name} \n");
                }else{
                &log ("   creating $f_test{display_name} \n");
                mkdir($f_test{name}, 666) || &gripe( "Failed to create directory $f_test{display_name} ($!)\n");
                $dupdate ++;
                if ( -d $f_test{name} ) {
                  $f_test{type} = "DIR" ;
                }
              }
            }
            elsif ( $f_ref{type} eq "FILE" ) {
              if ( $verify ) {
                &log ("   need to create $f_test{display_name} \n");
                next HOSTDIR;
                }else{
                $create = 1;
              }
            }
          }
          
          unless ( $f_test{type} || $create || $noexe ) {
            &gripe( "Failed to create $f_test{display_name}\n");
            &gripe( "Type  : $f_test{type} \n");
            &gripe( "create: $create \n");
            &gripe( "update: $update \n");
            exit 1;
          }
#==============================================================
# Check Native File System (FAT, NTFS, NFS, Samba, etc)
          fileparse_set_fstype("MSWin32");
          $f_ref{dir} = dirname($f_ref{name});
          $f_test{dir} = dirname($f_test{name});
          
          Win32::SetCwd($f_ref{dir});
          $f_ref{fstype}=Win32::FsType();
          Win32::SetCwd($f_test{dir});
          $f_test{fstype}=Win32::FsType();
          
          if ($debug & 16) {
            &log( "File System type:\n");
            &log(sprintf "   %s : %s\n",$f_ref{display_name},$f_ref{fstype});
            &log(sprintf "   %s : %s\n",$f_test{display_name},$f_test{fstype});
          }
#==============================================================
# Read file size and time stamps
          ($f_ref{size} ,$f_ref{ctime} ,$f_ref{mtime} ,$f_ref{atime} ) = &fstat($f_ref{name});
          ($f_test{size},$f_test{ctime},$f_test{mtime},$f_test{atime}) = &fstat($f_test{name});
#$kbtotal += $f_ref{size};
#=======================================================================
# Update file if sizes differ
          if ( $f_ref{type} eq "FILE" ) {
            $kbtotal += $f_ref{size};
            if ( ( $f_ref{mtime} < $f_test{mtime} ) & ( $datecheck == 1) ) {
# Log remote copy is newer - and prevent update
              &log( "   remote copy $f_test{display_name} is newer ! \n");
              &log( "      $f_ref{display_name} = $f_ref{mtime}\n");
              &log( "      $f_test{display_name} = $f_test{mtime}\n");
              }else{
              if ( ( ($f_ref{mtime} - $f_test{mtime}) > $fat_timestamp_tol )
                & ( ($f_ref{fstype} eq "FAT" ) | ($f_test{fstype} eq "FAT" ) ) ) {
# Log source copy is newer
                &log( "   source copy $f_ref{display_name} is newer!\n") if $debug;
                if ( $verify ) {
                  &log ("   update $f_test{display_name} \n" );
                  }else{
                  $update = 1;
                }
                }elsif ( $f_ref{mtime} > $f_test{mtime} ) {
#  Update file if sizes differ or reference file modified more recently
                if ( $verify ) {
                  &log ("   update $f_test{display_name}\n");
                  }else{
                  $update = 1;
                }
                }elsif ( $f_ref{size} != $f_test{size} ) {
#  Update file if sizes differ or reference file modified more recently
                if ( $verify ) {
                  &log ("   update $f_test{display_name}\n");
                  }else{
                  $update = 1;
                }
              }
            }
            
            
# This is where we install or update the file
            if ($update || $create) {
              &_cleardots;
              if ($noexe || $verify) {
                if ($create) {
                  &log( "   install $f_test{display_name}\n");
                  }elsif ($update) {
                  &log( "   update $f_test{display_name}\n")
                }
                }else{
                undef $msg;
                if ( ! -r $f_ref{name} ) {
                  $msg = "   -   unable to read $f_ref{display_name} - \n";
                }
                elsif ( ( -e $f_test{name} ) & ( ! -w $f_test{name} ) ) {
                  $msg = "   -   unable to write $f_test{display_name} - ";
                }
                else{
                  if ($create) {
                    &log( "   installing $f_test{display_name}\n");
                    }elsif ($update) {
                    &log( "   updating $f_test{display_name}\n")
                  }
                  if ( copy($f_ref{name}, $f_test{name}) ) {
                    $kbupdate += $f_ref{size};
                    $fupdate ++;
                    if ( $f_test{atime} < $f_ref{mtime} ) { $f_test{atime} = $f_ref{mtime} }
                    utime($f_test{atime},$f_ref{mtime},$f_test{name}) || &gripe ( "Failed to set modification time on $f_test{display_name}\n");
                    }else{
                    &gripe( "Failed to copy file $f_test{display_name} ($?)\n");
                  }
                }
                if ( $msg ) {
                  if ($create) {
                    &log( "$msg      unable to install $f_test{display_name}\n");
                    }elsif ($update) {
                    &log( "$msg      unable to update $f_test{display_name}\n")
                  }
                }
              }
            }
            else {
              &_printdot;
            }
          }
#==============================================================
# Test file attributes (using Win32::File module)
# Read Only =  1
# Hidden    =  2
#           =  4
#           =  8
#           = 16
# Archive   = 32
# Normal    =128
# Directory = ??
# System    = ??
# Compressed=2048
          
          Win32::File::GetAttributes($f_ref{name}, $f_ref{attrib});
          
#$f_ref{attrib} = $attrib;
          if ( -e $f_test{name} ) {
            Win32::File::GetAttributes($f_test{name}, $f_test{attrib});
            }else{
            undef $f_test{attrib};    # Use $f_test{attrib} to identify file exists
          }
#$f_test{attrib} = $attrib;
          
          if ($debug & 4) {
            &log("\nAttribute value for $f_ref{display_name}: $f_ref{attrib}\n");
            &print_attrib($f_ref{attrib});
            if ( $f_test{attrib} ) {
              &log("\nAttribute value for $f_test{name}: $f_test{attrib}\n");
              &print_attrib($f_test{attrib});
            }
          }
          
# Only if $f_test{name} exists can we try and set its attributes
#
          if ( $f_test{attrib} ) {
#  As we can't set the file to be compressed mask out the compress attribute
            if ( $f_test{fstype} eq "Samba" ) {
              $attribmask = 1;  # Samba File System can only set RO bit
              }else{
              $attribmask = 2047;
            }
            
            if ( ( ($f_ref{attrib} & $attribmask) != ($f_test{attrib} & $attribmask) ) ) {
              &_cleardots;
              if ( $f_test{attrib} < 0 ){
                if ( $verify ){
                  ;
                  }elsif ( $noexe ) {
                  &log( "   check/update attributes of $f_test{display_name}\n");
                  }else{
#       attribute of -1 occurs when directory deleted is held open !!!!
                  &log( "   -   unable to set attributes for $f_test{display_name} - file open, locked or does not exist\n");
                }
                }else{
                if ( $noexe ) {
                  &log( "   update attributes of $f_test{display_name}\n");
                  }elsif ( ! $verify) {
                  &log( "   updating attributes on $f_test{display_name}\n");
                  Win32::File::SetAttributes($f_test{name},($f_ref{attrib} & 2047))||
                  &gripe ("   -   failed to set attributes on $f_test{display_name}\n");
                  Win32::File::GetAttributes($f_test{name}, $attrib);
                  $f_test{attrib} = $attrib;
                  if ($debug & 4) {
                    &log("\nAttribute value for $f_test{display_name}: $f_test{attrib}\n");
                    &print_attrib($f_test{attrib});
                  }
                }
              }
            }
            
#==============================================================
# Gets the rights for all files listed on the command line.
# Only if both files are on NTFS.....
            
            
            if ( ( $f_ref{fstype} eq "NTFS"  ) &
              ( $f_test{fstype} eq "NTFS" ) &
              ( $ntfsignore == 0           ) ) {
              if ( ( $f_test{attrib} >= 0        ) &
                ( ! $verify)                  ) {
                %f_refperms = &readntfs($f_ref{name});
                %f_testperms = &readntfs($f_test{name});
                
#==============================================================
# Verify both files have same number of user names identified
# - and if not then quit
                
                while ( ($name,$mask) = each %f_refperms) {
                  next if ( $mask == $f_testperms{$name} );
                  $ntfschange = 1;
                  $f_testperms{$name} = $mask;
                }
                while ( ($name,$mask) = each %f_testperms) {
                  next if ($f_refperms{$name});
                  $ntfschange = 1;
                  undef $f_testperms{$name};
                }
                if ($ntfschange) {
                  if ($noexe) {
                    &_cleardots;
                    &log( "   update NTFS permissions on $f_test{display_name}\n");
                  }
                  elsif ( $ntfsupdate != 1 ) {
                    if ( $debug ) {
                      &_cleardots;
                      &log( "   NTFS permissions on $f_test{display_name} require update - configured to make no change\n");
                    }
                    }else{
                    &_cleardots;
                    &log( "   updating NTFS permissions on $f_test{display_name}\n");
                    Win32::FileSecurity::Set( $f_test{name}, \%f_testperms ) || &gripe ("   -   failed to update NTFS permissions\n");
                  }
                }
                }elsif ( ( $f_test{attrib} < 0 ) & $noexe ) {
                &log ("   check/update NTFS permissions of $f_test{display_name}\n");
              }
            }
          }
        }     # End of HOSTDIR loop
      }else{
        warn ("Failed to Set Cwd to $cwd\n ");
      }
      $srcmap=unmapdrive($cwddrive,$srcmap);
    }
    
    if ( $stats ) {
      $secs  = time - $targ_start_time;
      $mins  = int ( $secs/60 );
      $secs -= $mins * 60;
      $hours = int ( $mins/60 );
      $mins -= $hours * 60;
      
      $percent  = 100*$kbupdate/$kbtotal if ( $kbtotal > 0 );
      $kbupdate = &commas($kbupdate);
      $kbtotal  = &commas($kbtotal);
      &log("\nUpdated $target with \n");
      &log("   $kbupdate of a total $kbtotal bytes");
      &log(sprintf "( %04.2f %% )\n",$percent);
      &log("   $fupdate of $ftotal files installed/updated\n");
      &log("   $fremove files deleted\n");
      &log("   $dupdate of $dtotal directories created\n");
      &log("   $dremove directories deleted\n");
      &log( sprintf " in %02d:%02d:%02d \n",$hours,$mins,$secs );
    }
    $targmap=unmapdrive($hostdrive,$targmap);
  }       # End of TARGET loop
  
  
  
  &log("  All hosts updated at " . &datetime . "\n");
  $secs  = time - $start_time;
  $mins  = int ( $secs/60 );
  $secs -= $mins * 60;
  $hours = int ( $mins/60 );
  $mins -= $hours * 60;
  &log( sprintf " total elapsed time %02d:%02d:%02d \n",$hours,$mins,$secs );
# Send notifications
  if ( @notify ) {
    &notify;
  }
  
  &StopLog;
#=====================================================================
  
}
exit 0;
#=====================================================================
sub notify {
# Compile message
  my($from)  = 'rdist';
  my($reply) = 'Test Mail';
  my($smtp)  = $ENV{'MAILRELAY'};
  my($subject) = "files updated by win32rdist to " . $variable{$targets};
  my($message);
  open(READLOG,$log)||warn(" unable to open log file $log for notification\n");
#  open(DEBUGLOG,">C:/rdistdebug.log")||warn(" unable to open debug log\n");
  while (<READLOG>) {
    $message .= $_;
#    printf DEBUGLOG $_;
  }
#  close(DEBUGLOG);
  close(READLOG);
  
  open (LOG,">>$log")|| gripe("Unable to open log file $log");
  $mno = 0;
  foreach $to (@notify){
    $mno ++;
    &log("\nnotify $to\n");
    $message .= "\nnotify $to\n";
    $status=sendmail($from, $reply, $to, $smtp, $subject, $message );
    if     ( $status == -1 ) {
      &gripe("   SMTP host ($smtp) unknown\n");
      }elsif ( $status == -2) {
      &gripe("   socket() failed\n");
      }elsif ( $status == -3) {
      &gripe("   connect() failed\n");
      }elsif ( $status == -4) {
      &gripe("   service not available\n");
      }elsif ( $status == -5) {
      &gripe("   unspecified communication error\n");
      }elsif ( $status == -6) {
      &gripe("   local user ($to) unknown on SMTP host ($smtp)\n");
      }elsif ( $status == -7) {
      &gripe("   transmission of message failed\n");
      }elsif ( $status == -8) {
      &gripe("   argument \$to ($to) empty\n");
    }
  }
}
#=====================================================================
sub cleanup {
  &log("\nprocess interrupted - aborting now\n");
  if ( $cwddrive & $srcmap ) {
    &log("\nDeleting the $cwddrive drive mapping\n",$0) if $debug;
#    Delete the mapping
    Win32::NetResource::CancelConnection($cwddrive,0,1);
    undef $srcmap;
  }
  if ($target_drive eq $hostdrive ) {
    &log("\nDeleting the $target_drive drive mapping\n",$0) if $debug;
#   Delete the mapping
    Win32::NetResource::CancelConnection($target_drive,0,1);
    undef $targmap;
  }
  &StopLog;
  &notify;
}

#=====================================================================

sub deltree{
  my($root)=@_;
  find(\&findfile,$root);
  find(\&finddir,$root);
  print "verify: $verify    noexe: $noexe\n";
  while ( @dirlist ) {
    $dir = pop(@dirlist);
    if ( $noexe ) {
      &log( "   remove $dir\n");
      }elsif ( $verify ) {
      &log( "   remove $dir\n");
      }else{
      if ( rmdir($dir) ) {
        &log( "   removed $dir\n");
        $dremove ++;
        }else{
        &gripe("   -   unable to remove $dir\n");
      }
    }
  }
}
#=====================================================================
sub StartLog {
  my($logfile) = @_;
  open (LOG,">$logfile")|| die("Unable to open &log file $logfile");
}
#=====================================================================
sub StopLog {
  close LOG;
}
#=====================================================================
sub log {
  my( $message ) = @_;
  local($oldfh) = select(LOG);
  print $message;
  $| = 1;
  select($oldfh);
  print $message unless $quiet;
}
#=====================================================================
sub gripe {
  my( $message ) = @_;
  local($oldfh) = select(LOG);
  print $message;
  $| = 1;
  select($oldfh);
  warn($message);
}
#=====================================================================
sub td {
  my($td) = @_;
  my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $td;
  $month = (January,February,March,April,May,June,July,August,September,October,November,December) [$mon];
  return sprintf "%02d %s %4d %02d:%02d:%02d", $mday, $month, ($year+1900), $hour,$min,$sec;
}
#=====================================================================
sub findfile {
  if ( -f ) {
    if ($noexe) {
      &log( "   remove $File::Find::name\n");
      }elsif ( $verify ) {
      &log("   remove $File::Find::name\n");
      }else{
      &_dfile($File::Find::name,$File::Find::name);
    }
  }
}
#=====================================================================
sub _dfile {
  my($name,$display) =@_;
  if ( unlink $name ) {
    &log( "   deleted $display\n");
    $fremove ++;
    return 0;
    }else{
    &gripe  ("   -   unable to delete $display \n");
    return 1;
  }
}

#=====================================================================
sub finddir {
  if ( -d ) {
    push (@dirlist,$File::Find::name);
  }
}
#=====================================================================
sub fstat{
  my($filename) = @_;
  my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,
  $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
  
  if ($debug & 32) {
    &log( "\nstat result for $filename\n");
    &log( "   dev    :   $dev\n");
    &log( "   ino    :   $ino\n");
    &log( "   mode   :   $mode\n");
    &log( "   nlink  :   $nlink\n");
    &log( "   uid    :   $uid\n");
    &log( "   gid    :   $gid\n");
    &log( "   rdev   :   $rdev\n");
    &log( "   size   :   $size\n");
    &log( "   ctime  :   " . &td($ctime) . "\n");
    &log( "   mtime  :   " . &td($mtime) . "\n");
    &log( "   atime  :   " . &td($atime) . "\n");
    &log( "   blksize:   $blksize\n");
    &log( "   blocks :   $blocks\n");
  }
  return $size,$ctime,$mtime,$atime;
}
#=====================================================================
sub print_attrib{
  my ($attrib) = @_;
  if ( $attrib < 0 ) {
    &log( "  UNKNOWN - PROBABLY OPEN OR DOES NOT EXIST \n");
    }else{
    &log( "  READ ONLY \n") if $attrib & 1;
    &log( "  HIDDEN    \n") if $attrib & 2;
    &log( "  SYSTEM    \n") if $attrib & 4;
    &log( "  VVVV      \n") if $attrib & 8;
    &log( "  DIRECTORY \n") if $attrib & 16;
    &log( "  ARCHIVE   \n") if $attrib & 32;
    &log( "  WWWW      \n") if $attrib & 64;
    &log( "  NORMAL    \n") if $attrib & 128;
    &log( "  XXXX      \n") if $attrib & 256;
    &log( "  YYYY      \n") if $attrib & 512;
    &log( "  ZZZZ      \n") if $attrib & 1024;
    &log( "  COMPRESSED\n") if $attrib & 2048;
  }
}
#=====================================================================
sub readntfs {
  my($filename) = @_;
  my(@happy);
  if ( Win32::FileSecurity::Get( $filename, \%perms ) ) {
    if ($debug & 8) {
      &log( "\n\nNTFS permissions for $filename\n");
      while( ($name, $mask) = each %perms ) {
        &log( "\n   $name:\n      ");
        EnumerateRights( $mask, \@happy ) ;
        &log( join( "\n      ", @happy ), "\n");
      }
      &log("\n");
    }
    }else{
    &gripe( "Error #", int( $! ), ": $!" ) ;
    }
    return %perms;
  }
#=======================================================================
# Interpret Command Line Switches
sub args {
  PARSE_ARGS:
  while( $_[ 0 ] =~ /^-/ ){
    local( $arg ) = shift @_;
    
    if( $arg eq '-b' ){
#   Performs a binary comparison and updates files if they differ.
      $binary = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-c' ){
#   Directs the rdist command to interpret the remaining arguments
#   as a small distribution file.
      $distfile = @_;
      last;
    }
    
    
    if( $arg =~ /^-D([0-9]*)$/ ){
#   Turns on the debugging output.
      if (defined $1) {
        $debug = $1;
        }else{
        $debug = 1;
      }
      next PARSE_ARGS;
    }
    
    if( $arg =~ /^-f(.*)$/ ){
#   Specifies the name of the distribution file.
#   the next arg is the distfile to get
      $distfile = $1;
      if( ! $distfile ){
# Must be -f space arg
        $distfile = shift @_;
      }
      next PARSE_ARGS;
    }
    
    if( $arg eq '-h' ){
#  Copies the file that the link points to rather than the link itself.
#  This file type is invalid on Win32 systems and therefore this switch
#  has no function
      last;
    }
    
    if( $arg eq '-i' ){
#  Ignores unresolved links. The rdist command maintains the link
#  structure of files being transferred and warns users if it cannot
#  find all the links.
#  This file type is invalid on Win32 systems and therefore this switch
#  has no function
      last;
    }
    
    if( $arg eq '-I' ){
#   Ignores NTFS permission testing - useful when Read access only is available.
      $ntfsignore = 1;
      next PARSE_ARGS;
    }
    
    if( $arg =~  /^-l(.*)$/ ){
#   Identify the log file to use - rather than the default.  This is
#   deleted after the process is completed.
      $log = $1;
      $logset = 1;
      if( ! $log ){
# Must be -l space arg
        $log = shift @_;
      }
      next PARSE_ARGS;
    }
    
    
    if( $arg =~ /^-m(.*)$/ ){
#   -m Host Limits which machines are to be updated. You can use the -m
#   Host option multiple times to limit updates to a subset of the hosts
#   listed in the distfile file.
      local( $hostlimit ) = $1;
      if( ! $hostlimit ){
# Must be -m space arg
        $hostlimit = shift @_;
      }
      push @hostlimit, $hostlimit;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-n' ){
#   Prints the subcommands without executing them. Use the -n flag
#   to debug the distfile file.
      $noexe = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-N' ){
#   Update NTFS attributes (ownership/permissions) on destination
#   to debug the distfile file.
      $ntfsupdate = 1;
      next PARSE_ARGS;
    }
    
    
    if( $arg eq '-q' ){
#   Operates in quiet mode. The -q option suppresses printing of modified
#   files on standard output.
      $quiet = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-R' ){
#   Removes extraneous files. If a directory is being updated, any
#   files that exist on the remote host but not in the master directory
#   are removed. Use the -R flag to maintain identical copies of directories.
      $rmextra = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-s' ){
#   Print statistics evaluated for each host ( qty data updated, files, dirs etc.)
      $stats = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-v' ){
#   Verifies that the files are up-to-date on all hosts; files that
#   are out-of-date are then displayed. However, the rdist -v command
#   neither changes files nor sends mail.
      $verify = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-w' ){
#   Appends the entire path name of the file to the destination directory
#   name. Normally, the rdist command uses only the last component of
#   a name for renaming files, preserving the directory structure of the
#   copied files.
#
#   When the -w flag is used with a file name that begins with a ~ (tilde),
#   everything except the home directory is appended to the destination
#   name. File names that do not begin with a / (slash) or a ~ (tilde)
#   use the destination user's home directory as the root directory for
#   the rest of the file name.
      $appendpath = 1;
      next PARSE_ARGS;
    }
    
    if( $arg eq '-y' ){
#   Prevents recent copies of files from being replaced by files that
#   are not as recent. Files are normally updated when their time stamp
#   and size differ. The -y flag prevents the rdist command from updating
#   files more recent than the master file.
      $datecheck = 1;
      next PARSE_ARGS;
    }
    gripe("   unknown arg $arg, skipping\n");
  }
  
  
  
#  Switches not processed........................................
#
#-d Argument=Value	Defines the Argument variable as having the value
#specified by the Value variable. The -d flag defines or overrides
#variable definitions in the distfile file. The Value variable can
#be specified as an empty string, one name, or a list of names surrounded
#by parentheses and separated by tabs or spaces.
#
  
}

#=======================================================================
# Read the distribution file


sub ReadDistfile {
  
  open (DIST, "@_") || die "Unable to open @_";
  undef $getvar;
  undef @temp;
  
  while (<DIST>) {
    chop;
#--------------------------------------------------------------
# Clean out unwanted characters
#
# Loose comments in distfile
    next if /^([\s]*)#/;
    s/#.*//g;
# Loose leading and trailing white space
    s/^\s*//g;
    s/\s*$//g;
#--------------------------------------------------------------
# Determine variables
#
    if ( $getvar ) {
      if ( /([^\)]*)\)/ ) {
        push (@temp,$1);
        $variable{$getvar} = join('|',@temp);
#print " Consolidated $getvar : $variable{$getvar}\n";
        undef $getvar;
        undef @temp;
        next;
      }
      push (@temp,$_);
      next;
    }
    if ( /(\w+)\s*=\s*\(\s*(\w*)/ ) {
#   print "variable   : $1\n";
#   print "first word : $2\n";
      @temp = $2 if $2;
      $getvar = $1;
      next;
    }
#--------------------------------------------------------------
#  Determine rdist instructions
#
    if ($cseq) {
      $commd .= $_;
    }
    if ( /\$\{(\w+)\}\s*-\>\s*\$\{(\w+)\}\s*(.*)$/ ) {
      $files = $1;
      $targets = $2;
      $commd = $3;
      $cseq  = 1;
      &log("   Found instruction\n") if ($debug & 2);
    }
    
#  Interpret command switch
    if ( $cseq && $commd =~ /;$/ ) {
      $commd =~ s/;$//g;
      if ($debug & 2 ) {
        &log("Command : $commd\n");
        &log("files   : $files\n");
        &log("hosts   : $targets\n");
      }
      undef $cseq;
      $commd =~ s/install//g;
#$commd =~ s/(\S+)/"$1"/g;
      $commd =~ s/\s+/ /g;
      $commd =~ s/^\s+//g;
      $commd =~ s/\s+$//g;
#$commd =~ s/ /,/g;
      &args(split(/ /,$commd));
    }
    
#--------------------------------------------------------------
# Process any special instructions here
    if ($spcl) {
      $commd .= "\n";
      $commd .= $_;
    }
    if ( /special\s+(\S*)\s+(\S*)/ ) {
      $file  = $1;
      $commd = $2;
      $spcl  = 1;
#print "Found special\n";
#print "  file    : $file\n";
#print "  command : $commd\n";
    }
    if ( $spcl && $commd =~ /"\s*;\s*$/ ) {
      $commd =~ s/;$//g;
      print "===============================Special===============================\n";
      print "Command : $commd\n";
      print "file    : $file\n";
      print "=====================================================================\n";
      $special{$file} = $commd;
      undef $spcl;
    }
    
#--------------------------------------------------------------
# Process any notify instructions here
    if ($ntfy) {
      $addr .= $_;
    }
    if ( /notify\s+(\S*)/ ) {
      $addr  = $1;
      $ntfy  = 1;
    }
    if ( $ntfy && $addr =~ /;\s*$/ ) {
      $addr =~ s/;$//g;
      push (@notify,$addr);
      undef $ntfy;
    }
#--------------------------------------------------------------
# Process except entries
    if ( /except\s+(\S*)\s*;\s*$/ ) {
      my $pattern  = $1;
      if ( $pattern =~ /(\S+)\$\{(\S+)\}/ ) {
        if ( defined $variable{$2} ) {
          foreach $p1 (split (/ /,$variable{$2}) ) {
            push @exceptions, $k . $p1;
          }
          }else{
          &log("   invalid exception argument $pattern, {$2} does not exist\n");
        }
        }else{
        push @exceptions, $pattern;
      }
    }
#--------------------------------------------------------------
# Process except_pat entries
    if ( /except_pat\s+\(\s*(.*)\s*\)\s*;\s*$/ ) {
      foreach (split(/\s+/,$1)) {
        push @except_pats, $_;
      }
    }
    
    
  }
  close DIST;
  
}
#------------------------------------------------------------
# sub sendmail()
#
# send/fake email around the world ...
#
# Version : 1.21
# Environment: Hip Perl Build 105 NT 3.51 Server SP4
# Environment: Hip Perl Build 110 NT 4.00
#
# arguments:
#
# $from email address of sender
# $reply email address for replying mails
# $to email address of reciever
# (multiple recievers can be given separated with space)
# $smtp name of smtp server (name or IP)
# $subject subject line
# $message (multiline) message
#
# return codes:
#
# 1 success
# -1 $smtphost unknown
# -2 socket() failed
# -3 connect() failed
# -4 service not available
# -5 unspecified communication error
# -6 local user $to unknown on host $smtp
# -7 transmission of message failed
# -8 argument $to empty
#
# usage examples:
#
# print
# sendmail("Alice <alice\@company.com>",
# "alice\@company.com",
# "joe\@agency.com charlie\@agency.com",
# $smtp, $subject, $message );
#
# or
#
# print
# sendmail($from, $reply, $to, $smtp, $subject, $message );
#
# (sub changes $_)
#
#------------------------------------------------------------



sub sendmail {
  my ($from, $reply, $to, $smtp, $subject, $message) = @_;
  my ($fromaddr) = $from;
  my ($replyaddr) = $reply;
  
  $to =~ s/[ \t]+/, /g; # pack spaces and add comma
  $fromaddr =~ s/.*<([^\s]*?)>/$1/; # get from email address
  $replyaddr =~ s/.*<([^\s]*?)>/$1/; # get reply email address
  $replyaddr =~ s/^([^\s]+).*/$1/; # use first address
  $message =~ s/^\./\.\./gm; # handle . as first character
  $message =~ s/\r\n/\n/g; # handle line ending
  $message =~ s/\n/\r\n/g;
  $smtp =~ s/^\s+//g; # remove spaces around $smtp
  $smtp =~ s/\s+$//g;
  
  if (!$to) { return -8; }
  
  my($proto) = (getprotobyname('tcp'))[2];
  my($port) = (getservbyname('smtp', 'tcp'))[2];
  
  my($smtpaddr) = ($smtp =~
  /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
  ? pack('C4',$1,$2,$3,$4)
  : (gethostbyname($smtp))[4];
  
  if (!defined($smtpaddr)) { return -1; }
  
  if (!socket(S, AF_INET, SOCK_STREAM, $proto)) { return
  -2; }
  if (!connect(S, pack('Sna4x8', AF_INET, $port, $smtpaddr))) { return -3; }
  
  my($oldfh) = select(S); $| = 1; select($oldfh);
  
  $_ = <S>; if (/^[45]/) { close S; return -4; }
  
  print S "helo localhost\r\n";
  $_ = <S>; if (/^[45]/) { close S; return -5; }
  
  print S "mail from: <$fromaddr>\r\n";
  $_ = <S>; if (/^[45]/) { close S; return -5; }
  
  foreach (split(/, /, $to)) {
    print S "rcpt to: <$_>\r\n";
    $_ = <S>; if (/^[45]/) { close S; return -6; }
  }
  
  print S "data\r\n";
  $_ = <S>; if (/^[45]/) { close S; return -5; }
  
  print S "To: $to\r\n";
  print S "From: $from\r\n";
  print S "Reply-to: $replyaddr\r\n" if $replyaddr;
  print S "X-Mailer: Perl Sendmail Version 1.21 Christian Mallwitz Germany\r\n";
  print S "Subject: $subject\r\n\r\n";
  print S "$message";
  print S "\r\n.\r\n";
  
  $_ = <S>; if (/^[45]/) { close S; return -7; }
  
  print S "quit\r\n";
  $_ = <S>;
  
  close S;
  return 1;
}
#=====================================================================
sub mapdrive {
  my ($unc) = @_;
  my ($debug) = 0;
  my ($target,$share,$path,$share,%unc,$drive);
  if( $unc =~ /(\\\\|\/\/)([a-zA-Z0-9]+)(\\|\/)([a-zA-Z0-9]+[\$]?)(.*)$/ ) {
#   If Share is specified
#   Make Share Connection (if required)
#        - Return to TARGET on failure
    $target= $2;
    $share= $4;
    $path = $5;
    $path = '/' . $path;
    $path =~ s/\\/\//g;   # Substitute all back slashes for forward slashes
    $path =~ s/\/\//\//g; # Substitute double forward slashes for single
    if ( $debug > 0 ) {
      &log("Verifying host connetion\n");
      &log("  Host  : $target\n");
      &log("  Share : $share\n");
      &log("  Path  : $path\n");
    }
    
    $share = '\\\\' . $target . '\\' . $share;
    $share =~ tr/a-z/A-Z/;
#   Determine UNC's already mapped to drives
    undef %unc;
    open (NETUSE, "net use|");
    while (<NETUSE>) {
      chop;
      if ( /(OK|Disconnected)\s+([a-zA-Z]:)\s+(\S+)\s+.*/ ) {
        $unc = $3;
        $unc =~ tr/a-z/A-Z/;
        $unc{$unc} = $2;
      }
    }
    close NETUSE;

    if ( $unc{$share} ) {
#     Identify target drive - if its already mapped to the UNC
      $drive = $unc{$share};
      }else{
#     Add the new share - but only if we need to
      $drive = Win32::GetNextAvailDrive();# Temp drive for URL destination mapping
      &log("  mapdrive: Adding Drive $target_drive to share $share\n") if $debug;
#     call net use to add the drive/share
      if ( system "net use $drive $share /persistent:no" ) {
        &gripe("  mapdrive: Failed to map drive $drive to UNC $share ($!)\n",$0);
        return 1;
        }else{
        &log("  mapdrive: Added Drive mapping OK\n") if $debug;
      }
    }
    return $drive,$path,$share;
    }else{
    &gripe ("  mapdrive: Invalid UNC supplied ($unc)\n");
    return 1;
  }
#=====================================================================
}
sub _cleardots {
  unless ( $dotposn == 0 ) {
    printf "\n";
    $dotposn = 0;
  }
}
#=====================================================================
sub _printdot {
  printf ".";
  $dotposn ++;
  if ( $dotposn > 77 ) {
    $dotposn = 0;
    printf "\n";
  }
}
#=====================================================================
sub commas {
  local($_) = @_;
  1 while s/(.*\d)(\d\d\d)/$1,$2/;
  $_;
}
#=====================================================================
#
sub datetime {
  my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  my($month) = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$mon];
  my($datetime) = sprintf "%2d.%02d.%02d %2d %s %02d",
  $hour,$min,$sec,$mday,$month,($year+0);
  return $datetime;
}
#=====================================================================
sub unmapdrive {
  my($drive,$flag) = @_;
  if ( $drive && $flag ) {
    Win32::SetCwd($ENV{'SystemDrive'})
      || &gripe("Failed to set CWD to " . $ENV{'SystemDrive'} . "\n",$0); 
    &log("\nDeleting the $drive drive mapping\n") if $debug;
    if ( Win32::NetResource::CancelConnection($drive,0,1) ) {
      return undef;
    }else{
      &gripe("\n Failed to Delete the $drive drive mapping\n",$0);
      return 1;
    }
  }
}
  
#=====================================================================
1;
#=====================================================================
__END__

=head1 NAME

win32rdist - A Win32 based script that will maintain file structures
between various file systems and servers.  This is loosely based on
the unix rdist utility.

=head1 README

This is the README file for the win32rdist script, a MSWin32 utility
that attempts to mimic the unix based rdist utility.

This is version 0.13 of win32rdist. This version is still considered
alpha software. It has only been tested on Windows NT 4.0 (SP3 and
SP4) with perl 5.005_02 - based on Activeware 507 and 509 releases.
It has had limited testing on NTFS, FAT and Samba file systems.

AVAILABILITY

The latest version of the win32rdist script is available from the
Comprehensive Perl Archive Network (CPAN), where it can be found in the
Win32\Utilities directory.

INSTALLATION

Create an environment variable MAILRELAY, and set this to the local
internet mail relay host name. This will be used if any notifications
are to be sent.

Should be ready to use wherever you save it on your MSWin32 machine

SUPPORT

You can send bug reports and suggestions for improvements on this module
to me at <mailto:Dave.Roberts@iname.com>. However, I can't promise to offer
any other support for this script.

COPYRIGHT

This module is Copyright © 1999 Dave Roberts. All rights reserved.

This script is free software; you can redistribute it and/or modify it
under the same terms as Perl itself. This script 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. The copyright holder of this script can not be held liable
for any general, special, incidental or consequential damages arising
out of the use of the script.

CHANGE HISTORY


=item 15 Jan 1999

- beefed up the README section

=item 16 Jan 1999

- provided -l option for specifing logfile

=item 18 Jan 1999

- modified creation logic for logfile location, using SystemDrive
variable if TMP and TEMP not specified (typically when the NT
schedule service is used to call the script). This fixes problems
with the notify routine when logfile is not clearly specified.

- Identified the MAILRELAY environment variable to contain the
internet mail relay host used.

- Modified output of UNC defined file structures to display UNC name
(not the drive letter mapped to the UNC).

=item 22 Jan 1999

- Fixed bug in display of UNC name (was dropping last character of the
share name).

- Changed print of log file location to appear only in debug mode.

- Changed file timestamp tolerance variable from $tolerance to
$fat_timestamp_tol, and code so that it only applies when comparisons
to FAT based filesystems are made.

- Added "TO DO" section to README

- Set release version to 0.12 dated 22 JaN 1999

=item 25 Jan 1999

- Tidied documentation a little more.

- Modified stats collection to prevent size counts being made when
file copy fails.

- Fixed bug in stats data collection count - @srcfiles was not being
initialized correctly.

- Changed indentation of error messages - to standardize and aid
visibility

=item 28 Jan 1999

- Changed notification message subject - to be similar to that used in
unix utility.

=item 6 Feb 1999

- Single files deleted are now displayed in a URL format.  This does
not affect files deleted when a directory tree deletion is in
progress.

- Modified calls to Win32::File::GetAttributes

- Only trys to set file attributes or NTFS permissions if the
destination file exists - should prevent "Failed to copy..."
error then tries to set attributes

- Fixed bug to correct counter for number of files deleted.

- Corrected notify mail header.

- Set release version to 0.13

=item 22 July 1999

- The mapdrive subroutine now ignores case - preventing the adding of duplicate
shares.

- Improved logging information for deleting of shares.

- set release version to 0.14

=item 23 July 1999

- Fixed bug in deletion of shares

- Set release version to 0.14a

=item 26 July 1999

- set release to 0.15

- Added datetime subroutine

- Added date/time timestamps to log file

- Moved the disconnect of the $hostdrive - to outside of the TARGET
loop (this was a bug)...

=item 29 July 1999

- set release to 0.16

- Added unmapdrive subroutine, and corrected bitwise to logical AND
statement in the drive mapping test for this logic.  Added Win32::SetCwd
to the unmapdrive to prevent unmapping the CWD.

- moved both unmapdrive subroutines to correct locations (were
incorrectly placed in loops.

TO DO

- ignore blank/null entries for $variable{$targets} and $variable{$files}
and probably to generate error messages for unrecognized entries.

- deltree subroutine needs change to display URL's rather than file
names.

- change file to allow pb.pl to work without indent errors

- Improve the documentation. There are still some features that are
not coded or tested.

=head1 PREREQUISITES
This script requires the following modules

C<File::Compare>
C<Win32::File>
C<Win32::FileSecurity>
C<Win32::FileSecurity>
C<Win32::NetResource>
C<File::Basename>
C<File::Copy>
C<File::Find>
C<DirHandle>
C<Socket>

=pod OSNAMES
MSWin32

=pod SCRIPT CATEGORIES

Win32/Utilities

=head1 DESCRIPTION

Distributes identical copies of files on multiple hosts.

Syntax



To Use a Distribution File

win32rdist [ -n ] [ -q ] [ -b ] [ -D ] [ -R ] [ -h ] [ -i
] [ -v ] [ -w ] [ -y ] [ -f FileName ] [ -d Argument=Value
] [ -m Host ] ... [ Name ] ...

To Interpret Arguments as a Small Distribution File

win32rdist [ -n ] [ -q ] [ -b ] [ -D ] [ -R ] [ -h ] [ -i
] [ -v ] [ -w ] [ -y ] -c Name ... [ Login@ ] Host [ Destination
]

=head1 DESCRIPTION

The win32rdist command maintains identical copies of files on multiple
hosts. The win32rdist command preserves the DOS style attributes and modified
time of files.  It can optionally update NTFS style permissions.

The win32rdist command can receive direction from the following sources:

*	A distribution file specified by the -f flag.

*	Command-line arguments that augment or override variable definitions
in the distribution file.

*	Command-line arguments that serve as a small distribution
file.

The value specified by the Name parameter is read as the name of a
file to be updated or a subcommand to execute. If you do not specify
a value for the Name parameter on the command line, the win32rdist command
updates all the files and directories listed in the distribution file.
If you specify - (minus sign) for the Name parameter, the win32rdist command
uses standard input. If the name of a file specified by the Name parameter
is the same as the name of a subcommand, the win32rdist command interprets
the Name parameter as a subcommand.

The win32rdist command requires that a the owner of the win32rdist process has
permission to establish connections to both source and destination
file systems and has rights to create files and change permissions.

head2= Flags

-b	Performs a binary comparison and updates files if they differ.

-c	Directs the win32rdist command to interpret the remaining arguments
as a small distribution file. Available arguments are:

Name	Specifies single name or list of names separated by blanks. The
value can be either a file or a subcommand.

[Login@]Host	Specifies the machine to be updated and, optionally,
the login name to be notified of the update. The Login option is not
yet implemented. Host can be specified as an existinf file share (H:)
or as a machine name (\\desthost\).
  
Destination	Specifies a file on the remote machine if a single name
is specified in the Name argument; specifies a directory if more than
one name is specified.

Note:	Do not use the -c flag with the -f, -d, or -m flag.

-d Argument=Value	Defines the Argument variable as having the value
specified by the Value variable. The -d flag defines or overrides
variable definitions in the distfile file. The Value variable can
be specified as an empty string, one name, or a list of names surrounded
by parentheses and separated by tabs or spaces.

-D	Turns on the debugging output. This can also be used as -D#, where
# is a number that specifies additional debugging information. Values are

1   normal debugging information
2   display configuration settings (taken from both command line and
distribution file)
4   display Win32 style attributes
8   display NTFS permissions
16  display source and target file system types (FAT, NTFS, Samba etc)
32  display stat results
64  disply files in source tree(s)
128 display win32rdist file variables
256 display exception files

These values may be combined.

-f FileName	Specifies the name of the distribution file. If you do
not use the -f flag, the default value is the distfile or Distfile
file in your $HOME directory.

-h	Copies the file that the link points to rather than the link itself.
This file type is invalid on Win32 systems and therefore this switch
has no function

-i	Ignores unresolved links. The win32rdist command maintains the link
structure of files being transferred and warns users if it cannot
find all the links.This file type is invalid on Win32 systems and
therefore this switch has no function.

-I  Prevents NTFS permissions from being read - useful when only
limited privilege is available to NTFS drive.

-l FileName	Specifies the name of the process log file. If you do
not use the -l flag, the default value is $TMP\rdist.log, or if $TMP
is not defined $TEMP\rdist.log (note if executed from the schedular $TMP
and $TEMP may not be available).

-m Host	Limits which machines are to be updated. You can use the -m
Host option multiple times to limit updates to a subset of the hosts
listed in the distfile file.

-n	Prints the subcommands without executing them. Use the -n flag
to debug the distfile file.

-N	Update NTFS attributes (ownership and permissions).  Take care
using this when transferring files across NT Domains.

-q	Operates in quiet mode. The -q option suppresses printing of modified
files on standard output.

-R	Removes extraneous files. If a directory is being updated, any
files that exist on the remote host but not in the master directory
are removed. Use the -R flag to maintain identical copies of directories.

-s  Print statistics after processing each host.

-v	Verifies that the files are up-to-date on all hosts; files that
are out-of-date are then displayed. However, the win32rdist -v command
neither changes files nor sends mail.

-w	Appends the entire path name of the file to the destination directory
name. Normally, the win32rdist command uses only the last component of
a name for renaming files, preserving the directory structure of the
copied files. This option allows the directory structure (including any
share specified) to be included in the new file structure.

-y	Prevents recent copies of files from being replaced by files that
are not as recent. Files are normally updated when their time stamp
and size differ. The -y flag prevents the win32rdist command from updating
files more recent than the master file.

=head2 DESCRIPTION FILE
Distribution File (distfile File)

The distribution file specifies the files to copy, destination hosts
for distribution, and operations to perform when updating files to
be distributed with the win32rdist command.

Normally, the win32rdist command uses the distfile file in your $HOME directory.
You can specify a different file If you use the -f flag.

Entry Formats

Each entry in the distribution file has one of the following formats:

VariableName = NameList	Defines variables used in other entries of
the distribution file (SourceList, DestinationList, or SubcommandList).

[Label:] SourceList -> DestinationList SubcommandList	Directs the
win32rdist command to distribute files named in the SourceList variable
to hosts named in the DestinationList variable. Distribution file
commands perform additional functions.

[Label:] SourceList :: TimeStampFile SubcommandList	Directs the
win32rdist command to update files that have changed since a given date.
Distribution file subcommands perform additional functions. Each file
specified with the SourceList variable is updated if the file is newer
than the time-stamp file. This format is useful for restoring files.

Labels are optional and used to identify a subcommand for partial
updates.

Entries

VariableName	Identifies the variable used in the distribution file.

NameList	Specifies a list of files and directories, hosts, or subcommands.

SourceList	Specifies files and directories on the local host for the
win32rdist command to use as the master copy for distribution.

DestinationList	Indicates hosts to receive copies of the files.

SubcommandList	Lists distribution file subcommands to be executed.

The win32rdist command treats new-line characters, tabs, and blanks as
separators. Distribution file variables for expansion begin with a
$ (dollar sign) followed by a single character or a name enclosed
in {} (braces). Comments begin with a # (pound sign) and end with
a new-line character.

Source and Destination List Format

The distribution file source and destination lists comprise zero or
more names separated by blanks, as shown in the following format:

[Name1] [Name2] [Name3] ...

The win32rdist command recognizes and expands the following shell metacharacters
on the local host in the same way as for the csh command.

*	[ (left bracket)

*	] (right bracket)

*	{ (left brace)
  
*	} (right brace)

*	( (left parenthesis)
  
*	) (right parenthesis)

*	* (asterisk)

*	? (question mark)

To prevent these characters from being expanded, precede them with
a \ (backslash).

Distribution File Subcommands

Multiple commands to the shell must be separated by a ; (semicolon).
Commands are executed in the user's home directory on the host being
updated. The special subcommand can be used to rebuild private databases
after a program has been updated.

The distribution file subcommand list may contain zero or more of
the following subcommands:

install Options [OptionalDestName];	Copies out-of-date files and
directories. The win32rdist command copies each source file or directory
to each host in the destination list. The available options as specified
by the Options variable are the win32rdist command flags -b, -h, -i, -R,
-v, -w, and -y. These options only apply to the files specified by
the SourceList variable. When you use the -R flag, nonempty directories
are removed if the corresponding file name is absent on the master
host. The OptionalDestName parameter renames files.

If no install subcommand appears in the subcommand list or the destination
name is not specified, the source file name is used. Directories in
the path name are created if they do not exist on the remote host.
The login name used on the destination host is the same as the local
host unless the destination name is of the format login@host.

notify NameList;	Mails the list of updated files and any errors that
may have occurred to the listed names (the NameList parameter). If
no @ (at sign) appears in the name, the destination host is appended
to the name (name@host).

except NameList;	Causes the win32rdist command to update all the files
specified by the SourceList entry except for those files specified
by the NameList variable.

except_pat NameList;	Prevents the win32rdist command from updating any
files that contain a string that matches a member of the list specified
by the NameList variable.

special NameList "String";	Specifies shell commands (the "String"
variable) to be executed on the remote host after the file specified
by the NameList variable is updated or installed. If the NameList
variable is omitted, the shell commands are executed for every file
updated or installed. The shell variable FILE is set to the current
file name before the win32rdist command executes the "String" variable.
The "String" value must be enclosed in " " (double quotation marks)
and can cross multiple lines in the distribution file.

=head2 Exit Status

This command returns the following exit values:

0	Specifies that the command completed successfully.

>0	Specifies that an error occurred.

=head1 Examples



Examples of the Format: VariableName = NameList

1.	To indicate which hosts' files to update, enter a line similar
to the following:

HOSTS =( H:\ \\arpa\dest_dir )

where the HOSTS variable is defined to be H: and \\arpa\ on the dest_dir
share. The win32rdist command updates files on the hosts H: and \\arpa\dest_dir. You
could use this variable as a destination list.

2.	To indicate a name to use as a value for a SourceList entry, enter
a line similar to the following:

FILES = ( /bin /lib/usr/bin /usr/games
  /usr/include/{*.h,{stand,sys,vax*,pascal,machine}/*.h}
/usr/lib /usr/man/man? /usr/ucb /usr/local/win32rdist )

where the FILES value is defined to be the files to be used for the
SourceList entry. Note that a forward slash (/) is used to reprent the
Microsoft standard backslash (\).

3.	To indicate which files to exclude from the updating process, enter
a line similar to the following:

EXLIB = ( Mail.rc aliases aliases.dir aliases.pag crontab dshrc
sendmail.cf sendmail.fc sendmail.hf sendmail.st uucp vfont)

where the EXLIB value is defined as a list of files to exclude from
the updating process.

Examples of the Format: [label:] SourceList -> DestinationList
SubcommandList

1.	To copy a source list of files to a destination list of hosts,
enter a line similar to the following:

${FILES} ->${HOSTS}
install -R
except /usr/lib/${EXLIB}  ;
except /usr/games/lib  ;
special /usr/sbin/sendmail "/usr/sbin/sendmail.bz"  ;

The [Label:] entry of the line is optional and not shown here. The
$ (dollar sign) and the {} (braces) cause the file names FILES, HOSTS,
and EXLIB to be expanded into the lists designated for them in the
previous examples. The rest of the example comprises the subcommand
list.

2.	To use the [Label:] entry, enter the line as follows:

srcsL:
/usr/src/bin -> arpa
except_pat (\e\e.o\e$ /SCCS\e$ ) ;

The label is srcsL: and can be used to identify this entry for updating.
The /usr/src/bin file is the source to be copied and host arpa is
the destination of the copy. The third line contains a subcommand
from the subcommand list.

3.	To use a time-stamp file, enter a line similar to the following:

${FILES} :: stamp.cory
notify root@cory

The $ (dollar sign) and {} (braces) cause the name specified by FILES
to be expanded into the list designated for it in example . The time-stamp
file is stamp.cory. The last line is a subcommand from the subcommand
list.


=head1 Files

=head1 Related Information

=cut