#!/bin/bash shopt -s extglob # settings readonly LOGDIR='/var/log/tcpdump' # Name or location of main log file. If not a path, will be saved to LOGDIR MAINLOGFILE='main.log' ## File is reduced when this size is reached. readonly MAINLOGFILEMAXSIZE=$(( 20 * 1024 * 1024 )) ## bytes ## This is the extra space given when file is reduced. readonly MAINLOGFILEALLOWANCE=$(( 1 * 1024 * 1024 )) ## bytes ## Recommended: >= 300 readonly MAINLOGCHECKINTERVALS=300 ## seconds # Location of tcpdump readonly TCPDUMP='/usr/sbin/tcpdump' ## customize arguments here e.g. (-C 1 "another with spaces") readonly MY_IP=$(host `hostname` | cut -d' ' -f4) # Arguments for tcpdump readonly TCPDUMPARGS=("(src $MY_IP and dst port 22) or (dst $MY_IP and src port 22)") # Capture log prefix readonly TCPDUMPCAPTUREFILEPREFIX='thesis-capture-' # Capture log suffix readonly TCPDUMPCAPTUREFILESUFFIX='' # Frequency to check that tcpdump is running readonly TCPDUMPCHECKINTERVALS=60 ## seconds # Frequency to try and re-run tcpdump if it failed readonly TCPDUMPTRYTIMEOUT=20 ## seconds # How long to keep log files around readonly OLD=14 ## days # How large of a block to copy at a time readonly DDBLOCKSIZE=512 ## bytes # Location for temporary files readonly TEMPDIR='/var/tmp' # other variables CURRENTDATE='' QUIT=false TCPDUMPPID=0 # functions # Print to log file function log { echo "[$(date '+%F %T')] $1" >> "$MAINLOGFILE" echo "$1" } # Check if tcpdump process is running # Returns 1 if not found function checktcpdump { [[ $TCPDUMPPID -ne 0 ]] && [[ -e /proc/$TCPDUMPPID ]] && kill -s 0 "$TCPDUMPPID" 2>/dev/null } # Creates a new log file for tcpdump and forks a new process # Returns 1 on error function starttcpdump { log "Starting tcpdump..." CURRENTDATE=$(date +%F) local BASENAME="${TCPDUMPCAPTUREFILEPREFIX}${CURRENTDATE}${TCPDUMPCAPTUREFILESUFFIX}" local -a EXISTINGFILES # Generate a list of all files matching pattern and put into array { if [[ BASH_VERSINFO -ge 4 ]]; then readarray -t EXISTINGFILES else EXISTINGFILES=() local -i I=0 while read LINE; do EXISTINGFILES[I++]=$LINE done fi } < <(compgen -G "$LOGDIR/${BASENAME}.+([[:digit:]]).pcap*([[:digit:]])") # Finds the lowest log file number higher than all existing files local NEXT_SESSION=0 if [[ ${#EXISTINGFILES[@]} -gt 0 ]]; then local SESSION_NUMBER for FILE in "${EXISTINGFILES[@]}"; do SESSION_NUMBER=${FILE%.pcap*} SESSION_NUMBER=${SESSION_NUMBER##*.} # If session_number is a gigit and >= next_session, then # next_session = session_number + 1 [[ $SESSION_NUMBER == +([[:digit:]]) && SESSION_NUMBER -ge NEXT_SESSION ]] && NEXT_SESSION=$(( SESSION_NUMBER + 1 )) done fi # Forks tcpdump and checks that it succeeded local OUTPUTFILE=$LOGDIR/${BASENAME}.$NEXT_SESSION.pcap "$TCPDUMP" "${TCPDUMPARGS[@]}" -w "$OUTPUTFILE" & if [[ $? -ne 0 ]]; then TCPDUMPPID=0 return 1 fi TCPDUMPPID=$! disown "$TCPDUMPPID" checktcpdump } # Try to start tcpdump. If $QUIT, then exit after a failed attempt # otherwise try again function starttcpdumploop { until starttcpdump; do log "Error: Failed to start tcpdump. Waiting for $TCPDUMPTRYTIMEOUT seconds before next attempt..." read -t $TCPDUMPTRYTIMEOUT [[ $QUIT = true ]] && { log "Ending tcpdump manager script." exit } done } # Kill the current tcpdump process and set QUIT to true function stoptcpdump { log "Stopping tcpdump..." kill "$TCPDUMPPID" # If tcpdump is still running, sigkill it checktcpdump && kill -s 9 "$TCPDUMPPID" TCPDUMPPID=0 QUIT=true } # Stop tcpdump and start it again function restarttcpdump { local TMPQUIT=$QUIT log "Restarting tcpdump..." checktcpdump && stoptcpdump QUIT=$TMPQUIT starttcpdumploop } # Sets QUIT to true function catchsignals { log "Caught a signal." QUIT=true } function main { local CAPTUREFILEPATTERN FILE NEWDATE SIZE TEMPFILE local -i I # Initializes variables and starts tcpdump child CAPTUREFILEPATTERN="${TCPDUMPCAPTUREFILEPREFIX}[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]${TCPDUMPCAPTUREFILESUFFIX}.pcap*" [[ $MAINLOGFILE != */* ]] && MAINLOGFILE=$LOGDIR/$MAINLOGFILE log "Starting tcpdump manager script..." [[ $MAINLOGFILEMAXSIZE == +([[:digit:]]) && MAINLOGFILEMAXSIZE -gt DDBLOCKSIZE ]] || { echo "MAINLOGFILEMAXSIZE is not valid." return 1 } [[ $MAINLOGFILEALLOWANCE == +([[:digit:]]) && MAINLOGFILEALLOWANCE -gt DDBLOCKSIZE && MAINLOGFILEALLOWANCE -lt MAINLOGFILEMAXSIZE ]] || { echo "MAINLOGFILEALLOWANCE is not valid." return 1 } trap catchsignals SIGQUIT SIGINT SIGKILL SIGTERM mkdir -p "$LOGDIR" starttcpdumploop # for (( I = 1;; I = (I + 1) % 10000 )); do # Wait 1 second ## we have to separate this from the next statement ## to ensure proper handling of signals read -t 1 # If QUIT, exit loop [[ $QUIT = true ]] && break # Handle daily file rotations if (( (I % TCPDUMPCHECKINTERVALS) == 0 )); then NEWDATE=$(exec date +%F) if [[ ! $NEWDATE = "$CURRENTDATE" ]]; then log "A new day has come." # Delete files older than $OLD if read FILE; then log "Deleting $OLD-days old files..." while log "Deleting $FILE..." rm -f "$FILE" read FILE do continue done fi < <(exec find "$LOGDIR" -name "$CAPTUREFILEPATTERN" -daystart -ctime "+$OLD") # or -mtime? # Delete empty files find "$LOGDIR" -size 0 -delete restarttcpdump fi fi # Truncates main log from front if it exceeds maximum if (( (I % MAINLOGCHECKINTERVALS) == 0 )); then SIZE=$(exec stat --printf=%s "$MAINLOGFILE") if [[ $SIZE == +([[:digit:]]) && SIZE -gt MAINLOGFILEMAXSIZE ]]; then log "Reducing log data in $MAINLOGFILE..." TEMPFILE=$TEMPDIR/tcpdump-$RANDOM.tmp SKIP=$(( (SIZE - (MAINLOGFILEMAXSIZE - MAINLOGFILEALLOWANCE)) / DDBLOCKSIZE )) dd "bs=$DDBLOCKSIZE" "skip=$SKIP" "if=$MAINLOGFILE" "of=$TEMPFILE" ## better than mv cat "$TEMPFILE" > "$MAINLOGFILE"; rm -f "$TEMPFILE" fi fi done # Stop tcpdump checktcpdump && stoptcpdump log "Ending tcpdump manager script." } # start main