#!/bin/bash
# connect: connects to / disconnects from provider
# Author:  Bernhard Hailer <Bernhard.Hailer@lrz.uni-muenchen.de>
# Version: 1.0 (24-Feb-97)
# License: GNU General Public License V2 ("GPL")


# command line arguments

ARG=$1
if [ -n "$2" ]
then
  shift
  ARGV=$*
else
  ARGV=""
fi


# Search paths

PATH=/sbin:/usr/sbin:/bin:/usr/bin


# Files (it is save to leave these untouched!)

BASE=/usr/lib/connect/base              # basic definitions
PROV_DIR=/usr/lib/connect/providers     # config files, once per provider
STATUS=/var/lib/connect/status          # status file
STATUS_BAK=/var/lib/connect/status.bak  # status file backup
IF_FILE=/var/lib/connect/interface      # pppd interface - see /etc/ppp/ip-up!
PPP_PID_DIR=/var/run                    # PPP pid file/Linux: /var/run/pppx.pid

# Global variables
. $BASE
declare -i i       # i: standard counter
NEW_CONN=false
declare -i RETURN  # i: standard return value
RETURN=0
# wait before testing line (isdn4linux)
declare -i I4L_WAIT
I4L_WAIT=10
# wait before testing line (modems)
declare -i MODEM_WAIT
MODEM_WAIT=40


# process ID for lock file
declare -i PID
declare -i LCK_PID
PID=$$


# -----------------------------------------------------------------------------

# Functions

Fatal_Error()
# beeps, prints the "Usage:" message, removes lock file and exits (1)
{
  echo -e "\a\nUsage:"
  echo "connect <keyword> [<remote1> [<remote2> [<remote3> ...]]]"
  echo "keywords:"
  echo "  [on], off [<remote(s)>] opens/closes connection"
  echo "  off all                 forced closing"
  echo "  status                  displays status of all defined remotes"
  echo "  route <IP>|default      sets route for line"
  echo "  device <device>         modem device (e.g. \"ttyS1\")"
  echo "  maxtries <tries|0>      maximum number of dial attempts"
  echo "  help                    displays this message"
  echo "<remote1> [<remote2> [<remote3> [... ]]]:     remote(s) to open/close"
  echo "---------------------------------------------------------------------"
  echo "You have to edit these files for *each* remote:"
  echo "  $PROV_DIR/<remote>"
  echo "$BASE:  Default remote definitions"
  echo "Remotes must not be named like keywords! Avoid the following names:"
  echo "  on, off, all, status, route, device, maxtries, help"
  rm /var/lock/LCK..connect 2>/dev/null
  exit 1
}


# -----------------------------------------------------------------------------

# command line parsing

OP_MODE=""
ROUTE=""
declare -i MAX_TRIES
MAX_TRIES=0

until [ -z "$ARG" ]
do

  case $ARG in
  on)
    if [ -z "$OP_MODE" ]
    then
      OP_MODE=on
    else
      echo -e "\nOperation mode (on, off, status, help) set twice in command line."
      Fatal_Error
    fi
  ;;
  off)
    if [ -z "$OP_MODE" ]
    then
      OP_MODE=off
    else
      echo -e "\nOperation mode (on, off, status, help) set twice in command line."
      Fatal_Error
    fi
  ;;
  status)
    if [ -z "$OP_MODE" ]
    then
      OP_MODE=status
    else
      echo -e "\nOperation mode (on, off, status, help) set twice in command line."
      Fatal_Error
    fi
  ;;
  help)
    if [ -z "$OP_MODE" ]
    then
      OP_MODE=help
    else
      echo -e "\nOperation mode (on, off, status, help) set twice in command line."
      Fatal_Error
    fi
  ;;
  route)
    if [ -z "$ROUTE" ]
    then
      if [ -n "$ARGV" ]
      then
        set `echo $ARGV`
        ARG=$1
        shift
        ARGV=$*
        ROUTE="$ARG"
        if [ `cat $STATUS | grep "$ROUTE" >/dev/null` ]
	then
	  echo -e "\nRoute named in command line is already used!"
	  Fatal_Error
	fi
      else
	echo -e "\nRoute command set without route in command line!"
	Fatal_Error
      fi
    else
      echo -e "\nRoute set twice in command line!"
      Fatal_Error
    fi
  ;;
  device)
    if [ -z "$DEVICE" ]
    then
      if [ -n "$ARGV" ]
      then
        set `echo $ARGV`
        ARG=$1
        shift
        ARGV=$*
        DEVICE="$ARG"
        if [ `cat $STATUS | grep "$DEVICE" >/dev/null` ]
	then
	  echo -e "\nDevice named in command line already used!"
	  Fatal_Error
	fi
      else
	echo -e "\nDevice command set without device name in command line!"
	Fatal_Error
      fi
    else
      echo -e "\nDevice set twice in command line"
      Fatal_Error
    fi
  ;;
  maxtries)
    if [ $MAX_TRIES -eq 0 ]
    then
      if [ -n "$ARGV" ]
      then
        set `echo $ARGV`
        ARG=$1
        shift
        ARGV=$*
        MAX_TRIES=$ARG
      else
	echo -e "\nmaxtries command set without account of tries in command line!"
	Fatal_Error
      fi
    else
      echo -e "\nmaxtries set twice in command line!"
      Fatal_Error
    fi
  ;;
  *)
    REMOTE_LIST="$REMOTE_LIST $ARG"
  ;;
  esac

  # This construct is built against a bash bug.
  if [ -z "$ARGV" ]
  then
    ARG=""
  else
    set `echo $ARGV`
    ARG=$1
    shift
    ARGV=$*
  fi
  
done


# -----------------------------------------------------------------------------

# now fill up some variables, if empty.

# is there any remote defined in command line / $DEFAULT_REMOTES?   

if [ -z "$REMOTE_LIST" ] 
then
  REMOTE_LIST=$DEFAULT_REMOTES    # defined in $BASE
  if [ -z "$REMOTE_LIST" ]
  then
    echo -e "\a\nNo DEFAULT_REMOTES defined in $BASE! Exiting."
    exit 1
  fi
fi

# Operation mode. If none, then assume "on".
if [ -z "$OP_MODE" ]
then OP_MODE=on
fi

## tty device.
#if [ -z "$DEVICE" ]
#then
#  DEVICE=$DEFAULT_DEVICE
#fi

# Maximum number of dial tries.
if [ $MAX_TRIES -eq 0 ]
then  
  MAX_TRIES=$DEFAULT_MAX_TRIES
  if [ $MAX_TRIES -eq 0 ]
  then
    echo "DEFAULT_MAX_TRIES not set in $BASE!"
    Fatal_Error
  fi
fi

# Any route set?
if [ -z "$ROUTE" ]
then
  ROUTE=default
fi


# -----------------------------------------------------------------------------

# More functions

Connect_Dial()
# Dials out, automatically chooses i4l or modem.
# Needs $INTERFACE, $IP_ADDRESS and $ROUTE, and $DEVICE if modem. 
{
  BUFFER=$DEVICE 
  . $PROV_DIR/$REMOTE
  if [ -n "$BUFFER" ]
  then
    DEVICE=$BUFFER
  else
    if [ -z "$DEVICE" ]
    then
      DEVICE="$DEFAULT_DEVICE"
    fi
  fi

  case $INTERFACE in
  ippp*)
    # ISDN4Linux syncPPP connection
    ifconfig $INTERFACE up
    isdnctrl dial $INTERFACE
    echo "Sleeping $I4L_WAIT seconds for PPP handshaking..."
    sleep $I4L_WAIT
  ;;
  *)
    # Modem asyncPPP connection
    pppd connect "chat $CHAT_SCRIPT" file $OPTIONS_FILE /dev/$DEVICE &
    # the interface name was saved now by /etc/ppp/ip-up in IF_FILE.
    sleep 1
    echo "Sleeping $MODEM_WAIT seconds for establishing connection..."
    sleep $MODEM_WAIT
    if [ -e $IF_FILE ]
    then
      read INTERFACE <$IF_FILE  
    else
      INTERFACE="-----"
    fi
  ;;
  esac
  if [ "$INTERFACE" != "-----" ]
  then
    if [ "$ROUTE" = "default" ]
    then
      route add $ROUTE $INTERFACE
    else
      route add $IP_ADDRESS
      route add $ROUTE gw $IP_ADDRESS
    fi
  fi
}

Connect_Hangup()
# Hangs up device $INTERFACE. Automatically chooses i4l or modem.
# Needs $INTERFACE, $DEVICE, $REMOTE, $IP_ADDRESS and $ROUTE. 
{
  case $INTERFACE in
  ippp*)
    # ISDN4Linux hangup
    echo -e "$REMOTE: \c"
    isdnctrl hangup $INTERFACE
    ifconfig $INTERFACE down
  ;;  
  *)
    # Modem hangup
    if [ -e $PPP_PID_DIR/$INTERFACE.pid ]
    then
      kill -HUP `cat $PPP_PID_DIR/$INTERFACE.pid`
      echo "$REMOTE: $INTERFACE hung up"
    else
      echo "$REMOTE: $INTERFACE not connected"
    fi  
  ;;    
  esac
  if [ "$ROUTE" != "default" ]
  then
    route del $IP_ADDRESS 2>/dev/null
  fi
  route del $ROUTE 2>/dev/null
  rm $IF_FILE 2>/dev/null
}

Line_OK()
# Checks whether channel really is connected.
{
  case $INTERFACE in
  ippp*)   # test i4l connection
    # check whether physical connection is established:
    echo -e "Physical connection... \c" 
    # Therefore get phone number used:
    PHONE=`cat $PROV_DIR/$REMOTE | grep "PHONE="`
    # Now we have a string like PHONE=08912345 - use it as command to set PHONE
    export $PHONE;
    if [ -n "`imontty | grep $PHONE | grep outgoing`" ]
    then
      echo -e "established; PPP negotiation... \c"
      # check whether PPP negotiation was successful:
      set `ping -qc3 -i1 $IP_ADDRESS 2>/dev/null | grep transmitted`
      let RETURN=$4
      if [ $RETURN -gt 0 ]
      then
	echo "succeeded."
      else
	echo "failed!"
      fi
    else
      let RETURN=0
      echo "not established!"
    fi
  ;;
  *)     # test modem connection
    echo -e "Physical connection... \c" 
    if [ "$INTERFACE" = "-----" ]  # Modem connect failed
    then
      let RETURN=0
      echo "not established!"
    else
      echo -e "established; PPP negotiation... \c"
      set `ping -qc3 -i1 $IP_ADDRESS 2>/dev/null | grep transmitted`
      let RETURN=$4
      if [ $RETURN -gt 0 ]
      then
	echo "succeeded."
      else
	echo "failed!"
      fi
    fi
  ;;
  esac
}

Rd_Status()
# Reads status file. Sets INTERFACE, DEVICE, IP_ADDRESS, TIMES_OPEN and ROUTE;
# needs $REMOTE.
# If you want to change the format, don't forget the init file (rc.connect)! 
{
  set `cat $STATUS | grep $REMOTE`
  if [ -z "$2" ]
  then
    echo -e "$PROV_DIR/$REMOTE does not exist! Check also $BASE!"
    Fatal_Error
  fi
  INTERFACE=$2
  ACT_DEVICE=$3
  IP_ADDRESS=$4
  TIMES_OPEN=$5
  ACT_ROUTE=$6
}

Wr_Status()
# Actualizes status file. Needs $REMOTE, $IP_ADDRESS and actualized
# $TIMES_OPEN. Also needs actual $ACT_ROUTE.
# If you want to change the format, don't forget the init file (rc.connect)! 
{
  # actualize $REMOTE line and save it as first line
  echo "$REMOTE $INTERFACE $DEVICE $IP_ADDRESS $TIMES_OPEN $ACT_ROUTE" \
       >$STATUS_BAK

  # save all lines in status file but not the $REMOTE one. 
  cat $STATUS | grep -v $REMOTE >>$STATUS_BAK

  # restore status file.
  mv $STATUS_BAK $STATUS
}


# =============================================================================


# Main script


# -----------------------------------------------------------------------------

# Did rc.connect run?
if [ ! -e $STATUS ]
then
  echo -e "$STATUS missing - did you run rc.connect? Exiting."
  rm /var/lock/LCK..connect
  exit 1
fi


# -----------------------------------------------------------------------------

# Lock script (against errors in status file)
let i=3
while [ $i -gt 0 ]
do  
  let i-=1
  if [ -e /var/lock/LCK..connect ]
  then
    read LCK_PID </var/lock/LCK..connect
    set `ps -a | grep $LCK_PID`
    if [ $LCK_PID -eq $1 ]
    then
      echo "Script is locked - trying again in 30 seconds."
      sleep 30
    else
      echo "Unclean shutdown - removing lock file /var/lock/LCK..connect"
      rm /var/lock/LCK..connect
      break
    fi
  else
    break
  fi
done

# also locked after three attempts?
if [ -e /var/lock/LCK..connect ]
then
  echo -e "\aSorry - could not unlock for three times. Try later!"
  exit 1
fi

# create lock file
echo $PID >/var/lock/LCK..connect


# -----------------------------------------------------------------------------

# Main loop.

case $OP_MODE in
on)

  # check whether one of the remotes seem already open
  BUFFER=$REMOTE_LIST
  until [ -z "$BUFFER" ]
  do
    set `echo $BUFFER`
    REMOTE=$1
    shift
    BUFFER=$*
    Rd_Status
    if [ $TIMES_OPEN -gt 0 ]
    then
      ALREADY_OPEN=TRUE
      break
    fi
  done

  # Now main loop: open channel, if necessary, and check it.
  until [ $MAX_TRIES -eq 0 ]
  do

    if [ "$ALREADY_OPEN" = "TRUE" ]
    then
      ALREADY_OPEN=FALSE
    else
      # rotation of devices
      set `echo $REMOTE_LIST`
      REMOTE=$1
      shift
      REMOTE_LIST="$* $REMOTE"

      # Pick REMOTE's data from status file.
      Rd_Status
    fi
      
    # Found channel now. Already open?
    case $TIMES_OPEN in
    0)
      echo -e "\nCalling $REMOTE"
      Connect_Dial
      NEW_CONN=true  # this is to detect whether line was closed by HUPTIMEOUT.
    ;;
    1)
      echo "Line may be used already by an application - checking..."
    ;;
    *)
      echo "Line may be used already by $TIMES_OPEN applications - checking..."
    ;; 
    esac

    # check channel.
    echo "Line open - checking..."    
    Line_OK
    if [ $RETURN -gt 0 ]
    then
      # channel is ok
      if [ $TIMES_OPEN -eq 0 ]
      then
        ACT_ROUTE=$ROUTE
      else
        echo "Parameters set in command line will be ignored"
	Rd_Status
	DEVICE=$ACT_DEVICE
      fi
      if [ -n "`echo $INTERFACE | grep "ippp"`" ]
      then
        DEVICE="-----"
      fi
      let TIMES_OPEN+=1
      Wr_Status
      echo -e "8-) Line is ok - have fun!\n"
      break
    else
      Connect_Hangup
      if [ "$NEW_CONN" = "false" ]
      then
        # channel is closed - try to reopen
        echo ":-| Hmm, no. Trying to rebuild..."
        Connect_Dial      
        echo "Line open - checking..."    
        Line_OK
        if [ $RETURN -gt 0 ]
        then
         #channel now open
          if [ $TIMES_OPEN -eq 0 ]
          then
            ACT_ROUTE=$ROUTE
          else
            echo "Parameters set in command line will be ignored"
            Rd_Status
            DEVICE=$ACT_DEVICE
          fi
          if [ `echo $INTERFACE | grep "ippp"` ]
          then
            DEVICE="-----"
          fi
          let TIMES_OPEN+=1
          Wr_Status
          echo -e "8-) Line is ok - have fun!\n"
          break
        fi
      fi
      
      #channel could not be opened
      if [ $MAX_TRIES -eq 1 ]
      then
        let TIMES_OPEN=0
        ACT_ROUTE=""
        if ! [ `echo $INTERFACE | grep "ippp"` ]
	then
	  INTERFACE="-----"
	fi
	DEVICE="-----"
        Wr_Status
        echo -e ":-[ Sorry, all lines are down. Bad luck... Try later!\n"
        rm /var/lock/LCK..connect
        exit 1
      fi
    fi

    echo ":-( This line is down. Trying next one."
    let MAX_TRIES-=1
    
  done

;;

# -----------------------------------------------------------------------------


off)

  # parse device string
  until [ -z "$REMOTE_LIST" ]
  do
    set `echo $REMOTE_LIST`
    REMOTE=$1
    shift
    REMOTE_LIST=$*

    case $REMOTE in

    all)
      # force closing of all channels and clear status file
      for FILE in $PROV_DIR/*
      do
        REMOTE=`basename $FILE`
	Rd_Status
	TIMES_OPEN=0
        ACT_ROUTE=""
	Connect_Hangup
        if ! [ `echo $INTERFACE | grep "ippp"` ]
	then
	  INTERFACE="-----"
	fi
	DEVICE="-----"
        Wr_Status
      done
    ;;

    *) 
      # Pick REMOTE's data from status file.
      Rd_Status
      if [ -z "$INTERFACE" ]
      then
        echo "$PROV_DIR/$REMOTE does not exist! Check also $BASE!"
        echo "To force closing all channels use \"connect off all\"."
        Fatal_Error
      fi

      # close $INTERFACE, if no other application running
      case $TIMES_OPEN in
      0)
        echo "$REMOTE already closed! To force closing use \"connect off all\""
      ;;
      1)
        echo "Last application - hanging up $REMOTE ($INTERFACE)."
        Connect_Hangup
        let TIMES_OPEN=0
        ACT_ROUTE=""
        if [ -z "`echo $INTERFACE | grep "ippp"`" ]
	then
	  INTERFACE="-----"
	fi
	DEVICE="-----"
        Wr_Status
      ;;
      2)
        echo "One more application running - $REMOTE ($INTERFACE) left open."
        let TIMES_OPEN-=1
        ACT_ROUTE=$ROUTE
        DEVICE=$ACT_DEVICE 
        Wr_Status      
      ;;
      *)
        let TIMES_OPEN-=1
        echo "$TIMES_OPEN more applications running - $REMOTE ($INTERFACE) left open."
	ACT_ROUTE=$ROUTE
        DEVICE=$ACT_DEVICE 
        Wr_Status      
      ;;
      esac
    ;;
    esac

  done
;;

# -----------------------------------------------------------------------------


status)  

  for INTERFACE in $PROV_DIR/*
  do
    REMOTE=`basename $INTERFACE`
    Rd_Status
    echo -e "$REMOTE ($INTERFACE): \c"
    case $TIMES_OPEN in
    0)
      echo "closed";;
    1)
      echo "used by one application";;
    *)
      echo "used by $TIMES_OPEN applications";;
    esac  
  done

;;

# -----------------------------------------------------------------------------
  

help)

  Fatal_Error  # I know this is dirty...

;;

# -----------------------------------------------------------------------------
  

*)

  Fatal_Error

;;

esac

# -----------------------------------------------------------------------------


# unlock script
rm /var/lock/LCK..connect



# End of script.
