#! /bin/sh
# vim: set tabstop=4 syntax=sh :
#######################################################################################################
#                                                                                                     #
# provide a wrapper for a MD5 digest and an AES-256 decrypt function for AVM's cipher text decryption #
#                                                                                                     #
###################################################################################################VER#
#                                                                                                     #
# crypto, version 0.3, from decoder                                                                   #
#                                                                                                     #
# This script is a part of the project from https://github.com/PeterPawn/decoder.                     #
#                                                                                                     #
###################################################################################################CPY#
#                                                                                                     #
# Copyright (C) 2014-2018 P.Haemmerlein (peterpawn@yourfritz.de)                                      #
#                                                                                                     #
###################################################################################################LIC#
#                                                                                                     #
# This project 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 project 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 under http://www.gnu.org/licenses/gpl-2.0.html for more          #
# details.                                                                                            #
#                                                                                                     #
#######################################################################################################
#                                                                                                     #
# This script encapsulates all calls to the OpenSSL binary for the other scripts and to make things   #
# even easier, all calls to the YourFritz shell script library (data conversions and handling of a    #
# temporary file/directory) were gathered in this script. Meanwhile a customized version of the       #
# needed functions (with some verifications removed) was included directly into this script. This     #
# avoids the overhead of an additional filesystem access and - that's more important - the dependency #
# on an installation of the YourFritz script library for the system, where the script is running on,  #
# was removed now.                                                                                    #
#                                                                                                     #
# For the real crypto operations this script needs an OpenSSL binary with the 'dgst' function (for    #
# MD5 hashes) and knowledge of the AES-256 (CBC) encryption mode, which will be used with the         #
# 'enc -d' function.                                                                                  #
#                                                                                                     #
# If you'd like to use this project with another crypto solution, you may change the calls within     #
# this script.                                                                                        #
#                                                                                                     #
#######################################################################################################
usage_text()
{
	__purpose_hdr
	__nl "This script is only a wrapper to call the OpenSSL binary and it provides some customized"
	__nl "YourFritz script library functions (in a single place) for the other scripts."
	__usage_hdr
	__usage_opt "options"; __undl "function"; __usage_opt "parameters"
	__usage_end
	__options_hdr
	__option_debug
	__option_help
	__option_version
	__options_end
	__nl "The following "; __undl "functions"; printf " are implemented:\n"
	__nl; __bold "aes_decrypt"; __indent_on
	__nl "The hexadecimal strings from STDIN are decrypted with the specified "; __undl "key"; printf " (it's the"
	__nl "first argument) and "; __undl "iv"; printf " (the second argument, both specified as hexadecimal strings)"
	__nl "and the result is written to STDOUT as a hexadecimal string.\n"; __indent_off
	__nl; __bold "digest"; __indent_on
	__nl "The MD5 digest value for all data from STDIN is written to STDOUT as a hexadecimal"
	__nl "string. If an option '-x' is specified for "; __undl "parameters"; printf ", data from STDIN is assumed"
	__nl "to be a hexadecimal string and it gets converted to binary first.\n"; __indent_off
	__nl; __bold "b32dec"; __indent_on
	__nl "The Base32 encoded string from STDIN is converted and written as a hexadecimal"
	__nl "string to STDOUT.\n"; __indent_off
	__nl; __bold "hexdec"; __indent_on
	__nl "The hexadecimal string from STDIN is converted to its binary content and the result"
	__nl "is written to STDOUT (hexadecimal to binary).\n"; __indent_off
	__nl; __bold "hexenc"; __indent_on
	__nl "The binary data from STDIN is converted to a hexadecimal string on STDOUT.\n"; __indent_off
	__nl; __bold "hex2dec"; __indent_on
	__nl "The hexadecimal string from STDIN is converted to its decimal value (as a string)"
	__nl "on STDOUT (hexadecimal to decimal).\n"; __indent_off
	__nl; __bold "mktemp"; __indent_on
	__nl "Creates a temporary file or directory with a unique name - some platforms don't"
	__nl "have another "; __undl "mktemp"; printf " utility."; __indent_off
}
#######################################################################################################
#                                                                                                     #
# usage and display helpers from YourFritz framework                                                  #
#                                                                                                     #
#######################################################################################################
__bold__="$(printf "\033[1m")"
__undl__="$(printf "\033[4m")"
__rset__="$(printf "\033[0m")"
__bold() { printf "$__bold__"; printf -- "$@"; printf "$__rset__"; }
__undl() { printf "$__undl__"; printf -- "$@"; printf "$__rset__"; }
__show_script_name()
{
	printf "\033[1m\033[31m${0#*/}\033[0m: "
}
__get_script_lines()
{
	sed -n -e "/^#*${1}#\$/,/^#\{20\}.*#\$/p" "$0" | \
	sed -e '1d;$d' | \
	sed -e 's|# \(.*\) *#$|\1|' | \
	sed -e 's|^#*#$|--|p' | \
	sed -e '$d'
}
__license()
{
	__get_script_lines "LIC"
}
__version()
{
	__get_script_lines "VER" | sed -e "1,2s|^\([^,]*\),\(.*\)\$|$__bold__\1$__rset__,\2|"
}
__copyright()
{
	__get_script_lines "CPY"
}
__emsg()
{
	__show_script_name 1>&2
	mask="$1"
	shift
	printf "${__bold__}${mask}${__rset__}\a\n" "$@" 1>&2
}
__check_option()
{
	o="$1"
	shift
	for v in $*; do
		[ "$o" = "$v" ] && printf 1 && return 0
	done
	printf 0
	return 1
}
__is_option()
{
	[ "$(expr -- "$1" : "\(.\).*")" = "-" ] && return 0 || return 1
}
__is_last_option()
{
	[ "$1" = "--" ] && return 0 || return 1
}
__options_end__="eval while __is_option \"\$1\"; do __is_last_option \"\$1\" && shift && break;\
	__emsg \"Unknown option '%s'.\" \"\$1\"; exit 1; done;"
__version_option()
{
	if __check_option "$1" "-V" "--version" >/dev/null; then
		__version
		__copyright
		__license
		printf "\n"
		exit 1
	fi
	return 1
}
__version_option__="eval __version_option \$@ && exit 0"
__help_option()
{
	if __check_option "$1" "-h" "--help" >/dev/null; then
		__usage
		exit 1
	fi
}
__help_option__="eval __help_option \$@"
__debug_option()
{
	__check_option "$1" "-d" "--debug" && return 0
	return 1
}
__debug_option__="eval __debug_set__=\$(__debug_option \$1) && __debug_text__=\"\$1\" && shift"
__debug_on__="eval __debug_set__=1; __debug_text__=\"-d\";"
__is_debug() { [ $__debug_set__ -eq 1 ] && return 0 || return 1; }
__debug()
{
	[ $__debug_set__ -eq 1 ] || return;
	mask="$1"
	shift
	printf "$mask" "$@" 1>&2
}
__usage()
(
	indent=0
	__indent_on() { indent=$(( indent + 4 )); }
	__indent_off() { indent=$(( indent - 4 )); }
	__indent() { [ $indent -gt 0 ] && printf "%0${indent}s" " "; };
	__nl() { printf "\n%s" "$(__indent)"; printf -- "$1"; }
	__purpose_hdr() { __nl; __bold "Purpose:"; printf "\n"; }
	__usage_name() { __bold "${0#*/}"; }
	__usage_hdr() { printf "\n"; __nl; __bold "Usage:\n"; __indent_on; __nl "$(__usage_name)"; }
	__usage_end() { __indent_off; printf "\n"; }
	__usage_opt_int() { v="$1"; shift; [ $# ] && m="$@"; printf -- "[ %s%s ]" "$(__undl "$v")" "$m"; unset m v; };
	__usage_opt_end() { printf -- " [ -- ]"; }
	__usage_opt() { printf -- " %s" "$(__usage_opt_int "$@")"; }
	__usage_arg() { printf -- " %s" "$(__undl "$1")"; }
	__options_hdr() { __nl "Supported "; __undl "options"; printf " are:\n"; }
	__options_end() { printf "\n"; }
	__option_show_opt() {
		printf -- "%s, %s" "$2" "$3"
		__l4__=${#4}
		[ $__l4__ -gt 0 ] && printf " %s%s%s" "$__undl__" "$4" "$__rset__" && __l4__=$(( __l4__ + 1 ))
		printf "%0$(( $1 - ${#2} - ${#3} - __l4__ - 3 ))s" " "
		unset __l4__
	}
	__option_show_desc() { printf -- "- %s" "$@"; }
	__option_debug() { __nl; __option_show_opt ${1:-15} "-d" "--debug"; __option_show_desc "display debug info on STDERR; must prefix all other options, if used"; }
	__option_help()	{ __nl; __option_show_opt ${1:-15} "-h" "--help"; __option_show_desc "show this information (must be the first option)"; }
	__option_version()	{ __nl; __option_show_opt ${1:-15} "-V" "--version"; __option_show_desc "show version and exit (must be the first option)"; }
	__end() { printf "\n%s\n" "$__rset__"; }

	__version
	__copyright
	__license
	usage_text
	__end
)
__set_base_dir__="eval [ \"\$(expr \"\$0\" : \".*\(/\).*\")\" = \"/\" ] && __base_dir__=\"\${0%/*}\" || __base_dir__=\".\""
__set_base_dir() { __set_base_dir__="$1"; }
__check_required_scripts()
{
	d="$1"
	shift
	for n in $@; do
		eval $n="$d/$n"
		eval f="\$$n"
		if ! [ -x "$f" ]; then
			__emsg "Missing another needed executable: %s." "$n"
			return 1
		fi
		printf "$n=%s\n" $f
	done
	return 0
}
__check_required_scripts__="eval __scripts__=\"\$(__check_required_scripts \"\$__base_dir__\" \"\$__required_scripts\")\" && \
	eval \$__scripts__ || exit 1"
__check_required_commands()
{
	for n in $@; do
		command -v $n 2>/dev/null 1>&2 && continue
		__emsg "Missing a required command: %s." "$n"
		return 1
	done
	return 0
}
__check_required_commands__="eval __check_required_commands \"\$__required_commands\" || exit 1"
__check_terminal()
{
	[ -t $1 ] || return 1
	if [ $1 -eq 0 ]; then
		fd="STDIN"
	elif [ $1 -eq 1 ]; then
		fd="STDOUT"
	else
		fd="FILE ($1)"
	fi
	shift
	__emsg "%s is a terminal device. %s" "$fd" "$@"
}

#######################################################################################################
#                                                                                                     #
# subfunctions                                                                                        #
#                                                                                                     #
#######################################################################################################
yf_base32_decode()
(
	rc=1
	while read line; do
		rc=0
		! [ -z "$(expr -- "$line" : "\([^A-Z1-6\r]\)")" ] && return 1
		line=$(printf "%s" $line | sed -e "s|\r||g")
		[ $(( ${#line} % 8 )) -gt 0 ] && return 1
		i=0
		f=0
		while [ $i -lt ${#line} ]; do
			part=$(printf "%s" $(yf_substring "$line" $i 8) | sed -e "s|.|& |g")
			i=$(( i + 8 ))
			v=0
			j=0
			for c in $part; do
				v=$(( ( v << 5 ) + $(yf_index "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456" $c) - 1 ))
				j=$(( j + 1 ))
				[ $(( j % 4 )) -ne 0 ] && continue
				r=$(( j % 8 ))
				o=$(( v >> r ))
				s=$(( 24 - ( r * 2 ) ))
				v=$(( v & ( 0x00FFFFFF >> ( r * 5 ) ) ))
				while [ $s -gt 0 ]; do
					s=$(( s - 8 ))
					d=$(( ( o >> s ) & 255 ))
					[ $d -eq 0 ] \
						&& dd if=/dev/zero bs=1 count=1 2>/dev/null \
						|| printf "%b" "\0$(( d >> 6 ))$(( ( d >> 3 ) & 7 ))$(( d & 7 ))"
				done
			done
		done
	done
	return $rc
)
yf_bin2hex()
(
	yf_bin2hex_read_octal()
	{
		i=1
		ff=0
		while read pos left right; do
			while [ $i -lt $pos ]; do
				if [ $ff -eq 1 ]; then
					printf "ff"
					ff=0
				else
					printf "00"
				fi
				i=$(( i + 1 ))
			done
			if [ $right -eq 377 ] && [ $ff -eq 0 ]; then
				ff=1
				continue
			fi
			printf "%02x" 0$right
			i=$(( pos + 1 ))
		done
	}

	( cat; printf "%b" "\0377" ) | command cmp -l -- /dev/zero - 2>/dev/null | yf_bin2hex_read_octal
	return $?
)
yf_hex2bin()
(
	yf_hex2bin_read_octal()
	{
		i=1
		h=1
		z=0
		while read p l o; do
			[ $i -lt $p ] && return 1
			i=$(( i + 1 ))
			if [ $o -eq 11 ] || [ $o -eq 12 ] || [ $o -eq 15 ] || [ $o -eq 40 ]; then
				[ $h -eq 1 ] && continue || return 1
			fi
			if [ $o -ge 60 ] && [ $o -le 67 ]; then
				c=$(( o - 60 ))
			elif [ $o -ge 70 ] && [ $o -le 71 ]; then
				c=$(( o - 62 ))
			elif [ $o -ge 101 ] && [ $o -le 106 ]; then
				c=$(( o - 91 ))
			elif [ $o -ge 141 ] && [ $o -le 146 ]; then
				c=$(( o - 131 ))
			else
				return 1
			fi
			if [ $h -eq 0 ]; then
				v=$(( v + c ))
				if [ $v -eq 0 ]; then
					z=$(( z + 1 ))
				else
					if [ $z -gt 0 ]; then
						dd if=/dev/zero bs=$z count=1 2>/dev/null
						z=0
					fi
					printf "%b" "\0$(( v >> 6 ))$(( ( v >> 3 ) & 7 ))$(( v & 7 ))"
				fi
				h=1
			else
				v=$(( c << 4 ))
				h=0
			fi
		done
		[ $z -gt 0 ] && dd if=/dev/zero bs=$z count=1 2>/dev/null
		return 0
	}

	command cmp -l -- /dev/zero - 2>/dev/null | yf_hex2bin_read_octal
	return $?
)
yf_hex2dec()
(
	val="$1"
	out=0
	while [ ${#val} -gt 0 ]; do
		byte=$(yf_substring "$val" 0 2)
		val="$(yf_substring "$val" 2)"
		out=$(( ( out << 8 ) + 0x$byte ))
	done
	printf "%u" "$out"
	return 0
)
yf_index()
(
	string="$1"
	char="$2"
	[ ${#string} -eq 0 ] && return 1
	[ ${#char} -ne 1 ] && return 1
	tv="${string%${char}*}"
	[ "$tv" = "$string" ] && l=0 || l=$(( ${#tv} + 1 ))
	printf "%d" $l
	return 0
)
yf_is_decimal()
(
	in="$(printf "$1" | sed -e 's|[0-9]||g')"
	[ ${#in} -gt 0 ] && return 1
	return 0
)
yf_mktemp()
(
	name="$(date +%s)_$$"
	tmp="$TMP"
	[ "$1" = "-d" ] && dir=1 || dir=0
	[ "$1" = "-p" ] && tmp="$2"
	[ -z "$tmp" ] && tmp="/tmp"
	name="$tmp/$name"
	name="${name}_$(yf_random_string 8)"
	if [ $dir -eq 1 ]; then
		while [ -d "$name" ]; do
			name="${name%_*}_$(yf_random_string 8)"
		done
		mkdir -p "$name" 2>/dev/null
	else
		touch "$name" 2>/dev/null
	fi
	printf "$name"
)
yf_random_string()
(
	yf_random_string_read_octals()
	{
		yf_random_string_read_octals_print()
		{
			[ $2 -gt 0 ] || return
			[ $2 -eq 1 ] && l=1 || l=2
			printf "%0${l}x" "$1"
		}
		len=$1
		i=0
		while read j zero val; do
			i=$(( i + 1 ))
			if [ $j -gt $i ]; then
				while [ $i -lt $j ]; do
					yf_random_string_read_octals_print "$v" "$len"
					len=$(( len - 2 ))
					i=$(( i + 1 ))
				done
			else
				v=0
				while [ ${#val} -gt 0 ]; do
					v=$(( ( v << 3 ) + $(expr -- "$val" : "\([0-9]\).*") ))
					val="$(expr -- "$val" : "[0-9]\([0-9]*\)")"
				done
				yf_random_string_read_octals_print "$v" "$len"
				len=$(( len - 2 ))
			fi
			[ $len -lt 0 ] && break
			[ $i -gt 128 ] && break
		done
	}
	len="${1:-16}"
	size=$(( ( len + 1 ) / 2 ))
	dd if=/dev/urandom bs=$size count=1 2>/dev/null | cmp -l -- /dev/zero - 2>/dev/null | yf_random_string_read_octals $len
	return 0
)
yf_substring()
(
	in="$1"
	start="$2"
	len="$3"
	[ ${#in} -lt $start ] && return 0
	[ -z $len ] || [ $len -gt $(( ${#in} - start )) ] && len=$(( ${#in} - start ))
	len="\{$len\}"
	[ $start -gt 0 ] && mask=".\{$start\}\(.$len\).*" || mask="\(.$len\).*"
	expr -- "$in" : "$mask"
	return 0
)
#######################################################################################################
#                                                                                                     #
# compute the md5 digest of the data from STDIN, write hexadecimal string to STDOUT                   #
#                                                                                                     #
#######################################################################################################
digest()
{
	openssl dgst -md5 | sed -n -e "s|(stdin)= \(.*\)|\1|p"
}
#######################################################################################################
#                                                                                                     #
# decode a Base32 encoded value from STDIN to a hexadecimal string on STDOUT                          #
#                                                                                                     #
#######################################################################################################
b32dec()
{
	yf_base32_decode | yf_bin2hex
}
#######################################################################################################
#                                                                                                     #
# decode a Base32 encoded value from STDIN to a hexadecimal string on STDOUT                          #
#                                                                                                     #
#######################################################################################################
aes_decrypt()
{
	if [ "$1" = "-x" ]; then
		reader="cat"
		writer="cat"
		shift
	else
		reader="yf_hex2bin"
		writer="yf_bin2hex"
	fi
	$reader | openssl enc -d ${3:--aes-256-cbc} -K "$1" -iv "$2" 2>/dev/null | $writer
}
#######################################################################################################
#                                                                                                     #
# check parameters and run the requested function                                                     #
#                                                                                                     #
#######################################################################################################
$__help_option__
$__version_option__
$__debug_option__
$__options_end__
if [ $# -lt 1 ]; then
	__emsg "Missing at least one argument, the function to execute."
	exit 1
fi
rc=0
function="$1"
shift
case "$function" in
	("aes_decrypt")
		__check_terminal 0 && exit 1
		if [ ${#2} -eq 0 ]; then
			__emsg "Missing key and/or IV argument."
			exit 1
		fi
		if [ "$2" = "-x" ] && [ ${#3} -eq 0 ]; then
			__emsg "Missing key and/or IV argument."
			exit 1
		fi
		aes_decrypt $@
		rc=$?
		;;
	("digest")
		__check_terminal 0 && exit 1
		if [ ${#1} -gt 0 ] && [ "$1" = "-x" ]; then
			yf_hex2bin | digest
		else
			digest
		fi
		rc=$?
		;;
	("b32dec")
		__check_terminal 0 && exit 1
		b32dec
		rc=$?
		;;
	("hexdec")
		__check_terminal 0 && exit 1
		yf_hex2bin
		rc=$?
		;;
	("hexenc")
		__check_terminal 0 && exit 1
		yf_bin2hex
		rc=$?
		;;
	("hex2dec")
		__check_terminal 0 && exit 1
		yf_hex2dec $(cat)
		rc=$?
		;;
	("mktemp")
		yf_mktemp $@
		rc=$?
		;;
	(*)
		__emsg "Unknown function '%s' specified." "$function"
		rc=1
		;;
esac
#######################################################################################################
#                                                                                                     #
# no housekeeping needed                                                                              #
#                                                                                                     #
#######################################################################################################
exit $rc
#######################################################################################################
#                                                                                                     #
# end of script                                                                                       #
#                                                                                                     #
#######################################################################################################
