#!/bin/bash 

# This script measure the drift between the local system clock and 
# a NTP server

tmpdir="/tmp"
cronFile="/etc/cron.d/root"
tmpCronFile="$tmpdir/root.cron"
prefix="system_clock_monitor"
debugLogFile="$tmpdir/$prefix.log"
script="/etc/init.d/system_clock_monitor"
systemClockMonitoringStatus="$tmpdir/${prefix}_status"
systemClockMonitoringDrift="$tmpdir/${prefix}_drift"
dotConfig="/wr/etc/dot-config"
fileNtpServerConfig="/etc/wr_date.conf"
ntpTool="/usr/sbin/ntpd"
suspendKillDaemon=0
pidKillDaemon=0
verbose=0

#
# Write message to file
# $1: Message 
# $2: Output file

writeMsg() {
  msg=$1
  of=$2
  
  oft="$of.old"
  # If old file exists then remove it 
  if [ -f $oft ] ; then
     rm -f $oft
  fi
  # if file exists then rename it 
  if [ -f $of ] ; then
  	mv $of $oft
  fi
  # create the file
  echo "$msg" > $of
}

#
# Print message if verbose is set
#
debug () {
	if [[ $verbose == 1 ]]; then
		echo $1 >&1| tee -a $debugLogFile
		eval echo $1 $LOGPIPE 
	fi
}

#
# remove from cron file any entry related to system clock monitor
#  
cleanCronConfig ()
{
	local entryPresent=0
	
	set -f
	: > $tmpCronFile
	while IFS= read -r line; do
		if [[ "$line" =~ $script ]] ; then 
			entryPresent=1
		else 
			echo "$line" >> $tmpCronFile
		fi
	done < "$cronFile"
	if (( entryPresent == 1 )) ; then
	  	 debug "$cronFile has been cleaned up"
		 mv $cronFile $cronFile.old
		 mv $tmpCronFile $cronFile
	else
    	debug "$cronFile  does not need to be cleaned up "
		rm -f $tmpCronFile
	fi
	set +f
}

#
# Add system clock monitor entry in crontab
#  
setCronConfig ()
{
	local __entry=$1
	
	debug "Add new entry in cron file $cronFile"
	debug "New entry added \"$__entry\""
	echo "$entry" >> $cronFile
}

#
# Compare the offset to the threshold value
# 
compareToThreshold () 
{
	local  __resultvar=$1
	# $(( )) to remove leading "+"
	local  __c_offset=$(( $2 ))
	local  __c_threshold=$3

	if (( $__c_offset >= __c_threshold )) ; then
		x=$(( $__c_offset - $__c_threshold ))
		debug "X1System clock drift is exceeding the threshold by ${x} sec"
		eval $__resultvar="1"
	elif (( -$__c_offset >= __c_threshold )) ; then
		x=$(( $__c_offset + $__c_threshold ))
		debug "X2System clock drift is exceeding the threshold by ${x} sec"
		eval $__resultvar="1"
	else
		debug "System clock drift is not exceeding the threshold"
		eval $__resultvar="0"
	fi
}

#
# Decode NTPD deamon output to get the offset in seconds.useconds
#

decodeOffset ()
{
	local __resultvar=$1
	local __str=$2
	local __offset=$(echo $__str | sed -n 's/.* offset:\?\([+-][0-9]*\)\.\?\([0-9]*\) .*/\1.\2/p')
	
	if [[ -z "$__offset" ]] ; then
		# Empty string
		debug "Invalid Offset !!!"
		debug "NTP msg=\"$__str\""
		return 1
	else
		# Change the sign of the offset. ntpd returns a positive
		# offset when the ntp time is ahead of the local time, which is
		# counter-intuitive.
		if [ "${__offset:0:1}" = "-" ]; then
			__offset=+${__offset:1}
		elif [ "${__offset:0:1}" = "+" ]; then
			__offset=-${__offset:1}
		fi
		debug "NTP offset=$__offset s"
		eval $__resultvar="'$__offset'"
		return 0
	fi 
}


#
# Kill the NTPD daemon in background after few seconds
#
killNTPD () 
{
	local delay=$1 # Delay in seconds
	
	if (( $pidKillDaemon != 0 )) ; then  
		debug "Daemon actif !!!" 
		kill -9 $pidKillDaemon &>/dev/null
		pidKillDaemon=0
	fi
	( 
		sleep $delay
		ppid=$$
		# Search for a ntpd only within this script's childs
		p=$(pgrep -g $ppid $ntpTool)
		if [[ -n "$p" ]] ; then
			kill -9 $p &>/dev/null
		fi
		pidKillDaemon=0
	) &
	pidKillDaemon=$!
}

#
# Read the NTP server to get the offset between NTP and local system time
#
read_ntp_server()
{
	local __result=$1
	local ltThreshold=$2
	local server=$3
	local retries=2
	local offset=-1
	
	debug "NTP server=$server"
	for i in `seq $retries` ; do # Manual retries
		killNTPD 10
		ntpRes=$($ntpTool  -n -w -q -d -p  $server 2>&1)
		if [ -n "$ntpRes" ] ; then
			decodeOffset offset "$ntpRes"
			if [ $? -eq 0 ] ; then
				compareToThreshold alarmState ${offset%.*} $ltThreshold
				if (( $alarmState == 1 )) ; then
					# Exceeded Threshold  
					writeMsg "exceeded_threshold" $systemClockMonitoringStatus
				else
					writeMsg "no_error"  $systemClockMonitoringStatus				
				fi
				writeMsg  $offset $systemClockMonitoringDrift
				eval $__result="0"
				return
			fi
			eval echo "Retry $i/$N : Cannot extract offset from NTP message." $LOGPIPE 
		else 
			eval echo "Retry $i/$N : NTP query failed, unable to contact server ($server)." $LOGPIPE 
		fi
	done
	eval echo "ERROR: could not reach NTP server '$S' after $N retries" $LOGPIPE 
	eval $__result="1"
}

#
# Apply dot-config configuration
#
if [ -f $dotConfig ]; then
    # source dot-config
    . $dotConfig
else
	echo "$0 unable to source dot-config ($dotConfig)!"
fi

WRS_LOG=$CONFIG_WRS_LOG_OTHER

# if empty turn it to /dev/null
if [ -z $WRS_LOG ]; then
	WRS_LOG="/dev/null";
fi
# if a pathname, use it
if echo "$WRS_LOG" | grep / > /dev/null; then
	eval LOGPIPE=\" \> $WRS_LOG 2\>\&1 \";
elif [ "$WRS_LOG" = "default_syslog" ]; then
	# not a pathname: use verbatim
	eval LOGPIPE=\" 2\>\&1 \| logger -t $prefix --prio-prefix -p daemon.info\"
else
	# not a pathname: use verbatim
	eval LOGPIPE=\" 2\>\&1 \| logger -t $prefix -p $WRS_LOG\"
fi

debug "Script started with options \"$@\""

# Read options
if [ "$#" -eq 1 ] && [ "$1" == "-s" ] ; then
  
  	debug "Setup configuration"
	cleanCronConfig 
	if [ "$CONFIG_SNMP_SYSTEM_CLOCK_MONITOR_ENABLED" = "y" ] ; then
		set -f # Disable globbing 
		NEWLINE=$'\n'
		# System clock monitor enabled. Setup cron file
		if [ "$CONFIG_SNMP_SYSTEM_CLOCK_UNIT_MINUTES" = "y" ] ; then
			intervalValue=$CONFIG_SNMP_SYSTEM_CLOCK_CHECK_INTERVAL_MINUTES
			debug "Time interval: ${intervalValue} minutes"
			entry="# System clock monitor: Execute the script \"${script}\" every ${intervalValue} minute(s)${NEWLINE}"
			entry+="*/${intervalValue} * * * *  ${script}"; 
			setCronConfig "$entry"
		else 
			if [ "$CONFIG_SNMP_SYSTEM_CLOCK_UNIT_HOURS" = "y" ] ; then 
			    intervalValue=$CONFIG_SNMP_SYSTEM_CLOCK_CHECK_INTERVAL_HOURS
				debug "Time interval: ${intervalValue} hours"
			    entry="# System clock monitor: Execute the script \"${script}\" every ${intervalValue} hour(s)${NEWLINE}"
			    entry+="0 */${intervalValue} * * *  ${script}"
				setCronConfig "$entry" 
			else
				if [ "$CONFIG_SNMP_SYSTEM_CLOCK_UNIT_DAYS" = "y" ]; then
					intervalValue=$CONFIG_SNMP_SYSTEM_CLOCK_CHECK_INTERVAL_DAYS
					debug "Time interval: ${intervalValue} days"
				    entry="# System clock monitor: Execute the script \"${script}\" every ${intervalValue} day(s)${NEWLINE}"
					entry+="0 0 */${intervalValue} * *  ${script}" 
					setCronConfig "$entry" 
				else
 					eval echo "Invalid unit for system clock check interval." $LOGPIPE
					writeMsg "config_error" $systemClockMonitoringStatus
					writeMsg "0" $systemClockMonitoringDrift
					exit 1
				fi 
			fi 
		fi  
		set +f # Re-enable globbing 
		eval echo "cron file \"root\" configured" $LOGPIPE
	else
		# System clock monitor disabled. Make a clean up 
		rm -f  $systemClockMonitoringStatus
		rm -f  $systemClockMonitoringDrift
	fi
 	exit 0
fi

if [ "$CONFIG_SNMP_SYSTEM_CLOCK_MONITOR_ENABLED" = "y" ] ; then
    
    
    threshold=$CONFIG_SNMP_SYSTEM_CLOCK_DRIFT_THOLD
	ntpServer=""
	
	# Get the NTP server
	if [ -f $fileNtpServerConfig ]; then
		# pick the first server, if any
		ntpServer=$(grep 'ntpserver' $fileNtpServerConfig | sed 's/ntpserver//' | head -n 1)
	fi
		
	if [ -z  "$threshold" ] ; then 
		eval echo "System clock drift threshold not set." $LOGPIPE
		writeMsg "config_error" $systemClockMonitoringStatus
		writeMsg "0" $systemClockMonitoringDrift
		exit 1
	fi
	
	if [ -z "$ntpServer" ]; then
		eval echo "Empty NTP server name" $LOGPIPE
		writeMsg "config_error" $systemClockMonitoringStatus
		writeMsg "0" $systemClockMonitoringDrift
		exit 1
	fi

	read_ntp_server result $threshold $ntpServer
	if (( result != 0 )) ; then
		writeMsg "ntp_error" $systemClockMonitoringStatus
		writeMsg "0" $systemClockMonitoringDrift
		exit 1
	fi
fi
exit 0
