#!/bin/sh VERSION=`echo '$Id$' | cut -d' ' -f3` PROGNAME=`basename $0` # This specifies the default level of message logging. 3 means you will # recieve informational messages, warnings and errors; 2 means just # the latter two; 1 means just the last one and 0 means no messages will # be displayed. Higher numbers will give debugging information. This number # may be overridden by using the "-v" or "-d" options on the command line. DFLT_INCP_DEBUG_LEVEL=2 # This is the default idle timeout in seconds. Again it can be overridden # by using the "-i" option on the command line. DFLT_INCP_IDLE_TIME_OUT=180 # This says that line quality should be checked and the internet provider # possibly redialled if the quality is bad DFLT_INCP_CHECK_QUALITY=true # This says that news should be got by default. It can be overridden with # the "-n" or "-nn" options on the command line. DFLT_INCP_GET_NEWS=true # Locations of various programs # This should work, but doesn't - pppd has a bug whereby it gets stuck in # sleep commands. #PPP_DAEMON=/usr/sbin/pppd PPP_DAEMON=/usr/local/lib/ppp/pppd-experimental SLIP_IDLE=/usr/local/lib/ppp/slip_idle XMITNEWS=/usr/local/lib/nntp/xmitnews NEWSRUNNING=/usr/lib/newsbin/input/newsrunning NEWSRUN=/usr/lib/newsbin/input/newsrun NNMASTER=/usr/lib/nn/nnmaster NNTP_DIR=/usr/local/lib/nntp SLURP=/usr/local/lib/nntp/slurp CHAT=/usr/sbin/chat ############################ LEAVE THESE ALONE ############################### CONFIG_FILE=/usr/local/lib/ppp/$PROGNAME.cfg CHAT_FILE=/tmp/$PROGNAME.$$.chat LOCK_FILE=/var/run/$PROGNAME.pid TMP_LOCK_FILE=$LOCK_FILE.$$ ############################################################################### # # USEFUL FUNCTIONS FOR REPORTING MESSAGES # ############################################################################### usage() { # There are addition second parameters: upprocess, downprocess. But these # are not intended to be used by the user. { echo "Usage: $PROGNAME -V" echo " $PROGNAME [ -d dbglvl | -v ] [ -i timeout | -ni ] [ -n | -nn ] [ -q | -nq ] host connect" echo " $PROGNAME host disconnect" } >&2 exit 1 } error() { echo "$PROGNAME: ERROR: $1" exit 2 } internal() { echo "$PROGNAME: INTERNAL ERROR: $1" exit 3 } verbose() { [ $INCP_DEBUG_LEVEL -ge 3 ] && echo "$PROGNAME: INFO: $1" } debug() { [ $1 -le $INCP_DEBUG_LEVEL ] && echo "$PROGNAME[$$]: DEBUG[$1] $2" } incp_log() { logger -p local0.info -t "$PROGNAME[$INCP_MASTER_PID]" "$1" } ########################################################### # # # Temp file registration # # # ########################################################### delonexit() { DELONEXIT="$DELONEXIT $*" } dontdelonexit() { for FILE in $*; do DELONEXIT=`echo $DELONEXIT | sed "s@$FILE@@g"` done } exitdel() { rm -f $DELONEXIT } sighandler() { exitdel exit 2 } ############################################################################### # # ENTRY POINT - PARSE OPTIONS, DO CHECKS AND CALL USER-DEFINED FUNCTION # ############################################################################### main() { # Set some things from the environment otherwise set their values from # defaults defined at the top of this file [ $INCP_DEBUG_LEVEL ] || INCP_DEBUG_LEVEL=$DFLT_INCP_DEBUG_LEVEL [ $INCP_GET_NEWS ] || INCP_GET_NEWS=$DFLT_INCP_GET_NEWS [ $INCP_CHECK_QUALITY ] || INCP_CHECK_QUALITY=$DFLT_INCP_CHECK_QUALITY [ $INCP_IDLE_TIME_OUT ] || INCP_IDLE_TIME_OUT=$DFLT_INCP_IDLE_TIME_OUT [ $INCP_MASTER_PID ] || INCP_MASTER_PID=$$ # process options - over-ruling any previous definition while [ "X$1" != X ]; do case "$1" in -d) [ "X$2" = X ] && usage INCP_DEBUG_LEVEL=$2 shift ;; -v) INCP_DEBUG_LEVEL=3 ;; -i) [ "X$2" = X ] && usage INCP_IDLE_TIME_OUT=$2 shift ;; -ni) INCP_IDLE_TIME_OUT=0 ;; -q) INCP_CHECK_QUALITY=true ;; -nq) INCP_CHECK_QUALITY=false ;; -n) INCP_GET_NEWS=true ;; -nn) INCP_GET_NEWS=false ;; -V) echo "$PROGNAME version $VERSION" exit 0 ;; -*) usage ;; *) break ;; esac shift done # Put some of the things we've just set into the environment so that # when this program (called with connect or disconnect) calls this # this program a second time (with upprocess or downprocess) then # certain values are accessible export INCP_IDLE_TIME_OUT export INCP_DEBUG_LEVEL export INCP_GET_NEWS export INCP_CHECK_QUALITY export INCP_MASTER_PID # Parse the first of the two required parameters [ "X$2" = X -o "X$3" != X ] && usage debug 50 "main: checking required parameters" case "$1" in demon) ;; *) error "valid hosts are: demon, you gave $1" ;; esac INCP_HOST=$1 export INCP_HOST # Ok, there's no going back from here echo "$PROGNAME version $VERSION" trap sig_handler 1 2 15 # Parse the second of the two required parameters case "$2" in connect|disconnect|upprocess|downprocess) ;; *) usage ;; esac # Read in the config file and check it defines everything if [ -r $CONFIG_FILE ]; then . $CONFIG_FILE else error "can't read $CONFIG_FILE" fi [ $CONFIGURED = false ] && configure_me [ "X$PHONE_NUMBER" = X ] && configure_me [ "X$LOGIN" = X ] && configure_me [ "X$PASSWORD" = X ] && configure_me [ "X$PORT" = X ] && configure_me [ "X$NETMASK" = X ] && configure_me [ "X$NNTP_SERVER" = X ] && configure_me [ "X$TESTHOSTS" = X ] && configure_me [ "X$TESTLOSSES" = X ] && configure_me # Some non-host-specific sanity checks [ -x $PPP_DAEMON ] || error "can't execute $PPP_DAEMON" [ -x $SLIP_IDLE ] || error "can't execute $SLIP_IDLE" [ -x $SLURP ] || error "can't execute $SLURP" [ -x $NEWSRUNNING ] || error "can't execute $NEWSRUNNING" # Now call the function based on the two parameters, e.g. if we had # parameters "demon" and "connect", we know call "demon_connect" debug 20 "main: will call $1_$2" incp_log "$PROGNAME $VERSION started. ($INCP_HOST,$2,$LOGNAME)" $1_$2 incp_log "Exit ($INCP_HOST,$2,$LOGNAME)" } ############################################################################### # # USER DEFINED FUNCTIONS: HOST_CONNECT, HOST_DISCONNECT, HOST_UPPROCESS # HOST_DOWNPROCESS # ############################################################################### # # This function specifies how to establish the connection to a host # So if you want to connect to Microsoft Network then you will need to # write a function "msn_connect". This one details how to connect to # Demon. # demon_connect() { debug 50 "demon_connect: starting ..." lock_connect $INCP_HOST ;; # Flush outgoing newsbatches before we start if required to do so if [ $INCP_GET_NEWS = true ]; then enable_other_news_processing flush_batches_to_database_and_possibly_queue_out fi debug 50 "demon_connect: creating the chat script ..." # The chat script is now stored here and written out to a tmp file. This # means all the config stuff can be kept in this one file. OLDUMASK=`umask` umask 077 delonexit $CHAT_FILE cat > $CHAT_FILE < $LOCK_FILE # And this is in case pppd is running now kill_pppd } demon_upprocess() { debug 5 "demon_upprocess: starting ..." STATE=`cut -f2 $LOCK_FILE 2>/dev/null` case $STATE in AttemptingConnect) STATE=QualityCheck echo "$INCP_MASTER_PID $STATE" > $LOCK_FILE ;; *) internal "state is $STATE" ;; esac debug 5 "demon_upprocess: if quality check" if [ $INCP_CHECK_QUALITY != true ]; then debug 5 "demon_upprocess: no quality check" incp_log "no quality check" elif ! check_stable; then debug 5 "demon_upprocess: quality checked and unacceptable" STATE=AttemptingConnect echo "$INCP_MASTER_PID $STATE" > $LOCK_FILE kill_pppd exit fi STATE=Connected echo "$INCP_MASTER_PID $STATE" > $LOCK_FILE debug 5 "demon_upprocess: quality not checked or acceptable" incp_log "acceptable quality" say_internet_up kick_send_mail_queue start_idle_timeout [ $INCP_GET_NEWS = true ] && { disable_other_news_processing send_out_queued_news get_new_news enable_other_news_processing flush_batches_to_database_and_possibly_queue_out update_nn_db } verbose "Connection processing completed." } demon_downprocess() { # The point of this "if" is that if the connection was up but not # a good quality link then we would have retried and never announced # to the users that the the connection was up. As such if connection # was bad then we also do not want to tell them it's gone down since # they never knew it was up. STATE=`cut -f2 $LOCK_FILE 2>/dev/null` case $STATE in *) internal "state is $STATE" ;; esac } flush_batches_to_database_and_possibly_queue_out() { verbose "Flushing news buffers ..." su - news -c $NEWSRUN } kill_pppd() { PPPD_PID=`cat /var/run/ppp0.pid 2>/dev/null` if [ ! "$PPPD_PID" ]; then debug 5 "kill_pppd: no pid file or pid file empty" return elsif [ ! -d /proc/$PPPD_PID ]; then debug 5 "kill_pppd: stale pid file" rm -f /var/run/ppp0.pid return else debug 5 "kill_pppd: pid file valid - connection probably established - pppd will be terminated" kill $PPPD_PID fi } lock_connect() { # The complicated art of managing lock files! debug 5 "lock_connect: creating a private key" echo "$INCP_MASTER_PID AttemptingConnect" > $TMP_LOCK_FILE debug 5 "lock_connect: sliding key into lock" ln $TMP_LOCK_FILE $LOCK_FILE 2>/dev/null && { rm $TMP_LOCK_FILE return; } debug 5 "lock_connect: another key in lock already" LOCKINGPID=`cut -f1 $LOCK_FILE` debug 5 "lock_connect: pid holding lock is $LOCKINGPID" [ "X$LOCKINGPID" = X ] && error "corrupt lock file encountered" debug 5 "lock_connect: checking if locking pid proc dir exists" [ -d /proc/$LOCKINGPID ] && error "program already running!" rm -f $LOCK_FILE verbose "Removed stale lock file." ln $TMP_LOCK_FILE $LOCK_FILE && { rm $TMP_LOCK_FILE return; } error "still cannot lock after removing stale lock file" } unlock_connect() { rm -f $LOCK_FILE } check_stable() { verbose "Connected. Testing quality ..." incp_log "Testing quality" #debug 15 "check_stable: waiting 15 seconds for connection to settle" #sleep 15 # Now calculate the packet loss to a very close host MAXLOSSES="$TESTLOSSES" for HOST in $TESTHOSTS; do set $MAXLOSSES MAXLOSS=$1 shift MAXLOSSES="$*" debug 15 "check_stable: pinging $HOST, acceptable loss is $MAXLOSS%" #sleep 10 LOSS=`ping -c 10 -n -q $HOST | sed -n 's/^.*ed, \([0-9]*\)% pa.*$/\1/p'` if [ $LOSS -gt $MAXLOSS ]; then verbose "$LOSS% packet loss to $HOST, disconnecting ..." incp_log "$LOSS% packet loss to $HOST" return 1 fi done return 0 } say_internet_up() { verbose "Internet connection is officially up" } kick_send_mail_queue() { # Send post verbose "Dispatching outbound mail ..." /usr/lib/sendmail -q & } start_idle_timeout() { if [ $INCP_IDLE_TIME_OUT != 0 ]; then verbose "Idle timeout is $INCP_IDLE_TIME_OUT seconds." nohup $SLIP_IDLE -t $INCP_IDLE_TIME_OUT -i ppp0 -p `basename $PPP_DAEMON` > /dev/null 2>&1 & else incp_log "No idle timeout" verbose "No idle timeout set." fi } send_out_queued_news() { # Send news verbose "Dispatching outbound news ..." if [ $INCP_DEBUG_LEVEL -ge 3 ]; then su - news -c "$XMITNEWS -d -h $NNTP_SERVER -f /var/spool/news/out.going/$NNTP_SERVER/togo" else su - news -c "$XMITNEWS -d -h $NNTP_SERVER -f /var/spool/news/out.going/$NNTP_SERVER/togo" > /dev/null 2>&1 fi } fix_kacky_slurp_file() { [ `wc -l < $NNTP_DIR/slurp.$NNTP_SERVER` -gt 1 ] && { cp $NNTP_DIR/slurp.$NNTP_SERVER $NNTP_DIR/slurp.$NNTP_SERVER.bak head -1 $NNTP_DIR/slurp.$NNTP_SERVER.bak > $NNTP_DIR/slurp.$NNTP_SERVER verbose "Fixed corrupt slurp timestamp file." } } disable_other_news_processing() { su - news -c "$NEWSRUNNING off" } get_new_news() { fix_kacky_slurp_file # Get news SLURP_RC=1 verbose "Downloading news batches ..." while [ $SLURP_RC != 0 ]; do if [ $INCP_DEBUG_LEVEL -ge 3 ]; then su - news -c "$SLURP -d $NNTP_SERVER" else su - news -c "$SLURP -d $NNTP_SERVER" > /dev/null 2>&1 fi SLURP_RC=$? [ $SLURP_RC != 0 ] && debug 5 "get_new_news: failed - retrying" sleep 1 done } enable_other_news_processing() { su - news -c "$NEWSRUNNING on" } update_nn_db() { # Update the NN database verbose "Updating nn fast access database ..." su - news -c $NNMASTER } say_internet_down() { verbose "Internet connection is officially down" } configure_me() { cat <