#!/bin/bash -vx 

# Lander drop-in for capturing PCAP and transparently converting to ERF.
# Substitute for dagsnap_lander where appropriate.
# Nothing but a wrapper for tcpdump and dag_convert.
# See usage (-h) for more info.

usage() {
    cat <<EOF
USAGE: $0 [options]
where options are
  -h | --help    show this help
  -d             snap from device (defaults to eth0)
  -D <ival>      ignored
  -B <dir/file>  full path of the output file(s) - default: $TCPDUMPDIR/$TCPDUMPFILES\*
  -S <filesize>  max file size in megabytes
  -N <filename>  file is used to keep track of sequence numbers
  -O <tcpdumpopt>options to pass to tcpdump
  -U             use UTC
  -W <watchfile> sleep/suspend capture if this file doesn't exist
  -s <snaplen>   capture at most this many bytes from each packet
  --daemon       run in background
EOF
}

process_options() {
    OPTIND=1
    while getopts "d:hs:D:B:S:N:O:UW:-" opt; do
        case "$opt" in
            ( h ) usage; exit 0 ;;
            ( d ) opt_dev=$OPTARG; shift ;;
            ( D ) shift; ;; #ignore burst option
            ( B ) opt_outputbase=$OPTARG; shift ;;
            ( S ) opt_filesize=$OPTARG; shift ;;
            ( N ) opt_maxseqfile=$OPTARG; shift ;;
            ( O ) opt_tcpdumpopt=$(eval echo $OPTARG); shift ;;
            ( U ) opt_utc="yes" ;;
            ( W ) opt_watchfile=$OPTARG; shift ;;
            ( s ) opt_snaplen=$OPTARG; shift ;;
            ( - ) {
                case "$1" in
                    ( --daemon* ) opt_daemonize=yes ;;
                    ( "--help" ) usage; exit 0 ;;
                    ( * ) echo "unknown long option $1" 2>&1; usage; exit 1 ;;
                esac;
            } ;;
            ( * ) echo "unknown option $1" 2>&1; usage; exit 1 ;;
        esac
        shift
        OPTIND=1
    done
}

check_watchfile () {
    [ -z "$opt_watchfile" ] && return

    if [ -e $opt_watchfile ]; then
	[ $state == "SLEEPING" ] && kill -CONT $tcpdump_pid
	state="RUNNING"
    else
	[ $state == "RUNNING" ] && kill -STOP $tcpdump_pid
	state="SLEEPING"
    fi
}

exiting=
on_signal () {
    kill -HUP $tcpdump_pid
    exitting="yes"
    echo "Exiting on signal..." >&2
    #don't exit yet: wait until all files are renamed and converted
    #exit 1
}

rename_convert () {
    local _file=$1

    local _epoch=$($TCPDUMP -r $_file  -q -tt -c 1 -nn 2>/dev/null |cut -f1 -d.)
    local _utc=
    [ -n "$opt_utc" ] && _utc="-u"
    local _timestamp=$(date $_utc -d @$_epoch +"%Y%m%d-%H%M%S")
    local _newname=$(printf "%s-%s" $_timestamp $seq)
    if [ "$LANDER_FORMAT" == "pcap" ]; then
		#no conversion needed, just rename
		mv $_file $TCPDUMPDIR/$_newname || {
            echo "failed to rename $_file $TCPDUMPDIR/$_newname" >&2
	    	return 1
		}
    else
		$LANDER_TRACECONVERT -Z none pcapfile:$_file erf:$TCPDUMPDIR/$_newname || {
            #failure
			echo "failed on $_file -> $TCPDUMPDIR/bad-$(basename $_file)" >&2
		    rm -f $TCPDUMPDIR/$_newname
			mv -f $_file $TCPDUMPDIR/bad-$(basename $_file)
			return 1
		}
    fi
    #success
    /bin/rm -f $_file
    chown plander:${LANDER_GROUPS[raw]} $TCPDUMPDIR/$_newname
    mv $TCPDUMPDIR/$_newname $opt_outputbase/$_newname
    return 0
}

rename_convert_all_firstrun="yes"
rename_convert_all () {
    local _pn="%empty%"
    local _n
    for _n in `find  $TCPDUMPDIR -maxdepth 1 -name $TCPDUMPFILES* -type f -print \
	| sed -n 's/.*-\([0-9]*\)/\1-/; T; p' | sort -n`; do
	[ -n "$exiting" ] && exit 1
	_n=${_n%-}
	if [ "$_pn" != "%empty%" ]; then
	    #local _num=${-pn:-0}
	    seq="0000000000$(( 10#$seq+1 ))"
	    seq=${seq: -8}
	    rename_convert "$TCPDUMPDIR/$TCPDUMPFILES$_pn"
	    [ -n "$opt_maxseqfile" ] &&  echo $seq >$opt_maxseqfile
	    [ -z "$rename_convert_all_firstrun" ] && check_watchfile
	fi
	_pn=$_n
    done #processed all nums except for max (which may be in use)
    [ -z "$rename_convert_all_firstrun" ] && return
    # deal with the last one
    if [ "$_pn" != "%empty%" ]; then
	seq="0000000000$(( 10#$seq+1 ))"
	seq=${seq: -8}
	rename_convert "$TCPDUMPDIR/$TCPDUMPFILES$_pn"
	[ -n "$opt_maxseqfile" ] &&  echo $seq >$opt_maxseqfile
    fi
    # finally remember initial run was done
    rename_convert_all_firstrun=
}

#this is really just sleep/rename/checktcpdumphealth loop
capture () {
    trap on_signal EXIT
    trap on_signal HUP
    while [ -z "$exiting" ]; do
 	check_watchfile
	sleep 2s #XXX parametrize

	rename_convert_all
	#check if tcpdump child is still alive
	if ! kill -0 $tcpdump_pid; then
	    echo "tcpdump child is dead, exitting..." >&2
	    exiting="yes"
	    #don't exit yet: wait until all files are renamed and converted
	fi
    done #while -z "$exiting"
}

#MAIN:
TCPDUMP=/usr/sbin/tcpdump

#options
opt_outputbase=$TCPDUMPDIR/$TCPDUMPFILES
opt_filesize=1 #in megabytes
opt_maxseqfile=
opt_watchfile=
opt_snaplen=64
opt_dev=eth0
opt_daemonize=

cmdline=$@

LANDERCONFIG=~plander/lander.conf
#read config
if [ -r $LANDERCONFIG ]; then
    . $LANDERCONFIG
else
    echo "ERROR: Config file $LANDERCONFIG does not exist or not readable" >&2
    exit 1
fi

umask 0077 #just in case

#XXX move to config
TCPDUMPDIR=$LANDER_TMPDIR
TCPDUMPFILES="tcpdump-$(hostname -s)-" #relative to TCPDUMPDIR

[ -z "$LANDER_SNAP_LOGFILE" ] && {
    echo "LANDER_SNAP_LOGFILE is not set in lander.conf" >&2
    exit 1
}
exec >>$LANDER_SNAP_LOGFILE 2>&1 </dev/null 

process_options $cmdline

# initialize seq
seq=-1
[ -n "$opt_maxseqfile" ] && read seq < $opt_maxseqfile

# deal with any files remaining from the last run
rename_convert_all

#fork TCPDUMP
tcpdump_options="-w $TCPDUMPDIR/$TCPDUMPFILES"
[ -n "$opt_filesize" ] && tcpdump_options="$tcpdump_options -C $opt_filesize"
[ -n "$opt_snaplen" ]  && tcpdump_options="$tcpdump_options -s $opt_snaplen"
[ -n "$LANDER_RUNAS_USER1" ]  && tcpdump_options="$tcpdump_options -Z $LANDER_RUNAS_USER1"
[ -n "$opt_dev" ]      && tcpdump_options="$tcpdump_options -i $opt_dev"
tcpdump_options="$tcpdump_options $opt_tcpdumpopt"

echo $TCPDUMP $tcpdump_options &
$TCPDUMP $tcpdump_options &
tcpdump_pid=$!
state="RUNNING" #or SLEEPING

sleep 1
if ! kill -0 $tcpdump_pid; then
    echo "tcpdump child is dead, exitting..." >&2
    exit 1
fi

if [ -z "$opt_daemonize" ]; then
    capture
else
    capture &
    exit 0
fi
