#!/usr/bin/perl
#
# file_permissions_audit script.
#
#
#    This script is meant to be run as root on the HMC.
#
#    It reports on several criteria concerning files and directories on the system.
#
#    Enable debugging print messages by setting DEBUG=1 below.
#
#DJM0106

# Help.
#
$readme_file = "file_permissions_audit_README";
if (($ARGV[0] eq "-h") || ($ARGV[0] eq "--help")) {
   system("more $readme_file");
   exit;
}

# Must be user 'root' to run this script.
#
$CURRENT_USER = @ENV{USER};
if ($CURRENT_USER ne "root") {   #TO_DO change this to root
   print("\n CURRENT_USER=$CURRENT_USER\n");
   print("\n ERROR:  Must be root user to run $0.   Exiting...\n\n");
   exit;
}
# Global Variables / Initialization
#
$TOP_DIRECTORY    = ".";  #TO_DO Change this to /
$TOP_DIRECTORY    = "/";  #TO_DO Change this to /
$results_file_all = "/tmp/RESULTS_FILE_all";
$error_log        = "/tmp/ERROR_LOG";
$this_system      = `hostname`; chomp($this_system);
$today            = `date`;     chomp($today);
$DEBUG = 0;         # 1-enable debug printing, 0-disable



# I. SPECIFIC PERMISSIONS  --  Files & Directories Should Match Specified Permissions
#
$results_file_SP     = "/tmp/RESULTS_FILE_specific_permissions";
#$desired_permissions = "DESIRED_PERMISSIONS";
$desired_permissions = "/opt/hsc/data/DESIRED_PERMISSIONS";
print("\n PART I.  SPECIFIC PERMISSIONS");

# Cycle through all files in DESIRED_PERMISSIONS list.  These are file and directory
#   names which require specific permissions.  So, we list them in this file with
#   the desired permissions and check them against actual permissions.
#
format RESULTS_SP =
  @||||||    @||||||    @|||||||||||||||||| @|||||||||||||||||| @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    ${desired_permissions},  ${actual_permissions},  ${desired_user_group},  ${actual_user_group},  ${name}
.
$~ = RESULTS_SP;

if (! -e $desired_permissions) {
   print("\n WARNING:  Permissions checking skipped.   Permissions file $desired_permissions was not found ! \n");
   print("\n   To check permissions on files and directories, create a file called
                 $desired_permissions where the format of each line is the following:

                 655 root root /tmp/file1
                    - where '655' is file permission
                    - where 'root root' is user and group owner
                       - for multiple users or groups, use comma to separate:  'root root,ccfw'
                    - where '/tmp/file1' is a file or directory name (full path)\n\n");
} else {
  $mission_SP         = "#
# System:  $this_system
# Date:    $today
#
# The following files and directories do NOT match the permissions specified in the file $desired_permissions:
#";

   #$mission_SP        = "\n The following files and directories do NOT match the permissions specified in the file $desired_permissions:\n\n";
   open(RESULTS_SP,">$results_file_SP");
   print RESULTS_SP $mission_SP;
   print RESULTS_SP   "\n --------------------------------------------------------------------------------------------------------------------"; 
   print RESULTS_SP "\n     PERMISSIONS                OWNERSHIP (User/Group)";
   print RESULTS_SP "\n  Desired    Actual           Desired             Actual         File or Directory";
   #print RESULTS_SP "\n -------------------      -----------------------------------  ------------------------------------------------------\n"; 
   print RESULTS_SP   "\n --------------------------------------------------------------------------------------------------------------------\n"; 
   $total_correct     = 0;
   $total_wrong       = 0;
   $total_files       = 0;

   # Process user-created list, parsing each line for desired file names, permission and ownership.
   #   
   open(DESIRED_PERM,"<$desired_permissions");
   print("\n\n    Checking Specific HMC Files Against Permissions And Ownership Specified ");
   print("\n      in List of Desired Permissions:  $desired_permissions\n\n");
#  $first_time       = TRUE;
   foreach $line (<DESIRED_PERM>) {
      chomp($line);
      next if ($line =~ m/^\s*$/);  #ignore blank lines or white space
      next if ($line =~ m/^#/);     #lines beginning with # are comments, ignore.
      $total_files++;
      ($desired_permissions,$desired_uname,$desired_gname,$name) = split(/ +/,$line);  #lines in file are space-delimited 
      $desired_permissions_len = length($desired_permissions);
      ($actual_permissions,$actual_uid,$actual_gid) = (stat($name))[2,4,5];
      $actual_permissions = sprintf("%o",$actual_permissions); #convert to octal
      $actual_permissions_len = length($actual_permissions);

      # Actual permissions are usually listed like '100644' instead of '644', for example.
      #   So, compare only the number of permission digits requested by the user.
      #
      if ($actual_permissions_len > $desired_permissions_len) {
         $actual_permissions = substr($actual_permissions,($actual_permissions_len-$desired_permissions_len),$desired_permissions_len);
      }

      # UID & GID values are user and group ID numbers, convert to actual names.
      #
      $user_is_acceptable  = "false";
      $group_is_acceptable = "false";
      @acceptable_users    = split(/,/,$desired_uname); #does actual exist in desired list?
      @acceptable_groups   = split(/,/,$desired_gname); #does actual exist in desired list?


      $actual_uname = getpwuid($actual_uid);
      $actual_gname = getgrgid($actual_gid);
      $num_user_hits       = grep(/$actual_uname/,@acceptable_users);
      $num_group_hits      = grep(/$actual_gname/,@acceptable_groups);

      $actual_user_group  = $actual_uname . "/" . $actual_gname;
      $desired_user_group = $desired_uname . "/" . $desired_gname;
      DEBUG_print("\n\n name=$name= ");
      DEBUG_print(  "\n   desired_permissions=$desired_permissions=    actual_permissions=$actual_permissions= ");
      DEBUG_print(  "\n   desired_permissions_len=$desired_permissions_len=    actual_permissions_len=$actual_permissions_len= ");
      DEBUG_print(  "\n   desired_uname=$desired_uname=         actual_uname=$actual_uname=  ");
      DEBUG_print(  "\n   desired_gname=$desired_gname=         actual_gname=$actual_gname=  ");
      DEBUG_print(  "\n   actual_uname =$actual_uname=          actual_gname=$actual_gname=  ");
   
      # Check for any of the following error conditions:
      #   - actual file permissions don't match desired file permissions.
      #   - actual user ownership does not match any desired user ownerships.
      #   - actual group ownership does not match any desired group ownerships.
      #
      if (($actual_permissions != $desired_permissions)        ||
         #($actual_uname ne $desired_uname)                     ||
         #($actual_gname ne $desired_gname))                     {
         ($num_user_hits < 1) ||
         ($num_group_hits < 1)) {
         $total_wrong++;

         # If file from user's list of files does not exist, reassign permissions field to error msg 'file missing'.
         #    Otherwise, print line of output to output file.
         #
         if (! -e $name) {
            $desired_permissions = "file";
            $actual_permissions  = "missing";
            write RESULTS_SP; 
         } else {
            write RESULTS_SP; 
         }

      # No problem found with this file....
      } else {
         $total_correct++;
         #print("\n Actual Permissions SAME AS Desired Permissions");
      }
   }

   # Print summary.
   #
   print RESULTS_SP " --------------------------------------------------------------------------------------------------------------------"; 
   print RESULTS_SP "\n\n  System:                          $this_system";
   print RESULTS_SP   "\n  Date:                            $today";
   print RESULTS_SP   "\n  Total Files Processed:           $total_files";
   print RESULTS_SP   "\n  Total Files Out of Compliance:   $total_wrong\n\n";
   close(DESIRED_PERM);
   close(RESULTS_SP);
   print("\n      Total Files Processed:           $total_files");
   print("\n      Total Files Out of Compliance:   $total_wrong ");
   #if ($total_wrong > 0) {
      print("\n\n      Results In File:                 $results_file_SP \n\n");
   #} else {
   #   print("\n\n");
   #}
}
print("\n");




# II. NO OWNERSHIP  --  Find All Files On System Which Have No Owner
#
print(  "\n PART II.  NO OWNERSHIP");
print("\n\n    The Following Files Have No Ownership.  Files w/o owners can be a security risk.");
$mission_NO        = "#\n# The Following Files Have No Ownership (Files w/o owners can be a security risk):
#   System:  ${this_system}
#   Date:    ${today}
#";
$find_cmd_NO_OWNERS = "find $TOP_DIRECTORY -nouser -o -nogroup -print 2>>$error_log";
open(FILES_NO,"$find_cmd_NO_OWNERS |");

$results_file_NO   = "/tmp/RESULTS_FILE_no_owner";
open(RESULTS_NO,">$results_file_NO");
print RESULTS_NO "$mission_NO\n";

# Cycle through list of all files with no ownership.
#
$num_files_with_no_owner = 0;
foreach $file (<FILES_NO>) {
   chomp($file);
   print("\n      $file");
   DEBUG_print("\nRESULTS-No Owner: $file");
   print RESULTS_NO "$file\n";
   $num_files_with_no_owner++;
}

print("\n\n\n      Total Number of Files Found With No Owner:  $num_files_with_no_owner \n");
print RESULTS_NO   "\n\n  Total Number of Files Found With No Owner:   $num_files_with_no_owner";
close(RESULTS_NO);
#if ($num_files_with_no_owner > 0) {
   print("\n      Results In File:                            $results_file_NO \n\n\n");
#} else {
#  print("\n\n");
# }







# III. WORLD-WRITEABLE  --  Files & Directories Should Not Be World-Writeable (Unless Specifically Exempted)
#
#   Find all files which are world-writeable which should not be.
#
#   Exclude the following from search:
#     - /dev/
#     - /tmp/
#     - links
#
#$exempted_files_WW = "EXEMPTED_FILES_world_writeable";   # These files ok to be world-writeable
$exempted_files_WW = "/opt/hsc/data/EXEMPTED_FILES_world_writeable";   # These files ok to be world-writeable
print("\n PART III.  WORLD-WRITEABLE");
print("\n\n    Find All World-Writeable Files and Directories Which Are Not Explicitly exempted");
print(  "\n    in the User-Defined File:  $exempted_files_WW\n");
$mission_WW        = "#
# System:  $this_system
# Date:    $today
#
# The following files (& directories) are world-writeable, ie, any user has permission 
#   to write or delete them.  This may be a security problem since they were not 
#   specifically exempted in the file $exempted_files_WW.
#";


#print RESULTS_WW "$mission_WW\n";
$find_cmd_WW       = "find $TOP_DIRECTORY ! -type l -perm +2 ! -path '/dev/*' ! -path '/tmp/*' -print 2>ERROR_LOG ";  
$results_file_WW   = "/tmp/RESULTS_FILE_world_writeable";

open(RESULTS_WW,">$results_file_WW");
open(WW_FILES_ALL,"$find_cmd_WW |");
#print RESULTS_WW "#\n# List of All World-Writeable Files and Directories Found on System $this_system, ${today}: \n#\n";
print RESULTS_WW "#\n# List of Non-exempted World-Writeable Files and Directories. \n#\n";
print RESULTS_WW "$mission_WW\n";
#print RESULTS_WW "#\n# List of All World-Writeable Files and Directories Found on System $this_system, ${today}: \n#\n";


# Cycle through list of all world-writeable files.
#
$total_WW_files     = 0;
$total_exempt_files = 0;
foreach $file (<WW_FILES_ALL>) {
   chomp($file);
   $total_WW_files++;

   # If this file is in the exempt file, then skip to next filename, otherwise add to report.
   open(EXEMPTED_WW,"<$exempted_files_WW");
   $exempt_this_file = "FALSE";
   foreach $exempted_file (<EXEMPTED_WW>) {
      next if ($exempted_file =~ m/^#/);  # skip comment lines
      chomp($exempted_file);
      DEBUG_print("\n     file=$file=  exempted_file=$exempted_file=");
      #if ($file eq $exempted_file) {
      if (($file eq $exempted_file) || ($file =~ m/^$exempted_file/)) {
         DEBUG_print("\nEXEMPTED: $exempted_file");
         $exempt_this_file = "TRUE";
         close(EXEMPTED_WW);
         $total_exempt_files++;
         last;
      }
   }
   close(EXEMPTED_WW);
   if ($exempt_this_file eq "FALSE") {
      DEBUG_print("\nRESULTS: $file");
      print RESULTS_WW "$file\n";
   }
}

# Results summary for world-writeable files.
#
$files_of_concern = $total_WW_files - $total_exempt_files;

print("\n\n      Total Number of Files Found Which Are World-Writeable:   $total_WW_files ");
print(  "\n      Of Those Files Found, How Many Are Exempt:            -  $total_exempt_files ");
print(  "\n      Files of Concern:                                     =  $files_of_concern ");
print("\n\n      Results In File:                                         $results_file_WW \n\n");

# Close open files.
#
close(EXEMPTED_WW);
close(RESULTS_WW);
close(WW_FILES_ALL);





# IV. RUN-LEVEL 5 SERVICES  --  Validate System Services Running At Run-Level 5
#
#   Find all system services running at run-level 5 and validate against an exemption
#      list that they should be running at run-level 5.  The exemption list is a 
#      list of system services in the EXEMPTED_SERVICES_runlevel5 file.
#
$exempted_files_RL5 = "/opt/hsc/data/EXEMPTED_SERVICES_runlevel5";   # These services ok to run at runlevel 5.
print("\n PART IV.  RUNLEVEL 5");
print("\n\n    Find All System Services Running At Runlevel 5.  Any System Services Running At Run");
print(  "\n      Level 5 and Not on The List of Exempted Services (in file $exempted_files_RL5), ");
print(  "\n      Will Be Flagged As Errors.\n");
$mission_RL5        = "#
# System:  $this_system
# Date:    $today
#
# The following system services should not be running at runlevel 5, but are:
#";

$get_runlevel5_services = "chkconfig --list | grep 5:on";
$results_file_RL5       = "/tmp/RESULTS_FILE_runlevel5";


open(RESULTS_RL5,">$results_file_RL5");
open(RL5_SERVICES_ALL,"$get_runlevel5_services |");
print RESULTS_RL5 "$mission_RL5\n";
#print RESULTS_RL5 "#\n# List of All Services Which Should Not Be Running at Runlevel 5, But Are(${this_system}, ${today}): \n#\n";

$total_RL5_services        = 0;
$total_exempt_RL5_services = 0;
$services_in_error         = 0;

# Cycle through list of all system services running at runlevel 5
#
foreach $system_service (<RL5_SERVICES_ALL>) {
   $total_RL5_services++;
   chomp($system_service);
   $system_service =~ m/^(\w+) .*/;
   $system_service_name = $1;
   DEBUG_print("\n System Service Name=$system_service_name=");

   # Now, determine if this service should be permitted to run at runlevel 5.
   #   If not in exemption file, then flag as an error by dumping to results file.
   open(EXEMPTED_RL5,"<$exempted_files_RL5");
   $exempt_this_service = "FALSE";
   foreach $exempted_service (<EXEMPTED_RL5>) {
      next if ($exempted_service =~ m/^#/);  # skip comment lines
      chomp($exempted_service);
      DEBUG_print("\n   system_service_name=$system_service_name=  exempted_service=$exempted_service=");
      #if ($file eq $exempted_file) {
      if ($system_service_name eq $exempted_service) {
         DEBUG_print("\n   EXEMPTED: $exempted_service");
         $exempt_this_service = "TRUE";
         close(EXEMPTED_RL5);
         $total_exempt_RL5_services++;
         last;
      }
   }
   close(EXEMPTED_RL5);
   if ($exempt_this_service eq "FALSE") {
      DEBUG_print("\n   RESULTS: $system_service_name");
      print RESULTS_RL5 "$system_service_name\n";
   }
}

# Close open files.
#
close(RESULTS_RL5);
close(RL5_SERVICES_ALL);

$services_in_error = $total_RL5_services - $total_exempt_RL5_services;

print("\n\n      Total Number of Services Running at Runlevel 5:          $total_RL5_services ");
print(  "\n      Of Those Services, How Many Are Exempt:               -  $total_exempt_RL5_services ");
print(  "\n      Number of System Services In Error:                   =  $services_in_error ");
print("\n\n      Results In File:                                         $results_file_RL5 \n\n");



print("\n\n Done. \n\n");

# Function prints debug string if DEBUG variable != 0.
#
sub DEBUG_print($str) {
   my $str = $_[0];
   if ($DEBUG) {
      print("$str");
   }
   return;
}

