#----------------------------------------------------------------------------
#
#  syscheck.pl - Nightly security checks
#
#  Copyright (c) 1998, SaberNet.net, All rights reserved.
#
#  This software may be freely copied, modified and redistributed without
#  fee for non-commercial purposes provided that this copyright notice is
#  preserved intact on all copies.
#
#  There is no warranty or other guarantee of fitness of this software. It
#  is provided solely "as is". The author disclaims all responsibility and
#  liability with respect to this software's usage or its effect upon
#  hardware or computer systems.
#
#
#      Platform: Solaris 2.6
#  Perl version: 5.004_04
#
#  Installation:
#     Add a line simular to the follwing to the root crontab:
#         0 0 * * * /<path>/syscheck.pl
#
#     When the program is run for the first time, a number of WARNING
#     messages will be listed in the report.  Although the script will
#     automatically generate these files, their content should be
#     verified as they will be used as the baseline for successive runs.
#
#  Note:
#     Disable /usr/lib/newsyslog or equivalent in the root crontab when
#     using the new_syslog module.
#
#  Modules:
#     new_syslog     : Archive system logs (new log each day)
#     new_fw1log     : Archive FireWall-1 log (new log each day)
#     new_xferlog    : Archive FTP logs (new log each day)
#     expire_audits  : Expire old audit logs
#     check_passwd   : List changes in the password file
#     check_group    : List changes in the group file
#     check_setuid   : List changes in setuid files and devices
#     check_services : List changes in services
#     check_logins   : List active and dormant accounts
#     check_loginlog : Check for /var/adm/loginlog
#     check_disks    : Print disk usage information
#
#  Revision History: 
#     12-Mar-1999  1.5  Added uptime
#     10-Feb-1999  1.4  Added expiration for audit logs
#     18-Jan-1999  1.3  Added support for FW-1 accounting logs
#     22-Dec-1998  1.2  Added new_xferlog
#     01-Dec-1998  1.1  Added check_loginlog
#     31-Oct-1998  1.0   Script completed
#
#----------------------------------------------------------------------------

# 
#   EMAIL_ADDRESS    : Address to email report to
#   SYSLOG_EXPIRE    : Number of days to keep syslog files
#   FW1_EXPIRE       : Number of days to keep FW-1 log files
#   AUDIT_EXPIRE     : Number of days to keep audit log files 
#   SETUID_LOG       : Path to setuid log (created by script)
#   SRVCS_UDP        : Path to the udp port log (created by script)
#   SRVCS_TCP        : Path to the tcp port log (created by script)
#
#   EMAIL_CMD        : Path to sendmail program
#   ZIP_CMD          : Path to compression program
#   SYSLOG_RESTART   : Command to restart syslog
#   SYSLOG_TEST      : Command to send a message to the syslog
#   SYSLOG_RECOVER   : Command to stop and start the syslog daemon
#   SYSLOG_M_DIR     : Directory of the messages file
#   SYSLOG_M_LOG     : Name of the messages file
#   SYSLOG_L_DIR     : Directory of the syslog file
#   SYSLOG_L_LOG     : Name of the syslog file
#   AUDIT_DIR        : Path to the audit logs (see audit_control(4))
#   FW1_DIR          : Path to the FireWall-1 log directory
#   FW1_BIN          : Path to the FireWall-1 bin directory
#   FW1_LOG          : Prefix for FireWall-1 log files
#   FW1_ACC          : Prefix for FireWall-1 accounting files
#   PASSWD_FILE      : Path to the passwd file
#   GROUP_FILE       : Path to the group file
#   DISK_CMD         : Disk usage command
#   NETSTAT_UDP      : Netstat command for UDP port information
#   NETSTAT_TCP      : Netstat command for TCP port information
#   LISTEN_UDP       : UDP listen state
#   LISTEN_TCP       : TCP listen state
#   XFERLOG_DIR      : Path to the FTP log file directory
#   XFERLOG_LOG      : FTP transfer log file
#

# Operational settings
$EMAIL_ADDRESS 	= "root";
$SYSLOG_EXPIRE  = 30;
$FW1_EXPIRE    	= 30;
$AUDIT_EXPIRE   = 30;
$XFERLOG_EXPIRE = 30;
$SETUID_LOG     = "/var/adm/setuid";
$SRVCS_UDP      = "/var/adm/services_udp";
$SRVCS_TCP      = "/var/adm/services_tcp";
$LOGIN_LOG      = "/var/adm/loginlog";

# SunOS 5.x
$EMAIL_CMD      = "/usr/lib/sendmail"; 
$ZIP_CMD        = "/usr/local/bin/gzip";
$SYSLOG_RESTART = "/bin/kill -HUP `/bin/cat /etc/syslog.pid`";
$SYSLOG_TEST    = "/usr/bin/logger -t syslogd restarted";
$SYSLOG_RECOVER = "/etc/init.d/syslog stop;sleep 1;/etc/init.d/syslog start";
$SYSLOG_M_DIR   = "/var/adm";
$SYSLOG_M_LOG   =  "messages";
$SYSLOG_L_DIR   = "/var/log";
$SYSLOG_L_LOG   = "syslog";
$AUDIT_DIR      = "/var/audit";
$FW1_DIR        = "/etc/fw/log";
$FW1_LOG        = "fw1-log";
$FW1_ACC        = "fw1-acc";
$FW1_BIN        = "/etc/fw/bin";
$PASSWD_FILE    = "/etc/passwd";
$SHADOW_FILE    = "/etc/shadow";
$GROUP_FILE     = "/etc/group";
$DISK_CMD       = "/bin/df -k -l";
$UPTIME_CMD     = "/bin/uptime";
$NETSTAT_UDP    = "/bin/netstat -a -P udp";
$NETSTAT_TCP    = "/bin/netstat -a -P tcp";
$LISTEN_UDP     = "Idle";
$LISTEN_TCP     = "LISTEN";
#$XFERLOG_DIR    = "/var/log";
#$XFERLOG_LOG    = "xferlog";

# Global
$TIMESTAMP      = get_timestamp();

#
# main
#
{
	my(@UID) = getpwuid($<);

	if ($UID[2] != 0)
	{
		print STDERR "Script requires root privledges!\n";
		exit 0;
	}

	my($hostname) = `hostname`; chop($hostname);

	new_syslog();
	new_fw1log();
    new_xferlog();
    expire_audits();
	check_passwd();
	check_group();
	check_setuid();
	check_services();
	check_logins();
	check_loginlog();
	check_disks();

	open(EMAIL, "| $EMAIL_CMD $EMAIL_ADDRESS");
	$_ = `$UPTIME_CMD`;
	print EMAIL "Subject: Security Check : $hostname\n\n$_\n$REPORT";
	close(EMAIL);

	exit 1;
}

#
# check_logins : List active and dormant accounts
#
sub check_logins
{
	my($user, $pass, $users, @keys);
	my($begin) = `/bin/last | /bin/tail -1`;

	open(PASSWD, "<$PASSWD_FILE");
	while(<PASSWD>)
	{
		$_ =~ /^([^:]+):([^:]+)/;
		$user = $1;
		$pass = $2;

		if ($SHADOW_FILE ne '')
 		{
			$_ = `/bin/grep \"^$user\" $SHADOW_FILE`;
			$_ =~ /^([^:]+):([^:]+)/;
			$pass = $2;
		}

		if ($pass ne 'NP')
		{
			$user =~ s/^\+//;
			$users{$user} = `/bin/last | /bin/grep \"^$user\" | /bin/head -1`;
		}
	}	
	close(PASSWD);

	@keys = sort(keys(%users));

	$REPORT .= "\nActive Accounts\n---------------\n";
	foreach $user (@keys)
	{
		if ($users{$user} ne '')
		{ 
			$REPORT .= $users{$user}; 
		}
	}

	$REPORT .= "\nDormant Accounts\n----------------\n";
	foreach $user (@keys)
	{
		if ($users{$user} eq '')
		{ 
			$REPORT .= sprintf("%-20s        %s", $user, $begin); 
		}
	}
}

#
# check_loginlog : Check for /var/adm/loginlog
#
sub check_loginlog
{
	if (! -f $LOGIN_LOG )
	{
		$REPORT .= "\nWARNING: $LOGIN_LOG missing!\n";
		system("cp /dev/null $LOGIN_LOG;chmod 0600 $LOGIN_LOG;" .
		       "chown root $LOGIN_LOG;chgrp sys $LOGIN_LOG");
	}
}

#
# check_services : List changes in services
# 
sub check_services
{
	get_ports($SRVCS_UDP, $NETSTAT_UDP, $LISTEN_UDP);
	get_ports($SRVCS_TCP, $NETSTAT_TCP, $LISTEN_TCP);
	check_file($SRVCS_UDP, "UDP Service Differences\n-----------------------");
	check_file($SRVCS_TCP, "TCP Service Differences\n-----------------------");
}

#
# get_ports : List listening ports
#
sub get_ports
{
	my($file, $netstat, $listen) = @_;
	my($port, $ports);

	open(NETSTAT, "$netstat |");
	while(<NETSTAT>)
	{
		if ($_ =~ /$listen$/)
		{
			# local address
			if ($_ =~ /([\S]+)/)
			{
				# port number/name
				if ($1 =~ /([^\.]+)$/)
				{ 
					$ports{$1} = 1; 
				}
			}
		}
	}
	close(NETSTAT);

	system("cp /dev/null $file; chmod 600 $file");
	open(SRVCS, ">$file");
	foreach $port (sort(keys(%ports)))
	{ 
		print SRVCS "$port\n"; 
	}
	close(SRVCS);
}

#
# check_disks : Print disk usage information
#
sub check_disks
{
	$REPORT .= "\nDisk Usage\n----------\n";
	$REPORT .= `$DISK_CMD`;
}

#
# check_setuid : List changes in setuid files and devices
#
sub check_setuid
{
	system("cp /dev/null $SETUID_LOG; chmod 600 $SETUID_LOG");
	system("/bin/find / -local \\( -perm -4000 -o -perm -2000 -o -name ps -o -name ifconfig -o -name find -o -name df -o -name ls \\) -local -type f -exec /bin/ls -l {} \\; >> $SETUID_LOG 2>/dev/null");
	check_file($SETUID_LOG, "Setuid Differences\n-----------------");
}

#
# check_group : List changes in the group file
#
sub check_group
{
	check_file($GROUP_FILE, "Group Differences\n----------------");
}

#
# check_passwd : List changes in the password file
#
sub check_passwd
{
	check_file($PASSWD_FILE, "Account Differences\n-------------------");
}

#
# check_file : List changes in the specified file
#
sub check_file
{
	my($file, $name) = @_;
	my($diff) = 0;

	if (-f "$file.1day")
	{
		open(DIFF, "/bin/diff $file $file.1day |");
		while(<DIFF>)
		{
			if ( ($_ =~ s/^</  added:/) || ($_ =~ s/^>/removed:/) )
			{
				if (!$diff)
				{ 
					$REPORT .= "\n$name\n"; 
					$diff = 1;
				}
				$REPORT .= $_; 
			}
		}
		close(DIFF);

		if (-f "$file.2day")
		{ 
			system("/bin/rm -f $file.3day;/bin/mv $file.2day $file.3day"); 
		}

		system("/bin/rm -f $file.2day;/bin/mv $file.1day $file.2day");
	}
	else
	{
		$REPORT .= "\nWARNING: $file.1day missing!\n";
	}

	system("/bin/cp $file $file.1day; chmod 400 $file.1day");
}

#
# new_xferlog : Archive FTP logs
#
sub new_xferlog
{
	if ( ($XFERLOG_DIR ne '') && (-d $XFERLOG_DIR) )
    {
		if (archive_log($XFERLOG_DIR, $XFERLOG_LOG) != 1)
		{
			$REPORT .= "\nWARNING: $XFERLOG_DIR/$XFERLOG_LOG missing!\n";
		}
		compress_log($XFERLOG_DIR, $XFERLOG_LOG);
		expire_log($XFERLOG_DIR, $XFERLOG_LOG, $XFERLOG_EXPIRE);
	}
}

#
# new_fw1log : Archive FireWall-1 logs
#
sub new_fw1log
{
	if ( ($FW1_BIN ne '') && (-d $FW1_DIR) )
	{
		system("$FW1_BIN/fw logswitch old > /dev/null 2>&1");
		system("$FW1_BIN/fw logexport -i $FW1_DIR/old.log " .
               "-o $FW1_DIR/$FW1_LOG.$TIMESTAMP -n > /dev/null 2>&1");
		system("$FW1_BIN/fw logexport -i $FW1_DIR/old.alog " .
               "-o $FW1_DIR/$FW1_ACC.$TIMESTAMP -n > /dev/null 2>&1");
		system("/bin/rm -f $FW1_DIR/old.log*ptr*");
		compress_log($FW1_DIR, $FW1_LOG);
		compress_log($FW1_DIR, $FW1_ACC);
		expire_log($FW1_DIR, $FW1_LOG, $FW1_EXPIRE);
		expire_log($FW1_DIR, $FW1_ACC, $FW1_EXPIRE);
	}
}

#
# new_syslog : Archive system logs
#
sub new_syslog
{
	if (archive_log($SYSLOG_M_DIR, $SYSLOG_M_LOG) != 1)
	{
		$REPORT .= "\nWARNING: $SYSLOG_M_DIR/$SYSLOG_M_LOG missing!\n";
	}
	if (archive_log($SYSLOG_L_DIR, $SYSLOG_L_LOG) != 1)
	{
		$REPORT .= "\nWARNING: $SYSLOG_L_DIR/$SYSLOG_L_LOG missing!\n";
	}
	system("sleep 40; $SYSLOG_RESTART");
	system($SYSLOG_TEST);
	system($SYSLOG_RECOVER) if ( -z "$SYSLOG_M_DIR/$SYSLOG_M_LOG");
	compress_log($SYSLOG_M_DIR, $SYSLOG_M_LOG);
	compress_log($SYSLOG_L_DIR, $SYSLOG_L_LOG);
	expire_log($SYSLOG_M_DIR, $SYSLOG_M_LOG, $SYSLOG_EXPIRE);
	expire_log($SYSLOG_L_DIR, $SYSLOG_L_LOG, $SYSLOG_EXPIRE);
}

# 
# get_timestamp : Returns a timestamp in the format 21-Dec-1998
#
sub get_timestamp
{
	my($monthlist)  = "JanFebMarAprMayJunJulAugSepOctNovDec";
    my(%days)       = (0,Sun,1,Mon,2,Tue,3,Wed,4,Thu,5,Fri,6,Sat);
	my($now)        = time - 86400;
	my($sec,$min,$hours,$mday,$mon,$year,$wday,$yday,$dsflag) = localtime($now);
	
	my(@yesterday)  = ($mday,$mon+1,$year+1900,$days{wday},
						substr($monthlist,($mon)*3,3));
	sprintf("%02d%s%s", $yesterday[0],$yesterday[4], $yesterday[2]);
}

#
# archive_log : Archives a log file with a timestamp 
#
sub archive_log
{
	my($dir, $log) = @_;
	my($rval) = 0;

	if ( ($dir eq '') || ($log eq '') )
	{
		return($rval); 
	}

	if (-f "$dir/$log")
	{
		system("/bin/mv $dir/$log $dir/$log.$TIMESTAMP");
		system("/bin/chmod 400 $dir/$log.$TIMESTAMP");
		$rval = 1;
	}

	system("/bin/cp /dev/null $dir/$log");
	system("/bin/chmod 644 $dir/$log");
	return($rval);
}

#
# expire_audits : Remove old audit logs
#
sub expire_audits
{
	if ($AUDIT_DIR ne '')
	{
    	system("/usr/sbin/audit -n > /dev/null 2>&1");
		system("cd $AUDIT_DIR;/bin/find . -type f -mtime +$AUDIT_EXPIRE -exec rm {} \\; > /dev/null 2>&1");
	}
}

#
# expire_log : Removes old log files
#
sub expire_log
{
	my($dir, $log, $exp) = @_;

	if ( ($exp > 0) && (-d $dir) && ($log ne '') )
	{ 
		system("cd $dir;/bin/find . -name \"$log*\" -mtime +$exp -exec rm {} \\;");
	}
}	

#
# compress_log : Compresses archived log files
#
sub compress_log
{
	my($dir, $log) = @_;

	if ( (-f $ZIP_CMD) && (-f "$dir/$log.$TIMESTAMP") )
	{
		system("$ZIP_CMD $dir/$log.$TIMESTAMP");
	}
}


