#!/bin/bash
# Need bash because of "${FOO/bar}" and "for (( ))"

MYNAME="${0##*/}"

DEFAULT_TIMEOUT_SECONDS=300

report_err() { echo "ifupdown: ${MYNAME}${MODE:+ $MODE}: Error: $*" >&2 ; }

is_whitespace_free()
{
	local EXPANDED="$(echo $1)"
	[ "$1" = "${EXPANDED/ }" ]
}

is_numeral()
{
	[ "$1" ] || return 1
	[ "$1" = "${1/[^0-9]}" ]
}

[ "$MODE" ] || { report_err "MODE missing from env" ; exit 1 ; }

[ "$IFACE" ] || { report_err "IFACE missing from env" ; exit 1 ; }
is_whitespace_free "$IFACE" || { report_err "Interface name contains whitespace" ; exit 1 ; }
[ "$IFACE" != "${IFACE#ppp}" ] || { report_err "Interface name must begin with 'ppp'" ; exit 1 ; }

UNITSTR="${IFACE#ppp}"
is_numeral "$UNITSTR" || { report_err "Interface name must consist of 'ppp' followed by a numeral" ; exit 1 ; }
UNIT="$(( $UNITSTR ))"

if [ "$IF_DEVICE" ] ; then
	is_whitespace_free "$IF_DEVICE" || { report_err "Device name contains whitespace" ; exit 1 ; }
	[ "$IF_DEVICE" != "${IF_DEVICE#/dev/}" ] || { report_err "Device name must begin with '/dev/'" ; exit 1 ; }
fi

if [ "$IF_PROVIDER" ] ; then
	is_whitespace_free "$IF_PROVIDER" || { report_err "Provider name contains whitespace" ; exit 1 ; }
else
	IF_PROVIDER="provider"
fi
[ -f "/etc/ppp/peers/$IF_PROVIDER" ] || { report_err "/etc/ppp/peers/$IF_PROVIDER does not exist" ; exit 1 ; }

TIMEOUT_SECONDS="$DEFAULT_TIMEOUT_SECONDS"
if [ "$IF_UP_TIMEOUT" ] ; then
	is_whitespace_free "$IF_UP_TIMEOUT" || { report_err "Timeout argument contains whitespace" ; exit 1 ; }
	is_numeral "$IF_UP_TIMEOUT" || { report_err "Timeout argument is non-numeric" ; exit 1 ; }
	TIMEOUT_SECONDS="$(( $IF_UP_TIMEOUT ))"
fi

# Stop pppd process $1 for $IFACE
stop_pppd()
{
	local TMP_PIDFILE="/var/run/ifupdown-pppd.${IFACE}.pid"
	[ "$1" ] || return 0
	echo "$1" > "$TMP_PIDFILE"
	start-stop-daemon --stop --quiet --oknodo --pidfile="$TMP_PIDFILE" --retry=TERM/"$TIMEOUT_SECONDS"/KILL/1 >/dev/null
	rm -f "$TMP_PIDFILE"
	return 0
}

case "$MODE" in
start)
	# First pppd argument must be "unit" for stop method to work
	ARG_STR="unit $UNIT updetach persist maxfail 0${IF_PROVIDER:+ call $IF_PROVIDER}${IF_DEVICE:+ $IF_DEVICE}"
	if [ "$TIMEOUT_SECONDS" -gt "0" ] ; then
		PIDFILE=/var/run/ppp${UNIT}.pid
		stop_child_and_exit() { stop_pppd "$!" >/dev/null 2>&1 ; exit 1 ; }
		trap stop_child_and_exit INT
		pppd $ARG_STR &
		for (( T=$TIMEOUT_SECONDS ; T > 0 ; T-- )) ; do
			sleep 1
			[ -e "$PIDFILE" ] && exit 0
		done
		report_err "Timed out"
		stop_child_and_exit
	else
		pppd $ARG_STR
		exit
	fi
	;;
stop)
	# The pidfiles that pppd creates only exist so long as the ppp
	# interface exists.  This makes those pidfiles unsuitable for
	# telling us which pppd process to kill, whether or not the
	# interface currently exists.  We are forced to use ps to find
	# out the right pid.
	#
	# We need to check for pppd processes that were started by pon
	# since earlier versions of ifupdown called pon
	UPGRADE_FLAGFILE=/var/run/ifupdown-upgraded-while-pppd
	do_ps() { ps --no-headers -o pid,cmd -C pppd ; }
	do_ps | while read PID PRGM KYWRD VAL REST ; do
		if [ "$KYWRD" = "unit" ] && [ "$VAL" = "$UNIT" ] ; then
			# Stop pppd process presumably started by us
			stop_pppd "$PID"
		elif [ -e $UPGRADE_FLAGFILE ] && [ "$KYWRD" = "call" ] && [ "$VAL" = "$IF_PROVIDER" ] ; then
			# Stop pppd process apparently started by pon,
			# which was probably invoked by the old version of ifupdown
			stop_pppd "$PID"
		fi
	done
	[ "$(do_ps)" ] || rm -f $UPGRADE_FLAGFILE
	exit 0
	;;
*)
	report_err "Mode not recognized"
	exit 1
	;;
esac
