#!/bin/bash

# Copyright 2016 Peter Palfrader
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


set -e
set -u

MAX=2
SYNSTATUSONLY="0"

usage(){
	ret=$1

	cat <<EOF
$0: usage:
	$0 <options>

	Check NTP sync status (per timedatectl's output) and offset to RTC clock.
	The latter is particularly interesting for VMs.

	-o <secs>   Maximum offset to tolerate (Default: $MAX)
	-s          Check sync status only, do not diff against RTC.
EOF

	exit $ret
}

while getopts o:sh opt ; do
	case "$opt" in
		o) MAX="$OPTARG" ;;
		s) SYNSTATUSONLY="1";;
		h) usage 0
	esac
done
shift $(($OPTIND - 1))
if [ "$#" -gt 0 ]; then
	usage 1 >&2
fi


temp="$(mktemp)"
trap "rm -f '$temp'" EXIT

systemdversion="$(timedatectl --version | head -n1 | awk '{print $2}')"
if [ -z "$systemdversion" ]; then
	echo "Unknown: Cannot get systemd version"
	exit 3
fi
if [ "$systemdversion" -lt 241 ] ; then # before buster (Debian 10)
	LC_ALL=C timedatectl > "$temp"
	ut=$(sed '/Universal time:/ { s/^[^:]*: *//; p}; d' "$temp")
	rtc=$(sed '/RTC time:/ { s/^[^:]*: *//; p}; d' "$temp")
	ntpenabled=$(sed '/\(NTP enabled\|Network time on\|NTP service\):/ { s/^[^:]*: *//; p}; d' "$temp")
	ntpsynced=$(sed '/\(NTP synchronized\|System clock synchronized\):/ { s/^[^:]*: *//; p}; d' "$temp")
else
	LC_ALL=C timedatectl show > "$temp"
	ut=$(sed '/^TimeUSec=/ { s/^[^=]*=//; p}; d' "$temp")
	rtc=$(sed '/^RTCTimeUSec=/ { s/^[^=]*=//; p}; d' "$temp")
	ntpenabled=$(sed '/^NTP=/ { s/^[^=]*=//; p}; d' "$temp")
	ntpsynced=$(sed '/^NTPSynchronized=/ { s/^[^=]*=//; p}; d' "$temp")
	if [ "$ntpenabled" = "no" ]; then # in buster (Debian 10) ntpenabled no longer also considers the ntp service
		ntp_status=$(systemctl is-enabled 'ntp.service' 2>/dev/null) && rc=$? || rc=$?
		if [ "$rc" = 0 ] && [ "$ntp_status" = "enabled" ] ; then
			if systemctl --quiet is-active ntp.service; then
				ntpenabled=yes
			fi
		fi
	fi
fi

uts=$(TZ=UTC date -d "$ut" +%s)
rtcs=$(TZ=UTC date -d "$rtc" +%s 2>/dev/null || echo "N/A")
if [ "$rtcs" != "N/A" ]; then
	delta=$((uts - rtcs))
fi

if [ "$SYNSTATUSONLY" -ge 1 ]; then
	if [ "$ntpsynced" != "yes" ]; then
		echo "Warning: not synced with NTP."
		exit 1
	fi
else
	if [ "$rtcs" = "N/A" ]; then
		echo "Warning: Cannot parse RTC $rtc."
		exit 1
	fi

	if [ "$delta" -lt "-$MAX" ] ||
	   [ "$delta" -gt "$MAX" ]; then
		echo "Warning: time desync $delta: RTC vs. system time: $rtc vs. $ut"
		exit 1
	fi

	if [ "$ntpenabled" != "yes" -a "$ntpenabled" != "active"  ]; then
		echo "Warning: NTP not enabled!"
		exit 1
	fi

	if [ "$ntpsynced" != "yes" ]; then
		echo "Warning: not synced with NTP (but clock is OK for now)."
		exit 1
	fi
fi

echo "OK: synced at $ut."
