#!/usr/bin/perl

# check_ogp.pl
# Checks the ownership, group, and permissions for all files and directories in 
# a target directory against what you want them to be. You can use the default
# settings (useful for the collective) or enter your own. You can also have the
# changes made automatically or via prompting. Or, you can have no changes made
# but just get a report of what files and directories do not match what you want.

# Since the script can change ownership of files; you probably must be the root 
# (superuser) to run it successfully.

# TODO: Add option allowing output to be echoed to a log file; this can help 
# when generating a report on a directory with lots of files and directories
# in it.

use strict;
use File::Find;

if ( $^O eq "MSWin32" )
{
   print "ERROR: Wrong operating system! \n"
         . "This script must be run on a Linux box, but your OS says it is $^O. \n"
         . "Bye!";
   exit;
}

my $defDir      = "/inetpub/www";         # Default target dir.
my $targetDir   = "";                     # Target dir (may differ from default).
my $defOwner    = "collective";           # Default target owner.
my $targetOwner = "";                     # Target owner (may differ from default).
my $targetUid;                            # UID of target owner.
my $defGroup    = "inetadmin";            # Default target group.
my $targetGroup = "";                     # Target group (may differ from default).
my $targetGid;                            # UID of target owner.
my $defPerm     = sprintf "%04o", 0775;   # Default permissions (775 saved as octal-looking string).
my $targetPerm  = "";                     # Target permissions (may differ from default).
my $useDefaults = 1;   # 0 = get user input; 1 = use defaults.
my $appMode     = 1;   # 0 = make changes without prompting;
                       # 1 = prompt before making changes;
                       # 2 = make no changes; just report findings.
my @fileStat;          # Holds list data returned by stat().
my $fileMode;          # Mode (file type & permissions) of file/dir; $temp[2].
my $fileUid;           # User ID (owner) of file/dir; $temp[4].
my $fileGid;           # Group ID of file/dir; $temp[5].
my $fileOwner;         # Owner (user name), looked up from $fileUid.
my $fileGroup;         # Group, looked up from $fileGid.
my $filePerm;          # File permissions, extracted from $fileMode; stored as string
                       # that looks like an octal number (easy for user to read).
my $prompt;            # Hold user input from prompts.
my $curDir;            # Current directory of finddepth().
my $errorCount;

# Find out what user wants to do.
print "*** Check/Change Ownership and Permissions of Files and Directories *** \n"
      . "Default Settings: \n"
      . "   Starting Directory:               $defDir \n"
      . "   Files/Dirs owner should be:       $defOwner \n"
      . "   Files/Dirs group should be:       $defGroup \n"
      . "   Files/Dirs permissions should be: $defPerm \n"
      . "   Prompt before making changes \n";
$errorCount = 0;   # Count input errors; bail out if user can't get it right.
CheckUser();
print "\n";

# Now do it.
finddepth sub 
{
   return unless ( -f $_ or -d $_ );   # Skip everything except files and directories.
   if ( $curDir ne $File::Find::dir )
   {
      $curDir = $File::Find::dir;
      print "\nChecking $curDir... \n";
   }

   @fileStat = stat( "$_" );
   $fileMode = $fileStat[2];
   $fileUid  = $fileStat[4];
   $fileGid  = $fileStat[5];

   $fileOwner = getpwuid( "$fileUid" );   # Look up the owner's name.
   $fileGroup = getgrgid( "$fileGid" );   # Look up the group's name.
   $filePerm  = sprintf "%04o", $fileMode & 07777;   # Extract the permissions from the mode and save them 
                                                     # in octal format. (Otherwise, they'll look odd.)

   $prompt = "";   # Clear the prompt.
   if ( "$fileOwner" ne "$targetOwner" or "$fileGroup" ne "$targetGroup" )
   {
      if ( $appMode == 2 )
      {
         print "   $_ owner/group is $fileOwner/$fileGroup \n";
      } elsif ( $appMode == 1 )
      {
         print "   $_ owner/group is $fileOwner/$fileGroup. Change to $targetOwner/$targetGroup (y/any)? ";
         $prompt = ;
         chomp $prompt;
      }

      if ( $appMode == 0 or $prompt eq "y" )
      {
         chown( "$targetUid", "$targetGid", $_ );
         print "$_ changed to $targetOwner/$targetGroup \n";
      }
   }

   $prompt = "";   # Clear the prompt.
   if ( "$filePerm" ne "$targetPerm" )
   {
      if ( $appMode == 2 )
      {
         print "   $_ permissions are $filePerm \n";
      } elsif ( $appMode == 1 )
      {
         print "   $_ permissions are $filePerm. Change to $targetPerm (y/any)? ";
         $prompt = ;
         chomp $prompt;
      }

      if ( $appMode == 0 or $prompt eq "y" )
      {
         chmod( "$targetPerm", $_ );
         print "$_ changed to $targetPerm \n";
      }
   }
}, $targetDir;
print "\n";

sub CheckUser
{
   print "Use the default settings (y = yes; n = no; q = quit)? ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.
   print "\n";

   if ( $prompt eq "y" )
   {
      $targetDir   = $defDir;
      $targetOwner = $defOwner;
      $targetGroup = $defGroup;
      $targetPerm  = $defPerm;
      return;
   } elsif ( $prompt eq "n" )
   {
      print "Enter information as requested; you can also enter d "
            . "to use the default setting or q to quit \n";
      GetDir();
      GetOwner();
      GetGroup();
      GetPerm();
      GetAppMode();
      print "\n";
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } else
   {
      $errorCount += 1;
      if ( $errorCount > 3 )
      {
         print "Sorry, too many input errors. Bye! \n";
         exit;
      } else
      {
         CheckUser();
      }
   }
}

sub GetDir
{
   print "Starting directory (default $defDir): ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.

   if ( $prompt eq "d" )
   {
      $targetDir = $defDir;
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } else
   {
      if ( -d $prompt )
      {
         $targetDir = $prompt;
         return;
      } else
      {
         $errorCount += 1;
         print "ERROR: Could not find a directory named $prompt \n";
         if ( $errorCount > 3 )
         {
            print "Sorry, too many input errors. Bye! \n";
            exit;
         } else
         {
            GetDir();
         }
      }
   }
}

sub GetOwner
{
   print "Owner (default $defOwner): ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.

   if ( $prompt eq "d" )
   {
      $targetOwner = $defOwner;
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } else
   {
      $targetUid = getgrnam( $prompt );   # Get UID for input name.
      if ( $targetUid >= 0 )
      {
         $targetOwner = $prompt;
         return;
      } else
      {
         $errorCount += 1;
         print "ERROR: Could not find a user ID for $prompt \n";
         if ( $errorCount > 3 )
         {
            print "Sorry, too many input errors. Bye! \n";
            exit;
         } else
         {
            GetOwner();
         }
      }
   }
}

sub GetGroup
{
   print "Group (default $defGroup): ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.

   if ( $prompt eq "d" )
   {
      $targetGroup = $defGroup;
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } else
   {
      $targetGid = getgrnam( $prompt );   # Get GID for input name.
      if ( $targetGid >= 0 )
      {
         $targetGroup = $prompt;
         return;
      } else
      {
         $errorCount += 1;
         print "ERROR: Could not find a group ID for $prompt \n";
         if ( $errorCount > 3 )
         {
            print "Sorry, too many input errors. Bye! \n";
            exit;
         } else
         {
            GetGroup();
         }
      }
   }
}

sub GetPerm
{
   my $errPerm = 0;
   print "Permissions (enter with or without leading 0; default $defPerm): ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.

   if ( $prompt eq "d" )
   {
      $targetPerm = $defPerm;
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } elsif ( not $prompt =~ /^0/ )
   {
      $prompt = "0" . $prompt;
   }

   if ( length( $prompt ) == 4 )
   {
      my $temp = $prompt;       # Store $prompt in a var that will be modified in error checking.

      my $char = chop $temp;    # This is $prompt's 4th character, which is the "Other" permissions.
      $errPerm = 1 unless ( $char =~ /[0-7]/ );

      $char = chop $temp;    # This is $prompt's 3rd character, which is the "Group" permissions.
      $errPerm = 1 unless ( $char =~ /[0-7]/ );

      $char = chop $temp;    # This is $prompt's 2nd character, which is the "Owner" permissions.
      $errPerm = 1 unless ( $char =~ /[0-7]/ );
      if ( $char == 0 )
      {
         print "   WARNING: You want to set the owner's file permission to 0? This is very strange \n"
               . " Are you sure you want to do this (y/any)? ";
         my $reply = ;
         chomp $reply;
         unless ( $reply eq "y" )
         {
            print "Try again: \n";
            GetPerm();
         }
      }
   } else
   {
      $errPerm = 1;   # $prompt incorrect length.
   }

   unless ( $errPerm == 1 )
   {
      $targetPerm = $prompt;
      return;
   } else
   {
      $errorCount += 1;
      print "ERROR: Permissions as entered are incorrect in some way \n";
      if ( $errorCount > 3 )
      {
         print "Sorry, too many input errors. Bye! \n";
         exit;
      } else
      {
         GetPerm();
      }
   }
}

sub GetAppMode
{
   print "Application can: \n"
         . "   0 Make changes automatically without prompting you \n"
         . "   1 Prompt you whether or not make each change (default) \n"
         . "   2 Just report which files do not match the settings (no changes made) \n"
         . "Enter your choice (0, 1, 2, d, q )? ";
   $prompt = ;
   chomp $prompt;            # Lose line ending.

   if ( $prompt eq "d" or $prompt == 1 )
   {
      return;
   } elsif ( $prompt == 0 )
   {
      $appMode = 0;
      return;
   } elsif ( $prompt == 2 )
   {
      $appMode = 2;
      return;
   } elsif ( $prompt eq "q" )
   {
      exit;
   } else
   {
      $errorCount += 1;
      print "ERROR: Incorrect input \n";
      if ( $errorCount > 3 )
      {
         print "Sorry, too many input errors. Bye! \n";
         exit;
      } else
      {
         GetAppMode();
      }
   }
}