#! /bin/sh
# vim: set tabstop=4 syntax=sh :
#######################################################################################################
#                                                                                                     #
# decode every occurence of an encoded secret value from a file                                       #
#                                                                                                     #
###################################################################################################VER#
#                                                                                                     #
# decode_secrets, 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 is the replacement for the former script 'decode_passwords' in version 0.2, but it's a  #
# lot slower due to the overhead of shell programming. If you need more speed, you should consider to #
# use the provided C source to create a binary.                                                       #
#                                                                                                     #
#######################################################################################################
usage_text()
{
	__purpose_hdr
	__nl "This script decrypts all occurences of encrypted data on STDIN (if decryption is possible) and"
	__nl "writes the data to STDOUT, while replacing cipher-text with the corresponding clear-text values."
	__usage_hdr
	__usage_opt "options"; __usage_opt_end; __usage_opt "parameter" "$(printf " [ ... ]")"; printf " < "; __undl "input-file"
	__usage_end
	__options_hdr
	__nl; __option_show_opt 24 "-a" "--alt-env" "filename"; __option_show_desc "use an alternative source for the 'urlader environment'"
	__nl; __option_show_opt 24 "-t" "--tty"; __option_show_desc "do not abort execution, if STDIN is connected to a terminal device."
	__option_debug 24
	__option_help 24
	__option_version 24
	__options_end
	__nl "The "; __undl "input-file"; printf " may be any text file, starting from a real TFFS node up to a single line of Base32"
	__nl "encoded cipher-text, as long as any encrypted value starts with the four dollar-signs. This can"
	__nl "be useful, if you extract some settings from a configuration file with other utilities (e.g."
	__nl "'ar7cfgctl') and want to decode only a single value or a smaller amount of values.\n"
	__nl "STDIN has to be redirected or the script will be aborted - it's a protection against accidental"
	__nl "calls without proper redirection, otherwise the script would block the caller. If you really want"
	__nl "to call it with STDIN connected to a terminal device, specify the '"; __undl "--tty"; printf "' option for the call.\n"
	__nl "The number of "; __undl "parameters"; printf " depends on the mode of the call. If no parameter was specified, the"
	__nl "decryption key is built with "; __bold "password_from_device"; printf " (another part of 'decoder'). If the script"
	__nl "isn't called on a FRITZ!OS device, the 'urlader environment' may be substituted again by a text"
	__nl "file. Its location can be specified with an option (see above).\n"
	__nl "If a single "; __undl "parameter"; printf " is used, it has to be a hexadecimal string representing the key to be used"
	__nl "for decryption.\n"
	__nl "If more than one "; __undl "parameter"; printf " exists, it's an alternative approach to decrypt data from another"
	__nl "FRITZ!OS device with knowledge of its properties - I called it 'mimicry mode' in the past. The"
	__nl "decryption key will be computed from the given properties, their order has to be right for"
	__nl;  __bold "device_password"; printf " (see there). If you've gotten a dump of the 'urlader environment' from the other"
	__nl "device, using an alternative environment path can be simpler than specifying three or more "
	__nl; __undl "parameters"; printf "."
}
#######################################################################################################
#                                                                                                     #
# 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" "$@"
}

__required_scripts="password_from_device device_password decode_secret crypto"
__required_commands="sed touch"
#######################################################################################################
#                                                                                                     #
# subfunctions                                                                                        #
#                                                                                                     #
#######################################################################################################
#                                                                                                     #
# process encoded data entries                                                                        #
#                                                                                                     #
#######################################################################################################
__process_secret_value()
{
	while read line; do
		encoded="$(expr "$line" : "[\$]\{4\}\(.*\)")"
		decoded="$($decode_secret "$encoded" "$1" 2>/dev/null)"
		if [ $? -ne 0 ]; then # decrypt error
			__debug "decryption of '%s' failed\n" "$line"
			continue
		fi
		__debug "decrypted '%s' to '%s'\n" "$line" "$decoded"
		[ ${#decoded} -gt 0 ] && decoded="$(printf "%s" "$decoded" | sed -f "$2")"
		printf "s|$line|$decoded|\n"
	done
}
#######################################################################################################
#                                                                                                     #
# check parameters                                                                                    #
#                                                                                                     #
#######################################################################################################
$__help_option__
$__version_option__
$__debug_option__
tty=0
while [ $# -gt 0 ]; do
	__is_option "$1" || break
	if __check_option "$1" "-t" "--tty" >/dev/null;	then
		tty=1
		shift
		continue
	fi
	if __check_option "$1" "-a" "--alt-env" >/dev/null; then
		if [ ${#2} -eq 0 ] || __is_option "$2"; then
			__emsg "Missing file name after option '%s'." "$1"
			exit 1
		else
			altenv="$2"
			shift 2
			continue
		fi
	fi
	__is_last_option "$1" && shift && break
	__emsg "Unknown option '%s'." "$1" && exit 1
done
#######################################################################################################
#                                                                                                     #
# check environment                                                                                   #
#                                                                                                     #
#######################################################################################################
$__set_base_dir__
$__check_required_commands__
$__check_required_scripts__
#######################################################################################################
#                                                                                                     #
# create temporary directory                                                                          #
#                                                                                                     #
#######################################################################################################
td="$("$crypto" mktemp -d)"
if [ ${#td} -eq 0 ]; then
	__emsg "Error creating a temporary directory."
	exit 1
fi
trap "exit 1" INT HUP
trap "rm -r $td 2>/dev/null" EXIT
#######################################################################################################
#                                                                                                     #
# check parameters                                                                                    #
#                                                                                                     #
#######################################################################################################
[ $# -ge 2 ] && mimicry=1 || mimicry=0
[ $# -eq 1 ] && key=1 || key=0
#######################################################################################################
#                                                                                                     #
# check input file                                                                                    #
#                                                                                                     #
#######################################################################################################
if [ $tty -eq 0 ]; then
	__check_terminal 0 "If this was an intended call, specify the '-t' option to read input data from terminal." && exit 1
fi
cat - >"$td/input"
if ! [ -s "$td/input" ]; then
	__debug "Input file is empty.\n"
	exit 0
fi
#######################################################################################################
#                                                                                                     #
# extract encoded values from input file, prepare the device key and decode the encrypted values one  #
# by one                                                                                              #
#                                                                                                     #
#######################################################################################################
sed -n -e "s|.*\(\$\$\$\$[A-Z1-6]*\).*|\1|p" <"$td/input" 2>/dev/null | sed -e "/^[ \t]*\$/d" >"$td/encoded" 2>/dev/null
touch "$td/commands"
if [ -s "$td/encoded" ]; then
	if [ $mimicry -eq 1 ]; then
		cipher_key="$("$device_password" "$@")"
		if [ ${#cipher_key} -eq 0 ]; then
			__emsg "Error computing cipher key, please specify all needed values for the call to '%s'." "$device_password"
			exit 1
		fi
		__debug "mimicry mode for device cipher key '%s'\n" "$cipher_key"
	elif [ $key -eq 0 ]; then
		cipher_key="$("$password_from_device" ${altenv:+-a "$altenv"} 2>/dev/null)"
		if [ ${#cipher_key} -eq 0 ]; then
			__emsg "Error computing device cipher key, maybe it's not a FRITZ!OS system?\n"
			exit 1
		fi
		__debug "decrypt with device cipher key '%s'\n" "$cipher_key"
	else
		cipher_key="$1"
		__debug "decrypt with specified cipher key '%s'\n" "$cipher_key"
	fi
	escapes="$td/escapes"
	cat >"$escapes" <<'EOT'
s|\\|\\\\\\\\\\\\\\\\|g
s|&|\\\\&|g
s/|/\\\\|/g
s|"|\\\\\\\\"|g
EOT
	__process_secret_value "$cipher_key" "$escapes" <"$td/encoded" >"$td/commands"
fi
#######################################################################################################
#                                                                                                     #
# write the decoded data to STDOUT                                                                    #
#                                                                                                     #
#######################################################################################################
sed -f "$td/commands" <"$td/input"
#######################################################################################################
#                                                                                                     #
# no housekeeping needed, temporary data will be cleaned up by our trap command above                 #
#                                                                                                     #
#######################################################################################################
exit 0
#######################################################################################################
#                                                                                                     #
# end of script                                                                                       #
#                                                                                                     #
#######################################################################################################
