#!/bin/bash

# getnewip

#
# Copyright (C) 2018 Caleb Woodbine <calebwoodbine.public@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

VERSION=2.2.1

function helpMenu() {
#print help menu

echo "getnewip (version: $VERSION)"

local opt
opt=$1
case "$opt" in
	config)
echo "-------------------------
How to setup config files

Config files are just text files containing Bash variables.

1 EASIEST) Run 'getnewip -m'

2 Easy) Make a copy of '/etc/getnewip/getnewip-example.conf' into
	   '/etc/getnewip/units/' as a new config file, and customise variables.

Notes:
	Every variable in the config files is important, unless regarded otherwise.
"
	;;

	sshconfig)
echo "----------------
SSH config files

What are they for:
	SSH config files are a way of storing logins
	to make connecting to a host quicker.

Where:
	~/.ssh/config

Basic contents of a file:
	Host NICKNAME
		HostName IPADDRESS

Notes:
	Read 'man ssh_config' to read indepth about SSH user config files.
"
	;;

	service)
echo "--------------------
Systemd service info

Default seutp:
	By default, the service will
		- run as root.
		- run over all config files.
		- be DISABLED.

Location:
	The service file is located at 'etc/systemd/system/getnewip.service'.
	Note: You may need to reload the daemons after editing.

Control service:
	Enable: 'systemctl enable getnewip'.
	Start:'systemctl start getnewip'.
	Status: 'systemctl status getnewip'.
"
	;;

	settings)
echo "--------------------
Settings config info

Where is it located?
	/etc/getnewip/getnewip-settings.conf

Disabling Units:
	Add entries into 'disabledUnits=()'
	i.e: disabledUnits=('server1' 'mainServer2' 'homeServer')

Wait time between unit checking:
	'loopDelayTime' relates to the amount of time that is slept between checking units.
	Give it a reasonable amount of time, 600 (seconds) is default.

Amount of time until curl requests drops:
	'curlTimeoutTime' is a variable which dictates the amount of time that a curl request can last.
	Default is '180' (which is 3 minutes).
"
;;

	*)
echo "------------------------
Usage: getnewip -[OPT] [UNIT]

Running Modes:
	-n|--no-loop|-o|--once		perform a single check and exit.
	-d|--daemonise|--daemonize	to run in service mode, looping.
	-t|--testmode			for test mode.
	-l|--list			list available config files.
	-m|--make-unit			make a unit config file.
	-r|--remove-units		remove a unit config file.
	-h|--help			print this menu.
		config				get info on config files.
		sshconfig			get info on setting up '~/.ssh/config'.
		service				get info on systemd service.
		settings			get settings on configuring getnewip.

Config files:
	Units:
		You can call a config file in '/etc/getnewip/units' to run it.

	Settings:
		getnewip settings config is located at '/etc/getnewip/getnewip-settings.conf'.
"
	;;
esac

}

function printVersion() {
# print simple version information

echo "getnewip v$VERSION"

}

function getDefaultUnitConfig() {

echo "## mainServer, workServer, serverAtHome, etc. A pretty name which is the same as a unit used with uploadnewip.
unitName=''

## Address of local server: xxx.xxx.xxx.xxx (i.e 192.168.0.1).
localReferenceDevice=''

## open local ports to test from localReferenceDevice. i.e: (22 80 443) etc...
localTestPorts=()

## amount of time to wait until stop checking if port is active i.e: 3 (seconds).
portCheckTimeOut=

## always up external server to ping. i.e: '8.8.8.8'.
pingServer=''

## .ssh/config host(s) to modify i.e serverRemote, homeserver.
SSHcfgHost=('')

## dropbox app folder i.e ServerIP, IP, Addresses.
dropboxFolder=''

## user(s) to effect. i.e: ('jane' 'john' 'mark')
includedUsers=('')

## dropbox app key -- It should be a 64 character string.
dropboxAppKey=''"

}

function cleanVariables() {
# clean/clear variables between checks

localReferenceDevice=''
localTestPorts=()
portCheckTimeOut=
pingServer=''
SSHcfgHost=('')
dropboxFolder=''
unitName=''
dropboxAppKey=''
includedUsers=('')

}

function main() {
# main funtion that runs

if ! $changeIPRegardless
then
	connectionTest && checkIfNotAtLocalDevice

elif $changeIPRegardless
then
	IPfromDB
fi

}

function connectionTest() {
# test internet
if ping -q -c 1 -W 1 $pingServer > /dev/null
then
  	# if internet is up
	echo "> Can ping '$pingServer' for unit '$unitName'."
	return 0
else
  	# if internet is down
	echo ">> Can't ping '$pingServer' for unit '$unitName'."
	return 1
fi
}

function checkIfNotAtLocalDevice() {
# check if connection is external from server

capturePortActive=()
capturePortNum=0

for port in "${localTestPorts[@]}"
do
	localPortTest=$(nc -zv -w3 "$localReferenceDevice" "$port" 2>&1)
	capturePortActive+=($?)
	[ "${capturePortActive[-1]}" = 0 ] || echo ">> Port: '$port' closed." && capturePortNum=$((capturePortNum+=1))
done

if [ ! $capturePortNum = ${#localTestPorts[@]} ]
then
	echo ">> '$unitName' config not completing, because not all ports suggested of '$localReferenceDevice' were open."
else
	IPfromDB
fi

}

function IPfromDB() {
# download current IP from dropbox

echo "> Downloading copy of IP from dropbox."
tmpFile="/tmp/getnewip-response-$RANDOM-temp"
newIPnum=$(curl -m "$curlTimeoutTime" --progress-bar -X POST --globoff -D "$tmpFile" --header "Authorization: Bearer $dropboxAppKey" --header "Dropbox-API-Arg: {\"path\": \"/$dropboxFolder/currentip-$unitName.txt\"}" "https://content.dropboxapi.com/2/files/download")
checkHttpResponse

if [[ "$newIPnum" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
then
	echo "> Downloaded IP successfully."
	[ -f $tmpFile ] && rm "$tmpFile"
	modifySSHConfig
else
	echo ">> Download Failed. Retrying."
	sleep 20s
	IPfromDB
fi

}

function checkHttpResponse() {
# verify if response was successful

local response tempFile
response=$?

if [ ! "$response" = 0 ]
then
	echo ">> Failed to access dropbox in some way." && return 1
fi

for tempFile in $(command ls /tmp/getnewip-response-*-temp)
do
	[ -f "$tempFile" ] && rm "$tempFile"
done
return 0

}

function modifySSHConfig() {
# change $HOME/.ssh/config

local user SSHconfigFileLocation

for user in "${includedUsers[@]}"
do
	homeDir=$(getent passwd "$user" | cut -d : -f 6)
	[ -z "$homeDir" ] && echo ">> Could not find home directory of user '$user'."
	if [[ $(whoami) = $user || $(id -g) = 0 ]]
	then
		SSHconfigFileLocation="$homeDir/.ssh/config"
		if [ -f "$SSHconfigFileLocation" ]
		then
			for givenHost in "${SSHcfgHost[@]}"
			do
				oldIPnum=$(ssh -G "$givenHost" | grep -i "HostName " | head -n 1 | cut -d" " -f2)
				[[ $(id -g) = 0 ]] && oldIPnum=$(su "$user" -c "ssh -G $givenHost | grep -i 'HostName ' | head -n 1 | cut -d' ' -f2")

				[[ ! ${#includedUsers[@]} = 1 ]] && echo "> Updating '$user'."
				[[ -z "$oldIPnum" ]] && echo ">> Invalid host '$givenHost'." && return 1

				if [ ! "$oldIPnum" = "$newIPnum" ] || "$changeIPRegardless"
				then
					echo "> Host '$givenHost': Changing IP from $oldIPnum to $newIPnum in config"
					sed -i -e "s/$oldIPnum/$newIPnum/g" "$SSHconfigFileLocation" || echo ">> Failed to update IP for '$givenHost' for user '$user'."
				else
					echo "> Host '$givenHost': IP hasn't changed from '$oldIPnum', doing nothing..."
				fi
			done
		else
			echo ">> SSH config not found for user '$user'."
		fi
	else
		echo ">> Not updating config for '$user'. '$user' or root must do that."
	fi
done

}

function variableCheck() {
# check for empty variables in config file

local opt users host port digit
opt="$@"
users=$(awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' /etc/passwd)
digit=[0-9]

local var
if [[ -z "$localReferenceDevice" ]] || \
   [[ -z "${localTestPorts}" ]] || \
   [[ -z "$portCheckTimeOut" ]] || \
   [[ -z "${SSHcfgHost}" ]] || \
   [[ -z "$dropboxFolder" ]] || \
   [[ -z "$unitName" ]] || \
   [[ -z "$dropboxAppKey" ]] || \
   [[ -z "${includedUsers}" ]] || \
   [[ -z "$pingServer" ]]
then
	[ -z "$opt" ] && echo ">> Please fill in all required variabled -- Edit '/etc/getnewip/units/$configFile'." && exit 1
	return 1
else
	[ ! "${#dropboxAppKey}" = 64 ] && echo ">> Dropbox key invalid." && return 1
	[[ ! "$portCheckTimeOut" =~ $digit ]] && echo ">> 'portCheckTimeOut' must be a number." && return 1
	[[ ! "$pingServer" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && echo ">> 'pingServer' must be a valid IPv4 address." && return 1
	for port in "${localTestPorts[@]}"
	do
		[[ ! "$port" =~ $digit ]] && return 1
	done
	for user in "${includedUsers[@]}"
	do
		homeDir=$(getent passwd "$user" | cut -d : -f 6)
		if ! echo "$users" | grep -q -w "$user"
		then
			echo ">> Could not find user '$user'."
			return 1
		else
			for host in "${SSHcfgHost[@]}"
			do
				! grep -q -w "$host" "$homeDir/.ssh/config" && echo ">> Cannot find SSH config file for user '$user'." && return 1
			done
		fi
	done
	return 0
fi

}

function configLoad() {
# load each iteration of config file and run each.

local configFiles
configFiles="$@"

echo "${configFiles[@]}" | grep -q -w 'getnewip-example' && helpMenu config

for configFile in $configFiles
do
	! echo "$configFile" | grep -q ".conf" && configFile="$configFile.conf"

	if [ -f "/etc/getnewip/units/$configFile" ]
	then
		cleanVariables
		echo "> Loading config '$configFile'."
		. /etc/getnewip/units/"$configFile"
		variableCheck && main || echo "> Unit config variables not OK. Please fix stated problem with unit config."
	else
		echo ">> Config '$configFile' not found."
	fi
done

}

function listConfigUnits() {
# list all available units' config files

local unit opt out counter
opt="$@"
counter=0

[ ! -d /etc/getnewip/units ] && echo ">> Failed to find units folder." && exit 1

for unit in $(command ls /etc/getnewip/units)
do
	counter=$((counter+=1))
	out="$unit $out"
done

if [ -z "$opt" ]
then
	if [ "$counter" = 0 ]
	then
		echo ">> No units available. Please create one. 'getnewip -h config' for help, 'getnewip -m' to make one."
	elif [ "$counter" = 1 ]
	then
		echo "$counter available unit: $out"
	else
		echo "$counter available units: $out"
	fi
else
	echo "$out"
fi

}

function makeNewConfigUnit() {
# create a new unit file

local user file cont
user=$(whoami)

if [ ! -f "$tempFile" ] || [ -z "$tempFile" ]
then
	tempFile="/tmp/getnewip-unit-$RANDOM$RANDOM-temp"
	getDefaultUnitConfig > "$tempFile"
fi

nano "$tempFile"

echo "
	0) [E]dit config file again.
	1) [R]estart.
	2) [S]ave and exit.
	q) [Q]uit without saving.
"

read -r -p ": " continuevar
echo
case "$continuevar" in
	0|e|E)
		makeNewConfigUnit
	;;

	1|r|R)
		[ -f "$tempFile" ] && rm "$tempFile" && echo "> Cleaned up temporary files."
		tempFile=
		makeNewConfigUnit
	;;

	2|s|S)
		if . "$tempFile" && variableCheck a
		then
			if "$sudoProg" install -g root -o "$user" -m 0600 "$tempFile" "/etc/getnewip/units/$unitName.conf"
			then
				echo "> Config '$unitName' saved."
				checkToEnableSystemdService
				exit 0
			else
				echo ">> Failed to write config."
				[ -f "$tempFile" ] && rm "$tempFile" && echo "> Cleaned up temporary files."  && exit 1
			fi
		else
			echo ">> Variables not set correctly. Please fix them." 
			read -r -p "> Press enter to continue... " cont
			makeNewConfigUnit
		fi
	;;

	q|Q)
		[ -f "$tempFile" ] && rm "$tempFile" && echo "> Cleaned up temporary files."
		for file in /tmp/getnewip-unit-*-temp
		do
			[ -f "$file" ] && rm "$file"
		done
		exit 0
	;;
esac

}

function checkToEnableSystemdService() {
# check to enable systemd service upon creation on a unit, if using systemd.

local cont
which systemctl > /dev/null || return 1
[ -f /usr/lib/systemd/systemgetnewip.service ] || return 1
if ! systemctl is-active --quiet getnewip || ! systemctl is-enabled --quiet getnewip
then
	echo "> It appears that the systemd server is not enabled."
	read -r -p "> Would you like to enable it? [y|*] " cont
	case "$cont" in
		y|Y)
			"$sudoProg" systemctl start getnewip > /dev/null || return 1
			"$sudoProg" systemctl enable getnewip > /dev/null || return 1
			echo "> getnewip has been enabled and started."
		;;
	esac
fi
return 0

}

function checkSettings() {
# check /etc/getnewip/getnewip-settings.conf

if ${loopDelayTime+"false"} || [ -z "$loopDelayTime" ] || [ -z "$curlTimeoutTime" ]
then
	echo ">> Cannot find important config data." && exit 1
fi

local unit
if ! [[ -z "${disabledUnits}" ]]
then
	for unit in "${disabledUnits[@]}"
	do
		[ ! -f /etc/getnewip/units/"$unit".conf ] && echo ">> Cannot find unit '$unit' to disable. Ignoring..."
	done
fi

! [[ "$loopDelayTime" =~ [0-9] ]] && echo ">> Variable 'loopDelayTime' must only contain numbers." && exit 1
! [[ "$curlTimeoutTime" =~ [0-9] ]] && echo ">> Variable 'curlTimeoutTime' must only contain numbers." && exit 1

}

function removeUnit() {
# remove given units
local unitFiles unit unitFullFileName
unitFiles="$@"

[[ -z "$unitFiles" ]] && echo ">> No configs given to remove. Run 'getnewip -l' for a list of units" && exit 1

echo "> Preparing to remove '$unitFiles'."

for unit in $unitFiles
do
	unitFullFileName="$unit"
	! echo "$unitFullFileName" | grep -q ".conf" && unitFullFileName="$unitFullFileName.conf"
	if [ -f /etc/getnewip/units/"$unitFullFileName" ]
	then
		echo "> Removing unit '$unit'."
		if "$sudoProg" rm -v /etc/getnewip/units/"$unitFullFileName"
		then
			echo "> Unit '$unit' removed."
		else
			echo ">> Failed to remove unit '$unit'."
			return 1
		fi
	else
		echo ">> Could not find unit '$unit'."
	fi
done

}

function startMain() {
# pass opts to main function

local configFiles opt arrayOpts
configFiles="$@"
arrayOpts=()

[[ "$configFiles" = "-a" || "$configFiles" = "--all" ]] && configFiles=$(listConfigUnits a)

for opt in $configFiles
do
	arrayOpts+=($opt)
done

if [ "${#arrayOpts[@]}" = 0 ]
then
	helpMenu

else
	if ! "$isLooped"
	then
		configLoad "$configFiles"

	elif "$isLooped"
	then
		while true
		do
			configLoad "$configFiles"
			echo "> Waiting '$loopDelayTime'."
			sleep "$loopDelayTime"
		done
	fi
fi

exit

}

if [ -f /etc/getnewip/getnewip-settings.conf ]
then
	. /etc/getnewip/getnewip-settings.conf
	checkSettings
else
	echo ">> Missing settings config file. Please reinstall."
	helpMenu
	exit 1
fi

hasSudo=$(sudo -nv 2>&1)
if [[ "$hasSudo" = "sudo: a password is required" || "$hasSudo" = "" || -z "$hasSudo" ]] && which sudo > /dev/null
then
	sudoProg="sudo"

elif [ $(id -u) = 0 ]
then
	sudoProg=""
else
	echo ">> You must be in sudoers or be root to use some this program."
	exit 1
fi

optCount=0
givenOpt="$1"

case "$givenOpt" in
	-n|--no-loop|-o|--once)
		# no loop.
		shift
		readonly isLooped=false
		readonly changeIPRegardless=false
		echo "> Running single check."
		startMain "$@"
	;;

	-d|--daemonise|--daemonize)
		# service mode, run as loop.
		shift
		readonly isLooped=true
		readonly changeIPRegardless=false
		echo "> Running in service mode."
		echo ">> Date: $(date)."
		startMain "$@"
	;;

	-t|--testmode)
		# fetch IP regardless, once.
		shift
		readonly isLooped=false
		readonly changeIPRegardless=true
		echo "> Running in test mode."
		startMain "$@"
	;;

	-l|--list)
		# list all available units.
		listConfigUnits
	;;

	-m|--make-unit)
		# create a unit.
		makeNewConfigUnit
	;;

	-r|--remove)
		shift
		removeUnit "$@"
	;;

	-h|--help)
		# print help menu(s).
		shift
		helpMenu "$@"
		exit
	;;

	-v|--version)
		printVersion
	;;

	--print-blank-config)
		getDefaultUnitConfig
	;;

	*)
		# if not args are given, show help.
		helpMenu
		[[ $(listConfigUnits a) = "" ]] && listConfigUnits
	;;
esac
