#!/bin/sh
#
# anerd-client: Asynchronous Network Exchange Randomness Daemon Client
#
# Copyright 2012 Dustin Kirkland <dustin.kirkland@gmail.com>
#
# Authors:
#	- Dustin Kirkland
#	- Wesley Wiedenmeier
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#	http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

# Source configuration
PKG="anerd"
[ -r "/etc/default/${PKG}-client" ] && . "/etc/default/${PKG}-client"
[ -n "${1}" ] && POOL="${1}"
[ -n "${IPV6}" ] || IPV6=false
[ -n "${POOL}" ] || exit 0
[ -n "${PORT}" ] || PORT=26373
[ -n "${DEVICE}" ] || DEVICE="/dev/urandom"
[ -n "${BYTES}" ] || BYTES=64
[ -n "${HASH}" ] || HASH="sha512sum"
[ -n "${WAIT}" ] || WAIT="0.1"

# Detect the network tool
if command -v socat >/dev/null; then
	NET_TOOL="socat"
elif command -v nc >/dev/null; then
	NET_TOOL="nc"
else
	echo "ERROR: Please install socat or nc" 1>&2
	exit 1
fi

random_bytes() {
	# Read and print random bytes
	head -c "${BYTES}" "${DEVICE}"
}

salt() {
	# Append timestamp-based salt to input
	cat
	local i=0
	while [ ${i} -lt ${BYTES} ] ; do
		n=$(date +%N | sed "s/^0\+//")
		printf "%x" ${n}
		i=$((i+1))
	done
}

salt_hash_and_write() {
	# Salt input, whiten with a hash, and write to device
	local f=$(mktemp -t "${PKG}.XXXXXXXXXXXX")
	cat > "${f}"
	local bytes=$(wc -c "${f}" | awk '{print $1}')
	cat "${f}" | salt | "${HASH}" > "${DEVICE}"
	rm -f "${f}"
	printf "$(date '+%b %e %T') $(hostname) anerd[$$]: Client recv direct [bytes=${bytes}] from [${1}:${2}]\n" 1>&2
}

exchange_random_bytes() {
	local socat_cmd="" nc_cmd="" cmd=""
	case "${IPV6}" in
		true|TRUE|1)
			socat_cmd="socat -t ${WAIT} UDP6-DATAGRAM:[${1}]:${2}"
			nc_cmd="nc -6 -w 1 -u ${1} ${2}"
		;;
		*)
			socat_cmd="socat -t ${WAIT} - UDP-DATAGRAM:${1}:${2}"
			nc_cmd="nc -4 -w 1 -u ${1} ${2}"
		;;
	esac
	case "${NET_TOOL}" in
		socat)
			cmd="${socat_cmd}"
		;;
		nc)
			cmd="${nc_cmd}"
		;;
	esac
	printf "$(date '+%b %e %T') $(hostname) anerd[$$]: Client sent direct [bytes=${BYTES}] to   [${1}:${2}]\n" 1>&2
	random_bytes | $cmd | salt_hash_and_write ${1} ${2}
}

broadcast() {
	local socat_cmd="" nc_cmd="" cmd="" dest=""
	case "${IPV6}" in
		true|TRUE|1)
			dest="ff02::1"
			socat_cmd="socat -t ${WAIT} - UDP6-DATAGRAM:[${dest}]:${1}"
		;;
		*)
			dest="255.255.255.255"
			socat_cmd="socat -t ${WAIT} - UDP-DATAGRAM:${dest}:${1},broadcast"
		;;
	esac
	case "${NET_TOOL}" in
		socat)
			cmd="${socat_cmd}"
			printf "$(date '+%b %e %T') $(hostname) anerd[$$]: Client sent direct [bytes=${BYTES}] to   [${dest}:${1}]\n" 1>&2
			random_bytes | $cmd | salt_hash_and_write ${dest} ${1}
		;;
		nc)
			echo "INFO: You must install socat for broadcat" 1>&2
		;;
	esac
}

# Loop over servers configured in the pool
for i in ${POOL}; do
	# Get the hostname/ip
	h=${i%%:*}
	# Get the port, or set to default
	p=${i##*:}
	[ "${h}" = "${p}" ] && p="${PORT}"
	[ -n "${h}" ] || continue
	exchange_random_bytes "${h}" "${p}"
done

if [ -z "${1}" ]; then
	# Now broadcast to the local network if no server was specified
	broadcast ${PORT} || true
fi
