#!/bin/sh -
#
# NXFax accounting 1.0 - a gawk script to summarize NXFax usage
# by Andreas Mueller [afm@mathi.uni-heidelberg.de] and
# Adrian Steinmann [ast@lia.di.epfl.ch] 
#
#
PATH=/bin:/usr/bin:/usr/ucb:/etc:/usr/etc
IFS=' 	
'
export PATH IFS
#
TRIMDAYS=30	# how many days to wait between trimming
KEEPLINES=200	# how many lines to inherit when trimming
#
# define LOGFILE here, possibly override later
#
# NXFax 1.03 uses /usr/adm/nxfax/nxfaxlog
NXFAXDIR=/usr/adm/nxfax
if [ ! -d ${NXFAXDIR} ]
then
	# NXFax 1.02 uses /etc/nxfax/nxfaxlog
	NXFAXDIR=/etc/nxfax
	if [ ! -d ${NXFAXDIR} ]
	then
		echo "Fatal error: Cannot find NXFax log file directory" >&2
		usage
	fi
fi
#
LOGFILE=${NXFAXDIR}/nxfaxlog
#
# OLOGFILE is where we put the log file when we trim
#
OLOGFILE="${LOGFILE}.old"
#
# COSTFILE, usually in same directory as logfile, possibly override below
#
: ${COSTFILE:=${NXFAXDIR}/nxfax.cost}
#
# we use GNU awk for builtin and custom functions
#
GAWK=/usr/local/bin/gawk
#
# this is what we say as a heading and subject line in case of mail (-m option)
#
WELCOME="NXFax Accounting @ `hostname`"
#
# CMD may be changed to "Mail -s '${WELCOME}' $recipients" below (-m option)
#
CMD=cat
#
export CMD WELCOME GAWK COSTFILE LOGFILE OLOGFILE NXFAXDIR KEEPLINES TRIMDAYS
#
# temporary files are cleanup up on exit or trap
#
TMPFHED=/tmp/faxhed$$
TMPFSND=/tmp/faxsnd$$
TMPFCST=/tmp/faxcst$$
TMPFPAR=/tmp/faxcst$$
TMPFRCV=/tmp/faxrcv$$
TMPFSUM=/tmp/faxsum$$
#
export TMPFHED TMPFSND TMPFCST TMPFPAR TMPFRCV TMPFSUM
#
umask 077
#
help ()
{
echo
cat<<!!
This shell script uses GNU-awk '$GAWK' to 
parse log files generated by NXFax versions 1.02 and 1.03. By
default, the script will search for a NXFax log file in the
directories "/etc/nxfax/" and "/usr/adm/nxfax/".  It will
also look for a file called "nxfax.cost" in that directory
describing the calling costs for various times and phone
prefixes. For an example cost description file, invoke this
script with the option "-sample". If the cost description file
is missing, a simple grand total summary is calculated.
Alternate cost description or NXFax log files can be specified
via the -c and -l options respectively. With the option -m
<address_list>, the summary is sent to the recipients
specified in address_list.  
!!
echo
usage
}
#
usage ()
{
    echo "Usage: $0 [ -help | -sample ]" >&2
    echo "       $0 [ [-l] <log> ] [ -c <costs> ] [ -m {address_list} ]" >&2
    exit 1
}
#
cleanup ()
{
    rm -f ${TMPFHED} ${TMPFSND} ${TMPFPAR} ${TMPFCST} ${TMPFRCV} ${TMPFSUM}
}
#
# note that NXFax 1.02 and 1.03 trims itself to 1000 lines at startup time
#
trimlog ()
{
if [ -f ${OLOGFILE} ]
then
    (find ${OLOGFILE} -mtime +${TRIMDAYS} -print | 
	grep ${OLOGFILE} ) > /dev/null 2>&1
    if [ $? -eq 0 ]
    then
	( cp -p ${LOGFILE} ${OLOGFILE} &&
	    tail -${KEEPLINES} ${LOGFILE}.old > ${LOGFILE} &&
	    echo "${LOGFILE} rotated to ${OLOGFILE}, ${KEEPLINES} lines kept"
	) 2> /dev/null
    fi
else
    # if the old file doesn't exist, create it today so that
    # trimlog() will begin to rotate after ${TRIMDAYS}
    touch ${OLOGFILE}
fi
}
#
sample ()
{
cat<<!!
# Sample cost desciption file: '${NXFAXDIR}/nxfax.cost'
# 
# This file is used by the nxfaxsummary shell script to compute the
# approximate costs of outgoing fax traffic. Its precision depends
# highly on the amount of detail given in this file.
#
# FORMAT DESCRIPTION - there are three types of lines:
#
# $<currency-string>
# :<prefix>:<unit-time>:<unit-cost>:<connect-cost>:<comments>
# +<prefix>:<comments>
#
# The '$' line defines a currency string to be used in the summary.
# Lines beginning with ':' define cost computation parameters.
# Lines beginning with '+' are called summary lines and are used
# to indicate how costs should be summarized by nxfaxsummary.sh
#
# The script scans the called phone number and uses the parameters
# associated with the first matching <prefix> to compute the costs
# for such a call. If t is the connect time in seconds, then the
# cost is computed according to the formula
#
#     cost = connect-cost + (t/unit-time)*unit-cost
#
# connect-cost is the minimum fee per connection (0 in most situations)
#	       this can be used to account for connect time measured
#	       by NXFax which is usually shorter than the effective
#	       duration of a call including initial handshake time;
# unit-time    is the duration (in seconds) of a time unit; 
# unit-cost    is the price of one such unit-time. 
#
# In the final summary, these computations are cumulatively grouped
# according to the prefixes in the summary lines (the '+' lines).
#
# Empty lines and lines beginning with a '#' are ignored.
# 
# EXAMPLE COST DESCRIPTION (for Germany)
\$DM
:0041:0:12:0.23:Calls to Switzerland
:00:0:12:0.23:Other international calls
+00:International calls: 
:0:0:60:0.23:Long distance calls within Germany (Regionalzone)
::0:360:0.23:Local calls
+:Calls within Germany: 
#
# The cost description file should always include a line beginning
# with an empty prefix string, this is used as the default for phone
# numbers that don't match any other prefix. The same is true for
# summary lines. Note that the comments of the summary lines are
# used as line item titles, hence they are often ended with a ':'.
#
# In other words, a minimum cost file may consist of just three lines:
#
# \$\$
# ::0:3600:1.20:Detailed cost description
# +:Detailed summary description:
#
# If no cost description file is found nor specified, then a simple total
# transmit time and average per page is output.
#
# Most phone companies charge differently during and after business hours
# - use the parameters that are likely to be valid when *you* send faxes.
#
# To be done: - differentiate between rates during different time periods
#
!!
exit 0
}
#
trap 'cleanup; exit 9' 2 3 15
#
# parse options - default is output to stdout (use -m option to mail)
#
dorecipients=0
docost=0
dolog=0
while [ $# -gt 0 ]
do
    while [ $dorecipients -ge 1 -a $# -gt 0 ]
    do
	dorecipients=2
	case x$1 in
	x-*)
	    break
	    ;;
	*)
	    recipients="$recipients $1"
	    CMD="Mail -s '${WELCOME}' $recipients"
	    shift
	    ;;
	esac
    done
    if [ $dorecipients -eq 2 -a `echo $recipients | wc -w` -eq 0 ]
    then
	echo "Fatal error: -m option lacks at least one addressee" >&2
	usage
    else
	dorecipients=0
    fi 	    
    if [ $docost -eq 1 -o $dolog -eq 1 ]
    then
	if [ $# -gt 0 ]
	then
	    if [ ! -r $1 ]
		then echo "Fatal error: file '$1' unreadable" >&2
		usage
	    else
		if [ $dolog -eq 1 ]
		then
		    LOGFILE=$1
		    dolog=2
		else
		    COSTFILE=$1
		    docost=2
		fi
	    fi
	    shift
	else
	    echo "Fatal error: option lacks an argument" >&2
	    usage
	fi
    fi
    case x$1 in
    x-m)
	shift
	if [ $dorecipients -ne 0 -o $# -lt 1 ]; then usage; fi
	dorecipients=1
	;;
    x-c)
	if [ $docost -ne 0 ]; then usage; fi
	shift; docost=1
	;;
    x-l)
	if [ $dolog -ne 0 ]; then usage; fi
	shift; dolog=1
	;;
    x-h*)
	help
	;;
    x-s*)
	sample
	;;
    x-*)
	usage
	;;
    *)
	dolog=1
	;;
    esac
done
#
if [ ! -r $LOGFILE ]
then
    echo "Fatal error: Log file '${LOGFILE}' unreadable" >&2
    usage
fi
OLOGFILE="${LOGFILE}.old"
#
# Finally do some real work: find first and last date in logfile
#
( echo -n "\n${WELCOME} "
  ${GAWK} '
  BEGIN { found=1; }
  $3 ~ /Sun|Mon|Tue|Wed|Thu|Fri|Sat/ \
                      { date=sprintf("%2d %s %d %s",$5,$4,$7,$6);
  		      if (found) { printf("from %s ",date)
  		                   found=0 } 
  		    }
  END { printf("to %s\n",date); }' ${LOGFILE}
) > ${TMPFHED} 2>&1
#
# now search logfile for faxes sent, collect information in ${TMPFSND}
#
${GAWK} \
 'function resetvars() { connected=false; call=false
                               speed="?"; res="?"; errors="?"; number="?"
	         	       ctime="?"; ttime="?"; pages=0
			       date=" ?  ?       ?   "; calldate=date
		              }
  function printsummary() { printf("%16s %-20s %3s  %2s %4s  %5s  %5s %s\n",
                            calldate,number,pages,substr(res,1,2),
			    speed,
			    ttime,ctime,errors)
			  }

BEGIN { true=1; false=0; resetvars() }
$3 ~ /(Sun)|(Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)/ \
                    { date=sprintf("%2d %s  %s",$5,$4,$6) }
# Note that NXFax 1.03 frequently contains the line
# "%> NXFax: caller id or silent answer forces rings to 3"
$3 ~ /call/ { if ($4 !~ /complete/ && $8 !~ /forces/ ) { 
		call=true; number=substr($0,15,20)
	    }
	}
$3 ~ /line/ { if ($4 ~ /busy/) { calldate=date } }
$3 ~ /Connected/ { if (call==true) { connected=true; calldate=date } }
$4 ~ /resolution/ { res=$3 }
$4 ~ /bps/ { speed=sprintf("%4.1f",$3/1000) }
$3 ~ /page/ { pages=$4 }
$3 ~ /hangup/ { if ( substr($3, 8, 1) == "0" )
			{ errors=$4" "$5 }
		else
			{ errors="Hangup ERROR" }
              }
/connect time/ { ctime=$5 }
/transmit time/ { ttime=$5
                  if (connected) {
                      printsummary()
		  }
		  resetvars()
		}
$4 ~ /halt|busy/ { if (call) {
                  printsummary()
                  resetvars()
	      }
             }
/no carrier/ { if (call) { 
                   calldate=date; errors="no carrier" 
		   printsummary()
		   resetvars()
		  }
	     }
		
' ${LOGFILE} > ${TMPFSND} 2>&1
#
#
# use the information collected in the previous step to get a
# more accurate cost computation:
#
if [ ! -r $COSTFILE ]
then
    awk '{ print substr($0,53,5)" "substr($0,39,3)" "substr($0,19,20) }' \
    ${TMPFSND} | ${GAWK} '
    BEGIN { transmit=0; pages=0;  }
    $1 !~ /\?/ { pages+=$2; split($1,ttime,":")
                 transmit+=60*ttime[1]+ttime[2] }
		
    END { 
	time_h=int(transmit/3600); time_s=transmit-3600*time_h;
	printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
	    time_h,time_s/60,time_s%60,pages)
	if (pages>0) printf("\t~ %8.2f sec/page",transmit/pages)
	printf("\n")
    }
    ' > ${TMPFCST} 2>&1
else
    awk '{ print substr($0,53,5)" "substr($0,39,3)" "substr($0,60,5)\
           " "substr($0,19,20) }' ${TMPFSND} \
    | ${GAWK} -v costparamfile=${COSTFILE} '
    BEGIN { # initialize parameter arrays
	"sed -n -e s/\\^\\\\$//p "costparamfile | getline currency
	close("sed -n -e s/\\^\\\\$//p "costparamfile)
	FS=":"; counter=0;
	while ("sed -n -e s/\\^://p "costparamfile | getline ) {
	    counter++
	    costp[counter,"prefix"]=$1
	    costp[counter,"connect"]=$2
	    costp[counter,"tunit"]=$3
	    costp[counter,"unitc"]=$4
	}
	close("sed -n -e s/\\^://p "costparamfile)
	max=counter
	s=0
	while ("sed -n -e s/\\^\\+//p "costparamfile | getline) {
	    s++
	    summa[s,"prefix"]=$1
	    summa[s,"comment"]=$2
	    for (i=3; i<=NF; i++) 
		summa[s,"comment"]=summa[s,"comment"]":"$i
	}
	summax=s; FS=" "
	transmit=0; pages=0;
    }

    $2 !~ /\?/ { pages+=$2; split($1,ttime,":")
                 transmit+=60*ttime[1]+ttime[2] }
    $4 !~ /\?/ {# on every line where you have number and connect time, 
		# do the following
		if ( $3 !~ /\?/ ) {
		    split($3,timing,":"); time=60*timing[1]+timing[2];
		    counter=1; found=0;
		    while (counter<=max && !found) {
			if ( index($4,costp[counter,"prefix"])==1 ) {
			    found=1
			    cost[counter,"time"]+=time
			    cost[counter,"cost"]+=(costp[counter,"connect"]+ \
			    time*costp[counter,"unitc"]/costp[counter,"tunit"])
			} else if ( length(costp[counter,"prefix"])==0 ) {
			    cost[counter,"time"]+=time
			    cost[counter,"cost"]+=(costp[counter,"connect"]+ \
			    time*costp[counter,"unitc"]/costp[counter,"tunit"])
			}
			counter++
		    }
		}
    }
		
    END { 
	for (counter=1; counter<=max; counter++) {
	    s=1; found=0;
	    while (s<=summax && !found) {
		if ( index(costp[counter,"prefix"],summa[s,"prefix"])==1 ) {
		    found=1
		    summa[s,"time"]+=cost[counter,"time"]
		    summa[s,"cost"]+=cost[counter,"cost"]
		} else if ( length(summa[s,"prefix"])==0 ) {
		    found=1
		    summa[s,"time"]+=cost[counter,"time"]
		    summa[s,"cost"]+=cost[counter,"cost"]
		}
		s++
	    }
	}
	    
	# output collected information
	for (s=1; s<=summax; s++) {
	    time_h=int(summa[s,"time"]/3600); 
	    time_s=summa[s,"time"]-3600*time_h;
	    printf("%-45s\t%2d:%02d:%02d   ~%7.2f %s\n", \
		summa[s,"comment"],time_h,time_s/60,time_s%60, \
		summa[s,"cost"],currency)
	    totalcost+=summa[s,"cost"]
	}
	printf("%-45s\t\t   %8.2f %s\n","Total cost:", totalcost, \
	    currency)
	time_h=int(transmit/3600); time_s=transmit-3600*time_h;
	printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
	    time_h,time_s/60,time_s%60,pages)
	if (pages>0) printf("\t~ %8.2f sec/page",transmit/pages)
	printf("\n")
    }
    ' > ${TMPFCST} 2>&1

fi
#
# now search logfile for faxes received, collect information in ${TMPFRCV}
#
${GAWK} '
function resetvars () { connected=false; call=false;
                        speed="?"; res="?"; errors="?"; number="?"
			ctime="?"; ttime="?"; pages=0;
			calldate=" ?  ?       ?   "
		      }
BEGIN { true=1; false=0; resetvars(); t_ttime=0 }
$3 ~ /Sun|Mon|Tue|Wed|Thu|Fri|Sat/ \
                    { date=sprintf("%2d %s  %s",$5,$4,$6) }
# this might be special to NXFax 1.02
/%> NXFax: incoming call/ { call=true; calldate=date }
/%> NXFax: fax call/ { if (call) connected=true }
# this might be special to NXFax 1.03
/%> NXFax: from/ { if (connected) { 
                       calldate=date
		       number=substr($0,15,20) }
	         }
$4 ~ /halt/ { connected=false; call=false }
$4 ~ /resolution/ { res=$3 }
$4 ~ /bps/ { speed=sprintf("%4.1f",$3/1000) }
$3 ~ /page/ { pages=$4 }
$3 ~ /hangup/ { if ( substr($3, 8, 1) == 0 )
			{ errors=$4" "$5 }
		else
			{ errors="Hangup ERROR" }
              }
/transmit time/ { ttime=$5
                  if (connected) {
		      if (number=="call") { number="?" }
                      printf("%16s %-20s %3s  %2s %s  %5s        %s\n",
				calldate,number,pages,substr(res,1,2),
				speed,ttime,errors)
                      split(ttime,ttimecomp,":")
		      t_ttime+=ttimecomp[1]*60+ttimecomp[2]
		      t_pages+=pages
		  }
		  resetvars()
		}
# this can happen on an error like
# %> NXFax: hangup[70]: Unspecified receive Phase B error.
/call complete/ { if (connected) {
			printf("%16s %-20s %3s  %2s %s  %5s        %s\n",
				calldate,number,pages,substr(res,1,2),
				speed,ttime,errors)
		     split(ttime,ttimecomp,":")
		     t_ttime+=ttimecomp[1]*60+ttimecomp[2]
		     t_pages+=pages
		  }
		  resetvars()
		}
END {
    t_th=int(t_ttime/3600); t_ttime-=3600*t_th;
    printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
        t_th,t_ttime/60,t_ttime%60,t_pages);
    if ( t_pages != 0) { printf("\t~ %8.2f sec/page\n",t_ttime/t_pages) }
    printf("\n")
    }' ${LOGFILE} > ${TMPFRCV} 2>&1
# 
# Concatenate collected data
#
(
  cat ${TMPFHED}
  if [ `wc -l ${TMPFSND} | awk -e '{print $1}'` -gt 0 ]
  then
  	echo "\n                                  SEND FAX\n"
  	echo "Date    Time     Number               pgs res kbps    xmt   conn errors\n" 
  	cat ${TMPFSND} 
	if [ -r $COSTFILE ]
	then
	    if [ `wc -l ${TMPFCST} | awk -e '{print $1}'` -gt 0 ]
	    then
		echo "\nCost summary\t\t\t\t\t connect       cost"
		cat ${TMPFCST}
	    fi
	else
	    cat ${TMPFCST}
	fi
  fi
  if [ `wc -l ${TMPFRCV} | awk -e '{print $1}'` -gt 2 ]
  then
  	echo "\n\n                                 RECEIVE FAX\n"
  	echo "Date    Time     Number               pgs res kbps    xmt       errors\n" 
  	cat ${TMPFRCV}
  fi
  echo
) > ${TMPFSUM} 2>&1 
#
if [ `wc -l ${TMPFSUM} | awk -e '{print $1}'` -gt 3 ]
then
	cat ${TMPFSUM} | eval ${CMD}
fi
#
cleanup
#
exit 0
