#!/usr/bin/perl
#
# $Revision: 1.3 $
# Copyright 2009 Teleflora
# 
# Setup file permissions in a Daisy tree.
#

use strict;
use warnings;
use English;
use POSIX;
use Getopt::Long;
use File::Basename;

my $CVS_REVISION = '$Revision: 1.3 $';
my $TIMESTAMP = strftime("%Y%m%d%H%M%S", localtime());
my $PROGNAME = basename($0);

my $DEFAULT_DSYDIR = '/d/daisy';
my $DSYROOT = "";
my $DSYDIR = "";
my $HELP = 0;
my $VERSION = 0;
my $INSTALL = 0;
my $TOOLSDIR = "";


GetOptions(
	"help" => \$HELP,
	"version" => \$VERSION,
	"install" => \$INSTALL,
) or exit(1);


# --help
if ($HELP) {
	usage();
	exit(0);
}


# --version
if ($VERSION) {
	print("OSTools Version: 1.12\n");
	print("$PROGNAME $CVS_REVISION\n");
	exit(0);
}


# Where do "OSTOOLS" typically reside?
if (-e "/teleflora/ostools/bin") {
	$TOOLSDIR = "/teleflora/ostools";
} elsif (-e "/usr2/ostools/bin") {
	$TOOLSDIR = "/usr2/ostools";
} elsif (-e "/d/ostools/bin") {
	$TOOLSDIR = "/d/ostools";
} elsif (-e "/d") {
	system("mkdir -p /d/ostools/bin");
	$TOOLSDIR = "/d/ostools/bin";
} elsif (-e "/usr2") {
	system("mkdir -p /usr2/ostools/bin");
	$TOOLSDIR = "/usr2/ostools/bin";
} else {
	system("mkdir -p /teleflora/ostools/bin");
	$TOOLSDIR = "/teleflora/ostools";
}

unless (-d "$TOOLSDIR/bin") {
	logerror("required directory $TOOLSDIR/bin does not exist... can't continue");
	exit(1);
}

# --install
if ($INSTALL != 0) {
	install_dsyperms();
	exit(0);
}


#
# If there is more than one command line argument, it's an error.
# If there is one command line argument, use it as the path to the
# daisy database directory.  If there are no command line args,
# then use the default value.
#
if (@ARGV > 1) {
	usage();
	exit(1);
} elsif (@ARGV == 1) {
	$DSYDIR = $ARGV[0];
} else {
	$DSYDIR = $DEFAULT_DSYDIR;
}

#
# Validate user input.
#
$DSYDIR = validate_input($DSYDIR);
unless ($DSYDIR) {
	$DSYDIR = $DEFAULT_DSYDIR;
}

# make sure it is an absolute path
unless (substr($DSYDIR, 0, 1) eq '/') {
	print("\"$DSYDIR\" must be an absolute path.\n");
	usage();
	exit(1);
}

# make sure it's a daisy database directory
unless (-f "$DSYDIR/control.dsy") {
	print("\"$DSYDIR\" is not a daisy database directory.\n");
	usage();
	exit(1);
}

# the daisy root is one up from the daisy database directory.
$DSYROOT = dirname($DSYDIR);


# Make sure we are running as the root user.
if ($UID != 0) {
	print("Error: sudo privileges are required for execution.\n");
	exit(2);
}


# "New PABP" permission sets.
if (assign_pabp_permissions($DSYROOT, $DSYDIR) != 0) {
	exit(1);
}


exit(0);

###################################################################
###################################################################
###################################################################



sub usage
{
	print("$PROGNAME $CVS_REVISION\n");
	print("\n");
	print("Usage:\n");
	print("$PROGNAME --help\n");
	print("$PROGNAME --version\n");
	print("$PROGNAME --install\n");
	print("$PROGNAME [daisy_dir]\n");
	print("\n");
	print("--version:    Output version number and exit.\n");
	print("--help:       Output this Help Text and exit.\n");
	print("--install:    Install and exit.\n");
	print("daisy_dir:    path to a daisy database directory\n");
	print("no arguments: defaults to \"/d/daisy\"\n");
}


#
# PCI 6.3
#	Develop software appliations in accordance with PCI DSS and
#	based on industry best practices, and incorporate information
#	security throughout the software development life cycle.
#
#	PCI 6.3.1.1
#	Validation of all input.
#
# Examples of how potentially dangerous input is converted:
#
# "some string; `cat /etc/passwd`" -> "some string cat /etc/passwd"
# "`echo $ENV; $(sudo ls)`" -> "echo ENV sudo ls"
#
sub validate_input
{
        my $var = $_[0];
        my $temp = "";

        if(! $var) {
                return "";
        }

        $temp = $var;

        # "`bad command`" -> "bad command"
        $temp =~ s/\`//g;

        # "$(bad command)" -> "bad command"
        $temp =~ s/(\$\()(.*.)(\))/$2/g;


        # "stuff ; bad command" -> "stuff bad command"
        $temp =~ s/\;//g;

        # "stuff && bad command" -> "stuff bad command"
        $temp =~ s/\&//g;

        # "stuff | bad command" -> "stuff bad command"
        $temp =~ s/\|//g;

        # "stuff > bad command" -> "stuff bad command"
        $temp =~ s/\>//g;

        # "stuff < bad command" -> "stuff bad command"
        $temp =~ s/\<//g;

        # Filter non printables
        $temp =~ s/[[:cntrl:]]//g;


        return($temp);
}


sub install_dsyperms
{
	logit("---- BEGIN Installation $PROGNAME $CVS_REVISION ----", 'I');

	# If file exists and is the same, then it's already installed.
	my $already_installed = 0;
	if (-f "$TOOLSDIR/bin/dsyperms.pl") {
		system("diff $0 $TOOLSDIR/bin");
		$already_installed = 1 if ($? == 0);
	}

	# If file is not already installed, then copy instance to bin directory.
	unless ($already_installed) {
		system("cp $0 $TOOLSDIR/bin/dsyperms.pl");
	}

	# Always set owner:group and modes.
	system("chown tfsupport:daisy $TOOLSDIR/bin/dsyperms.pl");
	system("chmod 550 $TOOLSDIR/bin/dsyperms.pl");

	#
	# Always remember: there can be more than one daisy database dir.
	#
	# Now remove the old version in the "bin" directory of a daisy db dir and
	# make symlink to ostools script.
	#
	my @daisy_db_dirs = glob("/d/*");

	for my $daisy_db_dir (@daisy_db_dirs) {

		# must be a directory
		next unless (-d $daisy_db_dir);

		# skip old daisy dirs
		next if ($daisy_db_dir =~ /.+-\d{12}$/);

		# must contain the magic files
		next unless(-e "$daisy_db_dir/flordat.tel");
		next unless(-e "$daisy_db_dir/control.dsy");

		# must be daisy 8.0+
		next unless (-d "$daisy_db_dir/bin");

		system("rm -f $daisy_db_dir/bin/dsyperms.pl");
		system("ln -sf $TOOLSDIR/bin/dsyperms.pl $daisy_db_dir/bin/dsyperms.pl");
	}

	logit("---- END Installation $PROGNAME $CVS_REVISION ----", 'I');
}


sub assign_pabp_permissions
{
	my $dsyroot = $_[0];
	my $dsydir = $_[1];
	my $file = "";
	my $thisdir =  "";
	my $thisfile =  "";

	if ( (! defined $dsyroot) or ($dsyroot eq "") ) {
		return(-1);
	}

	# top level directories
	my @dsy_top_level_dirs = qw(
		backup
		config
		daisy
		menus
		ostools
		putty
		server
		startup
		utils
	); 

	#
	# Always remember, there can be daisy database dirs other than '/d/daisy'.
	# So add the current daisy db dir to the list if it's not '/d/daisy'.
	#
	if ($dsydir ne '/d/daisy') {
		push(@dsy_top_level_dirs, basename($dsydir));
	}

	foreach $thisdir (@dsy_top_level_dirs) {

		# the "ostools" dir does not have to exist
		if ($thisdir eq "ostools") {
			next if (! -d "$dsyroot/$thisdir");
		}

		unless (-d "$dsyroot/$thisdir") {
			print("Warning! Expecting to find \"$dsyroot/$thisdir\" but directory not found!\n");
			next;
		}

		if ($thisdir eq "backup" || $thisdir eq "config") {
			system("chown root:root $dsyroot/$thisdir");
			system("chmod 770 $dsyroot/$thisdir");
		} else {
			system("chown tfsupport:daisy $dsyroot/$thisdir");
			system("chmod 770 $dsyroot/$thisdir");
		}
	}


	#
	# Default permissions for all directories at the top of the daisy tree that
	# are not in the list of known directories.
	#
	# Each file should be owned by the "support" user and in the "admins" group.
	# We do not want the "non-privileged" daisy user being able to write these files.
	#
	# Note that skip setting defaults for known files/directories at the top level.
	# If this script is executed on a running system we could break software when
	# we "flip" file permissions from writable, to readable, back to writable.
	# See tracker 104357.
	#
	opendir(DIR, "$dsyroot");
	foreach $thisdir (readdir(DIR)) {
		#
		# Skip the typical file system stuff
		#
		next if ($thisdir =~ /\.$/);
		next if ($thisdir =~ /\.\.$/);
		next if ($thisdir =~ /lost\+found/);

		#
		# Skip the standard, expected dirs
		#
		next if (grep(/$thisdir/, @dsy_top_level_dirs)); 

		#
		# Remember, there can be daisy database dirs other than '/d/daisy'
		# at the top level... so determine if a dir is a daisy db dir, and
		# if it is, skip it.  A directory is classified as a daisy db dir
		# if it is a dir in the top level, ie in "/d", and it contains a
		# file named "control.dsy".
		#
		if (-d "$dsyroot/$thisdir") {
			next if (-f "$dsyroot/$thisdir/control.dsy");
		}

		#
		# Don't know what this file is, so, set default owner/group/perms.
		#
		system("chown -R tfsupport:dsyadmins $dsyroot/$thisdir");
		system("chmod -R 555 $dsyroot/$thisdir");

	}
	closedir(DIR);


	my @dsy_bin_dirs = qw(
		bin
		utils
	);

	# Executable directories and their contents in /d/daisy
	foreach $thisdir (@dsy_bin_dirs) {

		if (! -d "$dsydir/$thisdir") {
			print("Warning! \"$dsydir/$thisdir\" directory not found!\n");
			next;
		}

		# directory
		system("chown tfsupport:daisy $dsydir/$thisdir");
		system("chmod 770 $dsydir/$thisdir");
		# files & files referenced by symlinks
		system("chown tfsupport:daisy $dsydir/$thisdir/*");
		system("chmod 550 $dsydir/$thisdir/*");
	}

	# Explicitly handle tools from OSTools package since they are vital to daisy
	system("chown tfsupport:daisy $dsyroot/ostools");
	system("chmod 770 $dsyroot/ostools");
	system("chown tfsupport:daisy $dsyroot/ostools/bin");
	system("chmod 770 $dsyroot/ostools/bin");
	system("chown tfsupport:daisy $dsyroot/ostools/bin/*");
	system("chmod 550 $dsyroot/ostools/bin/*");

	my @dsy_dirs = qw(
		backup.dir
		blankdata
		comms
		config
		cubby
		docs
		dsy
		errors
		export
		log
		pospool
		recv
		submit
		tfm
	);

	# Directories and their contents in /d/daisy
	foreach $thisdir (@dsy_dirs) {

		if (! -d "$dsydir/$thisdir") {
			print("Warning! \"$dsydir/$thisdir\" directory not found!\n");
			next;
		}

		system("chown -R tfsupport:daisy $dsydir/$thisdir");
		system("chmod 770 $dsydir/$thisdir");

		if ($thisdir eq "log") {
			foreach my $logpath (glob("$dsydir/$thisdir/*")) {
				my $logfile = basename($logpath);
				if ($logfile =~ /dove-.*\.log/) {
					system("chmod 660 $dsydir/$thisdir/$logfile");
				} elsif ($logfile =~ /dove\.log/) {
					system("chmod 644 $dsydir/$thisdir/$logfile");
				} elsif ($logfile =~ /rtibackup-.*\.log/) {
					system("chmod 666 $dsydir/$thisdir/$logfile*");
				}
			}

		} elsif ($thisdir eq "comms") {
			if (-d "$dsydir/$thisdir/tmp") {
				system("chmod 770 $dsydir/$thisdir/tmp");
			}
		} else {
			if (count_dir_entries("$dsydir/$thisdir") > 2) {
				system("chmod -R 660 $dsydir/$thisdir/*");
			}
		}
	}

	# Files in daisy db dir config directories
	my @dsy_config_files = (
		[ '.',        'control.dsy',        'tfsupport', 'daisy', '640' ],
		[ 'config',   'backups.config',     'tfsupport', 'daisy', '440' ],
		[ 'config',   'daisybuildinfo.txt', 'tfsupport', 'daisy', '440' ],
		[ 'dsy',      'control.dsy',        'tfsupport', 'daisy', '440' ],
		[ 'dsy',      'printers',           'tfsupport', 'daisy', '440' ],
	);

	for my $i (0 .. $#dsy_config_files) {
		my $cfgdir = $dsy_config_files[$i][0];
		my $filename = $dsy_config_files[$i][1];
		my $owner = $dsy_config_files[$i][2];
		my $group = $dsy_config_files[$i][3];
		my $mode = $dsy_config_files[$i][4];
		if (-f "$dsydir/$cfgdir/$filename") {
			system("chown $owner:$group $dsydir/$cfgdir/$filename");
			system("chmod $mode $dsydir/$cfgdir/$filename");
		}
	}

	# Executable files in daisy db dir
	my @dsy_bin_files = qw(
		0branch
		convpos crd crdmenu crdpos crdterm crunch custpro
		daisy data dayend dcom delvmgr delvzip designer dove dpos dquery drawer dump
		fileutil edir einvoice
		glexport
		invlist invoice import
		marketpr map
		payroll pool poolmain pos posctrl posdraw posfax poslist posmerc posxfer pzlookup
		qrest
		route rundriv
		setup
		terreset timeclck
		*.sh
		pdayend.new
	);

	# Change permissions of all daisy executable files
	foreach $file (@dsy_bin_files) {
		system("chown tfsupport:daisy $dsydir/$file");
		system("chmod 550 $dsydir/$file");
	}

	my @dsy_data_files = qw(
		*.arc
		*.dsy
		*.ftd
		*.hsp
		*.idx
		*.lok
		*.log
		*.map
		*.pr
		*.pos
		*.sal
		*.scr
		*.tel
		*.tgz
		*.txt
		*.rep
		hp5crtl.*
		macros
		macros.*
		panelp.cnf
		poolflor.dat
		printers
		tcktlist.dat
		pospool/poolflor.*
		pospool/ticket.*
	);

	# Set permissions of all daisy data files
	foreach $file (@dsy_data_files) {

		foreach my $datafile (glob("$dsydir/$file")) {
			my $group;
			my $perms;

			next unless (-e "$datafile");

			if ($datafile eq "$dsydir/crddata.pos") {
				$group = "dsyadmins";
				$perms = "464";
			} else {
				$group = "daisy";
				$perms = "660";
			}

			system("chown tfsupport:$group $datafile");
			system("chmod $perms $datafile");
		}
	}

	# list of files in /d/backup
	my @backup_dir_files = qw(
		error.txt
		log
		verify
	);

	# Change owners and modes for files in /d/backup dir
	foreach $file (@backup_dir_files) {

		my $file_path = "$dsyroot/backup/$file";

		if (-d $file_path) {
			system("chown -R tfsupport:daisy $file_path");
			system("chmod 660 $file_path");
		}

		unless (-f $file_path) {
			next;
		}

		system("chown tfsupport:daisy $file_path");
		system("chmod 660 $file_path");
	}

	# Change permissions on the config files (if any)
	if (-d "$dsyroot/config/stuff") {
		system("chown -R root:root $dsyroot/config/*");
		system("chmod 750 $dsyroot/config/*");
	}

	# Change permissions on the crawl menu files
	# Note: skip changing the perms on the "market_input" script which
	# is a perl script that used to be located in /d/daisy/menus but is
	# is located in /d/daisy/bin... now there is just a symlink pointing
	# from /d/daisy/menus to /d/daisy/bin.

	foreach my $menufile (glob("$dsyroot/menus/*")) {

		next if ($menufile eq "$dsyroot/menus/market_input");

		system("chown tfsupport:dsyadmins $menufile");
		system("chmod 444 $menufile");
	}

	# Change permissions on the crawl program files
	my @crawl_prog_files = qw(
		crawlmenu
	);
	foreach $file (@crawl_prog_files) {
		system("chown tfsupport:dsyadmins $dsyroot/menus/$file");
		system("chmod 555 $dsyroot/menus/$file");
	}

	# Change permissions on the putty config files
	system("chown tfsupport:dsyadmins $dsyroot/putty/*");
	system("chmod 755 $dsyroot/putty/*");

	# Change permissions on the "server" (misnomer!) scripts
	system("chown tfsupport:daisy $dsyroot/server/*");
	system("chmod 750 $dsyroot/server/*");

	# Change permissions on the startup scripts
	system("chown tfsupport:daisy $dsyroot/startup/*");
	system("chmod 750 $dsyroot/startup/*");

	# Change perms on tcc dir and files
	system("chown -R tfsupport:daisy $dsydir/tcc");
	system("chmod 770 $dsydir/tcc");
	system("chown -R tfsupport:daisy $dsydir/tcc/*");
	system("chmod 550 $dsydir/tcc/*");

	# handle one or more optional credit card processing directories
	# within daisy db dir... usually there is only "crd2" if any.

	my @alt_crd_dirs = qw(
		crd2
		crd3
		crd4
		crd5
	);

	foreach my $alt_crd_dir (@alt_crd_dirs) {

		my $alt_crd_dir_path = "$dsydir/$alt_crd_dir";
		my $datafile;
		my $binary;
		my $subdir;

		next unless (-d $alt_crd_dir_path);

		# The alternate card dir itself
		system("chown tfsupport:daisy $alt_crd_dir_path");
		system("chmod 770 $alt_crd_dir_path");

		# Executable files in alternate card dir
		my @alt_crd_binaries = qw(
			crd
			crdmenu
			crdpos
			crdterm
		);
		foreach $binary (@alt_crd_binaries) {
			next unless (-f "$alt_crd_dir_path/$binary");
			system("chown tfsupport:daisy $alt_crd_dir_path/$binary");
			system("chmod 550 $alt_crd_dir_path/$binary");
		}

		# Data files in alternate card dir
		my @alt_crd_datafiles = qw(
			authdat.pos
			crdaudi0.pos
			crdaudi1.pos
			crdaudi2.pos
			crdctrl.pos
			crdinet.pos
			crdinput.pos
			crdmodem.pos
			crd_scr.scr
			cutilscr.scr
			doserror.log
			firsctrl.pos
			moductrl.pos
			ncrctrl.pos
			ndc2ctrl.pos
			panelp.cnf
			paymctrl.pos
			posctrl.pos
			prsetscr.scr
			settfil0.pos
			settfil1.pos
			settfil2.pos
			settfile.pos
			vnetctrl.pos
		);
		foreach $datafile (@alt_crd_datafiles) {
			next unless (-f "$alt_crd_dir_path/$datafile");
			system("chown tfsupport:daisy $alt_crd_dir_path/$datafile");
			system("chmod 660 $alt_crd_dir_path/$datafile");
		}

		# Sub directories in alternate card dir
		my @alt_crd_subdirs = qw(
			dsy
		);
		foreach $subdir (@alt_crd_subdirs) {
			next unless (-d "$alt_crd_dir_path/$subdir");
			system("chown tfsupport:daisy $alt_crd_dir_path/$subdir");
			system("chmod 770 $alt_crd_dir_path/$subdir");

			next unless (-d "$alt_crd_dir_path/$subdir/printers");
			system("chown tfsupport:daisy $alt_crd_dir_path/$subdir/printers");
			system("chmod 660 $alt_crd_dir_path/$subdir/printers");
		}

		# The tcc sub dir in the alternate card dir

		next unless (-d "$alt_crd_dir_path/tcc");

		system("chown -R tfsupport:daisy $alt_crd_dir_path/tcc");
		system("chmod 770 $alt_crd_dir_path/tcc");
		system("chown -R tfsupport:daisy $alt_crd_dir_path/tcc/*");
		system("chmod 550 $alt_crd_dir_path/tcc/*");
	}

	return 0;
}


sub count_dir_entries
{
        my $dirpath = $_[0];

        opendir(DIR, $dirpath) or return(0);
                my @dirents = readdir(DIR) or return(0);
        closedir(DIR);

        return(scalar @dirents);
}


sub set_dir_perms
{
	my $dirpath = $_[0];
	my $perms = $_[1];
	my $dirent;

	opendir(DIR, $dirpath);
	foreach $dirent (readdir(DIR)) {
		next if ($dirent =~ /\.$/);	# Non-admin writable directory
		next if ($dirent =~ /\.\.$/);	# A must have "skip".

		system("chmod -R $perms $dirpath/$dirent");

	}
	closedir(DIR);
}

sub loginfo
{
	my $msg = $_[0];

	print("$msg.\n");
	logit($msg, 'I');
}

sub logwarning
{
	my $msg = $_[0];

	print("Warning: $msg.\n");
	logit($msg, 'W');
}

sub logerror
{
	my $msg = $_[0];

	print("Error: $msg.\n");
	logit($msg, 'E');
}

sub logit
{
        my $msg = $_[0];
        my $msg_type = $_[1];
        my $tag = "$PROGNAME";

        system("/usr/bin/logger -i -t $tag -- \"$UID: <$msg_type> $msg\"");
}

