#!/bin/bash
#
# Nyquist Network Capture tool
#
# Uses tcpdump to capture network traffic, writes to nqnetcap.pcap
#
# Input:
#	/tmp/nqnetcap-seconds	 - Contains number of seconds to capture traffic
#				   (Default: 900)
#	/tmp/nqnetcap-options	 - Contains command-line options for tcpdump
#				   (Default: -i any)
#	/tmp/nqnetcap-expression - Contains expression to use via tcpdump -F
#				   (Default: <no expression>)
#
# Options:
#	--debug, -d		 - Dump all configuration and exit without running tcpdump
#
# (C) Copyright 2022-2024, Bogen Communications LLC. All rights reserved.
#

DEBUG_MODE=0
if [ "$1" = "--debug" ] || [ "$1" = "-d" ]; then
   DEBUG_MODE=1
fi

function remove_files {
   /bin/rm -f /tmp/nqnetcap-running /tmp/nqnetcap-seconds /tmp/nqnetcap-options /tmp/nqnetcap-expression /tmp/nqnetcap-stop &> /dev/null
   /bin/rm -f /run/nqnetcap.pid &> /dev/null
}

function set_owner {
   /bin/chown -f www-data:www-data "$1" &> /dev/null
}

if [ -e /run/nqnetcap.pid ]; then
   CURRENT_PID=$(< /run/nqnetcap.pid)

   case $CURRENT_PID in
      *[!0-9]*) echo "0:Bad PID detected!"
                /bin/rm -f /run/nqnetcap.pid &> /dev/null
                exit 1 ;;
      *) ;;
   esac

   if kill -0 "$CURRENT_PID" &> /dev/null
   then
      if [ -e /proc/$CURRENT_PID/status ]; then
         read -r CMD_HEAD CMD_NAME < /proc/$CURRENT_PID/status
         if [ "$CMD_NAME" != "nqnetcap" ]; then
            /bin/rm -f /run/nqnetcap.pid &> /dev/null
         else
            # NOTE: DO NOT change the content of the following echo, the web interface depends on it.
            echo -n "0:Capture already running, try again later"
            exit 1
         fi
      else
         # NOTE: DO NOT change the content of the following echo, the web interface depends on it.
         echo -n "0:Capture already running, try again later"
         exit 1
      fi
   else
      /bin/rm -f /run/nqnetcap.pid &> /dev/null
   fi
fi

trap remove_files EXIT

echo $$ > /run/nqnetcap.pid
set_owner /run/nqnetcap.pid

/bin/rm -f /tmp/nqnetcap-stop /tmp/nqnetcap-running /tmp/nqnetcap.log &> /dev/null

EXPORT_FOLDER=/var/www/html/exports/
mkdir -p $EXPORT_FOLDER
set_owner $EXPORT_FOLDER

PCAP_FILE=$EXPORT_FOLDER/nqnetcap.pcap

if [ ! -d $EXPORT_FOLDER ]; then
   echo "0:Exports folder does not exist!"
   exit 1
fi

/bin/rm -f $PCAP_FILE &> /dev/null

#
# Get number of seconds to run, and check validity
#
NQ_NETCAP_SECS=
if [ -e /tmp/nqnetcap-seconds ]; then
   NQ_NETCAP_SECS=$(< /tmp/nqnetcap-seconds)
   set_owner /tmp/nqnetcap-seconds
fi

if [ -z "$NQ_NETCAP_SECS" ]; then
   NQ_NETCAP_SECS=900
else
   case $NQ_NETCAP_SECS in
      *[!0-9]*) echo "0:Number of seconds is not numeric"
                exit 1 ;;
      *) ;;
   esac
fi

if [ $NQ_NETCAP_SECS -lt 1 ]; then
   echo "0:Seconds must be greater than 0"
   exit 1
fi

#
# Get tcpdump options; if options not provided, use default.
# Make sure that user did not include -W, -w, -G, or command execution.
#
NQ_NETCAP_OPTIONS=
if [ -e /tmp/nqnetcap-options ]; then
   NQ_NETCAP_OPTIONS=$(< /tmp/nqnetcap-options)
   set_owner /tmp/nqnetcap-options
fi

if [ -z "$NQ_NETCAP_OPTIONS" ]; then
   NQ_NETCAP_OPTIONS="-i any"
   VALID_OPTIONS=YES
else
   VALID_OPTIONS=NO
   case $NQ_NETCAP_OPTIONS in
      *-W*) echo "0:Can't use -W" ;;
      *-G*) echo "0:Can't use -G" ;;
      *-F*) echo "0:-F option not supported" ;;
      *-I*) echo "0:-I option not supported" ;;
      *--monitor-mode*) echo "0:--monitor-mode option not supported" ;;
      *--relinquish-privileges=*) echo "0:--relinquish-privileges option not supported" ;;
      *-r*) echo "0:-r option not supported" ;;
      *-z*) echo "0:-z option not supported" ;;
      *-Z*) echo "0:-Z option not supported" ;;
      *-V*) echo "0:-V option not supported" ;;
      *\$\(*) echo "0:Invalid options" ;;
      *\`*) echo "0:Invalid options" ;;
      *) VALID_OPTIONS=YES ;;
   esac
fi

if [ "$VALID_OPTIONS" = "NO" ]; then
   exit 1
fi

#
# Setup tcpdump expression to use
#
NQ_NETCAP_EXP=
if [ -e /tmp/nqnetcap-expression ]; then
   NQ_NETCAP_EXP="-F /tmp/nqnetcap-expression"
   /bin/chmod -f 666 /tmp/nqnetcap-expression &> /dev/null
   set_owner /tmp/nqnetcap-expression
   # Remove command-line escape characters that are not needed
   /bin/sed -i -e "s/'//g" -e "s/\\\//g" /tmp/nqnetcap-expression &>/dev/null
fi

#
# Build command-line for tcpdump
#
NQ_NETCAP_CMD="-G $NQ_NETCAP_SECS -W 1 -w $PCAP_FILE $NQ_NETCAP_OPTIONS $NQ_NETCAP_EXP"

TCPDUMP_BIN=$(whereis -b -B /bin /sbin /usr/bin /usr/sbin /usr/local/bin/ -f tcpdump)
TCPDUMP_BIN=$(echo $TCPDUMP_BIN | sed 's/^.*://' | awk '{print $1}')

if [ -z "$TCPDUMP_BIN" ]; then
   echo "0:tcpdump not found"
   exit 1
fi

echo -n "tcpdump $NQ_NETCAP_CMD" > /tmp/nqnetcap.log
set_owner /tmp/nqnetcap.log

if [ -n "$NQ_NETCAP_EXP" ]; then
   /bin/cat /tmp/nqnetcap-expression >> /tmp/nqnetcap.log
fi

echo "" >> /tmp/nqnetcap.log

#
# Debug mode: dump configuration and exit
#
if [ $DEBUG_MODE -eq 1 ]; then
   {
   echo "=== nqnetcap Debug Configuration ==="
   echo ""
   echo "Configuration Files:"
   echo "  /tmp/nqnetcap-seconds:    $([ -e /tmp/nqnetcap-seconds ] && echo "exists" || echo "not found")"
   echo "  /tmp/nqnetcap-options:    $([ -e /tmp/nqnetcap-options ] && echo "exists" || echo "not found")"
   echo "  /tmp/nqnetcap-expression: $([ -e /tmp/nqnetcap-expression ] && echo "exists" || echo "not found")"
   echo ""
   echo "Resolved Values:"
   echo "  Capture Duration: $NQ_NETCAP_SECS seconds"
   echo "  tcpdump Options:  $NQ_NETCAP_OPTIONS"
   if [ -e /tmp/nqnetcap-expression ]; then
      echo "  Filter Expression:"
      /bin/sed 's/^/    /' /tmp/nqnetcap-expression
   else
      echo "  Filter Expression: (none)"
   fi
   echo ""
   echo "Derived Settings:"
   echo "  Export Folder:    $EXPORT_FOLDER"
   echo "  PCAP File:        $PCAP_FILE"
   echo "  Timeout:          $((NQ_NETCAP_SECS + 30)) seconds"
   echo ""
   echo "tcpdump Command:"
   echo "  $TCPDUMP_BIN -G $NQ_NETCAP_SECS -W 1 -w $PCAP_FILE $NQ_NETCAP_OPTIONS $NQ_NETCAP_EXP"
   echo ""
   echo "=== End Debug Configuration ==="
   } >&2
   exit 0
fi

/usr/bin/touch /tmp/nqnetcap-running
/bin/chmod -f 444 /tmp/nqnetcap-running &> /dev/null
set_owner /tmp/nqnetcap-running

#
# Run tcpdump
#
# Monitor for and stop if:
# 1. Timeout (to prevent runaway)
# 2. Stop request from user (/tmp/nqnetcap-stop)
# 3. Available disk space is low (524288 blocks = ~2GB with 4KB block size)
#

coproc $TCPDUMP_BIN $NQ_NETCAP_CMD >> /tmp/nqnetcap.log 2>&1
set_owner $PCAP_FILE

NETCAP_TIMEOUT=$((NQ_NETCAP_SECS + 30))
NETCAP_STARTED=0

while kill -0 $COPROC_PID &> /dev/null
do
   if [ $NETCAP_TIMEOUT -le 0 ] || [ -e /tmp/nqnetcap-stop ] || [ $(/usr/bin/stat -f / --printf %a) -lt 524288 ]; then
      kill -SIGINT $COPROC_PID
      /bin/sleep 3
      if kill -0 $COPROC_PID > /dev/null 2>&1
      then
         kill -SIGKILL $COPROC_PID
      fi

      if [ $NETCAP_TIMEOUT -gt 0 ] && [ ! -e /tmp/nqnetcap-stop ]; then
         if [ $NETCAP_STARTED = 0 ]; then
            echo "Not enough free disk space to start capture; must have at least 2G of free disk space to start." >> /tmp/nqnetcap.log
         else
            echo "Not enough free disk space to continue capture; must have at least 2G of free disk space to continue. Capture stopped" >> /tmp/nqnetcap.log
	 fi
         /bin/df -h --output=avail / >> /tmp/nqnetcap.log
      fi
   else
      NETCAP_STARTED=1
      /bin/sleep 1
   fi

   NETCAP_TIMEOUT=$((NETCAP_TIMEOUT - 1))
done

wait -n $COPROC_PID

if [ $? -eq 0 ]; then
   set_owner /tmp/nqnetcap.log
   /bin/chmod -f 444 /tmp/nqnetcap.log &> /dev/null
   if [ ! -e $PCAP_FILE ]; then
      case $NQ_NETCAP_OPTIONS in
         *-D*) echo -n "0:Network Interface list" ;;
	 *--list-interfaces*) echo -n "0:Network Interface list" ;;
	 *-L*) echo -n "0:Data link types" ;;
	 *--list-data-link-types*) echo -n "0:Data link types" ;;
	 *--version*) echo -n "0:Version information" ;;
	 *-h*) echo -n "0:tcpdump help" ;;
         *) echo "0:Failed to create pcap file" ;;
      esac
      exit 1
   fi
   /bin/sync -f $PCAP_FILE &> /dev/null
   set_owner $PCAP_FILE
   /bin/sleep 5
   echo "1:Success"
else
   set_owner /tmp/nqnetcap.log
   /bin/chmod -f 444 /tmp/nqnetcap.log &> /dev/null
   echo -n "0:Capture failed, check command for errors"
fi

exit 0
