Revision $Id: webjob_manage_snort.base,v 1.2 2006/10/11 18:19:10 pab Exp $ Purpose This recipe demonstrates how to deploy and manage snort rule sets on multiple sensors. Motivation This recipe is motivate by the need to efficiently mananage snort rules across multiple sensors. This system is managed via a directory structure on a WebJob server (shown below). snort_manager | +- development | | | +- environments | | | | | +- etc | | | | | | | - oinkmaster.conf | | | | | +- sensor_1 | | | | | +- etc | | | | | | | - oinkmaster.conf | | | | | +- instance_1 | | | | | +- etc | | | | | | | - classification.config | | | - command.conf | | | - oinkmaster.conf | | | - reference.config | | | - snort.conf | | | - threshold.conf | | | | | +- rules | | | | | - local.rules | | | +- master | | | +- etc | | | | | - classification.config | | - command.conf | | - reference.config | | - snort.conf | | - threshold.conf | | | +- rules | | | - attack-responses.rules | - backdoor.rules | - bad-traffic.rules | - ... | +- production | +- sensor_1 | +- instance_1 | +- copy.0 | +- etc | | | - classification.config | - command.conf | - oinkmaster.conf | - reference.config | - snort.conf | - threshold.conf | +- rules | - attack-responses.rules - backdoor.rules - bad-traffic.rules - ... development: The development directory contains the majority of the configuration information for all sensors and instances on those sensors. Within the development directory are two sub-directories: environments and master. The environments directory contains most snort configuration information including the snort command used to launch snort, oinkmaster configuration files, and a local.rules file. Engineers will spend most of their time modifying the files contained in the environments directory. The second sub-directory, master, contains a master rule set as well as master configuration files. The contents of the master directory are automatically updated via the oinkmaster.pl program. production: The production directory contains all files that will be rsync'ed to the sensors. Some files are copied from the development directory, but most rules files are generated using oinkmaster and the master rule set contained in the master directory. The snort rule sets are automatically managed by two server scripts: sm_update_master_rules and sm_create_snort_env. The sm_update_master_rules program uses oinkmaster to download and update the master snort rule set and the sm_create_snort_env program uses oinkmaster to create snort run time environments in a production location. The client-server framework is completed by two client scripts: sm_deploy_snort_env and sm_launch_snort. sm_deploy_snort_env: The sm_deploy_snort_env program performs three major functions. First, it rsync's snort configuration files from a WebJob server to the local system. Next, if a change in the configuration occured, it tests the validity of the new configuration by launching snort commands contained in the command.conf file, substituting the deamon option, '-D', with the test option, '-T'. Lastly, if the new configuration is valid, sm_deploy_snort_env will "push" the new environment into a production area. sm_launch_snort: The sm_launch_snort program conditionally launches snort if the number of snort instances defined does not equal the number of snort instances running or if there is a change to the snort environment. When launching snort processes, sm_launch_snort loops over every snort instance defined on a machine, tests the snort environment, then launches if the test is successful. If the test is unsucessful, sm_launch_snort tries an older copy of the snort environment. This continues until either snort is launched successfully or the number of snort environment copies is exhausted. Requirements Cooking with this recipe requires an operational WebJob server. If you do not have one of those, refer to the instructions provided in the README.INSTALL file that comes with the source distribution. The latest source distribution is available here: http://sourceforge.net/project/showfiles.php?group_id=40788 The server must be running UNIX and have basic system utilities, Apache, PaD tools, WebJob (1.5.0 or higher), and wget installed. PaD tools are included in the WebJob distribution. Each client must be running UNIX and have basic system utilities, WebJob (1.5.0 or higher), and snort (2.3 or higher) installed. This recipe assumes that you have read and implemented the hourly script from the following recipe: http://webjob.sourceforge.net/Files/Recipes/webjob-pad-rsync.txt It also assumes you are familiar with configuration overrides and server-side triggers. Finally, the commands presented throughout this recipe were designed to be executed within a Bourne shell (i.e. sh or bash). Time to Implement Assuming all prerequisits have been met, implementation time for this recipe is approximately 4 hours to setup the WebJob server. The time to configure sensors varies based on the number of sensors, but it should take less than 10 minutes per sensor. Solution The following steps describe how to implement this solution. 1. Set SNORT_BASE_DIR, SNORT_GROUP, SNORT_ID, WEBJOB_BASE_DIR, WEBJOB_CLIENT, WEBJOB_COMMANDS, and WEBJOB_TRIGGER as appropriate for your server. The SNORT_BASE_DIR environment variable is the root directory to place the snort_manager directory and associated framework. a. Set the WebJob variables as appropriate. # WEBJOB_BASE_DIR="/var/webjob" # WEBJOB_CLIENT=common # WEBJOB_COMMANDS=${WEBJOB_BASE_DIR}/profiles/${WEBJOB_CLIENT}/commands # WEBJOB_TRIGGER="/usr/local/bin" b. Set the snort variables as appropriate. # SNORT_BASE_DIR=/usr/local # SNORT_GROUP=snort # SNORT_ID=1006 If the WebJob server does not contain the snort group, add the group using the appropriate commands below. FreeBSD: # pw groupadd ${SNORT_GROUP} -g ${SNORT_ID} Linux and Solaris: # groupadd -g ${SNORT_ID} ${SNORT_GROUP} Finally, add users to the snort group using the appropriate commands below. FreeBSD: # pw groupmod ${SNORT_GROUP} -m USER Linux and Solaris: # groupmod -G ${SNORT_ID} USER 2. Install the snort_manager environment on your WebJob server. This environment will be the working location for modifying the snort runtime environments. a. Extract snort_manager.tgz.uu from this recipe, uudecode it, unpack its contents in the SNORT_BASE_DIR directory, then set the ownership as appropriate for your system. # sed -e '1,/^--- snort_manager.tgz.uu ---$/d; /^--- snort_manager.tgz.uu ---$/,$d' webjob_manage_snort.txt > snort_manager.tgz.uu # uudecode snort_manager.tgz.uu # tar -C ${SNORT_BASE_DIR} -zxf snort_manager.tgz # find ${SNORT_BASE_DIR}/snort_manager -type d -exec chmod 770 {} \; # find ${SNORT_BASE_DIR}/snort_manager -type f -exec chmod 660 {} \; # chown -R 0:${SNORT_GROUP} ${SNORT_BASE_DIR}/snort_manager # chmod 775 ${SNORT_BASE_DIR}/snort_manager # find ${SNORT_BASE_DIR}/snort_manager | sort At this point, you should have a tree that looks like this: snort_manager snort_manager/CVS snort_manager/CVS/Entries snort_manager/CVS/Repository snort_manager/CVS/Root snort_manager/Makefile snort_manager/README snort_manager/development snort_manager/development/CVS snort_manager/development/CVS/Entries snort_manager/development/CVS/Repository snort_manager/development/CVS/Root snort_manager/development/environments snort_manager/development/environments/CVS snort_manager/development/environments/CVS/Entries snort_manager/development/environments/CVS/Repository snort_manager/development/environments/CVS/Root snort_manager/development/environments/etc snort_manager/development/environments/etc/CVS snort_manager/development/environments/etc/CVS/Entries snort_manager/development/environments/etc/CVS/Repository snort_manager/development/environments/etc/CVS/Root snort_manager/development/environments/etc/oinkmaster.conf snort_manager/development/master snort_manager/development/master/CVS snort_manager/development/master/CVS/Entries snort_manager/development/master/CVS/Repository snort_manager/development/master/CVS/Root snort_manager/development/master/etc snort_manager/development/master/etc/CVS snort_manager/development/master/etc/CVS/Entries snort_manager/development/master/etc/CVS/Repository snort_manager/development/master/etc/CVS/Root snort_manager/development/master/etc/command.conf snort_manager/development/master/etc/snort.conf snort_manager/development/master/rules.0 snort_manager/development/master/rules.0/CVS snort_manager/development/master/rules.0/CVS/Entries snort_manager/development/master/rules.0/CVS/Repository snort_manager/development/master/rules.0/CVS/Root snort_manager/production snort_manager/production/CVS snort_manager/production/CVS/Entries snort_manager/production/CVS/Repository snort_manager/production/CVS/Root b. Modify the snort.conf file to suit your environment because this file will be used as the base configuration file for every snort instance created. For example, you may wish to set the following variables: AIM_SERVERS, DNS_SERVERS, HOME_NET, SMTP_SERVERS, and WEBPROXY_SERVERS. If sending snort alerts to a database, modify the "output database:" line as appropriate for your environment. # vi ${SNORT_BASE_DIR}/snort_manager/development/master/etc/snort.conf c. Modify the command.conf file to suit your environment. Please read the documentation contained in the command.conf file for more information. # vi ${SNORT_BASE_DIR}/snort_manager/development/master/etc/command.conf d. Modify the high-level oinkmaster.conf file to suit your environment. Please read the documentation contained in the oinkmaster.conf file for more information. # vi ${SNORT_BASE_DIR}/snort_manager/development/environments/etc/oinkmaster.conf e. Set the SNORT_GID macro in the Makefile below to the SNORT_ID value from step 1. # vi ${SNORT_BASE_DIR}/snort_manager/Makefile f. Change to the snort_manager directory and read the README. This file contains additional information about the project and how to perform basic management tasks such as adding a new sensor to the management framework and modifying oinkmaster configuration files. After reading the README, perform the work-flow actions as appropriate for your environment. # cd ${SNORT_BASE_DIR}/snort_manager # less README 3. Extract and install the program sm_update_master_rules and the associated put trigger program sm_update_master_rules_pt as specified below. The sm_update_master_rules program uses oinkmaster to download and update a master snort rule set and the sm_update_master_rules_pt program sends an alert email to an email list if the snort rules set has changed. Please read the documentation contained within sm_update_master_rules and sm_update_master_rules_pt for a more detailed description of the programs and associated options. a. Extract and install the programs using the commands below. # sed -e '1,/^--- sm_update_master_rules ---$/d; /^--- sm_update_master_rules ---$/,$d' webjob_manage_snort.txt > sm_update_master_rules # cp sm_update_master_rules ${WEBJOB_COMMANDS}/sm_update_master_rules # chmod 644 ${WEBJOB_COMMANDS}/sm_update_master_rules # chown 0:0 ${WEBJOB_COMMANDS}/sm_update_master_rules # sed -e '1,/^--- sm_update_master_rules_pt ---$/d; /^--- sm_update_master_rules_pt ---$/,$d' webjob_manage_snort.txt > sm_update_master_rules_pt # cp sm_update_master_rules_pt ${WEBJOB_TRIGGER}/sm_update_master_rules_pt # chmod 0755 ${WEBJOB_TRIGGER}/sm_update_master_rules_pt # chown 0:0 ${WEBJOB_TRIGGER}/sm_update_master_rules_pt b. Create the WebJob config override for sm_update_master_rules by first creating the directory below. # mkdir -p ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_update_master_rules Next, populate the sm_update_master_rules/nph-webjob.cfg configuration file with the following content. Be sure to substitute the appropriate path for WEBJOB_TRIGGER and add the appropriate email addresses via the '-l' command line switch. Please read the documentation contained within sm_update_master_rules_pt for a more detailed description of the program and associated options. # vi ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_update_master_rules/nph-webjob.cfg --- nph-webjob.cfg --- OverwriteExistingFiles=Y PutNameFormat=snort_manager/%cmd/%cid_%Y-%m-%d_%H:%M PutTriggerEnable=Y PutTriggerCommandLine=WEBJOB_TRIGGER/sm_update_master_rules_pt -r %rdy --- nph-webjob.cfg --- c. Run sm_update_master_rules periodically (e.g., hourly) on the WebJob server to collect the latest master snort rule sets. Use the command template below as a guide, substituting the appropriate paths, files, and commands as appropriate. Please read the documentation contained within sm_update_master_rules for a more detailed description of the program and associated options. webjob -e -f WJ_UPLOAD sm_update_master_rules -d SNORT_ROOT_DIR -u URL SNORT_ROOT_DIR ${SNORT_BASE_DIR}/snort_manager URL Full URL to the snort rules tarball at snort.org 4. Extract and install the program sm_create_snort_env and the associated put trigger program sm_create_snort_env_pt as specified below. The sm_create_snort_env program uses oinkmaster to create snort runtime environments and the sm_create_snort_env_pt program sends an alert email to an email list if the runtime snort environments have changed. Please read the documentation contained within sm_create_snort_env and sm_create_snort_env_pt for a more detailed description of the programs and associated options. a. Extract and install the programs using the commands below. # sed -e '1,/^--- sm_create_snort_env ---$/d; /^--- sm_create_snort_env ---$/,$d' webjob_manage_snort.txt > sm_create_snort_env # cp sm_create_snort_env ${WEBJOB_COMMANDS}/sm_create_snort_env # chmod 644 ${WEBJOB_COMMANDS}/sm_create_snort_env # chown 0:0 ${WEBJOB_COMMANDS}/sm_create_snort_env # sed -e '1,/^--- sm_create_snort_env_pt ---$/d; /^--- sm_create_snort_env_pt ---$/,$d' webjob_manage_snort.txt > sm_create_snort_env_pt # cp sm_create_snort_env_pt ${WEBJOB_TRIGGER}/sm_create_snort_env_pt # chmod 0755 ${WEBJOB_TRIGGER}/sm_create_snort_env_pt # chown 0:0 ${WEBJOB_TRIGGER}/sm_create_snort_env_pt b. Create the WebJob config override for sm_create_snort_env by first creating the directory below. # mkdir -p ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_create_snort_env Next, populate the sm_create_snort_env/nph-webjob.cfg configuration file with the following content. Be sure to substitute the appropriate path for WEBJOB_TRIGGER and add the appropriate email addresses via the '-l' command line switch. Please read the documentation contained within sm_create_snort_env_pt for a more detailed description of the program and associated options. # vi ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_create_snort_env/nph-webjob.cfg --- nph-webjob.cfg --- OverwriteExistingFiles=Y PutNameFormat=snort_manager/%cmd/%cid_%Y-%m-%d_%H:%M PutTriggerEnable=Y PutTriggerCommandLine=WEBJOB_TRIGGER/sm_create_snort_env_pt -r %rdy --- nph-webjob.cfg --- c. Run sm_create_snort_env periodically (e.g., hourly) on the WebJob server to collect the latest master snort rule sets. Use the command template below as a guide, substituting the appropriate paths, files, and commands as appropriate. Please read the documentation contained within sm_update_master_rules for a more detailed description of the program and associated options. webjob -e -f WJ_UPLOAD sm_create_snort_env -d SNORT_ROOT_DIR SNORT_ROOT_DIR ${SNORT_BASE_DIR}/snort_manager 5. Extract and install the program sm_deploy_snort_env and the associated put trigger program sm_deploy_snort_env_pt as specified below. The sm_deploy_snort_env program deploys a new snort environment (rules and configuration) via rsync, tests the environment, then optionally moves the environment into a production location. The sm_deploy_snort_env_pt program sends an alert email to an email list if there were errors during the deployment. Please read the documentation contained within sm_deploy_snort_env and sm_deploy_snort_env_pt for a more detailed description of the programs and associated options. a. Extract and install the programs using the commands below. # sed -e '1,/^--- sm_deploy_snort_env ---$/d; /^--- sm_deploy_snort_env ---$/,$d' webjob_manage_snort.txt > sm_deploy_snort_env # cp sm_deploy_snort_env ${WEBJOB_COMMANDS}/sm_deploy_snort_env # chmod 644 ${WEBJOB_COMMANDS}/sm_deploy_snort_env # chown 0:0 ${WEBJOB_COMMANDS}/sm_deploy_snort_env # sed -e '1,/^--- sm_deploy_snort_env_pt ---$/d; /^--- sm_deploy_snort_env_pt ---$/,$d' webjob_manage_snort.txt > sm_deploy_snort_env_pt # cp sm_deploy_snort_env_pt ${WEBJOB_TRIGGER}/sm_deploy_snort_env_pt # chmod 0755 ${WEBJOB_TRIGGER}/sm_deploy_snort_env_pt # chown 0:0 ${WEBJOB_TRIGGER}/sm_deploy_snort_env_pt b. Create a symlink called webjob_rsync_id_sm_deploy_snort_env.pad that points to webjob_rsync_id.pad. The sm_deploy_snort_env program will use this key for the SSH tunnel constructed during the rsync operation. # ( cd ${WEBJOB_COMMANDS} && ln -s webjob_rsync_id.pad webjob_rsync_id_sm_deploy_snort_env.pad ) c. Create the WebJob config override for sm_deploy_snort_env by first creating the directory below. # mkdir -p ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_deploy_snort_env Next, populate the sm_deploy_snort_env/nph-webjob.cfg configuration file with the following content. Be sure to substitute the appropriate path for WEBJOB_TRIGGER and add the appropriate email addresses via the '-l' command line switch. Please read the documentation contained within sm_deploy_snort_env_pt for a more detailed description of the program and associated options. # vi ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_deploy_snort_env/nph-webjob.cfg --- nph-webjob.cfg --- OverwriteExistingFiles=Y PutNameFormat=snort_manager/%cmd/%cid_%Y-%m-%d_%H:%M PutTriggerEnable=Y PutTriggerCommandLine=WEBJOB_TRIGGER/sm_deploy_snort_env_pt -c %cid -r %rdy --- nph-webjob.cfg --- d. Run sm_deploy_snort_env periodically (e.g., hourly) on each client to deploy the latest snort rule sets. Use the command template below as a guide, substituting the appropriate paths, files, and commands as appropriate. Please read the documentation contained within sm_deploy_snort_env for a more detailed description of the program and associated options. webjob -e -f WJ_UPLOAD sm_deploy_snort_env -c WJ_STDOUT \ -r ROOT_DIR -H RSYNC_HOST -h WJ_HOME -k WJ_KEY \ -u RSYNC_USER -s SRC_DIR ROOT_DIR /opt/snort_manager/sensor_01 RSYNC_KEY webjob_rsync_id_sm_deploy_snort_env.pad RSYNC_HOST 10.1.1.1 RSYNC_USER rsync SRC_DIR /usr/local/snort_manager/production/sensor_01 WJ_STDOUT stdout.cfg WJ_UPLOAD upload.cfg WJ_HOME /usr/local Note: Long lines are broken with the backslash character for readability. Note: The ROOT_DIR must be the same when executing the sm_deploy_snort_env and sm_launch_snort scripts. 6. Extract and install the program sm_launch_snort and the associated put trigger program sm_launch_snort_pt as specified below. The sm_launch_snort program conditionally launches snort if the number of snort instances defined does not equal the number of snort instances running or if there is a change to the snort environment. The sm_launch_snort_pt program sends an alert email to an email list if there are snort execution errors. Please read the documentation contained within sm_launch_snort and sm_launch_snort_pt for a more detailed description of the programs and associated options. a. Extract and install the programs using the commands below. # sed -e '1,/^--- sm_launch_snort ---$/d; /^--- sm_launch_snort ---$/,$d' webjob_manage_snort.txt > sm_launch_snort # cp sm_launch_snort ${WEBJOB_COMMANDS}/sm_launch_snort # chmod 644 ${WEBJOB_COMMANDS}/sm_launch_snort # chown 0:0 ${WEBJOB_COMMANDS}/sm_launch_snort # sed -e '1,/^--- sm_launch_snort_pt ---$/d; /^--- sm_launch_snort_pt ---$/,$d' webjob_manage_snort.txt > sm_launch_snort_pt # cp sm_launch_snort_pt ${WEBJOB_TRIGGER}/sm_launch_snort_pt # chmod 0755 ${WEBJOB_TRIGGER}/sm_launch_snort_pt # chown 0:0 ${WEBJOB_TRIGGER}/sm_launch_snort_pt b. Create the WebJob config override for sm_launch_snort by first creating the directory below. # mkdir -p ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_launch_snort Next, populate the sm_launch_snort/nph-webjob.cfg configuration file with the following content. Be sure to substitute the appropriate path for WEBJOB_TRIGGER and add the appropriate email addresses via the '-l' command line switch. Please read the documentation contained within sm_launch_snort_pt for a more detailed description of the program and associated options. # vi ${WEBJOB_BASE_DIR}/config/nph-webjob/commands/sm_launch_snort/nph-webjob.cfg --- nph-webjob.cfg --- OverwriteExistingFiles=Y PutNameFormat=snort_manager/%cmd/%cid_%Y-%m-%d_%H:%M PutTriggerEnable=Y PutTriggerCommandLine=WEBJOB_TRIGGER/sm_launch_snort_pt -c %cid -r %rdy --- nph-webjob.cfg --- c. Run sm_launch_snort on each sensor periodically (e.g., hourly) via a cronjob. Use the command template below as a guide, substituting the appropriate paths and commands as appropriate. Please read the documentation contained within sm_launch_snort for a more detailed description of the program and associated options. The template command below will attempt to run sm_launch_snort via WebJob every hour. Running sm_launch_snort via WebJob has the advantage of aggregating output from the script on the WebJob server for processing and analysis. However, if the network connection is down, it is still important to execute sm_launch_snort. Therefore, the WebJob command is logically OR'ed with the local command, so sm_launch_snort runs even if the WebJob command fails. This provides a higher degree of robustness. 0 * * * * webjob -e -f WJ_UPLOAD sm_launch_snort -r ROOT_DIR || \ [ -x sm_launch_snort ] && sm_launch_snort -r ROOT_DIR ROOT_DIR /opt/data/sensor_01 WJ_UPLOAD upload.cfg Note: Long lines are broken with the backslash character for readability. Note: The ROOT_DIR must be the same when executing the sm_deploy_snort_env and sm_launch_snort scripts. Closing Remarks Credits This recipe is brought to you by Andy Bair. References [1] oinkmaster can be found here: http://www.snort.org/dl/contrib/rule_management/oinkmaster Appendix 1 -- sm_update_master_rules --- sm_update_master_rules --- #!/bin/sh # # NAME # sm_update_master_rules - Download and update snort rule sets # # SYNOPSIS # sm_update_master_rules [-c copy-count] [-g snort-group] # -d root-dir [-n base-name] [-p proxy] -u download-url # # DESCRIPTION # The sm_update_master_rules program uses oinkmaster to # download and update a master snort rule set. The following # is a more detailed explanation of the process. # # The snort rules are downloaded and placed in an initial # directory using oinkmaster. The initial directory (or # origin directory) is appended with the number zero (".0") # and is specified via command line arguements, as shown # below. # # root-dir/base-name.0 # # If a change is detected in the rule set, the contents of the # origin directory are copied into a production directory that # is appended with the number one (".1"), as shown below. # # root-dir/base-name.1 # # If there above directory exists, then the base-name.1 # contents are copied into the base-name.2 directory, # base-name.2 into base-name.3, base-name.3 into base-name.4, # and so on, up to the copy-count. The process is shown below # where copy-count is 3. # # base-name.2 ---> copied into ---> base-name.3 # base-name.1 ---> copied into ---> base-name.2 # base-name.0 ---> copied into ---> base-name.1 # # This allows you to maintain upto copy-count rules copies, # where lower numbered directories represent the most recent # copies. Maintainining multiple copies also allows you to # better track differences and potentially fall-back to an # older rule set. This increased flexibility and robustness. # # OPTIONS # [-c copy-count] # Specifies the number of snort rules copies to keep. The # allowable range is from 1 through 9 with the default # value of 3. # # -d root-dir # Specifies the root directory where the snort_manager # environment is located. # # [-g snort-group] # Specifies the snort group name to set the group # ownership of the development files and directories. The # default value is 'snort'. # # [-n base-name] # Specifies the base directory name where snort rule # copies are maintained. The default value is 'rules'. # # [-p proxy] # Specifies the proxy server to use when downloading rule # sets. The default value is to not use a proxy. # # -u download-url # Specifies the full URL to use when downloading a new # copy of the snort rules set. The example below # specifies the URL for the snort 2.3 rule set. # # http://www.snort.org/pub-bin/downloads.cgi/Download/vrt_pr/snortrules-pr-2.3.tar.gz # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # PushDirectory # ###################################################################### PushDirectory() { MY_DIR_ROOT=$1 MY_DIR_BASE=$2 MY_MAX_COUNT=$3 MY_FUNC_NAME="PushDirectory" #################################################################### # # Validate arguments # #################################################################### if [ ! -d "${MY_DIR_ROOT}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- is not a directory, '${MY_DIR_ROOT}'." >&2 return 1 fi if [ -z "${MY_DIR_BASE}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- is not a directory, '${MY_DIR_BASE}'." >&2 return 1 fi echo "${MY_MAX_COUNT}" | grep "^[1-9]$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid , must be in range 1-9, '${MAX_COUNT}'." >&2 return 1 fi #################################################################### # # Push new directory. # #################################################################### COPY_NUM_HI=${MY_MAX_COUNT} COPY_NUM_LO=`expr ${COPY_NUM_HI} - 1` while [ ${COPY_NUM_HI} -gt 0 ]; do DIR_HI="${MY_DIR_ROOT}/${MY_DIR_BASE}.${COPY_NUM_HI}" DIR_LO="${MY_DIR_ROOT}/${MY_DIR_BASE}.${COPY_NUM_LO}" if [ ${COPY_NUM_LO} -eq 0 ]; then rm -rf "${DIR_HI}" rsync -a "${DIR_LO}/" "${DIR_HI}" else if [ -d "${DIR_LO}" ]; then if [ -d "${DIR_HI}" ]; then rm -rf "${DIR_HI}" fi mv "${DIR_LO}" "${DIR_HI}" fi fi COPY_NUM_HI=`expr ${COPY_NUM_HI} - 1` COPY_NUM_LO=`expr ${COPY_NUM_HI} - 1` done return 0 } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} [-c copy-count] -d root-dir [-g snort-group] [-n base-name] [-p proxy] -u download-url" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### COPY_COUNT=3 ROOT_DIR="" SNORT_GROUP="snort" BASE_NAME="rules" PROXY="" DOWNLOAD_URL="" while getopts "c:d:g:n:p:u:" OPTION ; do case "${OPTION}" in c) COPY_COUNT="${OPTARG}" ;; d) ROOT_DIR="${OPTARG}" ;; g) SNORT_GROUP="${OPTARG}" ;; n) BASE_NAME="${OPTARG}" ;; p) PROXY="${OPTARG}" ;; u) DOWNLOAD_URL="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that COPY_COUNT is not null, and is a number in # the range 1-9. # #################################################################### if [ -z "${COPY_COUNT}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it is null." 1>&2 echo 1>&2 Usage fi echo "${COPY_COUNT}" | grep "^[1-9]$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it must be from 2-9, '${COPY_COUNT}'" 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that ROOT_DIR is not null and begins with a slash. # #################################################################### if [ -z "${ROOT_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, it is null, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${ROOT_DIR}" | grep "^/" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, does not begin with a slash, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that SNORT_GROUP is not null. # #################################################################### if [ -z "${SNORT_GROUP}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid SNORT_GROUP, it is null, '${SNORT_GROUP}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that BASE_NAME is not null. # #################################################################### if [ -z "${BASE_NAME}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid BASE_NAME, it is null, '${BASE_NAME}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that PROXY -- not necessary? # #################################################################### #################################################################### # # Validate that DOWNLOAD_URL is not null. # #################################################################### if [ -z "${DOWNLOAD_URL}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid DOWNLOAD_URL, it is null, '${DOWNLOAD_URL}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set the origin download location (directory) and create the # directory if it does not exist. # #################################################################### DEVEL_DIR="${ROOT_DIR}/development" MASTER_DIR="${DEVEL_DIR}/master" COPY_1="${MASTER_DIR}/${BASE_NAME}.1" ORIGIN_DIR="${MASTER_DIR}/${BASE_NAME}.0" if [ ! -d "${ORIGIN_DIR}" ]; then mkdir -p "${ORIGIN_DIR}" fi #################################################################### # # Set the proxy if PROXY is not null. # #################################################################### if [ ! -z "${PROXY}" ]; then http_proxy=${PROXY} export http_proxy fi #################################################################### # # Download the latest rule set. # #################################################################### oinkmaster -o ${ORIGIN_DIR} -Q -u ${DOWNLOAD_URL} #################################################################### # # If there is # #################################################################### if [ ! -d "${COPY_1}" ]; then PushDirectory ${MASTER_DIR} ${BASE_NAME} ${COPY_COUNT} if [ $? -eq 0 ]; then cd "${MASTER_DIR}" && \ rm -rf ${BASE_NAME} && \ cp -rfp ${BASE_NAME}.1 ${BASE_NAME} && \ tar czf ${BASE_NAME}.1.tgz ${BASE_NAME} && \ rm -rf ${BASE_NAME} fi else diff -r ${ORIGIN_DIR} ${COPY_1} if [ $? -ne 0 -a $? -ne 2 ]; then PushDirectory ${MASTER_DIR} ${BASE_NAME} ${COPY_COUNT} if [ $? -eq 0 ]; then cd "${MASTER_DIR}" && \ rm -rf ${BASE_NAME} && \ cp -rfp ${BASE_NAME}.1 ${BASE_NAME} && \ tar czf ${BASE_NAME}.1.tgz ${BASE_NAME} && \ rm -rf ${BASE_NAME} fi fi fi ###################################################################### # # Set appropriate ownship and permissions # ###################################################################### find ${DEVEL_DIR} -type d -exec chmod 770 {} \; find ${DEVEL_DIR} -type f -exec chmod 660 {} \; chown -R 0:${SNORT_GROUP} ${DEVEL_DIR} UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_update_master_rules --- Appendix 2 -- sm_update_master_rules_pt --- sm_update_master_rules_pt --- #!/bin/sh # # NAME # sm_update_master_rules_pt - Email on rule set changes # # SYNOPSIS # sm_update_master_rules_pt [-l email-list] -r rdy-file # # DESCRIPTION # The sm_update_master_rules_pt program sends an alert email # to the email-list if the snort rules set has changed. # # OPTIONS # [-l email-list] # Specifies a list of email addresses to send emails to if # the rule sets have changed. The default value is # 'root@localhost'. # # -r rdy-file # Specifies the WebJob ready file. # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} [-l email-list] -r rdy-file" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### EMAIL_LIST="root@localhost" RDY_FILE="" while getopts "l:r:" OPTION ; do case "${OPTION}" in l) EMAIL_LIST="${OPTARG}" ;; r) RDY_FILE="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that EMAIL_LIST is not null and each address looks # valid. # #################################################################### if [ -z "${EMAIL_LIST}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid EMAIL_LIST, it is null, '${EMAIL_LIST}'." 1>&2 echo 1>&2 Usage fi ERROR_FLAG=1 for EMAIL in ${EMAIL_LIST}; do echo "${EMAIL}" | grep "^[0-9a-zA-Z\._-][[0-9a-zA-Z\._-]*@[0-9a-zA-Z\.][0-9a-zA-Z\.]*$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: Invalid email address, '${EMAIL}'" 1>&2 ERROR_FLAG=0 fi done if [ ${ERROR_FLAG} -eq 0 ]; then Usage fi #################################################################### # # Validate that RDY_FILE is not null and exists. # #################################################################### if [ -z "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RDY_FILE, it is null, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi if [ ! -f "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: RDY_FILE does not exist, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set WebJob group variables. # #################################################################### ENV_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/env/'` ERR_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/err/'` OUT_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/out/'` #################################################################### # # If the out file is non-zero, send an email alert. # #################################################################### if [ -s "${OUT_FILE}" ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Master rule set has changed." ${EMAIL_LIST} fi if [ -s "${ERR_FILE}" ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Error with master rule set update." ${EMAIL_LIST} fi UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_update_master_rules_pt --- Appendix 3 -- sm_create_snort_env --- sm_create_snort_env --- #!/bin/sh # # NAME # sm_create_snort_env - Create snort runtime environment # # SYNOPSIS # sm_create_snort_env -d root-dir [-g snort-group] # [-n base-name] [-u rsync-user] # # DESCRIPTION # The sm_create_snort_env program uses oinkmaster to create # snort run time environments in a production location. # # OPTIONS # -d root-dir # Specifies the root directory to save copies of the snort # rule sets. # # [-g snort-group] # Specifies the snort group name to set the group # ownership of the development files and directories. The # default value is 'snort'. # # [-n base-name] # Specifies the base directory name where snort rule # copies are maintained. The default value is 'rules'. # # [-u rsync-user] # Specifies the rsync user to set the ownership of the # production environment files so that the rsync # operations are successful. The default value is # 'rsync'. # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # SnortEnabled # ###################################################################### SnortEnabled() { MY_CMD_CONF="$1" MY_FUNC_NAME="SnortEnabled" if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid , '${MY_CMD_CONF}'." >&2 fi grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} > /dev/null 2>&1 if [ $? -eq 0 ] ; then SWITCH=`grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} | sed -e 's/^SnortCommandEnable=\(.\).*$/\1/'` case ${SWITCH} in Y) return 0 ;; N) return 1 ;; *) echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}', '${SWITCH}'." >&2 return 2 ;; esac else echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}'." >&2 return 1 fi return 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} -d root-dir [-g snort-group] [-n base-name] [-u rsync-user]" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### ROOT_DIR="" SNORT_GROUP="snort" BASE_NAME="rules" RSYNC_USER="rsync" while getopts "d:g:n:u:" OPTION ; do case "${OPTION}" in d) ROOT_DIR="${OPTARG}" ;; g) SNORT_GROUP="${OPTARG}" ;; n) BASE_NAME="${OPTARG}" ;; u) RSYNC_USER="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that ROOT_DIR is not null and begins with a slash. # #################################################################### if [ -z "${ROOT_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, it is null, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${ROOT_DIR}" | grep "^/" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, does not begin with a slash, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that SNORT_GROUP is not null. # #################################################################### if [ -z "${SNORT_GROUP}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid SNORT_GROUP, it is null, '${SNORT_GROUP}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that BASE_NAME is not null. # #################################################################### if [ -z "${BASE_NAME}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid BASE_NAME, it is null, '${BASE_NAME}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that RSYNC_USER is not null. # #################################################################### if [ -z "${RSYNC_USER}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_USER, it is null, '${RSYNC_USER}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set global variables # #################################################################### DEVEL_DIR="${ROOT_DIR}/development" PROD_DIR="${ROOT_DIR}/production" ENV_DIR=${DEVEL_DIR}/environments OINKMASTER_CONF="oinkmaster.conf" OINKMASTER_CONF_LIST="" COPY_BASE="copy" MASTER_RULES_DIR="${DEVEL_DIR}/master/rules.1" MASTER_RULES_TGZ="${DEVEL_DIR}/master/rules.1.tgz" MASTER_RULES_URL="file://${MASTER_RULES_TGZ}" SENSOR_LIST=`ls -l ${ENV_DIR} 2> /dev/null | grep "^d" | awk '{print $NF}' | grep -v "^etc$"` TOUCH_FILE="${ROOT_DIR}/${PROGRAM}_done" if [ ! -f "${MASTER_RULES_TGZ}" ]; then echo 1>&2 echo "${PROGRAM}: Could not find the master rules tarball, '${MASTER_RULES_TGZ}'." 1>&2 echo 1>&2 Usage fi ###################################################################### # # If TOUCH_FILE is present, set the NEWER_DEVEL to the number of files # that are newer then the touch file. If this count is greater than # zero, then all production environments are rebuilt, otherwise the # SENSOR_LIST is nulled out so that the environments are not rebuilt. # If the TOUCH_FILE is not present, then all production environments # will be rebuilt without checking for newer files. # ###################################################################### if [ -f "${TOUCH_FILE}" ]; then NEWER_DEVEL=`find ${DEVEL_DIR} -newer ${TOUCH_FILE} | wc -l | awk '{print $1}'` if [ ${NEWER_DEVEL} -eq 0 ]; then SENSOR_LIST="" fi fi ###################################################################### # # For each sensor in SENSOR_LIST, rebuild the snort production # environment for each snort instance defined for that sensor. # ###################################################################### for SENSOR in ${SENSOR_LIST}; do #################################################################### # # Set SENSOR_DIR to the appropriate directory in the development # environment. This is used as a basis for other variables to # follow. # #################################################################### SENSOR_DIR="${ENV_DIR}/${SENSOR}" #################################################################### # # Set INST_LIST to the list of instances for this sensor. # #################################################################### INST_LIST=`ls -l ${SENSOR_DIR} 2> /dev/null | grep "^d" | awk '{print $NF}' | grep -v "^etc$"` #################################################################### # # For each snort instance in INST_LIST, rebuilt the snort # production environment as appropriate. # #################################################################### for INST in ${INST_LIST}; do INST_DIR="${SENSOR_DIR}/${INST}" DEV_CONF="${INST_DIR}/etc/command.conf" ################################################################## # # If snort is disabled, continue to the next instance # ################################################################## SnortEnabled "${DEV_CONF}" if [ $? -ne 0 ]; then continue fi ################################################################## # # Gather oinkmaster.conf configuration files # ################################################################## OINKMASTER_CONF_LIST="" FILE_LIST="\ ${ENV_DIR}/etc/${OINKMASTER_CONF} \ ${ENV_DIR}/${SENSOR}/etc/${OINKMASTER_CONF} \ ${ENV_DIR}/${SENSOR}/${INST}/etc/${OINKMASTER_CONF} \ " for FILE in ${FILE_LIST}; do if [ -f "${FILE}" ]; then OINKMASTER_CONF_LIST="${OINKMASTER_CONF_LIST} -C ${FILE}" fi done if [ -z "${OINKMASTER_CONF_LIST}" ]; then echo "${PROGRAM}: Warning: Did not find any ${OINKMASTER_CONF} files, skipping ${SENSOR} - ${INST}." 1>&2 continue fi ################################################################## # # Create output directories # ################################################################## OUT_INST_DIR="${PROD_DIR}/${SENSOR}/${INST}/${COPY_BASE}.0" OUT_ETC_DIR="${OUT_INST_DIR}/etc" OUT_RULES_DIR="${OUT_INST_DIR}/${BASE_NAME}" if [ ! -d "${OUT_ETC_DIR}" ]; then mkdir -p "${OUT_ETC_DIR}" fi if [ ! -d "${OUT_RULES_DIR}" ]; then mkdir -p "${OUT_RULES_DIR}" fi ################################################################## # # Create production rule sets using oinkmaster and the # oinmaster configuration files. # ################################################################## oinkmaster -o ${OUT_RULES_DIR} ${OINKMASTER_CONF_LIST} -Q -u ${MASTER_RULES_URL} ################################################################## # # Copy configuration files into the etc destination, where # lower-level configuration files trump higher-level # configuration files. So, the lower-level configuration files # are copied last. # ################################################################## ETC_DIR_LIST="${ENV_DIR}/etc ${ENV_DIR}/${SENSOR}/etc ${ENV_DIR}/${SENSOR}/${INST}/etc" for ETC_DIR in ${ETC_DIR_LIST}; do ls ${ETC_DIR}/* > /dev/null 2>&1 if [ $? -eq 0 ]; then cp ${ETC_DIR}/* ${OUT_ETC_DIR} fi done ################################################################## # # FIXME! Planned implementation. # # Copy program into the bin destination, where lower-level # program files trump higher-level program files. So, the # lower-level configuration files are copied last. # ################################################################## ################################################################## # # Copy custom rules files into the rules destination. # ################################################################## ls ${ENV_DIR}/${SENSOR}/${INST}/rules/* > /dev/null 2>&1 if [ $? -eq 0 ]; then cp ${ENV_DIR}/${SENSOR}/${INST}/rules/* ${OUT_RULES_DIR} fi done done ###################################################################### # # Set appropriate ownership and permissions # ###################################################################### find ${PROD_DIR} -type d -exec chmod 550 {} \; find ${PROD_DIR} -type f -exec chmod 440 {} \; chown -R ${RSYNC_USER}:${SNORT_GROUP} ${PROD_DIR} ###################################################################### # # Retouch the touch file # ###################################################################### touch ${TOUCH_FILE} ###################################################################### # # Unlock the program and exit # ###################################################################### UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_create_snort_env --- Appendix 4 -- sm_create_snort_env_pt --- sm_create_snort_env_pt --- #!/bin/sh # # NAME # sm_create_snort_env_pt - Email on environment change/error # # SYNOPSIS # sm_create_snort_env_pt [-l email-list] -r rdy-file # # DESCRIPTION # The sm_create_snort_env_pt program sends an alert email # to the email-list if the snort environment has changed. # # OPTIONS # [-l email-list] # Specifies a list of email addresses to send emails to if # the rule sets have changed. The default value is # 'root@localhost'. # # -r rdy-file # Specifies the WebJob ready file. # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} [-l email-list] -r rdy-file" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### EMAIL_LIST="root@localhost" RDY_FILE="" while getopts "l:r:" OPTION ; do case "${OPTION}" in l) EMAIL_LIST="${OPTARG}" ;; r) RDY_FILE="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that EMAIL_LIST is not null and each address looks # valid. # #################################################################### if [ -z "${EMAIL_LIST}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid EMAIL_LIST, it is null, '${EMAIL_LIST}'." 1>&2 echo 1>&2 Usage fi ERROR_FLAG=1 for EMAIL in ${EMAIL_LIST}; do echo "${EMAIL}" | grep "^[0-9a-zA-Z\._-][[0-9a-zA-Z\._-]*@[0-9a-zA-Z\.][0-9a-zA-Z\.]*$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: Invalid email address, '${EMAIL}'" 1>&2 ERROR_FLAG=0 fi done if [ ${ERROR_FLAG} -eq 0 ]; then Usage fi #################################################################### # # Validate that RDY_FILE is not null and exists. # #################################################################### if [ -z "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RDY_FILE, it is null, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi if [ ! -f "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: RDY_FILE does not exist, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set WebJob group variables. # #################################################################### ENV_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/env/'` ERR_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/err/'` OUT_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/out/'` #################################################################### # # If the out file is non-zero, send an email alert. # #################################################################### if [ -s "${OUT_FILE}" ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Created new snort environments." ${EMAIL_LIST} fi if [ -s "${ERR_FILE}" ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Error creating new snort environments." ${EMAIL_LIST} fi UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_create_snort_env_pt --- Appendix 5 -- sm_deploy_snort_env --- sm_deploy_snort_env --- #!/bin/sh # # NAME # sm_deploy_snort_env - Rsync & test new snort environment # # SYNOPSIS # sm_deploy_snort_env -c webjob-cfg [-C copy-count] # -H rsync-host -h webjob-home -k rsync-key-pad # [-l rsync-bandwidth-limit] -r root-dir -s rsync-src-dir # -u rsync-user # # DESCRIPTION # The sm_deploy_snort_env program performs three major # functions. First, it rsync's snort configuration files from # a WebJob server to the local system. Next, if a change in # the configuration occured, it tests the validity of the new # configuration by launching snort commands contained in the # command.conf file, substituting the deamon option, '-D', # with the test option, '-T'. Lastly, if the new # configuration is valid, sm_deploy_snort_env will "push" the # new environment into a production area. # # OPTIONS # -c webjob-cfg # Specifies the WebJob configuration file to use for # downloading the SSH key and then execting the rsync # command. If executing this script within the WebJob # framework, it is recommended that the user specify a # WebJob configuration file that does not contain the # URLPutURL control. That way, all output from the # PaD (rsync) command will be captured and uploaded to # the WebJob server making it easier to debug jobs. # # [-C copy-count] # Specifies the number of snort environments to keep per # snort instance, where an environment is defined as the # configuration files contained in the etc directory and # the rules files contained in the rules directory. # Having multiple snort environment copies facilitates # robustness. For example, other scripts can attempt to # launch snort on the first copy, then failover to the # second copy, then failover to the third copy, and so on. # The allowable range is from 2 through 9 with the default # value of 2. # # -H rsync-host # Specifies the rsync host name or IP address to # retrieve rsync data from, in this case the snort # configuration server. # # -h webjob-home # Specifies the home directory for the WebJob server. # # -k rsync-key-pad # Specifies the rsync (SSH) key PaD filename (e.g., # "webjob_rsync_id_snort.pad"). # # [-l rsync-bandwidth-limit] # Specifies the maximum transfer rate in kilobytes per # second. A value of zero specifies no bandwidth limit. # The default value is '0'. # # -r root-dir # Specifies the destination directory on the sensor # where the data will be rsync'ed to. This represents # the folder containing the configuration files for # each instance of Snort on the sensor. # # -s rsync-src-dir # Specifies the source directory on the client to # place rsync data. # # -u rsync-user # Specifies the username for the SSH tunnel. # # # AUTHOR # Andy Bair and Matt Burton # ###################################################################### IFS=' ' PATH=/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin:/ids/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # TestSnort # ###################################################################### TestSnort() { MY_DIR="$1" MY_CMD_CONF="$2" MY_FUNC_NAME="TestSnort" if [ ! -d "${MY_DIR}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Directory does not exist, '${MY_DIR}'" 1>&2 return 1 fi cd ${MY_DIR} if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- command.conf does not exist '${MY_CMD_CONF}'" 1>&2 return 1 fi CMD_COUNT=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | wc -l | awk '{print $1}'` if [ ${CMD_COUNT} -eq 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Did not find SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi if [ ${CMD_COUNT} -gt 1 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Found more than one SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi SNORT_CMD_PRODUCTION=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | sed 's/^SnortCommandLine=[ ]*\(.*\)[ ]*$/\1/'` echo "${SNORT_CMD_PRODUCTION}" | grep "\-D" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort command does not have '-D' switch: '${SNORT_CMD_PRODUCTION}'" return 1 fi SNORT_CMD_TEST=`echo "${SNORT_CMD_PRODUCTION}" | sed -e "s/-D/-T/"` echo "${PROGRAM}: ${MY_FUNC_NAME} -- Launching test snort: '${SNORT_CMD_TEST}'" ${SNORT_CMD_TEST} SNORT_RETURN_CODE=$? if [ ${SNORT_RETURN_CODE} -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort failed to execute properly, '${SNORT_CMD_TEST}'." >&2 return 1 else return 0 fi return 1 } ###################################################################### # # PushDirectory # ###################################################################### PushDirectory() { MY_DIR_ROOT=$1 MY_DIR_BASE=$2 MY_MAX_COUNT=$3 MY_FUNC_NAME="PushDirectory" #################################################################### # # Validate arguments # #################################################################### if [ ! -d "${MY_DIR_ROOT}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- is not a directory, '${MY_DIR_ROOT}'." >&2 return 1 fi if [ -z "${MY_DIR_BASE}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- is not a directory, '${MY_DIR_BASE}'." >&2 return 1 fi echo "${MY_MAX_COUNT}" | grep "^[2-9]$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid , must be in range 2-9, '${MAX_COUNT}'." >&2 return 1 fi #################################################################### # # Push new directory. # #################################################################### COPY_NUM_HI=${MY_MAX_COUNT} COPY_NUM_LO=`expr ${COPY_NUM_HI} - 1` while [ ${COPY_NUM_HI} -gt 0 ]; do DIR_HI="${MY_DIR_ROOT}/${MY_DIR_BASE}.${COPY_NUM_HI}" DIR_LO="${MY_DIR_ROOT}/${MY_DIR_BASE}.${COPY_NUM_LO}" if [ ${COPY_NUM_LO} -eq 0 ]; then rm -rf "${DIR_HI}" cp -rfp "${DIR_LO}" "${DIR_HI}" else if [ -d "${DIR_LO}" ]; then if [ -d "${DIR_HI}" ]; then rm -rf "${DIR_HI}" fi mv "${DIR_LO}" "${DIR_HI}" fi fi COPY_NUM_HI=`expr ${COPY_NUM_HI} - 1` COPY_NUM_LO=`expr ${COPY_NUM_HI} - 1` done return 0 } ###################################################################### # # SnortEnabled # ###################################################################### SnortEnabled() { MY_CMD_CONF="$1" MY_FUNC_NAME="SnortEnabled" if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid , '${MY_CMD_CONF}'." >&2 fi grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} > /dev/null 2>&1 if [ $? -eq 0 ] ; then SWITCH=`grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} | sed -e 's/^SnortCommandEnable=\(.\).*$/\1/'` case ${SWITCH} in Y) return 0 ;; N) return 1 ;; *) echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}', '${SWITCH}'." >&2 return 2 ;; esac else echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}'." >&2 return 1 fi return 1 } ###################################################################### # # SetOwnership # ###################################################################### SetOwnership() { find ${ROOT_DIR} -type d -exec chmod 550 {} \; find ${ROOT_DIR} -type f -exec chmod 440 {} \; chown -R 0:0 ${ROOT_DIR} } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} options" 1>&2 echo " -c webjob-cfg" 1>&2 echo " [-C copy-count]" 1>&2 echo " -H rsync-host" 1>&2 echo " -h webjob-home" 1>&2 echo " -k rsync-key-pad" 1>&2 echo " [-l rsync-bandwidth-limit]" 1>&2 echo " -r root-dir" 1>&2 echo " -s rsync-src-dir" 1>&2 echo " -u rsync-user" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### Main() { MY_FUNC_NAME="Main" LockProgram ${PROGRAM} ${LOCKFILE} 0 #################################################################### # # Set Variables # #################################################################### COPY_COUNT="2" ROOT_DIR="" RSYNC_BANDWIDTH_LIMIT="0" RSYNC_HOST="" RSYNC_KEY_PAD="" RSYNC_SRC_DIR="" RSYNC_USER="" WEBJOB_CFG="" WEBJOB_HOME="" #################################################################### # # Collect arguments. # #################################################################### while getopts "c:C:H:h:k:l:r:s:u:" OPTION ; do case "${OPTION}" in c) WEBJOB_CFG="${OPTARG}" ;; C) COPY_COUNT="${OPTARG}" ;; H) RSYNC_HOST="${OPTARG}" ;; h) WEBJOB_HOME="${OPTARG}" ;; k) RSYNC_KEY_PAD="${OPTARG}" ;; l) RSYNC_BANDWIDTH_LIMIT="${OPTARG}" ;; r) ROOT_DIR="${OPTARG}" ;; s) RSYNC_SRC_DIR="${OPTARG}" ;; u) RSYNC_USER="${OPTARG}" ;; *) echo 1>&2 echo "${PROGRAM}: Invalid option, \'${OPTION}\'." 1>&2 echo 1>&2 Usage ;; esac done if [ ${OPTIND} -le $# ] ; then Usage fi #################################################################### # # Validate that WEBJOB_HOME is not null and is a directory. # #################################################################### if [ -z "${WEBJOB_HOME}" -o ! -d "${WEBJOB_HOME}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid WEBJOB_HOME, '${WEBJOB_HOME}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that WEBJOB_CFG is not null. Then check to see if it # is a valid file. If it is not a valid file, prepend WEBJOB_HOME # and check to see if it is a valid file again. If it is not, # issue error and exit. This enables users to specify the full # path to a configuration file or just the configuration file # name. # #################################################################### if [ -z "${WEBJOB_CFG}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid WEBJOB_CFG, it is null." 1>&2 echo 1>&2 Usage fi if [ ! -f "${WEBJOB_CFG}" ]; then WEBJOB_CFG="${WEBJOB_HOME}/etc/${WEBJOB_CFG}" fi if [ ! -f "${WEBJOB_CFG}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid WEBJOB_CFG" 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that COPY_COUNT is not null, and is a number in # the range 2-9. # #################################################################### if [ -z "${COPY_COUNT}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it is null." 1>&2 echo 1>&2 Usage fi echo "${COPY_COUNT}" | grep "^[2-9]$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it must be from 2-9, '${COPY_COUNT}'" 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that ROOT_DIR is not null and begins with a slash. # #################################################################### if [ -z "${ROOT_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, it is null, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${ROOT_DIR}" | grep "^/" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, does not begin with a slash, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${ROOT_DIR}" | grep "/$" > /dev/null 2>&1 if [ $? -ne 0 ]; then ROOT_DIR="${ROOT_DIR}/" fi if [ ! -d "${ROOT_DIR}" ]; then mkdir -p ${ROOT_DIR} fi #################################################################### # # Validate that RSYNC_HOST is not null. # #################################################################### if [ -z "${RSYNC_HOST}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_HOST, it is null, '${RSYNC_HOST}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that RSYNC_KEY_PAD is not null and ends with the # '.pad' extension. # #################################################################### if [ -z "${RSYNC_KEY_PAD}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_KEY_PAD, '${RSYNC_KEY_PAD}'." 1>&2 echo 1>&2 Usage fi echo "${RSYNC_KEY_PAD}" | grep ".pad$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_KEY_PAD, it must end with .pad extension, '${RSYNC_KEY_PAD}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that RSYNC_BANDWIDTH_LIMIT is not null and is a number # greater than or equal to zero. # #################################################################### if [ -z "${RSYNC_BANDWIDTH_LIMIT}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_BANDWIDTH_LIMIT, '${RSYNC_BANDWIDTH_LIMIT}'." 1>&2 echo 1>&2 Usage fi if [ ${RSYNC_BANDWIDTH_LIMIT} -lt 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_BANDWIDTH_LIMIT, it be >= 0, '${RSYNC_BANDWIDTH_LIMIT}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that RSYNC_SRC_DIR is not null and begins with a slash. # #################################################################### if [ -z "${RSYNC_SRC_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_SRC_DIR, '${RSYNC_SRC_DIR}', it is null." 1>&2 echo 1>&2 Usage fi echo "${RSYNC_SRC_DIR}" | grep "^/" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_SRC_DIR, does not begin with a slash, '${RSYNC_SRC_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${RSYNC_SRC_DIR}" | grep "/$" > /dev/null 2>&1 if [ $? -ne 0 ]; then RSYNC_SRC_DIR="${RSYNC_SRC_DIR}/" fi #################################################################### # # Validate that RSYNC_USER is not null. # #################################################################### if [ -z "${RSYNC_USER}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RSYNC_USER, it is null, '${RSYNC_USER}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Set and verify WEBJOB_BIN, webjob binary variable. # #################################################################### WEBJOB_BIN="${WEBJOB_HOME}/bin/webjob" if [ -z "${WEBJOB_BIN}" -o ! -f "${WEBJOB_BIN}" ]; then echo 1>&2 echo "${PROGRAM}: Can not find webjob binary, '${WEBJOB_BIN}'." 1>&2 echo 1>&2 fi #################################################################### # # Setup program variables # #################################################################### COPY_BASE="copy" NEW_NAME="${COPY_BASE}.0" OLD_NAME="${COPY_BASE}.1" RDY_NAME="${OLD_NAME}.rdy" #################################################################### # # Transfer snort configuration files to sensor. Rsync is used # with the following options explained below. # # --delete # Deletes files from the server so when you remove a snort # environment on the server, it trickles down to the sensor. # # --exclude ${COPY_BASE}.[1-9]/ # Excludes deletion of the production directories (copy.[1-9]) # so that diff can compare the test (copy.0) and production # directories. This diff is used to test and optionally move # the new environment into a production location. # # --exclude /${RDY_NAME} # Excludes deletion of the ready file which is cleaned by # another script. # #################################################################### RSYNC_SRC="${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_SRC_DIR}" ${WEBJOB_BIN} -e -f ${WEBJOB_CFG} "${RSYNC_KEY_PAD}" \ "chmod 600 PAYLOAD && \ rsync --bwlimit=${RSYNC_BANDWIDTH_LIMIT} --delete --exclude ${COPY_BASE}.[1-9]/ --exclude ${RDY_NAME} -ave \"ssh -i PAYLOAD -o BatchMode=yes -o StrictHostKeyChecking=no\" ${RSYNC_SRC} ${ROOT_DIR}" RETURN_CODE=$? if [ ${RETURN_CODE} -ne 0 ]; then echo "${PROGRAM}: WebJob rsync error." 1>&2 UnlockProgram ${PROGRAM} ${LOCKFILE} 0 fi #################################################################### # # Set appropriate ownship and permissions # #################################################################### SetOwnership #################################################################### # # Check for new instances of snort. # #################################################################### INSTANCE_LIST=`ls -l ${ROOT_DIR} 2>/dev/null | grep "^d" | awk '{print $NF}' | xargs` if [ -z "${INSTANCE_LIST}" ]; then echo "${PROGRAM}: The list of snort instances is zero." else echo "${PROGRAM}: Snort instance list: ${INSTANCE_LIST}" fi #################################################################### # # Verify snort will execute each instance properly. Perform # these tests for each instance: # # copy.0 present n--> remove instance # # y # | # v # # diff detected n--> msg: no changes # # y # | # v # # test ok (copy.0) n--> msg: warning # # y # | # v # # push ok n--> msg: warning # # y # | # v # # snort enabled n--> continue # # y # | # v # # touch ready # #################################################################### for INSTANCE in ${INSTANCE_LIST}; do INST_DIR="${ROOT_DIR}/${INSTANCE}" NEW_CONF="${INST_DIR}/${NEW_NAME}/etc/command.conf" OLD_CONF="${INST_DIR}/${OLD_NAME}/etc/command.conf" NEW_DIR="${INST_DIR}/${NEW_NAME}" OLD_DIR="${INST_DIR}/${OLD_NAME}" RDY_FILE="${INST_DIR}/${RDY_NAME}" if [ ! -d "${NEW_DIR}" ]; then echo "${PROGRAM}: Warning -- directory does not exist, '${NEW_DIR}'" 1>&2 echo "${PROGRAM}: Removing instance='${INSTANCE}'" 1>&2 rm -rf ${INST_DIR} continue fi diff -r "${NEW_DIR}" "${OLD_DIR}" if [ $? -ne 0 ]; then TestSnort "${NEW_DIR}" "${NEW_CONF}" if [ $? -eq 0 ]; then PushDirectory "${INST_DIR}" "${COPY_BASE}" "${COPY_COUNT}" if [ $? -eq 0 ]; then SnortEnabled "${OLD_CONF}" if [ $? -eq 0 ]; then touch -f ${RDY_FILE} echo "${PROGRAM}: Change ready for production, snort instance='${INSTANCE}'" else rm -f ${RDY_FILE} fi else echo "${PROGRAM}: Warning -- Directory push failed for snort instance=${INSTANCE}" 1>&2 fi else ################################################################ # # Print an error message if the snort test failed. # # NOTE: The put trigger script uses the "Test failed" string # in the error file to send an error email message. If # the output message below is changed, the grep statement # put trigger script must also be changed to reflect the # different output statement. # ################################################################ echo "${PROGRAM}: Warning -- Test failed for snort instance=${INSTANCE}" 1>&2 fi ################################################################ # # Set appropriate ownship and permissions # ################################################################ SetOwnership else echo "${PROGRAM}: No changes for snort instance=${INSTANCE}" fi done UnlockProgram ${PROGRAM} ${LOCKFILE} 0 } Main "$@" --- sm_deploy_snort_env --- Appendix 6 -- sm_deploy_snort_env_pt --- sm_deploy_snort_env_pt --- #!/bin/sh # # NAME # sm_deploy_snort_env_pt - Email on snort deployment errors # # SYNOPSIS # sm_deploy_snort_env_pt -c client-id [-l email-list] -r rdy-file # # DESCRIPTION # The sm_deploy_snort_env_pt program sends an alert email # to the email-list if there are errors with the snort # environment deployment. # # OPTIONS # -c client-id # Specifies the uploading client ID. # # [-l email-list] # Specifies a list of email addresses to send emails to if # the rule sets have changed. The default value is # 'root@localhost'. # # -r rdy-file # Specifies the WebJob ready file. # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} -c client-id [-l email-list] -r rdy-file" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### CLIENT_ID="" EMAIL_LIST="root@localhost" RDY_FILE="" while getopts "c:l:r:" OPTION ; do case "${OPTION}" in c) CLIENT_ID="${OPTARG}" ;; l) EMAIL_LIST="${OPTARG}" ;; r) RDY_FILE="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that CLIENT_ID is not null. # #################################################################### if [ -z "${CLIENT_ID}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid CLIENT_ID, it is null, '${CLIENT_ID}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that EMAIL_LIST is not null and each address looks # valid. # #################################################################### if [ -z "${EMAIL_LIST}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid EMAIL_LIST, it is null, '${EMAIL_LIST}'." 1>&2 echo 1>&2 Usage fi ERROR_FLAG=1 for EMAIL in ${EMAIL_LIST}; do echo "${EMAIL}" | grep "^[0-9a-zA-Z\._-][[0-9a-zA-Z\._-]*@[0-9a-zA-Z\.][0-9a-zA-Z\.]*$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: Invalid email address, '${EMAIL}'" 1>&2 ERROR_FLAG=0 fi done if [ ${ERROR_FLAG} -eq 0 ]; then Usage fi #################################################################### # # Validate that RDY_FILE is not null and exists. # #################################################################### if [ -z "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RDY_FILE, it is null, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi if [ ! -f "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: RDY_FILE does not exist, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set WebJob group variables. # #################################################################### ENV_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/env/'` ERR_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/err/'` OUT_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/out/'` #################################################################### # # If the error file contains errors, send an email alert. # #################################################################### grep "^\(ERROR: \|rsync error: \|.*WebJob rsync error\|.*Test failed\)" ${ERR_FILE} > /dev/null 2>&1 if [ $? -eq 0 ]; then ( echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Deployment error on ${CLIENT_ID}." ${EMAIL_LIST} else ################################################################## # # If the error file contains and erroneous diff statement, # send an email alert indicating this is the initial deployment. # ################################################################## grep "^diff:[ ].*:[ ]No such file or directory" ${ERR_FILE} > /dev/null 2>&1 if [ $? -eq 0 ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Initial deployment on ${CLIENT_ID}." ${EMAIL_LIST} else ################################################################## # # If the output file contains differences, send an email alert. # ################################################################## grep "^[><] " ${OUT_FILE} > /dev/null 2>&1 if [ $? -eq 0 ]; then ( echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Deployed change on ${CLIENT_ID}." ${EMAIL_LIST} fi fi fi UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_deploy_snort_env_pt --- Appendix 7 -- sm_launch_snort --- sm_launch_snort --- #!/bin/sh # # NAME # sm_launch_snort - conditionally test and execute snort # # SYNOPSIS # sm_launch_snort [-C copy-count] -r root-dir # # DESCRIPTION # The sm_launch_snort program conditionally launches snort if # the number of snort instances defined does not equal the # number of snort instances running or if there is a change to # the snort environment. # # When launching snort processes, sm_launch_snort loops over # every snort instance defined on a machine, tests the snort # environment, then launches if the test is successful. If # the test is unsucessful, sm_launch_snort tries an older copy of # the snort environment. This continues until either snort is # launched successfully or the number of snort environment # copies is exhausted. # # OPTIONS # [-C copy-count] # Specifies the number of snort environments to keep per # snort instance, where an environment is defined as the # configuration files contained in the etc directory and # the rules files contained in the rules directory. # Having multiple snort environment copies facilitates # robustness. For example, other scripts can attempt to # launch snort on the first copy, then failover to the # second copy, then failover to the third copy, and so on. # The allowable range is from 2 through 9 with the default # value of 2. # # -r root-dir # Specifies the destination directory on the sensor # where the data will be rsync'ed to. This represents # the folder containing the configuration files for # each instance of Snort on the sensor. # # AUTHOR # Andy Bair # ###################################################################### IFS=' ' PATH=/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin:/ids/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # LaunchSnort # ###################################################################### LaunchSnort() { MY_DIR="$1" MY_CMD_CONF="$2" MY_FUNC_NAME="LaunchSnort" if [ ! -d "${MY_DIR}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Directory does not exist, '${MY_DIR}'" 1>&2 return 1 fi cd ${MY_DIR} if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- command.conf does not exist '${MY_CMD_CONF}'" 1>&2 return 1 fi CMD_COUNT=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | wc -l | awk '{print $1}'` if [ ${CMD_COUNT} -eq 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Did not find SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi if [ ${CMD_COUNT} -gt 1 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Found more than one SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi SNORT_CMD_PRODUCTION=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | sed 's/^SnortCommandLine=[ ]*\(.*\)[ ]*$/\1/'` echo "${SNORT_CMD_PRODUCTION}" | grep "\-D" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort command does not have '-D' switch: '${SNORT_CMD_PRODUCTION}'" return 1 fi echo "${PROGRAM}: ${MY_FUNC_NAME} -- Launching snort: '${SNORT_CMD_PRODUCTION}'" ${SNORT_CMD_PRODUCTION} SNORT_RETURN_CODE=$? if [ ${SNORT_RETURN_CODE} -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort failed to execute properly, '${SNORT_CMD_PRODUCTION}'." >&2 return 1 else return 0 fi return 1 } ###################################################################### # # ReapProcessByPid # ###################################################################### ReapProcessByPid() { PID="$1" #################################################################### # # If we have a pid, kill the process. # #################################################################### ps -p "${PID}" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: Pid no longer exists, PID='${PID}'" return 0 else kill -9 "${PID}" && \ echo "${PROGRAM}: Killed PID='${PID}'" && \ return 0 fi return 1 } ###################################################################### # # SnortEnabled # ###################################################################### SnortEnabled() { MY_CMD_CONF="$1" MY_FUNC_NAME="SnortEnabled" if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid , '${MY_CMD_CONF}'." >&2 fi grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} > /dev/null 2>&1 if [ $? -eq 0 ] ; then SWITCH=`grep "^SnortCommandEnable=[YN][ ]*$" ${MY_CMD_CONF} | sed -e 's/^SnortCommandEnable=\(.\).*$/\1/'` case ${SWITCH} in Y) return 0 ;; N) return 1 ;; *) echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}', '${SWITCH}'." >&2 return 2 ;; esac else echo "${PROGRAM}: ${MY_FUNC_NAME} -- Invalid SnortCommandEnable switch, '${MY_CMD_CONF}'." >&2 return 1 fi return 1 } ###################################################################### # # TestSnort # ###################################################################### TestSnort() { MY_DIR="$1" MY_CMD_CONF="$2" MY_FUNC_NAME="TestSnort" if [ ! -d "${MY_DIR}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Directory does not exist, '${MY_DIR}'" 1>&2 return 1 fi cd ${MY_DIR} if [ ! -f "${MY_CMD_CONF}" ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- command.conf does not exist '${MY_CMD_CONF}'" 1>&2 return 1 fi CMD_COUNT=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | wc -l | awk '{print $1}'` if [ ${CMD_COUNT} -eq 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Did not find SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi if [ ${CMD_COUNT} -gt 1 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Found more than one SnortCommandLine control in '${MY_CMD_CONF}'." >&2 return 1; fi SNORT_CMD_PRODUCTION=`grep "^SnortCommandLine=.*" ${MY_CMD_CONF} 2>/dev/null | sed 's/^SnortCommandLine=[ ]*\(.*\)[ ]*$/\1/'` echo "${SNORT_CMD_PRODUCTION}" | grep "\-D" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort command does not have '-D' switch: '${SNORT_CMD_PRODUCTION}'" return 1 fi SNORT_CMD_TEST=`echo "${SNORT_CMD_PRODUCTION}" | sed -e "s/-D/-T/"` echo "${PROGRAM}: ${MY_FUNC_NAME} -- Launching test snort: '${SNORT_CMD_TEST}'" ${SNORT_CMD_TEST} SNORT_RETURN_CODE=$? if [ ${SNORT_RETURN_CODE} -ne 0 ]; then echo "${PROGRAM}: ${MY_FUNC_NAME} -- Snort failed to execute properly, '${SNORT_CMD_TEST}'." >&2 return 1 else return 0 fi return 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} [-C copy-count] -r root-dir" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### Main() { MY_FUNC_NAME="Main" LockProgram ${PROGRAM} ${LOCKFILE} 0 #################################################################### # # Set Variables # #################################################################### COPY_COUNT="2" ROOT_DIR="" #################################################################### # # Collect arguments. # #################################################################### while getopts "C:r:" OPTION ; do case "${OPTION}" in C) COPY_COUNT="${OPTARG}" ;; r) ROOT_DIR="${OPTARG}" ;; *) echo 1>&2 echo "${PROGRAM}: Invalid option, \'${OPTION}\'." 1>&2 echo 1>&2 Usage ;; esac done if [ ${OPTIND} -le $# ] ; then Usage fi #################################################################### # # Validate that COPY_COUNT is not null, and is a number in # the range 2-9. # #################################################################### if [ -z "${COPY_COUNT}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it is null." 1>&2 echo 1>&2 Usage fi echo "${COPY_COUNT}" | grep "^[2-9]$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid COPY_COUNT, it must be from 2-9, '${COPY_COUNT}'" 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that ROOT_DIR is not null and begins with a slash. # #################################################################### if [ -z "${ROOT_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, it is null, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi echo "${ROOT_DIR}" | grep "^/" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, does not begin with a slash, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi if [ ! -d "${ROOT_DIR}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid ROOT_DIR, directory does not exist, '${ROOT_DIR}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Setup program variables # #################################################################### RESTART="1" COPY_BASE="copy" CUR_NAME="${COPY_BASE}.1" RDY_NAME="${CUR_NAME}.rdy" #################################################################### # # Restart snort processes if one or more snort environments have # been updated (have a rdy file), or if the number of snort # processes running does not match the number of snort processes # specified to run via the command.conf files. # #################################################################### SNORT_COUNT_RUNNING=`ps -ef | egrep "(\/bin\/snort | snort )" | grep -v "grep" | wc -l | awk '{print $1}'` SNORT_COUNT_SPECIFIED=`find ${ROOT_DIR} -type f | grep "\/${COPY_BASE}\.1\/etc\/command.conf$" | xargs grep "^SnortCommandEnable=Y" | wc -l | awk '{print $1}'` RDY_FILE_COUNT=`find ${ROOT_DIR} | grep "${RDY_NAME}" | wc -l | awk '{print $1}'` if [ ${RDY_FILE_COUNT} -gt 0 ]; then echo "${PROGRAM}: Restarting snort processes, found update trigger file." RESTART=0 fi if [ ${SNORT_COUNT_RUNNING} -ne ${SNORT_COUNT_SPECIFIED} ]; then echo "${PROGRAM}: Restarting snort processes, running snorts (${SNORT_COUNT_RUNNING}) != specified snorts (${SNORT_COUNT_SPECIFIED})." RESTART=0 fi if [ ${RESTART} -eq 0 ]; then ################################################################## # # Collect pids for the currently running snort processes for # later killing. # ################################################################## PID_LIST=`ps -ef | egrep "(\/bin\/snort | snort )" | grep -v "grep" | awk '{print $2}'` ################################################################## # # Collect instance list # ################################################################## INSTANCE_LIST=`ls -l ${ROOT_DIR} 2>/dev/null | grep "^d" | awk '{print $NF}' | xargs` if [ -z "${INSTANCE_LIST}" ]; then echo "${PROGRAM}: The list of snort instances is zero." else echo "${PROGRAM}: Snort instance list: ${INSTANCE_LIST}" fi ################################################################## # # Iterate over each instance then each environment testing # snort until it runs successfully, then launch it. Use the # logic below. # # count=1..9 # copy.count # # copy.count exist n--> msg, increment count, continue # # y # | # v # # snort enabled n--> msg, increment count, continue # # y # | # v # # snort test ok n--> msg, increment count, continue # # y # | # v # # snort launch ok n--> msg, increment count, continue # # y # | # v # # set done to true # remove ready file # ################################################################## for INSTANCE in ${INSTANCE_LIST}; do COPY_NAME="${COPY_BASE}.1" COPY_DIR="${ROOT_DIR}/${INSTANCE}/${COPY_NAME}" CMD_CONF="${COPY_DIR}/etc/command.conf" RDY_FILE="${ROOT_DIR}/${INSTANCE}/${RDY_NAME}" if [ -d "${COPY_DIR}" ]; then SnortEnabled "${CMD_CONF}" if [ $? -eq 0 ]; then TestSnort "${COPY_DIR}" "${CMD_CONF}" if [ $? -eq 0 ]; then LaunchSnort "${COPY_DIR}" "${CMD_CONF}" if [ $? -eq 0 ]; then DONE=0 rm -f "${RDY_FILE}" else echo "${PROGRAM}: Warning -- Snort launch failed, '${COPY_DIR}'" fi else echo "${PROGRAM}: Warning -- Snort test filed, '${COPY_DIR}'" fi else echo "${PROGRAM}: Warning -- Snort copy disabled, '${COPY_DIR}'" fi else echo "${PROGRAM}: Warning -- Directory does not exist, '${COPY_DIR}'" fi done ################################################################## # # Reap old snort processes # ################################################################## for PID in ${PID_LIST}; do ReapProcessByPid "${PID}" done else echo "${PROGRAM}: All snort processes running as specified." fi CMD_CONF_LIST=`find ${ROOT_DIR} -type f | grep "\/${COPY_BASE}\.1\/etc\/command.conf$"` echo "InstanceList=Sensor|Instance|Interface|State" for CMD_CONF in ${CMD_CONF_LIST}; do DIR_1=`dirname ${CMD_CONF}` DIR_2=`dirname ${DIR_1}` DIR_3=`dirname ${DIR_2}` DIR_4=`dirname ${DIR_3}` INST=`basename ${DIR_3}` SENSOR=`basename ${DIR_4}` INT=`grep "^SnortCommandLine=" ${CMD_CONF} | sed -e 's/^SnortCommandLine=[ ]*//' -e 's/.*-i[ ][ ]*//' | awk '{print $1}'` if [ -z "${INT}" ]; then INT="-" fi STATE="-" grep "^SnortCommandEnable=[YN][ ]*$" ${CMD_CONF} > /dev/null 2>&1 if [ $? -eq 0 ] ; then SWITCH=`grep "^SnortCommandEnable=[YN][ ]*$" ${CMD_CONF} | sed -e 's/^SnortCommandEnable=\(.\).*$/\1/'` if [ "${SWITCH}" = "Y" ]; then STATE="enabled" else STATE="disabled" fi fi echo "InstanceList=${SENSOR}|${INST}|${INT}|${STATE}" done UnlockProgram ${PROGRAM} ${LOCKFILE} 0 } Main "$@" --- sm_launch_snort --- Appendix 8 -- sm_launch_snort_pt --- sm_launch_snort_pt --- #!/bin/sh # # NAME # sm_launch_snort_pt - Email on snort execution errors # # SYNOPSIS # sm_launch_snort_pt -c client-id [-l email-list] -r rdy-file # # DESCRIPTION # The sm_launch_snort_pt program sends an alert email # to the email-list if there are snort execution errors. # # OPTIONS # -c client-id # Specifies the uploading client ID. # # [-l email-list] # Specifies a list of email addresses to send emails to if # the rule sets have changed. The default value is # 'root@localhost'. # # -r rdy-file # Specifies the WebJob ready file. # # AUTHOR # Andy Bair # ###################################################################### ###################################################################### # # NAME # LockProgram - Create a lock file containing program PID # # SYNOPSIS # LockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The LockProgram function creates a lock file # containing this program's process ID (PID). If the # LOCKFILE does not exist, this program's PID is placed into # a new LOCKFILE. If the LOCKFILE does exist, then the # PID is extracted from the file to determine if that PID # is still running and if it is, exit. If the PID from the # lock file is not running, then the LOCKFILE is recreated # with this program's PID. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### LockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Locking..." fi if [ -f ${LOCKFILE} -a -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile exists, file=${LOCKFILE}" fi TEST_PID=`cat ${LOCKFILE}` TEST_PS=`ps -p ${TEST_PID} | wc -l | awk '{print $1}'` if [ ${TEST_PS} -eq 1 ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) does not exist, removing stale lockfile." fi rm -f ${LOCKFILE} if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: PID (${TEST_PID}) in lockfile (${LOCKFILE}) is still running, exiting." fi exit 1 fi else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Creating lockfile=${LOCKFILE}, containing pid=${PID}" fi echo "${PID}" > ${LOCKFILE} fi } ###################################################################### # # NAME # UnlockProgram - Remove a lock file containing program PID # # SYNOPSIS # UnlockProgram PROGRAM LOCKFILE VERBOSE # # DESCRIPTION # The unlockProgram function removes a lock file containing # this program's process ID (PID). If the LOCKFILE doesn't # exist, exit. If the LOCKFILE does exist but does not # contain this program's PID, exit. Otherwise, remove the # LOCKFILE. # # PARAMETERS # PROGRAM # This parameter is mandatory and specifies the name of this # program and is used in debugging messages. The acceptable # way to determine the program's name is to use the basename # command as shown below. # # PROGRAM=`basename "${0}"` # # LOCKFILE # This parameter is mandatory and specifies the full path # and name of the LOCKFILE. The name of the LOCKFILE # must not change from one script execution to the next. # Here is the suggested LOCKFILE to use. # # LOCKFILE="/tmp/${PROGRAM}.lck" # # VERBOSE # This parameter is optional and specifies wheather to # print debugging messages or not. By specifying '1', # debugging messages will be printed, otherwise they will # not be printed. # # AUTHOR # Andy Bair, July 2004. # ###################################################################### UnlockProgram(){ PROGRAM=${1} LOCKFILE=${2} if [ $# -lt 3 ]; then VERBOSE=0 else VERBOSE=${3} fi PID=$$ if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Unlocking..." fi if [ ! -f ${LOCKFILE} -o ! -s ${LOCKFILE} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile does not exists or is zero size, file=${LOCKFILE}" fi exit 1 else TEST_PID=`cat ${LOCKFILE}` if [ ${TEST_PID} -ne ${PID} ]; then if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Lockfile pid (${TEST_PID}) does not match script pid (${PID}), exiting." fi exit 1 else if [ ${VERBOSE} -eq 1 ]; then echo "${PROGRAM}: Removing lockfile=${LOCKFILE}, containing pid=${PID}" fi rm -f ${LOCKFILE} fi fi exit 1 } ###################################################################### # # Usage # ###################################################################### Usage() { echo 1>&2 echo "Usage: ${PROGRAM} -c client-id [-l email-list] -r rdy-file" 1>&2 echo 1>&2 exit 1 } ###################################################################### # # Main # ###################################################################### IFS=' ' PATH=/usr/local/mysql/bin:/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin PROGRAM=`basename $0` LOCKFILE="/tmp/${PROGRAM}.lck" VERBOSE=0 LockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" ###################################################################### # # Collect program arguments # ###################################################################### CLIENT_ID="" EMAIL_LIST="root@localhost" RDY_FILE="" while getopts "c:l:r:" OPTION ; do case "${OPTION}" in c) CLIENT_ID="${OPTARG}" ;; l) EMAIL_LIST="${OPTARG}" ;; r) RDY_FILE="${OPTARG}" ;; *) Usage ;; esac done #################################################################### # # Validate that CLIENT_ID is not null. # #################################################################### if [ -z "${CLIENT_ID}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid CLIENT_ID, it is null, '${CLIENT_ID}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Validate that EMAIL_LIST is not null and each address looks # valid. # #################################################################### if [ -z "${EMAIL_LIST}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid EMAIL_LIST, it is null, '${EMAIL_LIST}'." 1>&2 echo 1>&2 Usage fi ERROR_FLAG=1 for EMAIL in ${EMAIL_LIST}; do echo "${EMAIL}" | grep "^[0-9a-zA-Z\._-][[0-9a-zA-Z\._-]*@[0-9a-zA-Z\.][0-9a-zA-Z\.]*$" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "${PROGRAM}: Invalid email address, '${EMAIL}'" 1>&2 ERROR_FLAG=0 fi done if [ ${ERROR_FLAG} -eq 0 ]; then Usage fi #################################################################### # # Validate that RDY_FILE is not null and exists. # #################################################################### if [ -z "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: Invalid RDY_FILE, it is null, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi if [ ! -f "${RDY_FILE}" ]; then echo 1>&2 echo "${PROGRAM}: RDY_FILE does not exist, '${RDY_FILE}'." 1>&2 echo 1>&2 Usage fi #################################################################### # # Do the users bidding # #################################################################### #################################################################### # # Set WebJob group variables. # #################################################################### ENV_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/env/'` ERR_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/err/'` OUT_FILE=`echo "${RDY_FILE}" | sed 's/rdy$/out/'` #################################################################### # # If the error file contains errors, send an email alert. # #################################################################### grep -i "error" ${ERR_FILE} > /dev/null 2>&1 if [ $? -eq 0 ]; then ( echo "--- ${ERR_FILE} ---" ; cat ${ERR_FILE} ; echo "--- ${ERR_FILE} ---" ; echo ; echo "--- ${OUT_FILE} ---" ; cat ${OUT_FILE} ; echo "--- ${OUT_FILE} ---" ; echo ; echo "--- ${ENV_FILE} ---" ; cat ${ENV_FILE} ; echo "--- ${ENV_FILE} ---" ; echo ; ) | /usr/bin/Mail -s "Snort Manager Report -- Launch error on ${CLIENT_ID}." ${EMAIL_LIST} UnlockProgram "${PROGRAM}" "${LOCKFILE}" "${VERBOSE}" --- sm_launch_snort_pt --- Appendix 9 -- snort_manager.tgz.uu --- snort_manager.tgz.uu --- begin 644 snort_manager.tgz M'XL(`,]2+44"`^Q=^Y/;-I+VS_XK4!I7>2:KH1[S\BJ/.B=V+JZ+XZSM7#97 MKG5!)"0A(Y%:/D:CG._^]NNO&P!)C1S;DXGM6XNI9"B*:#3Z\?4#T&Z19GGY M!6?S`\[A_? M4B>WWL-5%:7.E;IU/E^\X3V3%[?^Y:ZBI?]O_O-9[T/J__AT.#@C_1_U3XYV M^O\P^G^:9>4-ZW_0[Y]NZ+VE_Z/CAO_WH?_3L]-;JK_3_Y]^D=87HUY5Y+UY M%NMY+[XH>KF)[=(4MV_MKG_Y:XO_FV56V#++U^_-_T^&&_Z/^YW_OX_+^7IO M9<:_9N-#,81#MHI>RS9V:/")^/_#M,RMN4E3?Y/_#\Z.-O*_X^/^R<[_W\?5 M>ZS/S<3.36\0\3^]GTVBGL2E&@S4X-ZH/Q@=G:@A*;#7N]U[^O#^@\L_X_/AJ>[^N_#ZO_F M>@'O7O^?')V=[/3_X?5_,[V`=ZO_X?^G0_JSB_^[^G]W?5C_OY%>P!O]__1T MP__/*!#L_/^CJO^;MK%#AD_$_V^F%_!&_S_>S/].SX;]G?^_C^M!SZ07-L]2 MZ+QPA?I"%R59PZY(_Y3]OV47[[G^/SL^V=5_'Y/^_U`SX)WK_R%.@.ST_Y'I M_]K-@'>N_X=D+KOXOZO_=]='Y?_7;0:\U_U?QGO*OV=__^^_\-(WE_]/^P/C_J[^N\C MT_^U>P#7T/_IZ>[\_\>H_VOU`-Z]_A^<#(]V\7]7_^^NC\W_K],#>'/]?[99 M_Y^=[OI__T_J?]C&#BH^$?^_5@_@G<__#(?#P:[^?R]7+[/IN>SW1W&63M[J M;/_.W3]%_]^PE)OR_Q/Z^L3[?__D#/V_H^'IV<[_W\>UI^X\2D9J0[?="^5P M0'Q^T.\U<6"IQ^KAY5+=47NW;^^IYS-;*/R&2-'?96[*-JU(E67JW5*LLS]>1XG%&9>E\K%RLT_*YL3[NCY/S<(LQL1?FO$< M-%PNO[=I"8G88A41/8+O58K M+:J,9SJ=&KRY]^XST!0_T1I@(.I+]07]^:K35JE1*'Y+9PGXW+:7DJTLR5;I M/--)Q(H&-3;"L9F2`%:VG*E962Y'O5Z7;PJ^F[@GD#/=@`X97Q'C*:ONHG/[6Y6_!"WN6FTIIMI'?['))*J818QC4:J9+:+8D"R.N56)A M]%2DJ50OZ+T.+Z=#-DJ#X3VQ3L/R%^2?=CG?%!",NL#*ISEY>WB)*1$5)QKR M^SQ;T(03\A.*%$&01:3N$VDW_#ZTEI$#\QE1)MG=`Z4]!I3)>EL0"-T?%, M_?3T>R8.9RK,4I/_75DEY=5`D?L$:EVL-BR'!A>,B:(R"X0D6EEJG+DNX(_C M/#LW:7>##5XUEJ0LL9U8FI=6`8.CV=6DRFEX3A+)8A(!^;C8R`]9:4;@`7P3 M[7KB+=!'S)8J(RC-";$*1CCQ:KJ6&5$=^S749*PGTT`CUNI^<2`L/-_BE=ED M8F-+.,%([8P+6%',&!P9JLR2M`,3I7&KF27AR]L>Z_$^J2M27^O"QE!JMTEC M2H$CH]'.EU>K5<0Y4I3ETQ[/*'X[R>9S@68V@EQ^V(IY2\9^1"(;GPN:V.FL M9%ENBSS"'@WD\#@U9- M&"ELR:N>4A32,\=C&`&-!M,P.UG7,#KJ,H>`4A<[5SYN[WNOPPNK M7"\/1N^X!'K]]0PE\;V)'B?ZK&^&^EB:-RSGPR+52[+N\I#8Z<>(LG).0II\:2G\($&LE M2-`3$"!90XXP`F=2/AL3J[*I:,4;GLMN`%O0)"=##V5.]K"@#'HN0>V:AO(& M(6Z;]C"N%4>[F>?OFIZ=/'_[P_#7\?4.@7:66 M7-U'\;=@P&<]!<__P'WJ(0"\%',(9`^?LEMOG]QE`L@6H."OY\8T4(PT^2L% MP6TLC=V;PAK-V_-/HGQCOD>3C:S=,[\EIV-.R!L+"EH-'^@ZGP:`,NZTR@E. MQ3)GC;QA(KG-FH8N4.]4B*$NK2;^:Z#I2BX@`AF%=;KW>N5BV5#H%A$Z,*'$ MD?/A;+E^W9HT50.(Z;.L*&%D3U!A/:&@^.S9=X"TTJ`\B(+_P^G!VU5W]DQ* MM@J/^S<4)DS8K03Z&/7PE+*8[2MP2@'WAY6C%441@TQJ)(WRX*Z1FUT@YA7% M3)U3U;._U`75.P`$JE$.K?J"GG[E]0%QN%3D(*C-$Z.LA",QK4TG2)DX6Z3Z M:`U`^=]>1'/T)`-A`6R`(2.,1IU3Q+D=UTD+9.K$21-36LC%++'R$@Q_J7HS M$D?#<1NW+[$\>NOV;9?A_%Z1%5VU9[QF+HD0*DJ?1N9"%^_]%^_ MI.^*+P==2C<-11^"X8,N"@:6.PJ(4%+`7+,J3:3BX%QA!2BF6.C]!WQ=6(UJ M1HH;")6+'$GS&;'@WK-R2EA^!":D'P0J"QD/RZ*"JJ M''[ZX='?55&NR;?J/L7H-O-`.B1JLBT:;F1_E#XVW9)D1+ED"OFTF$&-5Q*K M>HXL7=8`KW?DX]&+VB@^;WUZ\?H9KE!D830LQ#V556&JQI2]>#U-R/I,K]G) M&VU_+,NDP3^3D`$(*'U:QE=;'FRE;5D'%+51_:!M`:W#=^_<%]D:CYSKOJD=/ M1J/_FEM.S-3W/_\X&E'5G]^?DL=W`S;0&CA#SPH3:C?)^;CQ42?/:"E=<2?Q M(]<.B&<9J$B)L3!$LZD+XAYE(\C0Z_V&C6'E8B1(^;W?ME9V(*XXP*L"YEY* M]?O;8.`@Q\+/;;F?%S/0R#QO$JDR",.R?F+WOT85LTO MGT3WHL%?NER+8VTTBZUA*8B5IDI]:>R[74N*"T27?:[,LCG!QCYJ3,=?P`>$ MG()-7>)<*WZ[*->MEXZW9/%KKE)+*[4G=\@.W-V&"K]4?4%?LR"N,++N,LA2 M7%U2/V:@Y#:#E&#H&)<2Y_31DF*:IQ@Z+KF'$2^!_&+!&K M-#HW>])=\#()#;"J$!=P-N2[C=ZD?!\EGIGXW"2L>A>M&FU\=4%6`NR>O[X!VM"/916-I6W+ M;50D&O/@"ZAQ7%%PH>>5:5M1(^>(?#.=]:DG$[2N,7R!$FELRT*&KG++(U.S MDORS:4?.@N#&%3-VCB\X8Q44(=?U[/`:.='CQ9)O](=G+(%OM[3L]HL#"MME MC!5*(YP*LFJND=>2J`MN:'A39(2LK5$ZN$7H:J:NN9&X],PP#B,?1'-[C[,* M@3YI'GJR=DKQ".GK+\TDW6$Q,LESN^26=&(O;%+Y!!WXZ_)"89[L&J_BRPX2 MS566)P+O$?>_7,RK&T/8NO#IJFM'N[T`JC^;&Q0DQ1=2F=QY]2*2_-+?X6]Y M6>+/0B_OW)8N_I3$![]R;#BT9OL`)A2A90+6'NNUVP285'-OA@YP6,D$F?QR MN5ZZ=$E`!.!,XT-?=XQ@,-4Y1$^^["H)DK29SR/7"WJII:M%:])SDY>ODCQ; MOIIGTU=(RU_E!D7;JX*?XE6D[J^2-54\-K[MDG].U"IN:663;0UA#_*\\2.% M3,/*P//=IGSO@FU(S/IXO+#I2[>/`=QK3HFI\?=5LU5%L>TDY%9'1M M>->,(RIZ+.&,L][01G8]9(CNZPQ?H&$KX7_0#/\2PAMNI=B*N+<JU"4!1LBTA,.2ZSDAQ'E62`O7 M]]`Q4Q`PV<'`?98!^`P]?]W8ZM-JC,ZJ*C3W&A@%>&?0Y"@N);_@W`]87/1\ MC>S[A2A81(EK7PHH\GX2SAR>EY.A86>+G02;1%2>F97?OJJ67(VQW=&<87+* M.LBC:<9M;!O9 M@A->5@78*0#1I")TXKLAT80L7KKOD3%&+GMI/O?2]MCG$K864.ITO9+-1[<) MV>A*=/P`[DM\U8G<3;T/HW,4V_N\3<([@3I@/*REO4M\`#\I,M*F+EPC7,@3 M=)%@C5@=J3-MP#Z0Q915+E$:^K5I)3-[9.8]5-_RS^W4I@[&N220.EXJ3<4Q MR.U$\CX$K\1O1DCNF9L+FY%..?;6@<,EK<0Z5?'K()M.S3W#H=21Q:Q1'?ME MF@N32MO$;Z2*#X8\6F)SLX?#CEJ45!C+JYU)EK$LN=GM"?N'U]E0??T^JX1T M=$XC MD^4COK]"9]`-M\/Z]JBKT'ZZ0N>&Y`SPX1Q%^H/11M^SW;!KA`4IF5<4^WBS M"#L#J'NQ0;GB0&P)8ZCT,F1CPS9O*PQ@8)_ M5^:&(FL8XRJS](5D9%C1X(UDW*%!\HC&\%-O>NJZ+/`>*;J_KT<1WI7A42-7J_U%H2[C7G M24,_-Q#F+5XKL8^3.)&-C8WDIAN>*]SY$SK8@9K.F(3AA%B0@*50EX>^,C3:"'TS*7<-'13+\O'L-81#THS";)SR?`E M%%`@F!%]$85OT;*4I<51.`#'5S5UB@.`-)\:4RC+%O8W5A1;+:U7FDU7W62, MW7VSS5/JY@VB4!#^V)2E]$#@J$BOV4T#Z[2.7E$MI3J"L:9-;V:Q@!?I7#CX M=3FG+_G#UBGOYQ.N2ZR\2`R<>8)=S),&&$'1)-(Q:#G+DB,SO;'>T%MI@YP:# MK\3?9X\>5Y-6J49MP$%.@$>XDQ<4F9+>9$5QJ2$&[P28DM1$4\KO:[;AFOO&3:E+,HR M&(BB('H?B,?DE>7$>ZA'")]*08U>1H<$!&-H9 MW@HC*,J;UYA7"ZEYEIU[:^PX&J3FS:K_;;BZH6NO`7]73F&)/HA!!_ARR*J) M?;1^M<]*$5(A>9=#/.("M#`IN/5R::A^(7QW765_MJYU*,3+RDKFS:8:SL_P MS-680*J4YI<[*H/C:,OEW%[1ZUY=^>M\6OE*P(5)%(%UJRP*=QQ)B[H*#J2X M,QJ20U^/MSB2AADQ@VR&Z]=@S6S#GI14$V$K8F MI*Y,XV@S*9E`N&4'=!4+U)/E;J8?Q4KU1683U]KV+=ZBL<#2M6H3$*DM@'LU M77%`J`S@Z\,"EVIY""B>5$&L^@-X?]!$;]#:?:I_`Z140SX0=2>7HR7`@(YZ MI3KHRLB'WR6%_S2*AYOGC*H/^N^0_^MJCK?B=>_/XXI#W/4$]B?+Z[/KZO'& M3178@$81FDE@P8'6/ZNL]&WH@K*N^5KZ)84'C"VD\$O:L*:>7T^OV1Y)>1_. M5SOKL"(MF#Y\I'9C>^ZR5#GTTS_VLC6O0G:]#E#G5HJZ>O)8^( MJF6%AV"*X;QU7CB$5A%=23&TF/OCF'B;M,>*!ZK/C5#QQSI#NRMPY;J?5V(* MZU[ZN,CI7`9'NH02QOZX(,UNYQP]D&?I^)Q8*6;^.(19<:2(/BC&-\[YU(6D MKXR;0;2VX:X+^63$&<7@:'AR$,Y/>$T0GVCIA[)YW8`\@D`L@#(^/B_5V>,MLXYL$W7D M`XK-_5_JT^:N>0.GN2L>9F$'0V6"):6'_1$0)%FV!?DDQ9>7O81H7N1KC[ MG(F%3XHHC/@,)@%\'G?54;^/S1YRD:3X?'-./@PGLYS=(YN\BQW5N\XT)1>H M#\GXPP[^C/1+FP+^KZR1*'7^40L;)#?G=8';A;A&9@!H?/CWYP^?_G#_^Y<_ M/'PN1GSGNR>/'_)'^=47.#[J#Z/ZEVCAG%$S+]F2O(2H!Y[Y>$T1ZZ7[A4M[ MSTUPN[TXFE1U7K0XY#6^"!QVFOMDK5*76=1;"]I00(;,!X7PE:F[Q\._=@?W MAH-WX:&$BG5R0=@*O'/%XD3'I0,9=-^V2,K]SB#)I&,E6X>78IONI)+#7[4? M#J%[DX`QHWB^ZQ1]%UEIR(%JRSX6'MQ;A>K@`&"OF'GPZDVR#!^O.,"QZNRC MR4_D7A2?C>C?S@N,?8&WZ=/G!Y#)BZMQJL.1V$TXHD$T`P\2WW#)!#=VL]#O M=;^ED)\HBJ=J%4Y,.5GM"X0''!$ M:6Z&GZ>4#7"LL.E$"`$@BW"B/C7_U]ZWM[=M''N__X:?8JLZE=60%$G=',9V MK4IRHM:2]8IR4A_'AP%)D$)-$BP!2E9L?_&7"?"9#.#OK5]5#7]Q<:F<1[!A(-(A%.L+P1%TZ^H5U MZUV8D@M(W$>K/G4D&I&-_1P]7UQ"I@_H)AF:89SS$E=;J*ZQTR6.C_A5#:_( MZLBR"\_=20JKJ#N:9,0JGCQ7GCJ"%5]4J]455/.A^RY#TWL)3$4RQJ$A-R8# M/MEDDVP!'BTD/E+^8+S99-<:1Q%A.G*E_J*7EGRM7< M3!EHGCL?'WQH?,H2>/!A@]G8-\RYY@&H\IR1#MV%08]^!XIF(KP'6[(0W0?$ M0XT,T6>+-R!F!RQV3&W@(^819A#5^SDND67:[I$;SOPBBF?Y6F!V@ND*<@FY M/FMCY$-[_FF72,G&&Z5164];=E_U#"24[B/3+I"1_D&F-A="86$P0F<(V7;$ M4SDCHH8"NX:M6XH$5_O#^')5>0L*M1T^!(.DN?O-=ZO* M7'A'[SQ]N3#3*B#3?!-4?BVWU=MO6(LR%++Z"HC2"V1TVZ=XR1&VP@EI@Z@= MCB8@5RI!;Q21%[)H4F8IF%LCR4125G&'G$2&5VMS-60="KCZSZ8D6>LRY:T9 M/4L9P7PF/H+&0HU&7AJNU%'85T?)8%4S!)DF3`.)'XSBCFE!?U@UAQ?"-W.5 MAMH"35J0J]_\96WE(2[&U6^^@W]0,ZW^.7=AIME:5BQ!L6Z!.'IV@L[5"DY= M/4W1/,1'H'Q&3&+=%?::5W*-2'@N01ZBT+'HWB6M69<>0+9A@:IO>S",@K%C MT2GL6N=HPVA!Z&U4%5A'%/^PZP M+X9SE1NO$,CQ'GMSZ5I;0O;LWSI`.(9&SV'$<3P0H^8:+PAD"@MH$"([;'/QKE7EQ6DG@X8][A_D&YJWUE#.A"CMGQWFR. MWKF@YB"2!7<@9(ZE7$(Y)W#7$?IM;(V>=Z5K3#*UQ1>_B1RSALZLN\F-?3%^ M;6L+=[J2R]?P-9Y6V.H%$%7]#X8MR MCF-7YTKITP'#,)U@&H_3S/(G-_UI;Q?Y;[_^7SNYK_=['[\\:1VV M%A"B-/L'K;W3PY.SPY?'3K*SW'AR?C5L9)%+NDR43N-A4G:(4"WW.(N^TC#N M>:]?4,@[NH-&8`#Z24;M7C@9QE=MGE/A^(*Q_49M;BB_)Q18^BT@"A@#@YMO M2#&PM+D=X[?*`?2O*NV@CM$U!"Q;/[VX2\A;@6!+:A]R=JBG6$+3:)(F!@W/ MR>N'`8(/>R^/STY?OCAH%?;5DP^O/QY_RK@2ML0CF.X($6J`==LDIS(*%"&7 MSGEQR:5PQ>Z3JZ]7M8>GX+Y)&#PA0K=*QFKU>#5#13NZY>7B1N8U#$?[R6,> M46&EIPL;2&YU+I_;P:ZJS`S($/&!?S0TC:ROY^EL;'I<]8)P1`C@O3`O;U?X MO4+35U'L5G\&F"E!2?)H1.K9(=X$[0?=\!F-:-3OFUO`1$*^SG47A[Y$8>*2 M2`F+G>_PS=(8IP8CB7:RY6LL)1O?3FZCZ\O-IF@*SDH7YS(D@EZ/>1WC.Q0Q M*G5M..2@&02F[D#SL802/TVAD@@WSO&.@?^U\+7"?QK2`_0`8DL=.LZ?6C:" M*0MB?3V6`?=[..!).B7H&)Q^F?Q=#)8Z22W,HF_]FJ_0;VW^VMCQ+/IMT09O\@K(XQ_9TJ18'L'1CH&\:POYL<\,+>.Z&W$Y9[.1*,XE)X:#`, MAHH14CFZ6SCAJR698"Y\*\\GUF096%]3K5!BX`6&J$A`;K\,1UP:XKW@U1OQ@B0)79IU9ZZA.&'*LQ(.' M"VJ]5F91-WPYQ/2P]H?ZHV-S:WMG4>5;VN[?ZWL[1\\K^A7 M^.:3H3/?GP]@)U'3WW-'A20+-MUVBLP)300O^U[I:.Q:S3D\8=A/UM,3"_W3 MG*_$&V=4R_5O&]7Z]B/Y^992'^W^_4"U7IT>J-K9^KDQ>[>@=H] M?JU:)_!72QT>X]=3]>*P=?8'S1S96*98-<,E?$T<+P_;MKDH1L#VD(T`IM'L[,3D%[QXO#\ZCE,"_V#,M``:UY&*_<6OTK]F&*X=Y#ET#2;M!`D#PK@, M9"Z?>M(X44>O6F=N2'C";<@HL*!V(WPU1I2E>#5\(_N"P^89@!QH%W;E"T'B MWC]N275S8))P'"!!NW5P^N/!:/V9$< MDE*/:H_J''6*6D%#=_+R]*QEY(LO2HS4LPF1!+T_08(L:!G$+R+[3B?4<9T0 M>^M-.(`<3TL!VC1B+M>'/[^MFHA8\2@D1'C&L#?9A&*[UP\&+ M%WLO]P^02YBS]1LA\8HW<,CW>6X@KQ\0=`:`?R_$_$-91Y;M&PX:3FHK('E]<)2\%U5`Y)!0$.$ ME=V+RPC1@K=="1(X%=!0S22Z'M0$*,S,ZC?;F]5ZH]K8Q*5ZHRR_'KF_ZMNR MK.N?&^Y/V$_PST9MJUI_]*BZX?_<\G_N^#^_]7_6MS*YZSN9%(W-1Z)4X'!A M..123(:J-` M`QXB>F:'L$*8$K*[5NH4!F,E4?F$W M[E$4PB<%#Y\*8(;51&<1,4&P7#'&P$HE^@+!%$KUU#DP$Y%/01AHR3HU-E3( M,!M3]!@U#,<#Z%5"_>LCJA+O"!AGKCO164I\SM%*XPE'/P&QS34B.*=4I*6$ ME!=[XX6\<0*9B[NO4<2C M"C*;K#N9CM!U:X*8QI!S%8F@#NK3*!SYC3EDH+Y3M"4E!:W@)>C0AH/&$)F3 M5*PAT32\Q+&B2IK#"*9]!#/TX1JN)B@_Q_&E`]@_I6+5111`2R?G5PE:_J$! MJ/Q14*W!-.A(^$MX*R$@I5@OEE=D`_G8/;B-LH.13]'`3N4)8G:F46_`G3`-$7<*K5+! M,#V_TJ$A=#E>"$Y4G)#H96CP)MVJB/V!NT=WW"%N*PC8&.&N*%`N"0PF>BFJ MH`Z[)IU#>)QV/*;!)2@.V!!K!\&5^&T MP=2;JE9KUK:;.]O-_?WFUO/FP4;ILRQ9BNQLD^IYC:67/KV%HO;2,7ZLD M=!IR)_*I"^GY&`TP[;C?-J^>-M5CCVQ;!,I3?Y(]IZTKJ'_OL)-!_9K13N!: MLZN@MN9E5X+\QL-'9H39V-7J$'V)PI/S4>0H1.M>E(P2T$LIAKZ3HQF?&:-$I'PFR%AQ52: M\?C?P?!/\2!M>,6,B2!O$GF!"/REC7M-14,T/*>,E1T%<]O4>:*=$PHX&[B17#T MYYB,O-N(LD@2PO?:KN'P$VP_`6\BF@XI.6KSZ*^F@L!4E<,PHHZ=P7030DA82X`D>%3TMT66\D9,!;55$D]_QPZO8S6PC&,,-@ MI\HXOHLQG4R]-4LC4B)4*)Y1N'@.Y$`Q1#E2-<9J64PM$A6,,,!!-8VFX@LQ M5TQ_.$O,X3.L\]U@HMYTKM*0VD<]Q)V`'_$0FH.#P/QXPVU^JRCUHKH\E$&& MK(/$=8#J*=JCR`V(A(@J M\/F@UM2U/PD!9_Y*L7MEB!\BO&D2C@@L"M^JIVI[\QV/UQKE;FA/+=#X$"6Y MHD&;85J7,@*,9@(A]"$6]6;3RF:8[1/6'=<%U-J4S,$S6^Q-5;F71Q1B=CE! MARSKUU'Y5;T!.?0Q3-*WQN&'-%4-U4YVHY`";T;==WB,CNRE)19N<33`)H4I M-L(3FLD[P;D&EDT_T.F/[@ED1IN>8YX`U3T\09#TPRL1M&2R(5!\67Y0G8"! M2#!$]G@03B?3"(UW@[(ZV#MF4B6-E>@/@`U1B<&`XR*QB_8)$6D;M;*>^X\V M'CW:KCU:,]HD)'/%8U9V$@-+.]H<(J"B640KC-1&45M-&_E`S,26U1NOG)E) MX0E0(>49-=(&)ZN;4JS1U*L)=@VJ7,#\(ZR2U(*'!`O4WV1;G(;H`\EZ*>Z7 M"M'WQG&47!E/'AN>+4##!P&@3X/)Y`KWB@E-I2)B1B.2@PL),#;5CE!Z:QE> M!(B,*YM+:%(ZFR*;R7+)88&NI&XP.-%`KZ?%2(*H,H%8FY"!ERPM74*9EN+G MI+0X=-$&(2")K04V60NL7B9"F$Z."IZY&24%YTA[+MALA"D$L][)T+$KU8-` M0A,))X!B<99<@Z5(*SB9*G0P!Y@78X.G#CHN:<@#T.23ZC6T3F'\6/&$V40A M0HE)M$7,65PPCFQ5+VZHM29"1) MOR+1`Q8]`S[/'S&6.U2/=JHC_L<8#*8,_4JD"XBM<.56="1[2QCW$W@0S0FT MST(!*:X$)0#>94GF>E,,]!C.)5LJL0I"(WX+N+YVD"ZJ41`8WJ4576ZA) M:0T5!,1&3:?F:2&JDBV7>56+OAMH2\-XT!;UJRW+!)`!U4W`Z%/7&B=8^M+)H7TN-:8VQIZ?&^UA7O(`4$Q#%6LM*:8!$@WZGMQ2T"2Q2 MMV!S)?9'Z(:!A!B88)>B%^H?Q`+'?9FG9=7_+5I62Q8QLH!&Z96G1!V@@+Z` M#6+K3/J*/F]D/N,2)%TY#2F&Y2@B3J74FUHC@U1\+*`NHG@H>TKXL"4)]H,T M0*9HO3YV2]O.U!27VB:K+_\XVF5G\9W<-$"HLKOW]\I)ZX?*J]/O*>6CW)3/ M#X^5J"E*?9N;Y/C5BQR.`R=18R$U1UVBUU9X MF"TMY-]8U'RO8?5-.Q+`3)<8`$?6RHQJ;#3B_*4:534\LYC7C6^NK&7VR%3! MEV."_]8Z/B7O#B,@4%[X79/SCVT?HEV#'(/7RHJ)?T^Q+UG5P*FTT@DHF`") M*9J=NV:C;!3%?.60ZT02H>+59QH@ZK^QEW(Z13'#*!P)M'F<%?A\A'@],3EC M+R9&<3X],O0&,TE$%%E$9'6:5[UT#VEG[?DQ3G1P.5?`_P5'`I<.$!*)H MCC[8L'>XSCG*&%UJN)(K7TGSXNJ-L]LP6@7HA%RM"".LV`QYU.9I<%4:];)J M;,!_6V6U!?\^@@U$?7,#XX#@7_5Z'C$4V%OUC?S)T[:#@/,%G5G;LN`W13=# M'T?"C6=5G@[\];#C>Z"9QMT8%,]QC,E#YE.M>9E=C.%;5+(Y<$,O[JZ+7<\M M&;4ST&?PM+`Z@J7)NO)?8EYD,'8\,2ZN,&07N&^#A@W@KS%A[)/TG<@I,>74 MWJC!F(.R1E"4URM^^\4I\><2[1>BI"V5:F.EW`K6&UL-55I,JLW3HJFGAQ8' M3!GRD/.LX1/U`;TFT`]"/:K#_SZ1`$0S0QM$5UL.:;=JZ+6`(ZY1X*%*L(_0 MA7BB"Q/>N'K:6UB"(CEUVJ@W'G'%/NFO:&'%^UY0HYI^%R3=*$+1*K][\

M`*LKF.7R80S2>MKOMC'*)910>U]S*'?/9^-W3FOA;#FYX-1T-SA\>;;,>AC9FH:?&N72+.2FETAC6.T[+KE,Q<@\L" M"SGR>L$K*V06QIX3=O#8DC3+@:=SUK:)RTWUF[GRFN++P0"WM1T.FVD<(_!H M`P;=&.D@$^H7/5#F2`=@YFAKKZ8VN3XD1(./Y9@2*.Z]PK[#1V-DA%ZE.W%1_A6SJY11]_$+W=*IH@NU3 MLL3/JQ>?.0^(,YH>GD8'@X/^&+S=1\;+K47>5J>VY6]U:&_C[79^DZU.;DN- M;XC?W9V8=%SR!S7]?L;NH>-P$*>1G&1Q,#DCZ:8W$6\W/.=:,523%>V:.E^V M5M7X._9K/YV00Q(U$+U74MF<1AATN'MNG8?Q2#[@,:!%*T=LD"\6>4_HXQK= M>^UAM)YW3TF)X)IT<[Y%3?0S9M1><$#)UICN3('H92 M5,I-8B-5,?:G1`9>\7U(D+@.7X@Z"1U]ZL,3CFDG9\@VP%DU=Q0D`)[/_%7M MTXBT\SC\6XE^)AZ?UB@\YJBVYDI(-<-X'LLA#^*A=T67U=2J8D`6]C"],LHT M&J!OSGSBJ]=4]F#=J\9#)#;QZL4E4_%IU-\H!?4\$C363 MB]<[;#;0?(AETW4DN79+HGO([C&N8X+<^AS$>"S-EY@HJF@\#BM/Z82=(_EB MQYMW2(162RJ9*^?7''U'8;6''=@LF=>HRZ`?F,OOJ);8HW)[M([=<1X.)T8O M-*NY.'ZIN8',8YI&L07H-Y&)7D.:ZGGT'J-J=0,,=P1OH'KJ!5D!#VBUDJ6T MD9^[-8S(>Z8HO_(L2`7%GP7#=_FE;WYU;=GYF;..+!D2H,]^E5+&2L+4*@E2 MJV``=4A>JV[5W$1]K&TE/0?A<([1!C=J>22N^W[))K%&#FGY)/F2;CP-.W$P M[56F\652X:2HB=]HG2K;T_6M7"(V MP69>?JF!R>QWIZ1U^W/:U5W%W6EN7]77-QKF,A:[:7,']Y+4SU*KUKP1($6Q M@@9AA4>]](X/%/CE*!DP'W0GE0F(6U":0W*1QV6X?V)/4[WYO#N#A0TVF?MX MJS`.D2_32,DWAR?E46I9B/?<5'D4-(PAPWLM.AY,C:/I]JH!04B9OT=186XI6,BPT+ M;@T400H'['K12#GK33AH>31I\WNT!E@$$5Q4K%8KI7)".9`(Q-G5=I+69[BG M4"H;><-0P?CZ_W7*5S@'EL>T. MQTG8'B)Z`+0<1/='V&!'L]''\VAP[C>'(\[+#2?;.'-[0S>)[%2D3%"(2*3L MG"UC@1&?BJ!>8CQH104S,T44DU6*5>YD,H2T!B3:#[O!8N-P1K)+,C`5WJK1 M.C;(L0FP,VA_N5SMA&>;,&.KRTP.0]C]G7=`Q[MGSD[_\`1Z#2\V=J%70GL'B)U6 MJ+W(-WFM/0]LG6!,413IA26Q%F-?L+*['OI\8G)<)&6U@=://=FPN)NK"+0B M#24I*ZU%VF(*(U<(0XZ=`:[D]H+>K>/P_P#5AD_B"3)74L*J,_.$(3>*D]3H MRG-RLSR"="7.P0+P>+[>"YCN.Q\(0PR&A_HX2 M38RCK-(EVYZ6.7(!B5R!PYZN[3`>D+'W`UU6(N`;MYIVWB<>F!+EH5MM`UO! MW,7*$+L(AC-JGKX;(A('A75@8LVN4^A9?7<%R&OAZ@@X\8SN4>D&%XGCQ,UN@H@G:$+JL) M)9-_-=.ZU6LH!*&%LO\D1P0\@Z+:$(*!TIVD/8A=IE]8 M)6VS1JZ9C4/O:,Z9^';V4/GZ:AK(PS+(A"0!]8Z^>,+/'SQ3I!7&-^N.GN,3 MG=,9=LE:W"E3MU,65LQ9)V[2)YF^2*X2G&#>*I#U9[E@-!4`OHM3;/EQE74-\.!+NE,Z M9T&[TK@J+M`$`AY@\'C8?UWRQ5!SCX>'CB2;L532Q1G5T$9U'%,@6S6X?:3G M848D?+2[A^I5)_8BYI+[J]QM4BL5:?2*N?44FT/FG/YP5RRD)>.5Z\C3N(49 MY[[L.*_FJ^Q[\N`:U9]B5])01@F92=5#V.2N^4X]BU+"WG;-<^C!SZQ=X0GA MY31*K:.U?R)JV3OW=5M<7''\FDKOJS?QKF>_UJSUF[5:4_]!UY/9AYF@&%O& M=W"A\1,/.SR<2AX"OL\)M;F(>M9IC->PHV`,FF>5[RW+IH7@+R,Z03],^72* M;M&;K-.0MZ]FRQ7QZ2`>Q1L(-"0[Z?7YU&3N6HM,K2:KD'C(2MK).FS3UQV\ M]"K[54[>I5W@&)(\V"__J+R(QN\:+?(&1C?6BEO`M<;A7/.WW")(Y=C`*^)B M-D3&[T1#U"PA^T.$ZAI%W6FK3MR!] MWZ#;/V/CK."?XA""?[*.02R*7HGF:B;97JIZ,G[U>7/1ZR(Y+A\0W!9-_#&, M5V,S8_7/;6!C2VU_6Z<%XI[BRM,UP8UF`0`77D48F[C>C!#J7/?UDS.@`\C? M7B@W0&'&R#5#<^?1O6B8R1Z9:YLXG?GNKB2Q=PPI;<$%PS^:XVE>Y)ND2HM: M@'<"Z>V-3D;D'@8N)=K0)8I#/^@BDT>,762/@11Z-&XT%!M5$FTQPDTK&NQD M;<(S["F(J-DP8&4'&[>.P^R"8&TTRIY#FT[(JL]JO;%#ML3Z*B_-;M*)`&]N MU3=Y'KV!]>`]>D1?Q+.I,;NX-X1AFL&$>6M[W._#%R^_;^^^.ON!_WAQ<'K& M=&USR0G77&M,0B&:W)9J?DIL_!/=`^6[9FQBQ^3F)B^J`=Z1[\U&$^89[4X, M8D%6S=>B^V\%N*#=(M1E*#$Y2?Y+P6-NJ.P&\VLD?561DO,XH7&U MZ>ZJOGV7V##ON@FR,O<@8I/$JT1#+G>"Z?@*W:G,ZKF=Z;2R8%=IBN\P_MY_D6MS'`&:M;11G10;_1 M,$.[`_CCH79U0@VWS:,WF82P"/9X=R&W3NA>E+8KHC\4.<7!;F'(%05>._JK MWWGBTYLH97A7,)#%U8?U99`47!5E]N;LH%TU4F\L]-U%+M[`)4'-<*#7W-VCECN0;L.TC)Z;TL M#O_$K;=>00BF$/1O3)G,D@GP=DR7R3[PY2-\S^J2REV);)XJ)_M$E3KXQ^[1 MR8L#`F92SU^>JM:KULGAWN'+5RUZ=_;ZY*!)J$XZ.QW-/7#A(U7EJ?-B>WM[ M1ST<)8/F"L%8(QC!X>D>K%-HPEGY;NVZSC"=/]\CHM2QS9Y6('=1=;MH&O:( M5?P.TJ_4+;0<=8,%<'[-F5_V%O3WZ<$^E>3UMJ[]@K[VT#4WZAL;.^+2R]W> MDBN#42(@22\.#LY6OD.M`#J^1,5-H3EUGOMVOSO" M37='J*^[$CPM@=;0MI3$OD$,L0!K;-(0_$2Z%H"W%>-^9O=G6I"42D(6O6\Z M49JT4;HW8;>[?<_-VFH604FK5Q,ZS$5IP4UAH#L?,0-187-0R`TDC?06@D#B M`G?.YA['UL1:\CF>!.I5,`NZK;5+OJ=O2'-UC)%)(`%067'M2YGKYA91EDP6 MCKS5%_RJ:C^<",Q1%B75"?M2U@"D;+8A3Q"$GP[%,GR)0I40A&$7'O4T1HU3 M8IDN'=O](;=G%"`M`?$TE<_<(5:X,,J1IQA>-4@;RI-,L03&)G>S<="^XP,Y M8,FP;"!Z\3[`#'4IFJ':PD'>H+-A:.^0DJ.W@4'ML*9!7>'&Q# (V[(!:@ M=CV8#`E'_6&G"X/]HX=S?@PE^@U(ZXB@W.C*]5A]/QL&ZC%H7,/@F1Z&R^A7 MT#H3M*P]1:+',G`M^0[2B1(0D:/@O?HQHAO#CR_HWV>7Y\"`YZC3(05)A:HG M_O-N,`WZZO&(7CP;P12H]F;503JVB9]?Q3V8Z:\#T''/WT&VQ_TKR'41/DNC M`?!?&(P07)\3'R/6\6D\P#,H]7@,OYY-@TG4@P26XM^B$5K],`R$>OS//O^5 MDZ[5C=-4'75!&;B"X7R.@-X+>Z`XB/0:6Q.D5^EO- MDH0H3.&7&:@^"`'I9+P6\^2FC[,*X=$3FI0O$-QJRE$.$KKAI:S/O>4_DP*Y M7:YMNM==!,T?955%G_V`M.B^Z\4(1Y2<0R/PV*C,$_T*_YV.R[3E+Y,#587_ MA%DPD_OEL"D!04%7.]"K(6#1,&E,_$L/!M#:2E@1`"0TN3Q[UF2=KK4'1(2@ MFB)CV$%H@>304A`]W7'KA7,66F%\MBTZKPYF98O5BVV!Q6/LN`"SSL?5(@?@ M6!1^>D7RJM^GTS;\(O4B)ZII-!B@NTSU-GQA=($'!OR4HRDS=F_>YT[0J^A; M``L3852!.$H7)Z!#](5?^7IRP?=TLO@C^[HO_CZ=%%1\JN]C+4[2BXL^%G\= M%WQ,;:OR/N,,`WFQ.#\EB(>]_@R%:&M%E MY>(TD_.BAL-.9G'V]_7ZXH\H7A9_!2;I1$6C5=PXEG85[7=60(>-<`7E7!4V M$7'?B[IG5#09HE%0\'423QJ%7S<*"AZ/BPHF7-E*U-/]8N'!,X,OJT91,KV> M%*4Q*TU1(EX3BE-,QT7?47`7?M M'E6#=6V4A/&5]4XDE7Y*=KFI0,7H2(!5V;9)2K[9;>!:.?8@PJX^Q5A<3H`V MQ]>LJO9T8$V^]#@.TX1$35D%/\;'C_^=WUGJU[_?VIK&?_M]S/^&`/^2XU_@SXOQ__W-OZG<9S> M?_SO&@9[],L:YW+Y)U4#"B">@RR]5QN?[[\S\D M8PXHG__>7D7RSC]OO_S:V= MQG+_]V\=_\_8[W_^^&\U&EO+\?^WC_\=]_N?O?^O;]>6Z_]R_[]\_MWS_V[[ M_5O/_^VMK/U_H[&<_[^S_;_EC:5@^`^9_W?<[W_V_K^^O;6YO9S_R_W_\OFR M\_\H>!>BC^7]EE$\_^L;.YMF_:]M[>S@_K^V4U_._R\R_P]^/'C1WC\\_>J) M/W@@\G[:=UQP$\HT<'9'B>2')_0>:ETM-LZ M.S@5NAX!MC;K!)@;$]CT7G[TP&K-)1`'K-;9[MFKEBT<:'U:QWLRZ/3='T"" MXY>G9^WO#_.+R.2FX^AG:^?3X=Z_ZV^9+S^#+W0SKX=66^ MF][CG4MU/Q>$_LB!`"D)1)X>_8(P_H5#1[-AW0:"6?D-J$OW^/3Q7XHADY==<4#7GOJHW@?3 M`?3P^2CNJ=K.3NTF^?K9?-O;M=N6AU>W:LT''\P<_W3;DG,HE%S>(3D1]=4; MC/^Z8BBMJ$HL;Z@+O=_X\^UWC#CX<^FKKU!N_?W@$P.3?X>O^A'_RV/HU$\) M/?D+*6'"$G(P504:U'YQV#I[LL)?OK(O'H"$E1^?&B0_3@-(AOZO+H'O5"^FP:&Q_8.J])04D!F\KT;OH/-59>)\)\)F$'OQ M.,2KE%:\W'*ZE4JW2MZT"R$M=AXI8!'TOJV$:B59__G9H5ZA?GZVS@RU/EA1 M#_ZBGJH'STHE*P9O)7]N4&&;.%M=APQ4%J]#_X7JXLAAXO0T1@?AZ\?9NWMS MGZL2K-_WN"H!M1O+DL\4'L*@TY&J3/N%/9A)J+7`A2GWCL^>_#),\H5KXZE" MCX+U,5X]_J@NNZJ"_P:7[]3J!PZ;^.!!_=/J+TR+.@*F%-"$NH?_4C6OW06U M][O!UBFG^K]5I?**DEK=*P^R+GB/;,@$[YD3]X[VVWLOCY^#C+Z5FN'T>:5/ M\E53RA0]F(8@?_^;D&+DZL,!->3)F]?';]^HMW]^\&!%N?F5.^Z-IW^JB]"6 M`?Z+JHS#S.#*A#I`G.^F.AR3"H"W.,;QN`)J+ZK0J9JO`]\QCH=EM>K68)5U M:BGW*]&;^5>(-R7I->B10U6)8'T)U6JR_M\/<]JX5H7FK0.;OEY?]1OI+T2& M*C?D)PX4H=PNYQO5-J1*ML:_!0_+ON0^92E37'+Q_THN/KX+%S?OPL8W7][N ME>%Q0WV/W([DK+EBI<7[^D-MB#`J'@&PPA;?6]WZ#U>^KC1JB?JZ4O?___-X MI8QK'_ZO@?_;P/]MKGU:==6+/-@Q-?>'_>O>BF\='+=>BNKOJQPP$=PEG695 MY0(F%LS,!RN_6+6?28CF[]"SRO_A<>LLKXAUDZ&X-%,4U]JO6C\=GNW]D-OBZXI^^L? MOC[2LN'L^_\BXX4F]JF-A@3L]2I:6#E1MZ>J5?6G/RD\GNC^VL=R(.,GY6;$ M[Y`P^XK&N5I=ERRE&Y__L$'SOL\8BL]_&@WT#>3S'_BOCN<_&QNUI?_'%WE. M0X8?*I6>[0/#/BN57EX@>$=X62I)9!8&6D.L.6:3GKJ(`A4X-]&3=#KKIH19 MC4%V?PH[?XL[$M$!J#Q,R*K:"8?QY5JU1!$8/+Z3F`P?Y=]O*MY1E'QT4WR4 M9-ZQDYMN+K')D7;G$^:G_LA`EW$T?L>G4VRCNE$QK$VVZW/?5'X655`]55"4 MND5%KRE:'PDMJG11]FNK?VT;W(;DPP!>G\TU>UZ;^D:]Y6:8`_J[-H=CV+PV M;0:UX,XC((`9"Y-<2T-Q?5P;:L$$E&-;Y^5\`0N88P'16S'"38;_IF-X_?LQI.K:FUQ4)]%6>>9,C?S=11NP*A%V>99MB!UOL0JR+"`VPMRS/%] M0=K<&7"'$7`Y\$XT;C)3"O+ESIG"]/FSIR`+SB-4>QRMIDD0@'@/$N8Y7,.A7]-L(S.>7I7Q(+D?B3P>"]KSG-0QC`DJB"!:UB8!]QK#"I M:>;H>&-`91C,QMUS_EQVYDHVU$DTU#"I06:1HT!\2AV,!]$XQ.!ZA$N=(""K M"Q8?21!)BD]_I:M%='EP7'RE19T@/92$D+[G]>Q5&:APU',@,)OT*`HLJOL4%,,*H,D0 MUX'!-!@1#]DU@?G:_LX;=V11JIP#_]U!MIPF5^/NJD20.P\-FRJ%@->2!VL* M4I^B#&!0@@SCXC0S74FP5C1V#&]I*6B02AT3PV$8A))4;B^9/I\;W&PW5GG3 MY*(6*P/\Z76OV4MAR%F<7+194AS3B.96,FKS`+2YE+8@="*_C-J,I=[F;13P MF&:HW$P\0CA:'/K,:2S"+,/F;!@'/8X00KG=QMFFD.!(382;G%H4%2/H[XRH M#&,]&_,\\B9(A/M'AWLH0"GA\$K',A)B1;J+8J$1RBX!F2'4?&K[E--F^K07 M3H;QE5-EZ5"6'_R>2LM)V]1]/$=$MUN\P!):'45XXU0$VA03"0-U1],$)%24 M"KLGN0*0.948/$`A[6ZI]?0@\27;)$[F)D=)^21 MLS*9)>PX2^@47#PS;<7%-XXG"<50P;#/:!'RZF-:0>:A48!T@1<,[^750P+TZA[3 M/47L$%&H%:Q.?S8T<7XULV!HUC%\Y\]SU55TIP41"$%WI65SXBA7>?UQ)C") MP+,8NG<&?PP-2O)8XGM)17'IL'7#(.?3W!%VN8U6,PJY%KX_#V8)A6TIE7X" MN59Y/HS)$E>OJMT>ZBS(JIG>)61%7$W$MJ:"JCJ=B?HA`3BTPD1&N/E9*+[! M9-#NQU.CTO)!0=F6I5/0V=;N\=Y!6;1,?>8X1^+P^.S@]/GNWH'M2:Z*&Q,$ MZ]`)DLC!MS0$K'61(S];'=6#?7?4\#\JX[4MOL_FY"])G^B:D\>VJ9WD[U2U M]RQ%G*1L6D1;.;;`M=9485'%7'P5=]U;YX+6==7FW.LT@0-@;(GWM[!N1L^: MAL.`D+<18=-N4["R&,F(XM'+Z#%TIHTF`6-U@IET>#H$\;<4''NNUSR@J@PZ MJJJN9[='NIPJGVA>LVGV4Q=L9OV$F7VI2><,J]/ZQ$"E7GD.TCG#!U(FD>`, M/`%M(,/`A(XIR[Z`@V1)`"$S_#J^S`I%OBURT,Z6WRW@2S.CYE6(LBG;78O+ M7KMQK#/`ZUZ<#(=$1OV=6]]SL=GO/@-\GUB>62P'J1C3'19I/T^)8I'GL*]M MN5NSZ]AQL?FE@#63J%<9)8/J*)@X;Q>:3C"Z,:(O8W+S&A%V2-]RNB0"FI%DH,=C*L-AG8/93V0S0/\!>MGA2-@9W>3(CLB'+8Y3:G4&@!L.X$^1EMRJE1%/'TDQ5,_L] MCF4.>DYO2.&\*G&_;TPBSL)DM*6;,:AO*;R.CV^7VN5ZJ4R_BF%'*"*9X?7` M"TOC[&G+KBFC[/+Z3>6+K)L7T?2G/R4EBXY*L7F>M5>2H&9MFH*G&H M^[_>(7)/[9K^V*RJ%H7=@-8`Q2DIWS(]K:*99/3*9*XT;-W:3K%4I#:8@ M]892CU=)*!L=8`*LRH*1Q]$)49$;XI[$*-PO#X__+IIK,X.6F?HG,+] MZ5Z@M#WZ0'5[]F^0`1DJ]IO3O978T%:5O6P659EYA9FRLNEXT]9>L+J:$NS: MB:D]Q=(MQJY2S74'U>:&*(=U]+^1FO*JR]'D<=]MU418L:8A;B(OW&!K$JJ* MQU*F&UEO(/$(,?)-(#)@DFZ8RPFXC42#%^V42Z(J.-'WUUK%.%^$ZSWQ-*?>T5$+60ZSDB=G+*6_3#R\ MOE841(6ULM&;J*GA;N<^**%@34[J840SY#QM?X%A$ARWV9:B;L4Y8( M<;BI9'4/X[+*A0!ND&L\[UPI#+WFGK)D]YUWZ/(S%.`<%A$E]M#A-&.@H;79 MB0W/O%NVVWR*+>MVCZ<"YZT<-!ZR2!I^OZ?&WH31EU`?RV?Y+)_ELWR6S_)9 M/LMG^2R?Y;-\EL_R63[+9_DLG^6S?);/\ED^RV?Y+)_ELWR6S_)9/LMG^2R? 5Y;-\EL_R63[_^Y__`;O*%#(`N`$` ` end --- snort_manager.tgz.uu ---