#!/bin/bash
#
# @author Gerhard Steinbeis (info [at] tinned-software [dot] net)
# @copyright Copyright (c) 2013
version=0.6.1
# @license http://opensource.org/licenses/GPL-3.0 GNU General Public License, version 3
# @package net
#
# Configuration file for the ssh-tunnel-manager script. Default location for 
# the config file is /etc/ssh-tunnel-manager.conf or if that does not exist, 
# the ssh-tunnel-manager.conf in the same directory as the script itself.
#

#
# Ths TUNNEL array is used to configure the individual tunnels. Each 
# configuration entry needs to follow the SSH options. An example of 
# how such a configuration line might look like is listed here.
#
# TUNNELS=(
#	"-p 1234 username@host1.example.com -L 10001:127.0.0.1:3306 -L 10011:127.0.0.1:27017"
#	"-p 1234 username@host2.example.com -L 10002:127.0.0.1:3306 -L 10012:127.0.0.1:27017"
#)

#
# The RECONNECT_TIMER is used in case of a tunnel connection to be lost. After 
# the script is detecting that the connection was lost, the time defined the 
# time to wait before the the script tries to reconnect the tunnel.
#
RECONNECT_TIMER=10

#
# The LOGFILE setting defines the path of the logfile. You have the possibility to use 
# the $SCRIPT_PATH variable to define the path of the logfile to be the same 
# as the script directory.
#
LOGFILE=""

#
# This DBG setting is adding additional details to the logfile. The values are 0 
# to hide the extra log content and 1 to show it.
#
DBG=0

#
# Default configuration file to load settings from can be defined here.
#
DEFAULT_CONFIG="ssh-tunnel-manager.conf"

#
# Non Config values
#
# Get script directory
SCRIPT_PATH="$(cd $(dirname $0);pwd -P)"
LOGFILE="$SCRIPT_PATH/$0.log"

#
# Parse all parameters
#
HELP=0
TERMINATE=0
while [ $# -gt 0 ]; do
	case $1 in
		# General parameter
        -h|--help)
			HELP=1
			shift
            ;;
        -v|--version)
			echo 
			echo "Copyright (c) 2013 Tinned-Software (Gerhard Steinbeis)"
			echo "License GNUv3: GNU General Public License version 3 <http://opensource.org/licenses/GPL-3.0>"
			echo 
            echo "`basename $0` version $version"
            echo
            exit 0
            ;;

        # specific parameters
        --config)
			# load settings file
			if [[ -f "$2" ]]; then
				. $2
				CONFIG_FILE=$2
				CONFIG_LOADED=1
			else
				HELP=1
				echo "ERROR: Specified config file cound not be found." 
			fi
            shift 2
            ;;

        # specific parameters
        start)
            COMMAND='start'
            shift
            ;;
		
        stop)
            COMMAND='stop'
            shift
            ;;
		
        restart)
            COMMAND='restart'
            shift
            ;;
		
        status)
            COMMAND='status'
            shift
            ;;
		
        show)
            COMMAND='show'
            shift
            ;;
		
        manage)
            COMMAND='manage'
            INDEX=$2
            shift 2
            ;;
		

		# undefined parameter        
        *)
			echo "ERROR: Unknown option '$1'"
			HELP=1
			shift
			break
			;;
    esac
done

# check if a command has been provided
if [[ -z "${COMMAND}" ]]; then
	if [ "$HELP" -ne "1" ]; then
		echo "ERROR: No command defined in parameter list."
		HELP=1
	fi
fi

# Load the default configuration file if no other configuration file is provided
if [[ -z "$CONFIG_LOADED" ]]; then
	# Check if configuration file exists
	if [[ -f "/etc/$DEFAULT_CONFIG" ]]; then
		. /etc/$DEFAULT_CONFIG
		CONFIG_FILE=/etc/$DEFAULT_CONFIG
	else
		if [[ -f "$SCRIPT_PATH/$DEFAULT_CONFIG" ]]; then
			. $SCRIPT_PATH/$DEFAULT_CONFIG
			CONFIG_FILE=$SCRIPT_PATH/$DEFAULT_CONFIG
		fi
	fi
fi

# check if a tunnel configuration has been provided
if [[ "${#TUNNELS[@]}" -lt "1" && ! -z "${COMMAND}" ]]; then
	if [ "$HELP" -ne "1" ]; then
		echo "ERROR: No tunnel configuration found."
		HELP=1
	fi
fi

# show help message
if [ "$HELP" -eq "1" ]; then
	echo 
	echo "Copyright (c) 2013 Tinned-Software (Gerhard Steinbeis)"
	echo "License GNUv3: GNU General Public License version 3 <http://opensource.org/licenses/GPL-3.0>"
	echo 
	echo "This script is used to setup multiple ssh tunnels and manage to keep "
	echo "them alive. This script will launch monitoring instances to keep the "
	echo "individual tunnels alive. See the configuration file for more details "
	echo "about the configuration."
	echo 
	echo "Usage: `basename $0` [-hv] [--config filename.conf] [start|stop|status|restart]"
  	echo "  -h  --help         print this usage and exit"
	echo "  -v  --version      print version information and exit"
    echo "      --config       Configuration file to read parameters from"
	echo "      start          Start the ssh tunnels configured"
	echo "      stop           Stop the ssh tunnels configured"
	echo "      status         Check the status of the ssh tunnels configured"
	echo "      show           Show the ssh tunnels configured"
	echo "      restart        Restart the ssh tunnels configured"
	echo 
	exit 1
fi



#
# Function to print out a string including a time and date info at the 
# beginning of the line. If the string is empty, only the timestamp is printed 
# out.
#
# @param $1 The string to print out
# @param $2 (optional) Option to echo like ">>logfile.log"
#
function echotime {
	TIME=`date "+[%Y-%m-%d %H:%M:%S]"`
	echo -e "$TIME - $@" >>$LOGFILE
}


# Execute function signal_terminate() receiving TERM signal
#
# Function to print out a string including a time and date info at the 
# beginning of the line. If the string is empty, only the timestamp is printed 
# out.
#
# @param $1 The string to print out
# @param $2 (optional) Option to echo like ">>logfile.log"
#
function signal_terminate {
	echotime "MANAGER - Received TERM for tunnel ID $INDEX"
	TERMINATE=1
}
trap 'signal_terminate' TERM

SCRIPT_PID=$$

#
# start procedure according to the action
#
case $COMMAND in
    restart)
		echotime "COMM - Execute RESTART procedure ... "
		$0 --config $CONFIG_FILE stop
		sleep 2
		$0 --config $CONFIG_FILE start
		echotime "COMM - Execute RESTART procedure ... Done"
		echotime ""
		;;

    stop)
		echotime "COMM - Execute STOP procedure ... "
		for (( idx=0; idx<${#TUNNELS[@]}; idx++ ));
		do
			# notify "manage" script of terminate request. This avoids the restart of the tunnel
			RESULT_PID=`ps aux | grep -v grep | grep "$0 --config $CONFIG_FILE manage $idx" | awk '{print $2}' | tr '\n' ' '`
			[ "$DBG" -gt "0" ] && echotime "STOP - *** DBG-CMD: ps aux | grep -v grep | grep \"$0 --config $CONFIG_FILE manage $idx\" | awk '{print \$2}'"
			for PID in $RESULT_PID; do
				kill $PID &>/dev/null
			done
			echotime "STOP - Stop sent manager of tunnel ID $idx ... PID: $RESULT_PID"

			# Terminate the ssh tunnel processes.
			RESULT_PID=`ps aux | grep -v grep | grep "ssh -N ${TUNNELS[$idx]}" | awk '{print $2}' | tr '\n' ' '`
			[ "$DBG" -gt "0" ] && echotime "STOP - *** DBG-CMD: ps aux | grep -v grep | grep \"ssh -N ${TUNNELS[$idx]}\" | awk '{print \$2}'"
			for PID in $RESULT_PID; do
				kill $PID &>/dev/null
			done

			echotime "STOP - Stopped tunnel ID $idx ... PID: $RESULT_PID"
			
			# check if the tunnels really down
			sleep "0.3"
			TUNNELS_COUNT=0
			TMANAGER_COUNT=0
			TUNNELS_COUNT=`ps aux | grep -v grep | grep "ssh -N ${TUNNELS[$idx]}" | awk '{print $2}' | wc -l`
			TMANAGER_COUNT=`ps aux | grep -v grep | grep "$0 --config $CONFIG_FILE manage $idx" | awk '{print $2}' | wc -l`
			if [[ "$TUNNELS_COUNT" -lt "1" ]] && [[ "$TUNNELS_COUNT" -lt "1" ]]; then
				echo "Stopping tunnel ID $idx ... Done"
			else
				echo "Stopping tunnel ID $idx ... Failed"
			fi
        done
		echotime "COMM - Execute STOP procedure ... Done"
		echotime ""
		;;

    status)
		echotime "COMM - Execute STATUS procedure ... "
		for (( idx=0; idx<${#TUNNELS[@]}; idx++ ));
		do
			# get the list of processs
			RESULT=0
        	RESULT=`ps aux | grep -v grep | grep "ssh -N ${TUNNELS[$idx]}" | wc -l`
        	# show the result
        	if [[ "$RESULT" -gt "0" ]]; then
        		echotime "STATUS - Status of Tunnel ID $idx is ... running"
        		echo "Status of Tunnel ID $idx is ... running"
        	else
        		echotime "STATUS - Status of Tunnel ID $idx is ... NOT running"
        		echo "Status of Tunnel ID $idx is ... NOT running"
        	fi
        done
		echotime "COMM - Execute STATUS procedure ... Done"
		echotime ""
        ;;

    show)
		for (( idx=0; idx<${#TUNNELS[@]}; idx++ ));
		do
        	# show the tunnel config
    		echo "Configuration of Tunnel ID $idx ... ${TUNNELS[$idx]}"
        done
        ;;

    start)
		echotime "COMM - Execute START procedure ... "
		for (( idx=0; idx<${#TUNNELS[@]}; idx++ ));
		do
			RESULT_PID=0
			RESULT_PID=`ps aux | grep -v grep | grep "$0 --config $CONFIG_FILE manage $idx" | awk '{print $2}' | tr '\n' ' '`
			if [[ ! -z $RESULT_PID ]]; then
				echotime "START - Already running tunnel ID $idx ... PID: $RESULT_PID"
				echo "Starting tunnel ID $idx ... Already running"
			else
				$0 --config $CONFIG_FILE manage $idx &
				sleep "0.2"
				RESULT_PID=`ps aux | grep -v grep | grep "$0 --config $CONFIG_FILE manage $idx" | awk '{print $2}' | tr '\n' ' '`
				[ "$DBG" -gt "0" ] && echotime "START - *** DBG-CMD: ps aux | grep -v grep | grep \"$0 --config $CONFIG_FILE manage $idx\" | awk '{print \$2}'"
				echotime "START - Starting tunnel ID $idx ... PID: $RESULT_PID"
				echo "Starting tunnel ID $idx ... Done"
			fi
			# sleep before every cycle to aviod overloading 
        done
		echotime "COMM - Execute START procedure ... Done"
		echotime ""
        ;;

    manage)
		echotime "MANAGE - Connecting tunnel ID $INDEX with parameters ... ${TUNNELS[$INDEX]}"
		while [ "$TERMINATE" -eq "0" ]
		do
		  	SSH_RESULT=`ssh -N ${TUNNELS[$INDEX]} 2>&1`
		  	if [[ "$TERMINATE" -eq "0" ]]; then
			  	echotime "MANAGE - Detected tunnel ID $INDEX disconnected. $SSH_RESULT"
				sleep $RECONNECT_TIMER
				echotime "MANAGE - Reconnecting tunnel ID $INDEX with parameters ... ${TUNNELS[$INDEX]}"
			else
				echotime "MANAGE - Shutdown manager for tunnel ID $INDEX"
		  	fi
		done
		;;
esac