#! /bin/sh
##  $Revision: 1.22 $
##  @(#) Under RCS control in /usr/local/news/src/inn/samples/RCS/nntpsend,v
##  Send news via NNTP by running several innxmit processes in the background.
##  Usage:
##	nntpsend [-F][-n][-p][-r][-s siz][-S][-t timeout][-T lim][host fqdn]...
##	-F		Use ctlinnd flush on each site rather than flushfile
##	-a		Always have innxmit rewrite the batchfile
##	-d		debug mode, run ixxmmits with debug as well
##	-D		same as -d except innxmits are not debugged
##	-p		Run innxmit with -p to prune batch files
##	-r		innxmit, don't requeue on unexpected error code
##	-l		turn off logging of reject reasons.
##	-s size		limit the !n file to size bytes
##	-S		send via xreplic protocol (send to a slave site)
##	-c		disable message-ID checking in streaming mode
##	-t timeout	innxmit timeout to make connection (def: 180)
##	-T limit	innxmit connection transmit time limit (def: forever)
##	-P portnum	port number to use
##	-n		do not lock LOCK.nntpsend
##	host fqdn	send to host and qualified domain (def: nntpsend.ctl)
##  If no "host fqdn" pairs appear on the command line, then ${CTLFILE}
##  file is read.
##  =()<. @<_PATH_SHELLVARS>@>()=
. /var/news/etc/innshellvars

PROGNAME=`basename $0`
LOCK=${LOCKS}/LOCK.${PROGNAME}
CTLFILE=${NEWSLIB}/${PROGNAME}.ctl
LOG=${MOST_LOGS}/${PROGNAME}.log

##  Set defaults.
A_FLAG=
D_FLAG=
NO_LOG_FLAG=
L_FLAG=
P_FLAG=
R_FLAG=
S_FLAG=
C_FLAG=
TRUNC_SIZE=
T_FLAG=
TIMELIMIT=
PP_FLAG=
NOLOCK=
FLUSHFILE=true

##  Parse JCL.
MORETODO=true
while ${MORETODO} ; do
    case X"$1" in
    X-F)
	FLUSHFILE=
	;;
    X-a)
	A_FLAG="-a"
	;;
    X-d)
	D_FLAG="-d"
	NO_LOG_FLAG="true"
	;;
    X-D)
	NO_LOG_FLAG="true"
	;;
    X-p)
	P_FLAG="-p"
	;;
    X-r)
	R_FLAG="-r"
	;;
    X-S)
	S_FLAG="-S"
	;;
    X-c)
	C_FLAG="-c"
	;;
    X-l)
        L_FLAG="-l"
	;;
    X-s)
	if [ -z "$2" ] ; then
	    echo "${PROGNAME}: option requires an argument -- s" 1>&2
	    exit 1
	fi
	TRUNC_SIZE="$2"
	shift
	;;
    X-s*)
	TRUNC_SIZE="`echo $1 | ${SED} -e 's/-s//'`"
	;;
    X-t)
	if [ -z "$2" ] ; then
	    echo "${PROGNAME}: option requires an argument -- t" 1>&2
	    exit 1
	fi
	T_FLAG="-t$2"
	shift
	;;
    X-t*)
	T_FLAG="$1"
	;;
    X-P)
	if [ -z "$2" ] ; then
	    echo "${PROGNAME}: option requires an argument -- P" 1>&2
	    exit 1
	fi
	PP_FLAG="-P$2"
	shift
	;;
    X-P*)
	PP_FLAG="$1"
	;;
    X-T)
	if [ -z "$2" ] ; then
	    echo "${PROGNAME}: option requires an argument -- T" 1>&2
	    exit 1
	fi
	TIMELIMIT="-T$2"
	shift
	;;
    X-T*)
	TIMELIMIT="$1"
	;;
    X-n)
	NOLOCK=true
	;;
    X--)
	shift
	MORETODO=false
	;;
    X-*)
	echo "${PROGNAME}: illegal option -- $1" 1>&2
	exit 1
	;;
    *)
	MORETODO=false
	;;
    esac
    ${MORETODO} && shift
done

## grab the lock if not -n
NNTPLOCK=${LOCKS}/LOCK.nntpsend
if [ -z "${NOLOCK}" ]; then
    shlock -p $$ -f ${NNTPLOCK} || {
	# nothing to do
	exit 0
    }
fi

##  Parse arguments; host/fqdn pairs.
INPUT="${TMPDIR}/nntpsend$$"
trap "rm -f ${INPUT}; exit" 0 1 2 3 15
cp /dev/null ${INPUT}
while [ $# -gt 0 ]; do
    if [ $# -lt 2 ]; then
	echo "${PROGNAME}:  Bad host/fqdn pair" 1>&2
	rm -f ${NNTPLOCK}
	exit 1
    fi
    echo "$1 $2" >>${INPUT}
    shift
    shift
done

##  If nothing specified on the command line, read the control file.
if [ ! -s ${INPUT} ] ; then
    if [ ! -r ${CTLFILE} ]; then
	echo "${PROGNAME}: cannot read ${CTLFILE}"
	rm -f ${NNTPLOCK}
	exit 1
    fi
    ${SED} -e 's/#.*//' -e '/^$/d' -e 's/::\([^:]*\)$/:max:\1/' \
	-e 's/:/ /g' <${CTLFILE} >${INPUT}
fi

##  Go to where the action is.
if [ ! -d ${BATCH} ]; then
    echo "${PROGNAME}: directory ${BATCH} not found" 1>&2
    rm -f ${NNTPLOCK}
    exit 1
fi
cd ${BATCH}

##  Form the FLUSH_SET file
FLUSH_SET="${TMPDIR}/nntpflush$$"
trap "rm -f ${INPUT} ${FLUSH_SET}; exit" 0 1 2 3 15
rm -f "${FLUSH_SET}"

##  Set up log file.
umask 002
if [ -z "${NO_LOG_FLAG}" ]; then
    test ! -f ${LOG} && touch ${LOG}
    chmod 0660 ${LOG}
    exec >>${LOG} 2>&1
fi
PARENTPID=$$
echo "${PROGNAME}: [${PARENTPID}] start"

##  Set up environment.
export BATCH PROGNAME PARENTPID INNFLAGS FLUSH_SET

##  If we are going to flushfile (no -F), then move all SITE files into SITE!w
if [ -n "$FLUSHFILE" ]; then

    ## Loop over all sites and assume it matches the flushfile set
    ## But even if it does not match, no great hard is done.  Sites
    ## not in ${INPUT} will have a needless close/reopen on them.
    ## Extra sites in ${INPUT} will be processed as is, but since
    ## they are not active, no harm will be done here either.
    cat ${INPUT} | while read SITE HOST SIZE_ARG FLAGS; do

	## Parse the input parameters.
	if [ -z "${SITE}" -o -z "${HOST}" ] ; then
	    echo "Ignoring bad line: ${SITE} ${HOST} ${SIZE_ARG} ${FLAGS}" 1>&2
	    continue
	fi

	## We must move the SITE file.  If the SITE!w file exists, we
	## will move SITE to SITE!f, flush and then copy SITE!f
	## onto the end of SITE!w.  If the SITE!w file does not
	## exist, we will just move SITE to SITE!w and then flush.
	##
	## If SITE is empty, we will do nothing and let innd just close
	## and reopen it as SITE.

	## Now if for some reason SITE!f exists, we will first
	## copy it onto the end of SITE!w or move it to SITE!w.
	if [ -f "${SITE}!f" ]; then
	    if [ -f "${SITE}!w" ]; then
		cat "${SITE}!f" >> "${SITE}!w"
		rm -f "${SITE}!f"
	    else
		mv "${SITE}!f" "${SITE}!w"
	    fi
	fi

	## Here we know that SITE!f does not exist.  So if
	## SITE exists and is non-empty, move it to SITE!w or
	## move it to SITE!f (before catting onto the end
	## of SITE!w) before doing the flush.
	if [ -s "${SITE}" ]; then
	    if [ -f "${SITE}!w" ]; then
		mv "${SITE}" "${SITE}!f"
		echo "${SITE}" >> "${FLUSH_SET}"
	    else
		mv "${SITE}" "${SITE}!w"
	    fi
	fi
    done

    ## Now every SITE that was non-empty has been moved to SITE!f
    ## or SITE!w ready for the flush.  So flush it!
    if ctlinnd -s -t60 flushfile ; then

	## Now that we have flushed, we need to cat any SITE!f
	## files into the end of SITE!w and then remove SITE!f.
	if [ -s "${FLUSH_SET}" ]; then
	    ## Why fizzbin?  Ask kendy@epic.com or mfrost@horizsys.com.  :-)
	    for fizzbin in `cat ${FLUSH_SET}`; do
		cat "${fizzbin}!f" >> "${fizzbin}!w"
		rm -f "${fizzbin}!f"
	    done
	fi
    else
	echo "${PROGNAME}: bad flushfile"
	exit 1
    fi

    ## We have now flushed all site files.  All sites in ${INPUT} that
    ## had non-empty SITE files have their new work in SITE!w by now.
fi
## cleanup
if [ -s "${FLUSH_SET}" ]; then
    rm -f "${FLUSH_SET}"
fi

PREV_SIZE_ARG=
##  Loop over all sites.
cat ${INPUT} | while read SITE HOST SIZE_ARG FLAGS; do
    ## Parse the input parameters.
    if [ -z "${SITE}" -o -z "${HOST}" ] ; then
	echo "Ignoring bad line: ${SITE} ${HOST} ${SIZE_ARG} ${FLAGS}" 1>&2
	continue
    fi

    ## Compute the shrinkfile size args.
    test "${SIZE_ARG}" = "max" && SIZE_ARG=
    if [ -n "${TRUNC_SIZE}" ]; then
	SIZE_ARG="${TRUNC_SIZE}"
    fi
    # only compute sizes if we have a new or the first one
    if [ "${SIZE_ARG}" != "${PREV_SIZE_ARG}" ]; then
	## Parse the SIZE_ARG for either MaxSize-TruncSize or TruncSize
	case "${SIZE_ARG}" in
	*-*) MAXSIZE="`echo ${SIZE_ARG} | ${SED} -e 's/-.*//'`";
	     SIZE="`echo ${SIZE_ARG} | ${SED} -e 's/^.*-//'`" ;;
	*) MAXSIZE="${SIZE_ARG}";
	   SIZE="${SIZE_ARG}" ;;
	esac
	PREV_SIZE_ARG="${SIZE_ARG}"
    fi

    ## We will pre-shrink the SITE!w file if it is too large.  While normally
    ## the shrinkfile operates on the SITE!n below, we need to do it here
    ## as well.  Should, for some reason, the innxmit get stuck (some have
    ## been known to get stuck due to OS bogons or if innxmit was not given
    ## a -Ttimelimit arg), data could pile up in the SITE!w file.  To guard
    ## against this, we pre-shink now.  After all, if the SITE!w is too large,
    ## then surely SITE!w concatinated onto the end of SITE!n will be too
    ## large as well.
    if [ -n "${SIZE}" -a -f "${SITE}!w" ]; then
	shrinkfile -m${MAXSIZE} -s${SIZE} -v "${SITE}!w"
    fi

    ## give up early if we cannot even lock it
    ##
    ## NOTE: This lock is not nntpsend's lock but rather the
    ##	     lock that the parent shell of innxmit will use.
    ##	     Later on the child will take the lock from us.
    ##
    LOCK="${LOCKS}/LOCK.${SITE}"
    shlock -p $$ -f "${LOCK}" || continue

    ## Compute the specific parameters for this site.
    D_PARAM=
    L_PARAM=
    R_PARAM=
    S_PARAM=
    C_PARAM=
    PP_PARAM=
    TIMEOUT_PARAM=
    TIMELIMIT_PARAM=
    if [ -z "${FLAGS}" ]; then
	MORETODO=false
    else
	MORETODO=true
	set -- ${FLAGS}
    fi
    while ${MORETODO} ; do
	case "X$1" in
	X-a)
	    ;;
	X-d)
	    D_PARAM="-d"
	    ;;
	X-c)
	    C_PARAM="-c"
	    ;;
	X-l)
	    L_PARAM="-l"
	    ;;
	X-p)
	    P_PARAM="-p"
	    ;;
	X-r)
	    R_PARAM="-r"
	    ;;
	X-S)
	    S_PARAM="-S"
	    ;;
	X-t)
	    if [ -z "$2" ] ; then
		echo "${PROGNAME}: option requires an argument -- t" 1>&2
		rm -f "${NNTPLOCK}" "${LOCK}"
		exit 1
	    fi
	    TIMEOUT_PARAM="-t$2"
	    shift
	    ;;
	X-t*)
	    TIMEOUT_PARAM="$1"
	    ;;
	X-P)
	    if [ -z "$2" ] ; then
		echo "${PROGNAME}: option requires an argument -- P" 1>&2
		rm -f "${NNTPLOCK}" "${LOCK}"
		exit 1
	    fi
	    PP_PARAM="-P$2"
	    shift
	    ;;
	X-P*)
	    PP_PARAM="$1"
	    ;;
	X-T)
	    if [ -z "$2" ] ; then
		echo "${PROGNAME}: option requires an argument -- T" 1>&2
		rm -f "${NNTPLOCK}" "${LOCK}"
		exit 1
	    fi
	    TIMELIMIT_PARAM="-T$2"
	    shift
	    ;;
	X-T*)
	    TIMELIMIT_PARAM="$1"
	    ;;
	*)
	    MORETODO=false
	    ;;
	esac
	${MORETODO} && shift
    done
    if [ -z "${SIZE}" -o -n "${A_FLAG}" ]; then
	# rewrite batch file if we do not have a size limit
	INNFLAGS="-a"
    else
	# we have a size limit, let shrinkfile rewrite the file
	INNFLAGS=
    fi
    if [ -n "${D_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${D_FLAG}"
    else
	test -n "${D_PARAM}" && INNFLAGS="${INNFLAGS} ${D_PARAM}"
    fi
    if [ -n "${C_FLAG}" ]; then
        INNFLAGS="${INNFLAGS} ${C_FLAG}"
    else
        test -n "${C_PARAM}" && INNFLAGS="${INNFLAGS} ${C_PARAM}"
    fi
    if [ -n "${L_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${L_FLAG}"
    else
	test -n "${L_PARAM}" && INNFLAGS="${INNFLAGS} ${L_PARAM}"
    fi
    if [ -n "${P_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${P_FLAG}"
    else
	test -n "${P_PARAM}" && INNFLAGS="${INNFLAGS} ${P_PARAM}"
    fi
    if [ -n "${R_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${R_FLAG}"
    else
	test -n "${R_PARAM}" && INNFLAGS="${INNFLAGS} ${R_PARAM}"
    fi
    if [ -n "${S_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${S_FLAG}"
    else
	test -n "${S_PARAM}" && INNFLAGS="${INNFLAGS} ${S_PARAM}"
    fi
    if [ -n "${T_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${T_FLAG}"
    else
	test -n "${TIMEOUT_PARAM}" && INNFLAGS="${INNFLAGS} ${TIMEOUT_PARAM}"
    fi
    if [ -n "${PP_FLAG}" ]; then
	INNFLAGS="${INNFLAGS} ${PP_FLAG}"
    else
	test -n "${PP_PARAM}" && INNFLAGS="${INNFLAGS} ${PP_PARAM}"
    fi
    if [ -n "${TIMELIMIT}" ]; then
	INNFLAGS="${INNFLAGS} ${TIMELIMIT}"
    else
	test -n "${TIMELIMIT_PARAM}" \
	    && INNFLAGS="${INNFLAGS} ${TIMELIMIT_PARAM}"
    fi

    ## If we were invoked with -F (the individual flush option) then we
    ## will flush the buffers for the site now, rather than in the child.
    ## This helps pace the number of ctlinnd commands because the
    ## nntpsend process does not proceed until the site flush has
    ## been completed.
    ##
    ## If we were NOT invoked with -F (the flushfile option), then we
    ## have already done our flushing.  So form our batch file out of
    ## the SITE!w file and move on.

    ## Carry old unfinished work over to this task, or carry in newly
    ## flushfile work into this task (if no -F)
    BATCHFILE="${SITE}!n"
    if [ -f "${SITE}!w" ] ; then
	cat "${SITE}!w" >>"${BATCHFILE}"
	rm -f "${SITE}!w"
    fi

    ## Only if -F (the individual flush option)
    ##
    ## With the flushfile option (no -F) we can skip this
    ## since the ctlinnf flushfile loop way above already did
    ## this work.
    if [ -z "$FLUSHFILE" ]; then
	# form BATCHFILE to hold the work for this site
	if [ -f "${SITE}" ]; then
	    mv "${SITE}" "${SITE}!w"
	    if ctlinnd -s -t30 flush ${SITE} ; then
		cat ${SITE}!w >>"${BATCHFILE}"
		rm -f ${SITE}!w
	    else
		# flush failed, continue if we have any batchfile to work on
		echo "${PROGNAME}: bad flush for ${HOST} via ${SITE}"
		if [ -f "${BATCHFILE}" ]; then
		    echo "${PROGNAME}: trying ${HOST} via ${SITE} anyway"
		else
		    echo "${PROGNAME}: skipping ${HOST} via ${SITE}"
		    rm -f ${LOCK}
		    continue
		fi
	    fi
	else
	    # nothing to work on, so flush and move on
	    ctlinnd -s -t30 flush ${SITE}
	    echo "${PROGNAME}: file ${BATCH}/${SITE} for ${HOST} not found"
	    if [ -f "${BATCHFILE}" ]; then
		echo "${PROGNAME}: trying ${HOST} via ${SITE} anyway"
	    else
		echo "${PROGNAME}: skipping ${HOST} via ${SITE}"
		rm -f ${LOCK}
		continue
	    fi
	fi
    fi

    ##  Start sending this site in the background.
    export MAXSIZE SITE HOST PROGNAME PARENTPID SIZE TMPDIR LOCK BATCHFILE
    sh -c '
	# grab the lock from the parent
	#
	# This is safe because only the parent will have locked
	# the site.  We break the lock and reclaim it.
	rm -f ${LOCK}
	trap "rm -f ${LOCK} ; exit 1" 1 2 3 15
	shlock -p $$ -f ${LOCK} || {
	    WHY="`cat ${LOCK}`"
	    echo "${PROGNAME}: [${PARENTPID}:$$] ${SITE} locked ${WHY} `date`"
	    exit
	}
	# process the site BATCHFILE
	if [ -f "${BATCHFILE}" ]; then
	    test -n "${SIZE}" && shrinkfile -m${MAXSIZE} -s${SIZE} -v ${BATCHFILE}
	    if [ -s ${BATCHFILE} ] ; then
		echo "${PROGNAME}: [${PARENTPID}:$$] begin ${SITE} `date`"
		echo "${PROGNAME}: [${PARENTPID}:$$] innxmit ${INNFLAGS} ${HOST} ..."
		eval innxmit ${INNFLAGS} ${HOST} ${BATCH}/${BATCHFILE}
		echo "${PROGNAME}: [${PARENTPID}:$$] end ${SITE} `date`"
	    else
		rm -f ${BATCHFILE}
	    fi
	else
	    echo "${PROGNAME}: file ${BATCH}/${BATCHFILE} for ${HOST} not found"
	fi
	rm -f ${LOCK}
    ' &
done

## release the nntpsend lock and clean up before we wait on child processes
if [ -z "${NOLOCK}" ]; then
    rm -f ${NNTPLOCK}
fi
rm -f ${INPUT}

## wait for child processes to finish
wait

## all done
echo "${PROGNAME}: [${PARENTPID}] stop"
exit 0
