#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2005,2019 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
# sccsid = "@(#)03   1.6   src/rsct/utils/cli/bin/ctadmingroup.perl, common_utils, rsct_rady, rady2035a 9/24/15 07:55:48"
######################################################################
#                                                                    #
# Module: ctadmingroup                                               #
#                                                                    #
# Purpose:                                                           #
#   ctadmingroup - define a cluster administrative group             #
#                                                                    #
# Syntax:                                                            #
#                                                                    #
# ctadmingroup [-h]                                                  #
#            [-TV] [-u]                                              #
#            [-TV] [Group_name]                                      #
#                                                                    #
# Flags                                                              #
#                                                                    #
#   -h   Writes the command's usage statement to standard output.    #
#                                                                    #
#   -T   Writes the command's trace messages to standard error. For  #
#        your software service organization's use only.              #
#                                                                    #
#   -V   Writes the command's verbose messages to standard output.   #
#                                                                    #
#   -u   removes (unsets) the cluster administrative group           #
#                                                                    #
# Operands:                                                          #
#                                                                    #
#  Group_name:  administrative group name. This group must already   #
#               exist in the group database (likely /etc/group)      #
#                                                                    #
#                                                                    #
# Description:                                                       #
#                                                                    #
#                                                                    #
# The ctadmingroup command is used to define a cluster               #
# administrative group. The users belonging to such administrative   #
# group are given permission, by this command, to examine trace      #
# files produced by RSCT subsystems. The command has the effect of   #
# setting trace file group ownership, so that users that belong to   #
# the given group have appropriate permissions to the trace files.   #
# Existing trace files are changed by the command to the             #
# new permissions and group ownership, and trace files created after #
# the command is run will contain the new permissions.               #
#                                                                    #
# Note that the command does not create the given group, nor does it #
# add users to that group -- it only allows users of that group      #
# access to the trace files.                                         #
#                                                                    #
# The command can also be invoked to remove (unset) the cluster      #
# administrative group. After such action is taken ("-u" option),    #
# users that belong to that group may be unable to examine trace     #
# files. No output is produced if the "-u" option is used but no     #
# administrative group was previously defined.                       #
#                                                                    #
# Running the command with a different group name results in the     #
# new group taking effect as the cluster administrative group,       #
# replacing the previous group.                                      #
#                                                                    #
# When run with no arguments or options, the command will display    #
# the group name and ID of the administrative group. No output is    #
# produced if no administrative group is defined.                    #
#                                                                    #
#                                                                    #
# Exit Values:                                                       #
# 0     The command ran successfully.                                #
# 1     Given group name is not in the group database                #
# 2     Internal error                                               #
# 3     An incorrect flag was entered on the command line            #
# 4     An incorrect operand was entered on the command line         #
#                                                                    #
# Man Page:                                                          #
#   For the most current detailed description of this command see    #
#   the mkrpdomain man page in /opt/rsct/man.                   #
#                                                                    #
#--------------------------------------------------------------------#
#                                                                    #
# Inputs:                                                            #
#   /opt/rsct/msgmaps/cucli.ctadmingroup.map         -          #
#       message mapping                                              #
#                                                                    #
# Outputs:                                                           #
#   stdout - none.                                                   #
#   stderr - any error message.                                      #
#                                                                    #
# External Ref:                                                      #
#   Commands: ctdspmsg                                               #
#   Modules:  CT_cli_utils.pm, CU_cli_rc.pm,                         #
#             CRM_cli_include.pm                                     #
#   Perl library routines: Getopt::Std                               #
#                                                                    #
#                                                                    #
# Change Activity:                                                   #
#   050516 FK  118713: Initial design & write.                       #
#   050609 JAC 123591: Change use CRM_cli_include to MC_cli_utils.   #
#   050705 JAC 125004: Use octal when printing file/directory mode.  #
#                                                                    #
#                                                                    #
######################################################################



#--------------------------------------------------------------------#
# Included Libraries and Extensions                                  #
#--------------------------------------------------------------------#
use lib "/opt/rsct/pm";

use locale;
use Getopt::Std;

use CU_cli_rc qw(CU_CLI_SUCCESS
                 CU_CLI_ERROR
                 CU_CLI_BAD_FLAG
                 CU_CLI_BAD_OPERAND
                 CU_CLI_USER_ERROR);

use CT_cli_utils qw(printIMsg
                    printEMsg
		    $TRUE $FALSE
                    $CTBINDIR
                    $CTDIR);



#--------------------------------------------------------------------#
# Global Variables                                                   #
#--------------------------------------------------------------------#

$Trace = $FALSE;                        # default - trace off
$Verbose = $FALSE;                      # default - verbose turned off


$PROGNAME = "ctadmingroup";             # Program Name for messages
$LSMSG = "$CTBINDIR/ctdspmsg";          # list / display message rtn
$MSGCAT = "cucli.cat";                  # msg catalogue for this cmd
$ENV{'MSGMAPPATH'} = "$CTDIR/msgmaps";  # msg maps used by $LSMSG

$CACHED_GID_FILENAME = "/var/ct/cfg/ctgroups";
                                        # file with group name and id
                                        # This must be kept in sync with
                                        # cu_getadmin_group_id()

$exit_val = 0;  # exit value



($rc, $group_name, $unset_option) = &parse_cmd_line;

($rc == 0) || exit($rc);

#
# set new admin group
#

if(!$unset_option && ($group_name ne "") ) {
   $gid = getgrnam($group_name);

   if(!defined ($gid)) {
       # cannot find group $group_name
       printEMsg("EMsgctadmingroupCannotFindGrp", $group_name);
       exit (1);
   }

   if($Trace) {
       print STDERR "GID = $gid\n";
   }

   # create file with group name/ID
   $rc = create_cached_group_file($group_name, $gid);
   if($rc) {

       # success ... now change directory and file permissions

       set_dir_permissions();
       set_file_permissions($gid);

   }
   else {  # a problem ... likely full /var/ filesystem
      $exit_val = 2;
   }

}

#
# Unset admin group
#

if($unset_option) {

    if($Trace) {
        print STDERR "Unset Option!\n";
    }

    delete_cached_group_file();

    # set file groups to group id 0
    set_file_permissions(0);
}


#
# Print current admin group
#

if(!$unset_option && ($group_name eq "")) {

    if($Trace) {
        print STDERR "Print current group\n";
    }

    $rc = print_cached_group_file();
    if($rc == 2) {  # unexpected error
        $exit_val = 2;
    }

}

exit($exit_val);

#
# END main program
#


#--------------------------------------------------------------------#
# parse_cmd_line - Parse the command line for options and operands.  #
#   Set appropriate global variables as outlined below, make sure we #
#   have a valid combination of arguments / options.                 #
#                                                                    #
# Return:                                                            #
#   $rc   0                  Command line parsed fine, no problem.   #
#         CU_CLI_BAD_FLAG    Command line contained a bad flag.      #
#         CU_CLI_BAD_OPERAND Command line contained some other       #
#                            incorrect input                         #
#   $group_name              Group name provided in the command line #
#   $unset_option            Whether the "unset" option has been set #
#--------------------------------------------------------------------#

sub parse_cmd_line {

my(@original_argv) = @ARGV;
my $group_name = "";                  # group name
my %opts = ();

if (!&getopts('huVT', \%opts)) { # Gather options;
                                        # if errors
    print_usage();                      # display proper usage
    return CU_CLI_BAD_FLAG;             # return bad rc - bad flag
}

# process h flag
if (defined $opts{h}) {                 # -h, help request
    print_usage();                      # print usage statement
    exit(0);                            # all done with good return!
}

if (defined $opts{T}) {                 # -T turn trace on
    $Trace = 1;
}

if (defined $opts{V}) {                 # -V turn verbose mode on
    $Verbose = 1;
}


if (defined $opts{u}) {                 # -u, unset
    $unset_option = 1;
}


# Get the arguments...cluster name followed by node names
if ($#ARGV >= 0) {                      # group name
    $group_name = $ARGV[0];
}

# cannot specify -u and also a group name
if(($group_name ne "") && $unset_option) {
    print_usage();
    return CU_CLI_BAD_OPERAND;
}

# only one argument allowed
if ($#ARGV >= 1) {
    print_usage();
    return CU_CLI_BAD_OPERAND;
}


return(0, $group_name, $unset_option); # success
}


#--------------------------------------------------------------------#
# get_file_names - get the set of files that will have their         #
# permissions/group changed                                          #
#                                                                    #
# Parameters:                                                        #
#                                                                    #
# Return code:                                                       #
#     @filelist: list of files that will have their permissions      #
#                changed                                             #
#--------------------------------------------------------------------#

sub get_file_names {

# list of pathnames that define the files that will have their permissions
# and group changed
my(@filepathnames) = ( "/var/ct/IW/log/mc/trace*",
                       "/var/ct/IW/log/ctsec/ctcasd/trace*",
                       "/var/ct/*/log/mc/*/trace*" );

my(@filelist) = ();
my(@list) = ();
my($f);
my($ctcasd_file);

# add ctcasd trace file name to the list
$ctcasd_file = get_ctcasd_filename();
if($ctcasd_file ne "") {
    push (@filepathnames, $ctcasd_file);
}

#
# files may be visited more than once, because of the symlink in the
# dirs
#

foreach $pattern (@filepathnames) {

    @list = glob $pattern;

    push (@filelist, @list);
}

if($Trace) {
    foreach $f (@filelist) {
       print STDERR "File: $f\n";
    }
}



return @filelist;

}

#--------------------------------------------------------------------#
# get_ctcasd_filename - ctcasd's trace file location is              #
#                       configurable. This function fetches the      #
#                       trace file name from the ctcasd config       #
#                       file, which has 2 possible locations         #
#                                                                    #
# Return code:                                                       #
#    pathname of ctcasd trace file                                   #
#                                                                    #
#--------------------------------------------------------------------#

sub get_ctcasd_filename {
my($SEC_CONFIG1) = "/var/ct/cfg/ctcasd.cfg"; # 1st location of config file
my($SEC_CONFIG2) = "/opt/rsct/cfg/ctcasd.cfg"; # 2nd location
my($sec_config_file) = "";
my($l);
my($trace_file) = "";

if (-e $SEC_CONFIG1 ) {
    $sec_config_file = $SEC_CONFIG1;
}
else {
    if(-e $SEC_CONFIG2) {
        $sec_config_file = $SEC_CONFIG2;
    }
}

if($sec_config_file ne "") {

    if(open(FD_SEC_CONFIG,"$sec_config_file")) {

        sec_cfg: while ($l = <FD_SEC_CONFIG>) {

            # typical line in the config file looks like
            # TRACEFILE= /var/ct/IW/log/ctsec/ctcasd/trace

            $l =~ s/[ ]+$//;   # Remove possible trailing spaces
            $l =~ s/^[ ]+//;   # Remove possible leading spaces
            if($l =~ "TRACEFILE=([ ]*)(.*)([ ]*)") {

                $trace_file = $2;

                if($Trace) {
                    print STDERR
                        "ctcasd: Found $trace_file in $sec_config_file.\n";
                }
                last sec_cfg;
            }
        } # while

    } # open

} # if $sec_config_file


if($trace_file eq "") {
    $trace_file = "/var/ct/IW/log/ctsec/ctcasd/trace";
}


return $trace_file;
}


#--------------------------------------------------------------------#
# set_file_permissions - set the file permissions and group          #
#                        Uses get_file_names() to get a list of files#
#                        that will have their permissions changed    #
#                                                                    #
# Parameters:                                                        #
#     $gid: group id to which the files should be set                # 
#                                                                    #
#                                                                    #
#--------------------------------------------------------------------#

use Fcntl ':mode';   # needed for S_IRUSR, etc

sub set_file_permissions {
my($gid) = @_;
my(@filelist);
my($mode);
my($newmode);
my($f);
my($rc);

@filelist = get_file_names();

if($Trace) {
    foreach $f (@filelist) {
        print STDERR "....File: $f\n";
    }
}


foreach $f (@filelist) {
    next unless -e $f;
    next if -d $f;   # skip if dir

    # do a "stat" on the file to get its permissions and prepare new
    # permissions to set

    $mode = (stat($f))[2];
    $mode &= 07777;
    $newmode = $mode | (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);

    if($newmode != $mode) {

        if($Trace) {
            # print STDERR "Changing mode for $f from $mode to $newmode.\n";
            print STDERR "Changing mode for %s from %04o to %04o.\n", $f, $mode, $newmode;
        }

        # set new permissions
        $rc = chmod $newmode, $f;
        if($rc <= 0) {
            printEMsg("EMsgctadmingroupCannotChangeMode", $f);
        }
    }
    else {
        if($Trace) {
            # print STDERR "Mode for $f is $mode: no change required.\n";
            printf STDERR "Mode for %s is %04o: no change required.\n", $f, $mode;
        }
    }

    # change group of file (keep file owner intact)
    $rc = chown -1, $gid, $f;
    if($rc <= 0) {
        printEMsg("EMsgctadmingroupCannotChangeGroup", $f);
    }


} # foreach


} # set_file_permissions()

#--------------------------------------------------------------------#
# get_dir_names - get the set of directories that will have their    #
# permissions changed                                                #
#                                                                    #
# Parameters:                                                        #
#                                                                    #
# Return code:                                                       #
#     @dirlist: list of directories that will have their permissions #
#               changed                                              #
#--------------------------------------------------------------------#

sub get_dir_names {

# list of pathnames that define the dirs that will have their permissions
# changed
my(@dirpathnames) =  ( "/var/ct/IW/log/mc",
                       "/var/ct/IW/log/ctsec",
                       "/var/ct/IW/log/ctsec/ctcasd",
                       "/var/ct/*/log",
                       "/var/ct/*/log/cthats",
                       "/var/ct/*/log/cthags",
                       "/var/ct/*/log/mc/*" );

my(@dirlist) = ();
my(@list) = ();
my($f);


#
# dirs may be visited more than once, because of the symlink in the
# dirs

foreach $pattern (@dirpathnames) {

    @list = glob $pattern;

    push (@dirlist, @list);
}

if($Trace) {
    foreach $f (@dirlist) {
       print STDERR "Dir: $f\n";
    }
}

return @dirlist;

} # get_dir_names


#--------------------------------------------------------------------#
# set_dir_permissions - set the directory permissions                #
#                                                                    #
# Parameters:                                                        #
#                                                                    #
#--------------------------------------------------------------------#

use Fcntl ':mode';   # needed for S_IRUSR, etc

sub set_dir_permissions {
my($gid) = @_;
my(@dirlist);
my($mode);
my($newmode);
my($f);
my($rc);

@dirlist = get_dir_names();

if($Trace) {
    foreach $f (@dirlist) {
        print STDERR "....Dir: $f\n";
    }
}

foreach $f (@dirlist) {
    next unless -e $f;   # skip if dir does not exist
    next unless -d $f;   # skip if not dir

    $mode = (stat($f))[2];
    $mode &= 07777;
    $newmode = $mode | (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); # 0755

    if($newmode != $mode) {

        if($Trace) {
            # print STDERR "Changing mode for $f from $mode to $newmode.\n";
            print STDERR "Changing mode for %s from %04o to %04o.\n", $f, $mode, $newmode;
        }

        $rc = chmod $newmode, $f;
        if($rc <= 0) {
            printEMsg("EMsgctadmingroupCannotChangeMode", $f);
        }
    }
    else {
        if($Trace) {
            # print STDERR "Mode for $f is $mode: no change required.\n";
            printf STDERR "Mode for %s is %04o: no change required.\n", $f, $mode;
        }
    }

    # the following is not needed for now. Enable if the dirs need to have
    # a specific group
    ## # change group of file (keep file owner intact)
    ## $rc = chown -1, $gid, $f;
    ## if($rc <= 0) {
    ##     printEMsg("EMsgctadmingroupCannotChangeGroup", $f);
    ## }


} # foreach


} # set_dir_permissions()


#--------------------------------------------------------------------#
# create_cached_group_file - write file that contains admin          #
# group name and id                                                  #
#                                                                    #
# Parameters:                                                        #
#     $group_name: admin group name                                  #
#     $gid: group id of the admin group                              # 
#                                                                    #
# Return code:                                                       #
#     1: success                                                     #
#     0: some error occurred (likely lack of filesystem space)       #
#                                                                    #
#--------------------------------------------------------------------#

sub create_cached_group_file {
my($group_name, $gid) = @_;
my($rc) = 1;


umask 0133;  # does not allow group, others to write/execute file

if(!open(CACHED_GID_FILE_HANDLE, ">".$CACHED_GID_FILENAME)) {
    printEMsg("EMsgctadmingroupCannotCreateFile", $CACHED_GID_FILENAME);
    return 0;
}

# Format:
#  #Version  <number>
#  #<format description>
#  group name
#  group id


$rc = $rc && print CACHED_GID_FILE_HANDLE "# Admin Group name and id\n";
$rc = $rc && print CACHED_GID_FILE_HANDLE "#Version 1\n";
$rc = $rc && print CACHED_GID_FILE_HANDLE "#Format: group_name\n";
$rc = $rc && print CACHED_GID_FILE_HANDLE "#        group_id\n";
$rc = $rc && print CACHED_GID_FILE_HANDLE "$group_name\n";
$rc = $rc && print CACHED_GID_FILE_HANDLE "$gid\n";

if($rc == 0) {
    printEMsg("EMsgctadmingroupErrorWriteFile", $CACHED_GID_FILENAME);
}

if(!close(CACHED_GID_FILE_HANDLE)) {
    printEMsg("EMsgctadmingroupErrorCloseFile", $CACHED_GID_FILENAME);
    $rc = 0;
}

return $rc;  # ok
}

#--------------------------------------------------------------------#
# delete_cached_group_file - delete file that contains admin         #
# group name and id                                                  #
#                                                                    #
# Parameters:                                                        #
#                                                                    #
# Return code:                                                       #
#                                                                    #
#--------------------------------------------------------------------#

sub delete_cached_group_file {

# just rename the file, to keep a copy

rename $CACHED_GID_FILENAME, $CACHED_GID_FILENAME.".last";

}


#--------------------------------------------------------------------#
# print_cached_group_file - print group name and id info from        #
# the cached group file. If the file is not present then assume      #
# that no admin group is defined and produce no output               #
#                                                                    #
# Parameters:                                                        #
#                                                                    #
# Return code:                                                       #
#     1: a group is defined                                          #
#     0: cached group file is not present                            #
#     2: some error occurred                                         #
#                                                                    #
#--------------------------------------------------------------------#

sub print_cached_group_file {
my ($l);
my ($version) = 1;
my (@tmp_l);
my ($group_name) = "";
my ($got_group_name) = 0;
my ($gid);
my ($got_gid) = 0;
my ($rc) = 0;

if(open(CACHED_GID_FILE_HANDLE, $CACHED_GID_FILENAME)) {

    while_file: while ($l = <CACHED_GID_FILE_HANDLE>) {

        if($l =~ /^#Version /) {
            @tmp_l = split(" ", $l);
            if(defined(@tmp_l[1])) {
                $version = @tmp_l[1];
            }
            next while_file;
        }

        
        # ignore other comment lines
        next while_file if $l =~ /^#/;

        # if this is not the Version line and it's not a comment line then
        # it must be a group name (1st line) and the group id (next line)
        if($group_name eq "") {
            $group_name = $l;
            chomp($group_name);
            $got_group_name = 1;
            next while_file;
        }

        # must be the group id
        $gid = $l;
        chomp($gid);

        # make sure GID looks valid (should have only digits)
        if($gid =~ /^\d+$/) {
            $got_gid = 1;
        }
        last while_file;
    }

    close CACHED_GID_FILE_HANDLE;

    if(!$got_group_name || !$got_gid) {
        printEMsg("EMsgctadmingroupCorruptedFile", $CACHED_GID_FILENAME);
        $rc = 2;
    }
    else {

        print "$group_name ($gid)\n";
        $rc = 0;
    }

    return $rc;
}
else {
    if($Trace) {
        print STDERR "Cannot open $CACHED_GID_FILENAME for reading.\n";
    }

    return 0;
}



return 2;
}


#--------------------------------------------------------------------#
# print_usage : print the usage statement (syntax) to stdout.        #
#--------------------------------------------------------------------#
sub print_usage {
printIMsg("IMsgctadmingroupUsage");
}   # end print_usage