#!/bin/bash

# uploadnewip

#
# Copyright (C) 2018 Caleb Woodbine <gitlab.com/BobyMCbobs>
#
# 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
opt=$1

function helpMenu() {
# print help menu and given options


echo "uploadnewip (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 'uploadnewip -m'.

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

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

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

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

Location:
	The service file is located at '/usr/lib/systemd/system/uploadnewip.service'.
	Note: You should need to reload the daemons after editing, if you choose to do so.

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

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

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

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

Get logging information:
	Set logging to true.
	A master log file is kept in '/var/log/uploadnewip.log' with permissions '0600/-rw-------'.
	This logging includes IP addresses, hence why it is disable by default.

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, 30 (seconds) is default.

Change server to get IP addresses from:
	'fetchIPServer' is a server that can directly output public IP address of device requesting it.
	The default 'ipinfo.io/ip' is recommended.

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: uploadnewip -[OPT] [UNIT]

Running Modes:
	-s|--single|-o|--once 		perform a single check and exit.
	-d|--daemonise|--daemonize	start as looping daemon.
	-l|--list			list available config files.
	-m|--make			make a unit config file.
	-r|--remove-unit		remove a config unit and associated files.
	-h|--help			print this menu.
		config				get info on config files.
		service				get info on systemd service.
		settings			get info on uploadnewip-settings.

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

Notes:
	- This program needs root to run, because of where it writes to, and for security/private reasons relating to dropbox keys.
	- Unlike getnewip, uploadnewip only supports bulk operations (use all units at once).
	- All config files (that are vaild) will run, unless configured otherwise (check 'uploadnewip -h settings').
"
	;;

esac

}

function printVersion() {
# print basic version information

echo "uploadnewip v$VERSION"

}

function getDefaultUnitConfig() {
# generate default config

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

## folder in dropbox which uploadnewip will upload to.
dbFolder=''

## an always up server that uploadnewip will ping to confirm internet connection.
pingServer=''

## dropbox app key -- 64 character string.
dropboxAppKey=''"

}

function cleanVariables() {
# clean variables between checks

unitName=
dbFolder=
dropboxAppKey=
pingServer=

}

function dbmkdir() {
# make directory in dropbox filesystem

local opt
opt=$@

curl -m "$curlTimeoutTime" --progress-bar -X POST -L -s --show-error --globoff -i -o "/tmp/uploadnewip-response-$RANDOM-temp" --header "Authorization: Bearer $dropboxAppKey" --header "Content-Type: application/json" --data "{\"path\": \"/$opt\"}" "https://api.dropboxapi.com/2/files/create_folder" 2> /dev/null
checkHttpResponse

}

function dbupload() {
# upload file to dropbox filesystem

curl -m "$curlTimeoutTime" --progress-bar -X POST -i --globoff -o "/tmp/uploadnewip-response-$RANDOM-temp" --header "Authorization: Bearer $dropboxAppKey" --header "Dropbox-API-Arg: {\"path\": \"/$dbFolder/currentip-$unitName.txt\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}" --header "Content-Type: application/octet-stream" --data-binary @"$currentIPPos" "https://content.dropboxapi.com/2/files/upload"
checkHttpResponse

}

function dbdownload() {
# download file from dropbox filesystem

curl -m "$curlTimeoutTime" --progress-bar -X POST --globoff -D "/tmp/uploadnewip-response-$RANDOM-temp" -o "/tmp/uploadnewip-$unitName-tmp.txt" --header "Authorization: Bearer $dropboxAppKey" --header "Dropbox-API-Arg: {\"path\": \"/$dbFolder/currentip-$unitName.txt\"}" "https://content.dropboxapi.com/2/files/download"
checkHttpResponse

}

function dbdelete() {
# delete file in dropbox filesystem

curl -m "$curlTimeoutTime" --progress-bar -X POST -L -s --show-error --globoff -i -o "/tmp/uploadnewip-response-$RANDOM-temp" --header "Authorization: Bearer $dropboxAppKey" --header "Content-Type: application/json" --data "{\"path\": \"/$dbFolder/currentip-$unitName.txt\"}" "https://api.dropboxapi.com/2/files/delete" 2> /dev/null
checkHttpResponse

}

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

cleanupTempFiles
return 0

}

function cleanupTempFiles() {
# find and cleanup past run temp files

for tempFile in /tmp/uploadnewip-response-*-temp
do
	[ -f "$tempFile" ] && rm "$tempFile"
done

}

function makeNewConfigUnit() {
# create a new unit file

if [ ! -f "$tempFile" ] || [ -z "$tempFile" ]
then
	tempFile="/tmp/uploadnewip-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 [ -f /etc/uploadnewip/units/"$unitName".conf ]
			then
				echo ">> '$unitName.conf' already exists. Exiting..."
				[ -f "$tempFile" ] && rm "$tempFile" && echo "> Cleaned up temporary files."  && exit 1
			else
				if install -g root -o "$(whoami)" -m 0600 "$tempFile" /etc/uploadnewip/units/"$unitName".conf
				then
					echo "> Config '$unitName' saved."
					rm "$tempFile"
					checkToEnableSystemdService
					exit 0
				else
					echo ">> Failed to write config." && [ -f "$tempFile" ] && rm "$tempFile" && echo "> Cleaned up temporary files."  && exit 1
				fi
			fi
		else
			echo ">> Variables not set correctly. Please fix them." && sleep 1 && makeNewConfigUnit
		fi
	;;

	q|Q)
		[ -f $tempFile ] && rm $tempFile && echo "> Cleaned up temporary files."
		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/system/uploadnewip.service ] || return 1
if ! systemctl is-active --quiet uploadnewip || ! systemctl is-enabled --quiet uploadnewip
then
	echo "> It appears that the systemd service is not enabled."
	read -r -p "> Would you like to enable it? [y|*] " cont
	case "$cont" in
		y|Y)
			systemctl start uploadnewip || return 1
			systemctl enable uploadnewip || return 1
		;;
	esac
fi

}

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

local opt users host
opt=$@

local var
if [[ -z "$unitName" || -z "$dbFolder" || -z "$dropboxAppKey" ]]
then
	[ -z "$opt" ] && echo ">> Please fill in all required variabled -- Edit '/etc/uploadnewip/units/$configFile'." && exit 1
	return 1
else
	[ ! ${#dropboxAppKey} = 64 ] && echo ">> Dropbox key invalid." && return 1
	if ! pingTest
	then
		echo ">> Cannot reach '$pingServer'."
		return 1
	fi
	return 0
fi

}

function loadUnits() {

local unit count disabledUnitList unitFile
count=0
disabledUnitList=""

for unitFile in "${disabledUnits[@]}"
do
	! echo "$unitFile" | grep -q ".conf" && disabledUnitList="$disabledUnitList $unitFile.conf"
done

for unit in $(command ls /etc/uploadnewip/units)
do
	count=$((count+=1))
	if ! echo "$disabledUnitList" | grep -q -w "$unit"
	then
		cleanVariables
		. /etc/uploadnewip/units/"$unit"
		if variableCheck a
		then
			echo "> Running unit '$unit'."
			main
		else
			echo ">> Invalid unit '$unit'. Please input correct formats/data/variables."
		fi
	else
		echo "> Ignoring '$unit', because it was disabled."
	fi
done

[ $count = 0 ] && echo ">> No units available. Use 'uploadnewip -m' to create one. Otherwise, for help on creating units run 'uploadnewip -h config'." && exit 1

}

function main() {
# main routine

serverIPFolder="/var/cache/uploadnewip"
tempIPPos="$serverIPFolder/temp/temp-ip-$unitName.txt"
currentIPPos="$serverIPFolder/currentip-$unitName.txt"

if pingTest
then
	echo "> Checking against fresh IP from '$fetchIPServer'."
	curl "$fetchIPServer" | cat > "$tempIPPos"
	chmod 0600 "$tempIPPos"
	echo "> Verifying."

	if [ -f "$currentIPPos" ]
	then
		chk1=$(sha256sum "$tempIPPos" | awk '{print $1}')
		chk2=$(sha256sum "$currentIPPos" | awk '{print $1}')

		if [ "$chk1" == "$chk2" ] && [ -f "$currentIPPos" ]
		then
			echo ">> IP address for '$unitName' hasn't changed."
		else
			echo "> IP address for '$unitName' has changed."
			mv "$tempIPPos" "$currentIPPos"
			[ -f "$currentIPPos" ] && uploadContinue || echo ">> Unable to find '$currentIPPos'."
		fi
	else
		echo "> Running for first time."
		mv "$tempIPPos" "$currentIPPos"
		[ -f "$currentIPPos" ] && uploadContinue || echo ">> Unable to find '$currentIPPos'."
	fi

else
	echo ">> Unable to upload IP address for '$unitName' due to possible lack of internet connection.."
fi

}

function pingTest() {
# test internet

echo "> Running a quick ping test to '$pingServer' to test internet."
if ping -q -c 1 -W 1 "$pingServer" > /dev/null
then
	echo "> Internet is connected, continuing."
	return 0

else
	echo ">> No internet."
	return 1
fi

}

function uploadContinue() {
# upload to dropbox.

local tempFile outIP
tempFile="/tmp/uploadnewip-$RANDOM-tmp"
echo "> Updating Dropbox files..."
echo "> Removing old IP file..."

dbmkdir "$dbFolder"

dbdelete

echo "> Uploading 'currentip-$unitName.txt' to Dropbox folder '$dbFolder'."
if dbupload
then
	outIP=$(cat $currentIPPos)
	[ "$logging" ] && echo "'$unitName' IP has updated to $outIP at $(date) (automatically)." | tee -a /var/log/uploadnewip.log
	echo "> Done."
else
	echo ">> Upload failed."
fi

}

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

if ${loopDelayTime+"false"} || \
   [ -z "$loopDelayTime" ] || \
   [[ -z $fetchIPServer ]] || \
   [[ -z $curlTimeoutTime ]] || \
   ! [[ $logging = true || $logging = false ]] || \
   ! [[ $logging = true || $logging = false ]] || \
   ! [[ $removeFromDBAfterRemoval = true || $removeFromDBAfterRemoval = false ]]
then
	echo ">> Cannot find important config data." && exit 1
fi

local unit
if [[ ! -z ${disabledUnits} ]]
then
	for unit in ${disabledUnits}
	do
		if [ ! -f "/etc/uploadnewip/units/$unit.conf" ]
		then
			echo ">> Cannot find unit '$unit' to disable. Ignoring..."
		fi
	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 listConfigUnits() {
# output list of available units

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

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

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

if [ -z "$opt" ]
then
	if [ $counter = 0 ]
	then
		echo ">> No units available. Please create one. 'uploadnewip -h config' for help, 'uploadnewip -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 removeUnit() {
# remove a given unit
local unitFiles unit unitFullFileName
unitFiles="$@"

[[ -z $unitFiles ]] && echo ">> No configs given to remove. Run 'uploadnewip -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/uploadnewip/units/"$unitFullFileName" ]
	then
		. /etc/uploadnewip/units/"$unitFullFileName"
		[ "$removeFromDBAfterRemoval" ] && variableCheck a && dbdelete && echo "> Removed unit data from Dropbox."
		echo "> Removing unit config '$unit'."
		if rm -v /etc/uploadnewip/units/"$unitFullFileName"
		then
			echo "> Unit '$unit' removed."
			echo "> Cleaning up..."
			[ -f "/var/cache/uploadnewip/currentip-$unit.txt" ] && rm -v "/var/cache/uploadnewip/currentip-$unit.txt"
			[ -f "/var/cache/uploadnewip/temp/temp-ip-$unit.txt" ] && rm -v "/var/cache/uploadnewip/temp/temp-ip-$unit.txt"
			echo "> Finished removal of unit '$unit'."
		else
			echo ">> Failed to remove unit '$unit'."
			return 1
		fi
	else
		echo ">> Could not find unit '$unit'."
	fi
done

}

function sudoChecker() {
# check if user is root

if [ ! $(id -u) = 0 ]
then
	echo ">> You must be root to use some this program."
	echo "> Run 'uploadnewip -h' for help."
	exit 1
fi

}

function loopInit() {
# loop main function

while true
do
	loadUnits
	echo "> Now waiting $loopDelayTime."
	sleep "$loopDelayTime"
done

}

if [ -f /etc/uploadnewip/uploadnewip-settings.conf ]
then
	. /etc/uploadnewip/uploadnewip-settings.conf
	checkSettings
else
	echo ">> Failed to find settings config."
	exit 1
fi

cleanupTempFiles

case $opt in
	-h|--help)
		# get help menu
		shift
		helpMenu "$@"
	;;

	-m|--make-unit)
		# create a unit config file
		sudoChecker
		makeNewConfigUnit
	;;

	-d|--daemonise|--daemonize)
		# run as daemon
		sudoChecker
		loopInit
	;;

	-s|--single|-o|--once)
		# perform single check and exit
		sudoChecker
		loadUnits
	;;

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

	-r|--remove-unit)
		# remove a unit config file and clean up
		shift
		removeUnit "$@"
	;;

	-v|--version)
		printVersion
	;;

	*)
		# default option
		helpMenu
		[[ $(listConfigUnits a) = "" ]] && listConfigUnits
	;;
esac
