Mktwpol.sh

From Research
Revision as of 15:44, 6 July 2011 by Gordp (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
 #! /bin/bash

# /usr/local/sbin/mktwpol.sh

[[ "${EUID}" != "0" ]]		&& echo " Got root?"		&& exit
[[ ! "${BASH}" =~ "bash" ]]	&& echo " ${0##*/} needs bash"	&& exit

VERSION=14APR11		# c.cboldt at gmail.com

# A Gentoo-oriented Tripwire Policy Text Generator
# Creates tripwire policy text from lists of package names
# Obtains lists of files associated with a package by reading from /var/db/pkg
# See mktwpol-generic.sh for script that is adaptable to other package managers
#
# - Make sure packages critical to YOUR system are named in a PACKAGES[] list !!
#   (many package names are noted in default lists, but my view is myopic)
# - Optional mktwpol.cfg (or other) script configuration file can be used to:
#	- set default command-line switches
#	- substitute, augment, or modify package lists and file lists

# Tripwire configuration is extracted from tw.cfg (not the text file), if it exists.
# The twcfg.txt file can be removed, for (cough, cough) security purposes.
# The script reads from twcfg.txt, when tw.cfg hasn't been created yet.

TW_CFG=${TW_CFG:=/etc/tripwire/tw.cfg}

# Brief description of array variables used to generate tripwire policies
# =======================================================================
# RULENAME[]	Rule Name, unique to avoid errors by tripwire --init
# PACKAGES[]	Optional list of package names under this Rule Name
# IGNORLST[]	Optional list of files to ignore under this Rule Name
# FILELIST[]	Optional lists of individual file names (wildcards okay)
# COMMENTS[]	Optional comments associated with FileList
# SEVERITY[]	defaults to 100   - written rule-by-rule
#  EMAILTO[]	defaults to empty - written rule-by-rule
#		Note: This e-mail depends on tripwire's `--email-report`
#
# Optional rule-by-rule Security *Aliases* for File Property Inspection Masks
# ===========================================================================
#  BIN_SEC[]	defaults to ReadOnly - also applies to files in lib/
#  ETC_SEC[]	defaults to Dynamic  - also applies to Directories
#  LOG_SEC[]	defaults to Growing
#
# Optional list-by-list modifications to default or defined Inspection Masks
# ==========================================================================
#  SEC_MOD[]	defaults to empty - written file-by-file across PACKAGE or FILELIST
#  SFT_MOD[]	defaults to empty - when assigned, applies only to SoftLinks
#  RECURSE[]	defaults to (recurse=true) - when assigned, applies only to Directories
#   EXCEPT[]	defaults to empty - "special" file(s) in expanded wildcard list
#   SEC_EX[]	defaults to empty - *non-alias* security rule for "special" files

# The script handles variables for multiple filelists within a single rule
#   - Useful for specializing Comments and Inspection Masks
# FILELIST_x variable names MUST be sequential, starting with "x" = "2"
# Available correlated variables: COMMENTS_x, SEC_MOD_x, SFT_MOD_x, and RECURSE_x

###########  Start Default Package Lists and RuleName Definitions

# Note:	Script configuration file can replace or augment the default set of rules

  RULENAME[0]='System Auditing Programs'
  PACKAGES[0]="tripwire aide chkrootkit fwlogwatch logwatch lynis nagios openscap \
 osiris rkhunter sleuthkit yasat"
   EMAILTO[0]='"root@localhost"'
   ETC_SEC[0]='ReadOnly'
COMMENTS_2[0]='Audit programs data directories'
FILELIST_2[0]="/var/lib/rkhunter/db"
 RECURSE_2[0]=' (recurse = 0)'
 SEC_MOD_2[0]=' -sbmcCM'

RULENAME[1]='Invariant Directories'
COMMENTS[1]='Owner and group should remain static'
FILELIST[1]='/ /home /etc /mnt /opt'
SEVERITY[1]='66'
 ETC_SEC[1]='Invariant'
 RECURSE[1]=' (recurse = 0)'

RULENAME[2]='Temporary Directories'
FILELIST[2]='/usr/tmp /var/tmp /tmp'
SEVERITY[2]='33'
 ETC_SEC[2]='Invariant'
 RECURSE[2]=' (recurse = 0)'

RULENAME[3]='[core|diff|find]utils procps'
PACKAGES[3]='coreutils diffutils findutils procps htop lsof'

# Disambiguation by including category with a package name is superfluous
# The script only prints rules for packages that are installed, and then
# only for files that are tested to exist

RULENAME[4]='Compression/Archiving Programs'
PACKAGES[4]="tar star bzip2 app-arch/gzip zip unzip xz-utils app-arch/lzma sharutils"
SEVERITY[4]='66'

RULENAME[5]='Network - Setup/Services'
PACKAGES[5]="net-tools iproute2 iputils dhcpcd ppp \
 madwifi-ng-tools \
 bind bind-tools djbdns dnsmasq maradns mydns pdns pdnsd unbound \
 distcc mgetty rsync samba telnet-bsd netkit-talk ytalk \
 yp-tools ypserv ypbind"
 ETC_SEC[5]='ReadOnly'

RULENAME[6]='Network - Filter/View'
PACKAGES[6]="tcpdump tcp-wrappers mrtg netcat nmap wireshark \
 iptables iptstate pktstat denyhosts fail2ban knock"
 ETC_SEC[6]='ReadOnly'

RULENAME[7]='Hardware and Device Programs'
PACKAGES[7]="udev uam pciutils util-linux psmisc kbd hdparm smartmontools \
 lshw ethtool hotplug-base module-init-tools setserial dmraid \
 gpart gparted parted partimage ddrescue dd-rescue \
 bluez cups"
COMMENTS[7]='udev device creation policies and scripts'
FILELIST[7]='/etc/udev/rules.d /etc/udev/scripts'

# progsreiserfs produces no bin/ or /etc/ files  ...
# it produces  /usr/lib/libdal.so and /usr/lib/libreiserfs.so

RULENAME[8]='Filesystem Programs'
PACKAGES[8]="e2fsprogs reiserfsprogs reiser4progs xfsprogs nfs ntfsprogs jfsutils \
 sysfsutils autofs lvm2 mdadm mtools fuse sshfs-fuse dosfstools"
COMMENTS[8]='From sys-fs/progsreiserfs'
FILELIST[8]='/usr/lib/libdal.* /usr/lib/libreiserfs.*'

RULENAME[9]='File Manipulation Programs'
PACKAGES[9]="gawk grep patch cpio file gettext groff less sys-apps/man mlocate which \
 ncurses slang sed slocate patchutils debianutils"

RULENAME[10]='Toolchain Programs'
PACKAGES[10]='gcc binutils glibc libtool make autoconf automake'
COMMENTS[10]='From sys-devel/autoconf-wrapper'
FILELIST[10]='/usr/lib/misc/ac-wrapper.sh'

RULENAME[11]='Security Related Programs'
PACKAGES[11]='shadow sys-libs/pam sudo openldap openssl openssh gnupg gnutls pax-utils'

RULENAME[12]='Database Related Programs'
PACKAGES[12]='gdbm dev-db/mysql postgresql-server qdbm sqlite sys-libs/db'

RULENAME[13]='Programming Languages'
PACKAGES[13]='nasm perl dev-lang/python qt-core ruby swig tcl tk expect'
SEVERITY[13]='66'

RULENAME[14]='MTA Related Programs'
PACKAGES[14]='sendmail postfix ssmtp mail-client/mailx procmail dovecot clamav spamassassin'

RULENAME[15]='IRC/P2P Related Programs'
PACKAGES[15]="net-irc/inspircd irc-server ircservices ngircd ptlink-ircd \
 ejabberd jabberd jabberd2 mu-conference"

RULENAME[16]='WWW Related Programs'
PACKAGES[16]="apache bozohttpd lighttpd mini_httpd thttpd \
 dev-haskell/cgi dev-libs/cgicc dev-libs/fcgi \
 www-apache/mod_fastcgi www-apache/mod_fcgid www-apache/mod_scgi \
 dev-lang/php php-toolkit phpBB"

RULENAME[17]='Shell Programs'
PACKAGES[17]='bash zsh csh tcsh sash rssh busybox screen'
 ETC_SEC[17]='ReadOnly'

RULENAME[18]='Editor Programs'
PACKAGES[18]='nano joe vim ed emacs'
COMMENTS[18]='Shared config files'
FILELIST[18]='/usr/share/nano'
SEVERITY[18]='66'

# Detect new and removed user crontabs.  Ignore modification of existing crontabs.

RULENAME[19]='Cron, Inetd, and Logging'
PACKAGES[19]="anacron bcron cronie dcron fcron incron vixie-cron xinetd \
 metalog newsyslog rsyslog syslog-ng logrotate tmpwatch"
COMMENTS[19]='User-installed crontabs'
FILELIST[19]='/var/spool/cron/crontabs'

  RULENAME[20]='Boot Selector and Kernel'
  PACKAGES[20]='grub lilo kccmp kerneloops ksymoops module-rebuild syslinux'
  COMMENTS[20]='Contents of /boot directory are safer on an unmounted partition'
  FILELIST[20]='/boot/* /lib/modules'
COMMENTS_2[20]='Detect current mounting of /boot'
FILELIST_2[20]='/boot'
 SEC_MOD_2[20]=' +mc'

  RULENAME[21]='Package Manager Programs'
  PACKAGES[21]='pacman paludis rpm'

# process_packagename routine filters out */lib/* filenames, but captures *.sh
# If a packages installs scripts without the .sh suffix,
# FILELIST[] can be used to assign rules for scripts installed in /lib/rcscripts

  RULENAME[22]='Gentoo Specific Programs'
  PACKAGES[22]='sys-apps/portage portage-utils gentoolkit baselayout openrc sysvinit eix'
#  COMMENTS[22]='Gentoo-installed scripts'
#  FILELIST[22]='/lib/rcscripts/*/*'
  COMMENTS[22]='Local ebuilds - skip "files" subdirs'
  FILELIST[22]='/usr/local/portage'
   SEC_MOD[22]=' +M'
   RECURSE[22]=' (recurse = 3)'
COMMENTS_2[22]='/usr/lib/pkgconfig is active at package add/remove'
FILELIST_2[22]='/usr/lib/pkgconfig'
 SEC_MOD_2[22]=' -mc'
 RECURSE_2[22]=' (recurse = 0)'

#####  End of PACKAGES[] package lists  #####
#####  Some   FILELIST[] rules below cribbed from Red Hat policy file

# Some local config files, not owned by a package, can be found with:
# for i in `locate --ignore-case --regexp etc.*local`
#   do [ -z "`qfile $i`" ] && echo $i
# done
#
# If one wants to find SUID and SGID files ...
# find / -group kmem -perm -2000 -print # Finds SGID files, owned by kmem
# find /  -user root -perm -4000 -print # Finds SUID files, owned by root

RULENAME[23]='Local Config Files'
FILELIST[23]="/etc/bash/bashrc.local \
 /etc/dovecot/dovecot-local.conf \
 /etc/dnsmasq-local.conf \
 /etc/env.d/00Local \
 /etc/host-local-block \
 /etc/host-banner-ads \
 /etc/hosts \
 /etc/hosts.allow \
 /etc/hosts.deny \
 /etc/lilo.conf \
 /etc/lynx/lynx-site.cfg \
 /etc/ppp/chap-secrets \
 /etc/ppp/ip-up.d/00-local.sh \
 /etc/ppp/ip-down.d/00-local.sh \
 /etc/rkhunter.conf.local \
 /etc/screenrc-local \
 /etc/syslog-ng/syslog-local.conf \
 /etc/udev/rules.d/10-local.rules"
ETC_SEC[23]='ReadOnly'

# Avoids opening devices (recursion) by applying the $(Device) policy to
# block and character special devices.  See "select_policy" routine.
# Assumed that Inode changes for listed /dev files, most under creation by udev
# Note:	A number of changes occur when moving from 2.6.34 to 2.6.35 kernels
#	- /dev/files device number  changes, from "9" to "10"
#	- /proc file inode  numbers change

  RULENAME[24]='Critical Devices'
  COMMENTS[24]='Red Hat config named kmem, mem, null, zero'
  FILELIST[24]="/dev/kmem /dev/mem /dev/null /dev/zero \
 /dev/console /dev/cua0 /dev/initctl /dev/log \
 /dev/tty[0-9] /dev/tty1[012] /dev/urandom"
   SEC_MOD[24]=' -i'
COMMENTS_2[24]='/proc/mounts softlink undergoes time modification'
FILELIST_2[24]='/proc/*'
 SFT_MOD_2[24]=' -mc'

# Note:	Stifle the temptation to use EXCEPT[]/EX_SEC[] for /lib/splash/cache
#	EXCEPT[] requires the listed name appear in expanded FILELIST[] wildcard
# Use (recurse = 0) (or 1), rather than IGNORLST, to limit range of inspection

  RULENAME[25]='OS Bin and Lib Directories'
  FILELIST[25]='/bin /lib /sbin /usr/sbin /usr/local/sbin'
   ETC_SEC[25]='ReadOnly'
COMMENTS_2[25]='/lib/splash/cache is an active directory'
FILELIST_2[25]='/lib/splash/cache'
 RECURSE_2[25]=' (recurse = 0)'

# Note:	Inspection of /usr/lib/pkgconfig should be expanded on a system
#	that has a fixed or stable set of installed packages.

  RULENAME[26]='User Bin and Lib Directories'
  COMMENTS[26]=
  FILELIST[26]="/usr/bin /usr/local/bin /usr/local/games /usr/local/lib"
  SEVERITY[26]='66'
   ETC_SEC[26]='ReadOnly'
COMMENTS_2[26]='Full recursion of /usr/lib is prolix'
FILELIST_2[26]='/usr/lib'
 RECURSE_2[26]=' (recurse = 1)'

# Note:	logrotate can create false alarms
#  - new log file will be smaller than tripwire observation at database creation
#  - logrotate.conf 'nocreate' option can result in alarms at absent log files
# Note: "SEC_MOD=-il" removes the "Growing" test for ALL /var/log files, which
#	makes the "EXCEPT[] / SEC_EX[]" example moot, as a practical matter.
#	It might be feasible to set observed filesize of usually growing logs
#	to a very small value, to enable use of "growing" check, but this moots
#	the point of looking for unauthorized tampering based on shrinking file.
#	Tripwire compares to a "fixed" reference point of day-0, and is unsuited
#	to detect log file tampering based on day-to-day size reduction.
# Note: permissions of /var/log/rkhunter.log change during a --propupd operation
#	going from -rw-r--r-- to -rw-------
#
# Exception for log files in /var/log/*g wildcard, that don't necessarily grow

  RULENAME[27]='Log Files'
  COMMENTS[27]='`logrotate` may change logfile inodes'
  FILELIST[27]='/var/log/critical /var/log/messages /var/log/*g'
  SEVERITY[27]='66'
   SEC_MOD[27]=' -il'
    EXCEPT[27]='/var/log/rkhunter.log /var/log/Xorg.0.log'
    SEC_EX[27]='$(Dynamic) -i'

# Note: Stifling creation of .xauth?????? files is done under xauth program
#	These .xauth?????? files are made by action of a regular user, who uses
#	`su`.  The regular user can stifle creation of .xauth?????? files in
#	/root by adding "export XAUTHORITY=.xauth" in the regular user's .bashrc
#	** but ** this affects display opening permissions under X-win!

RULENAME[28]='Root User Directory'
IGNORLST[28]="/root/.lesshst /root/.bash_history \
 /root/.aumixrc /root/.calc_history \
 /root/.fonts.cache-1 \
 /root/.lynx_cookies \
 /root/.mysql_history \
 /root/.rnd \
 /root/.sc_history \
 /root/.stack.wcd /root/.treedata.wcd /root/bin/wcd.go"
COMMENTS[28]='Config and files for console applications'
FILELIST[28]="/root \
 /root/.bashrc /root/.bash_profile /root/.bash_logout \
 /root/.cshrc /root/.tcshrc /root/.screenrc \
 /root/.htoprc /root/.mc /root/.ncftp \
 /root/Mail /root/mail \
 /root/.elm /root/.pinerc /root/.pinepwd \
 /root/.mailcap /root/.mime.types \
 /root/.addressbook.lu /root/.addressbook /root/.sendxmpprc \
 /root/.links /root/.lynxrc \
 /root/.riprc /root/.sversionrc \
 /root/.esd_auth"
COMMENTS_2[28]='Action in these directories will trigger a warning'
FILELIST_2[28]="/root/bin /root/.ssh /root/.amandahosts /root/.gnupg"
 SEC_MOD_2[28]=' +srbmcCM'
COMMENTS_3[28]='X-Windows should not be run as Root User!'
FILELIST_3[28]="/root/.ICEauthority /root/.xsession-errors /root/.Xresources /root/.Xmodmap \
 /root/.config  /root/.enlightenment /root/.fltk /root/.fvwm /root/.fvwmrc \
 /root/.gconf /root/.gconfd \
 /root/.gnome /root/.gnome2 /root/.gnome_private /root/.gnome-desktop \
 /root/.qt /root/.sawfish \
 /root/.xauth"
COMMENTS_4[28]='Files that change inode number'
FILELIST_4[28]='/root/.Xauthority'
 SEC_MOD_4[28]=' -i'

# Note using an incrementing variable instead of hardcoded rule number.
# Using a variable for this is especially useful when adding rules from cfg file.

let NEXT=${#RULENAME[@]}
  RULENAME[${NEXT}]='Security Control File'
  FILELIST[${NEXT}]='/etc/security'
   ETC_SEC[${NEXT}]='ReadOnly'

let NEXT=${#RULENAME[@]}
  RULENAME[${NEXT}]='System Boot Changes'
  COMMENTS[${NEXT}]='Many files change inode number'
  FILELIST[${NEXT}]='/etc/mtab /var/run /var/run/dhcpcd /var/run/sudo'
   SEC_MOD[${NEXT}]=' -i'
   RECURSE[${NEXT}]=' (recurse = 1)'
COMMENTS_2[${NEXT}]='Red Hat Policy File: Files that change when the system boots'
FILELIST_2[${NEXT}]='/etc/ioctl.save /etc/.pwd.lock /var/lock/subsys'
 SEC_MOD_2[${NEXT}]=' -i'

###########  End Default Package Lists and RuleName Definitions

###########  Subroutines for Generating Policy Text Output
#################################################################

# "select_policy" routine runs each filename through a gauntlet
# $Filetype assignment is based on which attribute matched last.

select_policy ()
{
						   Filetype=Config	# Default
  $targetfile =~ ^/etc/ 			&& Filetype=Config
  $targetfile =~ /lib/ 			&& Filetype=Lib
  $targetfile =~ /log/ 			&& Filetype=Log
  $targetfile =~ ^/root/ 			&& Filetype=RootFile
  [ -x $targetfile ]				&& Filetype=Bin
  [ -b $targetfile ]				&& Filetype=Block
  [ -c $targetfile ]				&& Filetype=Char
  [ -p $targetfile ]				&& Filetype=Pipe
  [ -S $targetfile ]				&& Filetype=Socket
  [ -d $targetfile ]				&& Filetype=Dir
  "`file -b $targetfile`" =~ kernel 	&& Filetype=Kernel
  $targetfile =~ ^/lib/modules 		&& Filetype=Kernel
  [ -h $targetfile ]				&& Filetype=SoftLink
  $targetfile =~ ^/dev/tty 		&& Filetype=Tty
  [ $targetfile == "/root" ]			&& Filetype=RootDir
  [ ! -d $targetfile ] && \
  [ -u $targetfile -o -g $targetfile ]		&& Filetype=SUID
  [[ "${EXCEPT[$i]}" =~ $targetfile ]]		&& Filetype=Special

case $Filetype in
  Dir	   )	echo "-> \$(${ETC_SEC[$i]:-Dynamic})${SEC_MOD[$i]}${RECURSE[$i]} ;"	;;
  RootFile )	echo "-> \$(${ETC_SEC[$i]:-Dynamic})${SEC_MOD[$i]} ;  # Rootfile"	;;
  RootDir  )	echo "-> \$(IgnoreNone)-amcSH ;  # Catch changes to /root"	;;
  Config   )	echo "-> \$(${ETC_SEC[$i]:-Dynamic})${SEC_MOD[$i]} ;"		;;

  Kernel   )	echo "-> \$(${BIN_SEC[$i]:-ReadOnly})${SEC_MOD[$i]} ;  # Kernel"	;;
  Bin	   )	echo "-> \$(${BIN_SEC[$i]:-ReadOnly})${SEC_MOD[$i]} ;"		;;
  Lib	   )	echo "-> \$(${BIN_SEC[$i]:-ReadOnly})${SEC_MOD[$i]} ;"		;;
  SoftLink )	echo "-> \$(SoftLink)${SFT_MOD[$i]} ;  # Softlink"		;;

  Log	   )	echo "-> \$(${LOG_SEC[$i]:-Growing})${SEC_MOD[$i]} ;"		;;
  SUID	   )	echo "-> \$(IgnoreNone)-aSH ;  # SUID or SGID"			;;
  Tty      )	echo "-> \$(Dynamic)-ipug ;"					;;
  Char	   )	echo "-> \$(Device) ;  # Character device"			;;
  Block	   )	echo "-> \$(Device) ;  # Block device"				;;
  Pipe	   )	echo "-> \$(Device) ;  # Pipe"					;;
  Socket   )	echo "-> \$(Device) ;  # Socket"				;;
  Special  )	echo "-> ${SEC_EX[$i]} ;  # Exception"				;;
esac
}

print_header ()
{
# if there is no config file for this script, print warning.
# if there is a config file for this script, search it for a rulename assignment
# if the config file has no rulename assignment, print warning.

[ ! -r "$CONFIG_FILE" ] && default_rule_warning
[ -r "$CONFIG_FILE" ] && \
[ -z "`grep -l RULENAME\\\[.*\\\]= $CONFIG_FILE`" ] && default_rule_warning

echo "  # =================================================="
echo "  # Tripwire Policy File  http://bugs.gentoo.org/34662"
echo "  # =================================================="
echo "  # Generated by $0"
echo "  # Version $VERSION"
echo "  # `date '+%B %e, %Y at %R'`"
echo
echo '  # ============================================================================'
echo '  #'
echo '  # Tripwire, Inc. permission statements apply to some fully hardcoded document,'
echo "  # not to generated content produced by the script, ${0##*/}"
echo '  #'
echo '  # That said, here are the Tripwire, Inc. permission statements ...'
echo '  #'
echo '  # Permission is granted to make and distribute verbatim copies of this document'
echo '  # provided the copyright notice and this permission notice are preserved on all'
echo '  # copies.'
echo '  #'
echo '  # Permission is granted to copy and distribute modified versions of this'
echo '  # document under the conditions for verbatim copying, provided that the entire'
echo '  # resulting derived work is distributed under the terms of a permission notice'
echo '  # identical to this one.'
echo '  #'
echo '  # Tripwire is a registered trademark of Tripwire, Inc.'
echo '  # (in the United States and other countries)'
echo '  # All rights reserved.'
echo '  #'
echo '  # ============================================================================'
echo
echo "@@section GLOBAL"
echo "HOSTNAME=\"`uname -n`\" ;"
echo
echo '  # Standard Tripwire File Property Mask Aliases (from `man twpolicy`)'
echo '  # --------------------------------------------'
echo '  #	ReadOnly	+pinugtsdbmCM-rlacSH'
echo '  #	Dynamic		+pinugtd-srlbamcCMSH'
echo '  #	Growing		+pinugtdl-srbamcCMSH'
echo '  #	Device		+pugsdr-intlbamcCMSH'
echo '  #	IgnoreAll	-pinugtsdrlbamcCMSH'
echo '  #	IgnoreNone	+pinugtsdrbamcCMSH-l'
echo
echo '@@section FS'
echo
echo '  #   Non-standard File Property Mask Aliases'
echo '  # --------------------------------------------'
echo '  	Invariant	= +pugt ;	 # Permissions, UID, GID, and filetype'
echo '  	SoftLink	= +pinugtsdbmc ; # Skip checking hash values'
echo
echo '# ================== [ Begin Hardcoded Tripwire Rules ] ======================'
echo
echo '('
echo '  rulename = "Tripwire CFG and Data",'
echo '  severity = 100'
echo ')'
echo '{'
echo "  # Tripwire file locations were transposed from ${TW_CFG}"
echo '  # Tripwire creates backup files by renaming tw.cfg and tw.pol,'
echo '  # then creating new files.  The new files have new inode numbers.'
echo "  # Database and backup files in ${DBDIR} do not appear to change inode."
echo
echo "  ${TWCFG_DIR}/tw.cfg			-> \$(ReadOnly) -i ;"
echo "  ${POLFILE}			-> \$(ReadOnly) -i ;"
echo "  ${SITEKEYFILE}		-> \$(ReadOnly) ;"
echo "  ${LOCALKEYFILE}	-> \$(ReadOnly) ;"
echo "  ${DBDIR}			-> \$(Dynamic) ;"
echo
echo '  # Do not scan individual `tripwire --check` reports'
echo
echo "  ${REPORTDIR}		-> \$(Dynamic) (recurse = 0) ;"
echo '}'
echo
echo '# ================== [ Begin Generated Tripwire Rules ] ======================'
}

print_footer ()
{
echo
echo '# ============================================================================'
echo '#'
echo '# Hardcoded and generated output is based on:'
echo '#'
echo '#	- tripwire.pol.gentoo : Darren Kirby : September 5, 2006'
echo '#	  http://bugs.gentoo.org/34662'
echo '#	- Policy file for Red Hat Linux : V1.2.0rh : August 9, 2001'
echo '#	- FreeBSD: ports/security/tripwire/files/twpol.txt : v 1.3 : 2005/08/09'
echo '#	  http://lists.freebsd.org/pipermail/freebsd-security/2005-October/003221.html'
echo '#	- Examples found in tripwire-2.4.2-src.tar.bz2 source code distribution'
echo '#'
echo '# FreeBSD is a registered trademark of the FreeBSD Project Inc.'
echo '# Red Hat is a registered trademark of Red Hat, Inc.'
echo '#'
echo '#################      END of tripwire Policy Text File      #################'
echo
}

###########  Cycle through RULENAME variable arrays

# "print_generated_rules" routine cycles each group of array variables through "print_a_rule"

print_generated_rules ()
{
for (( i = 0 ; i < ${#RULENAME[@]} ; i++ ))
do
  [ "${QUIET^^}" != "YES" -a "${VERBOSE^^}" != "YES" ] && \
  echo -n -e "\\r Processing rule $i of $[(10#${#RULENAME[@]}-1)] rules" >&2
  print_a_rule
done
[ "${QUIET^^}" != "YES" ] && echo >&2
}

# -------------------------------
# "print_a_rule" routine runs once for each RULENAME[]
#   - make a title header for the tripwire rule, including optional "emailto" field
#   - print ignorefiles, if any
#   - forward proposed package names, one-by-one, to process_packagename
#   - forward filelist array(s), if any, to process_filelist

print_a_rule ()
{
echo
echo "# ========================================================================="
echo "#  RuleName: ${RULENAME[$i]}"
echo "# -------------------------------------------------------------------------"
[ -n "${PACKAGES[$i]}" ] && echo " Packages: ${PACKAGES[$i]}" | eval ${FOLD} | sed s/^/"# "/
[ -n "${FILELIST[$i]}" ] && echo "FileNames: ${FILELIST[$i]}" | eval ${FOLD} | sed s/^/"# "/
echo "# ========================================================================="
echo \(
echo "  rulename = \"${RULENAME[$i]}\","
echo -n "  severity = ${SEVERITY[$i]:-100}"
[ -n "${EMAILTO[$i]}" ] && echo -e ",\\n  emailto = ${EMAILTO[$i]}" || echo
echo \)
echo \{

[ -n "${IGNORLST[$i]}" ] && \
  echo -e "\\n# ${RULENAME[$i]}: Ignore changes to these files\\n"
for targetfile in ${IGNORLST[$i]}
  do [ -e "$targetfile" ] && echo "  !$targetfile ;"
done

if [ "${SKIP_PACKAGES}" == "Yes" ]; then
   [ ! -z "${PACKAGES[$i]}" ] && \
   echo -e "\\n# !! NOTICE !!\\n# Skipping ${RULENAME[$i]} Packages !!"
else
  for package in ${PACKAGES[$i]}
    do process_packagename
  done
fi

[ -n "${FILELIST[$i]}" ] && process_filelist

# Code to process pseudo-two-dimensional arrays.
# FLST, CMTS, SCMD, SFMD, and RCRS hold numerically-specific variable names,
# Those variable names are in the style of an array name, e.g., FILELIST_2[26]
# The specific variable names are then indirectly expanded to their contents

for j in {2..100}; do
  FLST=FILELIST_$j[$i]
  CMTS=COMMENTS_$j[$i]
  SCMD=SEC_MOD_$j[$i]
  SFMD=SFT_MOD_$j[$i]
  RCRS=RECURSE_$j[$i]
  FILELIST[$i]="${!FLST}"
  COMMENTS[$i]="${!CMTS}"
  SEC_MOD[$i]="${!SCMD}"
  SFT_MOD[$i]="${!SFMD}"
  RECURSE[$i]="${!RCRS}"
  [ -n "${FILELIST[$i]}" ] && process_filelist  || break
done

echo \}
}

# -------------------------------
# "process_packagename" routine is applied to every listed package name
#   - to qualify for being listed in tmp_array[], and eventually in policy text file:
#     - $targetfile contains "bin/", begins "/etc/" or "/var/log/", or ends ".sh"
#     - $targetfile is not a directory or zero-size file
#   - calls filename and rule printing subroutines for each targetfile in $tmp_array[]
# Note: adding "lib/" results in MUCH longer list, and is not necessary because
#       all files in "/usr/lib" and "/usr/local/lib" directories can be watched
#       under a FILELIST[] rule, albeit not associated with a particular package
# Note: adding the test for executables also results in a MUCH longer list

process_packagename ()
{
unset tmp_array
[[ "${package}" =~ "/" ]] || package="*/${package}"
for package_contents_file in `ls /var/db/pkg/${package}-[0-9]*/CONTENTS 2> /dev/null`; do
tmp_array+=(`
  while read -a line
  do fname=${line[@]:1:1}
    case $fname in
	/etc/hosts )
	true ;;
	*bin/* | /etc/* | /var/log/* | *.sh )
	[ ! -d ${fname} -a -s ${fname} ] && echo $fname ;;
	* )
	[ ! -d ${fname} -a -x ${fname} -a "${INCL_EXEC}" == "Yes" ] && echo $fname ;;
    esac
  done < ${package_contents_file}`)
done

if [ -n "${tmp_array}" ]; then		# Empty array results in NUL output
  echo
  echo "# ${RULENAME[$i]}: $package"
  echo
  for targetfile in ${tmp_array[@]}; do
    output_line; select_policy
  done
fi
}

# -------------------------------
# "process_filelist" routine is used only for FILELIST[] arrays
#   - outputs COMMENTS[], if any, for FILELIST[] array
#   - if file exists, calls for printing filename and tripwire policy
#   - blocks listing of:
#	- any file named "lost+found"
#	- any directory in the /proc/* wildcard

process_filelist ()
{
echo -e "\\n# ${RULENAME[$i]}: ${COMMENTS[$i]}\\n"
for targetfile in ${FILELIST[$i]}; do
  case ${targetfile} in
    */lost+found* )
	true
	;;
    /proc/* )
	if [ ! -d "$targetfile" ]; then
	  output_line; select_policy; fi
	;;
    * )
	if [ -e "$targetfile" ]; then
	  output_line; select_policy; fi
	;;
  esac
done
}

# -------------------------------
# "output_line" routine adds a variable number of tabs to obtain alignment
# The width of the targetfile name is increased by 2 to account for indent
# The maximum number of additional tabs is the digit after "10#"
# The width of the TAB is taken as 8 characters

output_line ()
{
  MAKE_TABS=$[(10#4-(${#targetfile}+2)/8)]	# Calculate number of TABs
  echo -n "  $targetfile"
  echo -e -n \\t				# Output at least one TAB
  for (( z = 0 ; z < MAKE_TABS ; z++ ))		# Up to five TABs, total
  do
    echo -e -n \\t
  done
}

###########  Main Routine for Generating Policy Text Output
#################################################################

print_policy_text ()
{
print_header
print_generated_rules
print_footer
}

###########  Subroutines for the User Interface
#################################################################

###########  Subroutines for Informing the User

# Most messages sent to STDERR and only STDERR (>&2)
#  - to avoid appearing in redirected STDOUT output (the text policy)
#  - to avoid disappearing from view when STDOUT has been redirected

default_rule_warning ()
{
echo
echo "  #############       !!!!  WARNING  !!!!      #############"
echo "  #  Default policies may be useless on your system ...    #"
echo "  #   - the script may have overlooked critical packages   #"
echo "  #   - tripwire inspection policies may be too lax        #"
echo "  ##########################################################"
echo
echo "  #  To view scope of tripwire inspection policies:"
echo "  #  \`twprint -m d -d ${DB_FILE}\`"
echo
}

recite_ver ()
{
echo "
 This is ${0##*/} version $VERSION
 A Gentoo-oriented Tripwire Policy Text Generator
" >&2
}

recite_help ()
{
recite_ver
echo " Usage: ${0##*/} [-c configfile] [-u[-r][-q|-v]] [-s] [-x] [-h|-V] [debug [#]]

	-c Read RULENAME[], PACKAGELIST[], FILELIST[] from configfile
	   Default (optional) configfile = $TWCFG_DIR/mktwpol.cfg
	-u Create tripwire policy and database after producing policy text file
	-r Remove policy text file after tripwire has processed it
	-q Quiet   - stifle progress display and confirmation prompts
	-v Verbose - display policy text production
	-s Skip processing of PACKAGELIST[] arrays
	-x Include all executable files
	-h Output version and help information
	-V Output version information

 \`${0##*/}\` without \"-u\" command line parameter:
	- sends policy text to STDOUT, suitable for redirection with \">\"

 \`${0##*/} -u\`  produces no policy on STDOUT. WON'T REDIRECT!
	- sends policy text to a file in $TWCFG_DIR
	- calls \`twadmin\`  to create tw.pol from that file
	- calls \`tripwire\` to create the system database using tw.pol

 \`${0##*/} debug\`
	- limits output to one selected rule, default RULENAME[0]
" >&2
exit
}

###########  Subroutines for Configuring the Script

assign_misc_mktwpol_defaults ()
{
# Default settings unless changed by script configuration file

# FOLD="${FOLD:=fold -s -w 75}"
FOLD="${FOLD:=fmt -u}"

# User may change ${TW_CFG} from CONFIG_FILE
# Multiple tripwire configuration locations can be maintained this way

# mktwpol.sh -c mktwpol.cfg1 -> /etc/tripwire1/tw.cfg (and /etc/tripwire1/twcfg.txt)
# mktwpol.sh -c mktwpol.cfg2 -> /etc/tripwire2/tw.cfg (and /etc/tripwire2/twcfg.txt)

TWCFG_DIR=${TW_CFG%/*}		# bash shell equivalent to the `dirname` command
}


# -------------------------------
# "config_mktwpol" checks for and reads a script configuration file

config_mktwpol ()
{
TWCFG_DIR=${TW_CFG%/*}
CONFIG_FILE=${CONFIG_FILE:=$TWCFG_DIR/mktwpol.cfg}

# Exit on "non-existence" of non-default script configuration file

if [ "$CONFIG_FILE" != "$TWCFG_DIR/mktwpol.cfg" -a ! -f "$CONFIG_FILE" ]; then
  echo "
 ${0##*/} configuration file, $CONFIG_FILE, does not exist.
 Exiting.  Goodbye.
" >&2
  exit 2
fi

# If a script config file exists, check for the string "RULENAME[.*]="
# If the script config file purports to set a RULENAME[] (any number), and the
# script config file does not say "KEEP_DEFAULT_RULES", unset the defaults

if [ -f "$CONFIG_FILE" ]; then
  if  [ -n "`grep -l RULENAME\\\[.*\\\]= $CONFIG_FILE`" -a \
	-z "`grep -l KEEP_DEFAULT_RULES  $CONFIG_FILE`" ]; then
  unset RULENAME SEVERITY EMAILTO \
	BIN_SEC  ETC_SEC  LOG_SEC \
	IGNORLST \
	PACKAGES \
	FILELIST FILELIST_2 FILELIST_3 FILELIST_4 \
	COMMENTS COMMENTS_2 COMMENTS_3 COMMENTS_4 \
	SEC_MOD  SEC_MOD_2  SEC_MOD_3  SEC_MOD_4  \
	SFT_MOD  SFT_MOD_2  SFT_MOD_3  SFT_MOD_4  \
	RECURSE  RECURSE_2  RECURSE_3  RECURSE_4  \
	EXCEPT   SEC_EX
  fi
  source "$CONFIG_FILE"			# if there is a CONFIG_FILE, source it
fi
}

test_for_dupe_rules ()
{
DUPE_PACKAGES="`echo ${PACKAGES[@]} | tr [:space:] '\n' | sort | uniq -d`"
DUPE_FILELIST="`echo ${FILELIST[@]} ${FILELIST_2[@]} ${FILELIST_3[@]} ${FILELIST_4[@]} \
		| tr [:space:] '\n' | sort | uniq -d`"

if [ -n "${DUPE_PACKAGES}" -o -n "${DUPE_FILELIST}" ]; then
  echo " Uh Oh!  Duplicates Found!" >&2
  echo >&2
  [ -n "${DUPE_PACKAGES}" ] && echo " In more than one PACKAGES[]:" ${DUPE_PACKAGES}	>&2
  [ -n "${DUPE_FILELIST}" ] && echo " In more than one FILELIST[]:" ${DUPE_FILELIST}	>&2
  echo >&2
  echo " Edit $CONFIG_FILE or $0 to resolve the situation."	 >&2
  echo " Exiting.  Goodbye."					 >&2
  exit 4
fi
}

# -------------------------------
# "get_twcfg_variables" reads tripwire configuration file (not the script config file)
# Assuming existence of twadmin ... if tw.cfg exists, it was made by twadmin
# Filename "tw.cfg" is hardcoded in tripwire, and hardcoded here

get_twcfg_variables ()
{
if [ -r ${TWCFG_DIR}/tw.cfg ]; then
  [ "${VERBOSE^^}" == "YES" ] && echo " Reading ${TWCFG_DIR}/tw.cfg"		>&2
  tmp_array=(`twadmin -m f -c ${TWCFG_DIR}/tw.cfg`)
elif [ -r ${TWCFG_DIR}/twcfg.txt ]; then
  [ "${VERBOSE^^}" == "YES" ] && echo " Reading ${TWCFG_DIR}/twcfg.txt"		>&2
  tmp_array=(`cat ${TWCFG_DIR}/twcfg.txt`)
else
  echo " ${0##*/} depends on finding a tripwire configuration file
	${TWCFG_DIR}/tw.cfg	is preferred
	${TWCFG_DIR}/twcfg.txt	is acceptable
 Exiting.  Goodbye.
" >&2
  exit 3
fi

# `z+=2` incrementing shaves a few thousandths of a second
# but risks skipping over the required variable names

for (( z = 0 ; z < ${#tmp_array[@]} ; z++ ))
do
  case ${tmp_array[@]:$z:1} in
    POLFILE | DBFILE | REPORTFILE | SITEKEYFILE | LOCALKEYFILE  )
      assignment=${tmp_array[@]:$((z+1)):1}
      [ "${assignment:0:1}" == "=" ] && \
        export ${tmp_array[@]:$z:1}${assignment}
  ;;
  esac
done
REPORTDIR=${REPORTFILE%/*}	# bash shell equivalent to the `dirname` command
DBDIR=${DBFILE%/*}
DB_FILE=${DBFILE/\$\(HOSTNAME\)/`uname -n`}
[ "${VERBOSE^^}" == "YES" ] && echo >&2
}


###########  Subroutines for Operating

# -------------------------------
update_tripwire_policy ()
{
if [ "${QUIET^^}" != "YES" ]; then
  echo " To create the tripwire policy file (${POLFILE}),
 and tripwire database (in ${DBDIR}/), run:

	twadmin --create-polfile $TRIPWIRE_POL
	tripwire --init
" >&2
  echo -n " Create tripwire policy and database now? [Y/n]: " >&2
  read -n 1 -t 15 RUN_TRIPWIRE
  echo >&2
fi
if [ "${RUN_TRIPWIRE^}" == "N" ]; then
  echo " Skipping creation of tripwire policy and database.  Goodbye.
" >&2
  exit
fi
twadmin --create-polfile $TRIPWIRE_POL

if [ "${REMOVE_POL}" == "Yes" ]; then
  if [ "${QUIET^^}" != "YES" ]; then
    echo -n " Delete $TRIPWIRE_POL now? [Y/n]: " >&2
    read -n 1 -t 15 YES_IM_SURE
    echo >&2
  fi
  if [ "${YES_IM_SURE^}" != "N" ]; then
    rm -f $TRIPWIRE_POL
  fi
fi
tripwire --init
}

# -------------------------------
mode_auto_update ()
{
TRIPWIRE_POL=$TWCFG_DIR/twpol-`date +%y%m%d-%H%M`.txt

if [ "${VERBOSE^^}" == "YES" ]; then
  print_policy_text | tee $TRIPWIRE_POL
elif [ "${QUIET^^}" != "YES" ]; then
  echo " Writing to $TRIPWIRE_POL ..." >&2
fi
if [ "${VERBOSE^^}" != "YES" ]; then
  print_policy_text > $TRIPWIRE_POL
  [ "${QUIET^^}" != "YES" ] && echo >&2
fi
update_tripwire_policy
}

# -------------------------------
mode_echo_policy ()
{
if [ "${QUIET^^}" != "YES" ]; then
  echo " Run \`${0##*/} -h\` for tips on use.
 Creating tripwire policy text ...
" >&2
fi
print_policy_text
}

# -------------------------------
mode_debug ()
{
DEBUGME=y	# Unused variable.  Can be useful for internal debugging.
i=${1:-0}	# User can debug any single rule, default RULENAME[0]
echo
echo "  !! WARNING !!  ${0##*/} is in DEBUG Mode !!"
echo "  !! WARNING !!  Processing --ONLY-- RULENAME[${i}]"
echo
print_a_rule
echo
echo "  !! WARNING !!  ${0##*/} was in DEBUG Mode !!"
echo "  !! WARNING !!  Processed --ONLY-- RULENAME[${i}]"
exit 1
}

###########  Main Routine
#################################################################
# Default RULENAME[] arrays and TW_CFG variable are in memory

# Process command line parameters

while getopts :c:uqvrsxhV OPTION
do
  case $OPTION in
    c	) CONFIG_FILE=$OPTARG	;;
    u	) UPDATETW=Yes		;;
    q	) QUIET=Yes		;;
    v	) VERBOSE=Yes		;;
    r	) REMOVE_POL=Yes	;;
    s	) SKIP_PACKAGES=Yes	;;
    x	) INCL_EXEC=Yes		;;
    h	) config_mktwpol; recite_help		;;
    V	) recite_ver; exit	;;
    *	) config_mktwpol; recite_help		;;
  esac
done
shift $(($OPTIND - 1))

config_mktwpol
test_for_dupe_rules
assign_misc_mktwpol_defaults
get_twcfg_variables

[ "$1" == "debug" ] && mode_debug $2

if [ "$UPDATETW" == "Yes" ]; then
     mode_auto_update
else mode_echo_policy
fi