#! /bin/sh
#
# decode_passwords for FRITZ!OS versions > 06.05
#
# Copyright (C) 2014 P.Haemmerlein (http://www.yourfritz.de)
#
# This program 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 program 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.
#
# There's a small license deviance, if you want to incorporate the script into your
# own software, please read the description below to take note of that.
#
# "FRITZ!Box" and "FRITZ!" are registered word marks and "AVM" is a registered
# word and figurative mark of:
# AVM Computersysteme Vertriebs GmbH, 10559, Berlin, DE.
#
# A registered entry for "FRITZ!OS" could not be found as of that date (09-05-2014),
# but perhaps the legal protection of "FRITZ!" is expanded to the term "FRITZ!OS" too.
#
#######################################################################################
#
# Purpose:
# --------
# - decode encrypted credentials from a configuration file
# - sustitute the former -c switch for {all,usb,wlan}cfgconv utilities
#
# Usage:
# ------
#
#   decode_passwords < {input_data}
#
# The script is designed as a filter using standard input and standard output. If you
# use it as standard input for a command interpreter via a pipe, you can use file
# descriptor 3 to provide the data stream to process instead of stdin.
#
# The input data may be any text. Any data looking like an encrypted item as it is
# used by AVMs reversible encryption (that is a string starting with four dollar signs
# followed only by the characters A to Z and 1 to 6) will be extracted and the script 
# will try as hard as possible to decrypt it.
#
# The simplest input contains only one such secret value, but you may let point stdin
# of the script to a whole file like /var/flash/ar7.cfg too.
#
# If it's impossible to decode an encrypted value, it will remain unchanged.
#
# The converted input (substitution between encrypted and decrypted data is done
# using the 'sed' command) will be written to stdout.
#
# To prevent unexpected behaviour (waiting for input because of an omitted redirection)
# the script will refuse to work, if its standard input is attached to a terminal
# device. If it's your intention to use it such way, specify 'tty' as 1st argument.
#
# Exit codes:
# -----------
#     0 - input data (if any) processed
#   121 - unable to create a private temporary directory
#   122 - the stdin file points to a terminal device and the 'tty' argument is absent
#   123 - the specified WLAN key for mimicry seems to be unusual
#   124 - the specified MAC address for mimicry seems to be invalid
#   125 - the temporary path looks suspicious
#   126 - missing 'webdavcfginfo' binary
#   127 - invalid arguments specified, usage help will be shown
#
# Lean and mean version:
# ----------------------
# There's a tradeoff between a well-documented script with embedded comments and the
# requirement for some purposes (a.e. the ruKernelTool utility) to write the script
# to a device using a telnet shell session, execute it one or more times and forget
# it afterwards.
#
# To produce such a lean version, you can call the script with the argument
# 'leanandmean' as first parameter and it will write a version of itself without error
# messages or any comments and line indentations to stdout. The pure content will be
# put into a short wrapper script like this:
#
# cat >/var/decode_passwords <<-"LEANANDMEAN"
# >> script code is placed here <<
# LEANANDMEAN
# chmod 550 /var/decode_passwords
# [ $(md5sum </var/decode_passwords | sed -n -e 's/^\([0-9a-f]*\).*/\1/p') \
# (cont)  == 39f69aa3fb14198462c45fe56f7ed4cc ] || \
# (cont)  echo "Hash difference found, transmission failed." 1>&2
#
# You can customize the above "wrapper" script with some more arguments. The target
# file name at the device may be specified as second parameter, default value is
# '/var/decode_passwords' which will write the script to volatile storage at tmpfs.
# If you'd prefer to do without the final hash check, you can specify *any* item as
# third argument. If its length is greater than zero, no MD5 check will take place
# at the wrapper.
#
# And finally you can specify another word (the fourth argument) with 2any content
# and its pure presence will remove some additional code (look below for 'mimicry'
# explanations) from the lean script version.
#
# At the time of this writing the lean version (without mimicry) uses 90 lines
# with 2046 characters (bytes) at the target system and 4 additional lines (+258
# bytes) for the default wrapper.
#
# If you compare these values with the "full blown" version (~520 lines and ~19,600
# bytes), there's a significant difference. And as long as nobody will really read
# the lean version to understand what it's doing, there's no need to transfer so
# much useless data to the box.
#
# For that (and only that) purpose you may use the special lean version without any
# copyright notice and without the included license reference(s).
# If you want to incorporate the lean version of this script into your own software,
# you are obligated to bundle the full version with your software too, without any
# additional need for the casual user to send a special request for it.
#
# In this case you may bundle the script with your own software without the need
# to publish your own source code too, if it's not required by other licenses.
#
# Mimicry of another box:
# -----------------------
# If you've got the *internal* presentation of any configuration file from another
# box (which is out of reach yet - for example due to a hardware failure as the
# result of overvoltage) together with the WLAN key printed on the back of that
# device *and* its MAC address (could be found at the "urlader environment" or may
# be extracted from another computer, which had network access to the device
# earlier - but it is send as "serial number" to AVMs DynDNS service and with
# every TR-069 INFORM request too), you can try to decrypt the secret data from
# that file, if you specify the two values mentioned above as parameters:
#
# decode_passwords wlan_key mac_address < input_file
#
# Currently this has been tested to be interoperable between the following
# FRITZ!Box models:
# 7270v1, 7270v2, 7270v3, 7390, 7490
#
# It does *not work* with a 6360 router, probably the IV for the AES encryption
# will use some additional "device specific" data there.
#
# The 'mimicry' will *not work* with an exported configuration file. There is a
# realistic possibility to import such a file, if you can fool the configuration
# importer (/usr/www/cgi-bin/firmwarecfg or /usr/bin/tr069fwupdate) with a chroot
# environment, but that's another story ...
#
# To check the ability of your device to mimicry another one, I've encoded a known
# cleartext (username = "ippf@myfritz.net" and password = "1234567890") with a
# 'faked' WLAN key (1234567890123456) and even a different MAC address value of
# '11:22:33:44:55:66'. You can find the sample as (unreachable) shell code around
# line 330.
#
# Prerequisites:
# --------------
# The whole script needs only a busybox with the following commands supported:
#   cat, sed, grep, mount/umount, cp, mkdir, date, echo, chroot, expr,
#   test (called as [)
#
# It's a matter of course that the 'webdavcfginfo' binary from the original firmware
# has to be reachable (and that includes 'executable') too.
#
# If you create the lean and mean version, there are some more depedencies:
#   md5sum, chmod
#
# Just for fun:
# The (imho) leanest version - with some additional limitations, but using the same
# control flow - *could* be:
#
# b=/bin
# l=/lib
# f=/var/flash
# t=/var/$$
# m=mount
# r=proc
# mkdir -p $t$f $t$l $t$b $t/$r
# cd $t
# cat $*>i
# sed -ne's/.*\(\$\$\$\$[A-Z1-6]*\).*/\1/p'<i>p
# cat>s<<'Q'
# q=\\\\
# while read x;do
# echo -e "$1$2 {$5=$x;}">$4
# o="$($1$3 -p$5)"
# o="${o//$q/$q$q}"
# o="${o//|/\\|}"
# o="${o//&/\\&}"
# o="${o//\"/$q\"}"
# echo "s|$x|$o|">>c
# done<p
# Q
# $m -o bind $l .$l
# $m -o bind $b .$b
# $m -t $r . ./$r
# chroot . sh s webdav client cfginfo $f/usb.cfg username
# sed -fc<i
# cd ..
# u$m $t/$r $t$b $t$l
# rm -r $t
#
# That version needs only 469 bytes on 30 lines and could do the basic job too. And
# another benefit: You can specify one or more names of files to decode as arguments,
# for example: 'sh micro_decode /var/flash/*.cfg'.
# It's only a proof of concept and not intended for distribution from other sources.
# Please respect that license limitation.
#
#######################################################################################
#
# the "abused" AVM binary
#
decode=/bin/webdavcfginfo
if [ ! -x $decode ]; then
	# LAM-EXCLUDE 1
	echo "Missing $decode executable ..." 1>&2
	exit 126
fi
#
# the config file name for the decoder above and the needed entries within
#
cfg=/var/flash/usb.cfg
client=webdavclient
username=username
#
# some file names pooled here for easy changes in case of conflicts
#
in=input
enc=passwords
sc=sedcmds
ms=script
ue=urenv
shl=/bin/sh
pm=/proc/mounts
tddef=/var/tmp
# LAM-EXCLUDE 1
leanname=/var/decode_passwords
#
# here we'll store our temporary files ...
#
tmpdir=$([ -z $TMP ] && echo $tddef || echo $TMP)/dpwd-$$-$(date +%s)
if [ ${#tmpdir} -lt 20 ]; then
	# LAM-EXCLUDE 1
	echo -e "The path to my temporary directory ($tmpdir) seems to be too short.\nAt least 20 characters are expected." 1>&2
	exit 125
fi
#
# if we can write there
#
mkdir -p $tmpdir || exit 121
#
# to ease things in case of an error, we'll put together the needed commands for a clean exit and execute them using a trap
#
cleanup()
{
	#
	# ensure a valid directory name first (shortest possible value is regexp('/./dpwd-[0-9]?-[0-9]{10,}') = 20)
	#
	[ ${#tmpdir} -lt 20 ] && return
	# LAM-EXCLUDE-MIMICRY-BEGIN
	#
	# unmount urlader environment first, if it was overlayed within our procfs
	#
	grep "${tmpdir}.*${urenv}" $pm | while read t mp m; do umount $mp; done
	# LAM-EXCLUDE-MIMICRY-END
	#
	# unmount everything linked under our temporary directory
	#
	grep $tmpdir/proc $pm | while read t mp m; do umount $mp; done
	grep $tmpdir/bin $pm | while read t mp m; do umount $mp; done
	grep $tmpdir/lib $pm | while read t mp m; do umount $mp; done
	grep $tmpdir $pm | while read t mp m; do umount $mp; done
	#
	# remove our temp files
	#
	[ -d $tmpdir ] && rm -r $tmpdir >/dev/null 2>&1
}
#
# the next section is only necessary for "double use" to create the smaller version from the "well-documented" one
#
# LAM-EXCLUDE-BEGIN
if [ x$1 == xleanandmean ]; then
	# reproduce a (temporary) lean and mean version of myself for easy transfer to a box within a shell session
	# - to exclude only a few lines from the output, add a line "# LAM-EXCLUDE n" before them where n is the number of following lines to ignore
	# - to exclude a continuous section, place "# LAM-EXCLUDE-BEGIN" and "# LAM-EXCLUDE-END" around it
	# the second parameter could be the name at the target system
	[ ${#2} -gt 0 ] && target=$2 || target=$leanname
	# the third argument (if any) is used to exclude the final hash check
	[ ${#3} -gt 0 ] && nomd5=1 || nomd5=0
	# exclude support for "foreign" encoded values from small version
	[ ${#4} -gt 0 ] && nomimicry=1 || nomimicry=0
	# build the small versions
	leanandmean()
	{
		out() { echo "$1" 1>&3 ; echo "$1" >>$tmpout; }
		local tmpout=$tmpdir/out$(date +%s)
		local tmpin=$tmpdir/in$(date +%s)
		local tmpcmd=$tmpdir/cmd$(date +%s)
		local mode=$1 nomim=$2 lineno lines
		out "#! $shl"
		cat $0 >$tmpin
		# remove all mimicry code first
		[ $nomim -eq 1 ] && sed -e '/^[ \t]*# LAM-EXCLUDE-MIMICRY-BEGIN/,/^[ \t]*# LAM-EXCLUDE-MIMICRY-END/d' -i $tmpin
		# remove all complete LAM-EXCLUDE sections
		sed -e '/^[ \t]*# LAM-EXCLUDE-BEGIN/,/^[ \t]*# LAM-EXCLUDE-END/d' -i $tmpin
		# now we will look for '# LAM-EXCLUDE n' comments
		touch $tmpcmd
		for lineno in $(sed -n -e '/^[ \t]*# LAM-EXCLUDE [0-9]*$/=' $tmpin); do
			lines=$(sed -n -e "${lineno}s/^.* \([0-9]*\).*$/\1/p" $tmpin)
			echo "${lineno},$(( lineno + lines ))d" >>$tmpcmd
		done
		[ -s $tmpcmd ] && sed -f $tmpcmd -i $tmpin
		sed -e '/^[ \t]*#.*/d' -e 's/^[ \t]*\(.*\)$/\1/' -i $tmpin
		cat $tmpin >>$tmpout
		cat $tmpin 1>&3
		echo $(md5sum $tmpout | sed -n -e 's/^\([0-9a-f]*\).*/\1/p')
	}
	# output the wrapper
	echo "cat >$target <<-\"LEANANDMEAN\""
	MD5=$(leanandmean ${1:0:1} $nomimicry) 3>&1
	echo "LEANANDMEAN"
	echo "chmod 550 $target"
	[ $nomd5 -eq 0 ] && echo "[ \$(md5sum <$target | sed -n -e 's/^\([0-9a-f]*\).*/\1/p') == $MD5 ] || echo \"Hash difference found, transmission failed.\" 1>&2"
	cleanup
	exit 0
	#
	# the unreachable code here can be used to check the 'mimicry' ability, copy the lines to a separate file, change them as needed and try it yourself
	#
	#! /bin/sh
	urenv=/proc/sys/urlader/environment
	key="wlan_key"
	mac="maca"
	decode="decode_passwords"
	encoded="username = \"\$\$\$\$GDAC24NUP3BNNQZ2SUWNFP2FH5ZLOA6ORXMYP1UOUEZE2UI3SYXXVVS15SRGNUW1C2I4V2FZLARCYKXO\";\npassword = \"\$\$\$\$WDLEUNJT1IJWZSQBBQMGXXXHTX2FV3ZQYMYCZPJEREFYXQ4CAYIWSXIXRVITC4HSA6AY65IRK5SSOKXO\";"
	original_key="$(sed -n -e "/^$key/s/^[^\t]*\t\(.*\)/\1/p" <$urenv)"
	original_mac="$(sed -n -e "/^$mac/s/^[^\t]*\t\(.*\)/\1/p" <$urenv)"
	delim="======================================================"
	echo $delim
	echo "original wlan key is '$original_key'"
	echo "original maca is '$original_mac'"
	echo "decode with original wlan key and maca ..."
	echo -e "$encoded" | $decode
	echo $delim
	echo "replace wlan key with '1234567890123456'"
	echo "replace maca with '11:22:33:44:55:66'"
	echo "decode now with replaced values ..."
	echo -e $encoded | $decode 1234567890123456 11:22:33:44:55:66
	echo $delim
	if echo -e $encoded | $decode 1234567890123456 11:22:33:44:55:66 | grep -q "ippf@myfritz.net"; then
	        echo "Decoding with foreign $key and $mac is possible."
	else
	        echo "Decoding with foreign $key and $mac is NOT POSSIBLE."
	fi
	#
	# end of embedded mimicry sample
	#
fi
# LAM-EXCLUDE-END
# LAM-EXCLUDE-MIMICRY-BEGIN
#
# the "box specific" variables for the AES IV
#
key_name="wlan_key"
mac_name="maca"
#
# and where to find them
#
urenv="/proc/sys/urlader/environment"
#
# check arguments - we do not expect any, if not "mimicry" is intended too or tty detection is set to off
#
igntty=0
if [ ${#1} -gt 0 ]; then
	if [ ${#2} -eq 0 ]; then
		# LAM-EXCLUDE 4
		if [ x$1 != xtty ]; then
			echo "Usage: $0 {${key_name}} {${mac_name}} <{config_file}" 1>&2
			echo "- decode {config_file} from a foreign box which had the specified {${key_name}} and {${mac_name}} address" 1>&2
			echo "- if called without {${key_name}} and {${mac_name}}, decode with settings from the box where the script is running" 1>&2
			exit 127
		# LAM-EXCLUDE 3
		else
			igntty=1
		fi
	else
		key="$1"
		mac="$2"
		if [ ${#key} -ne 16 -a ${#key} -ne 20 ] || [ -n "$(echo -n ${key} | sed -e 's/[0-9]//g')" ]; then
			# LAM-EXCLUDE 1
			echo "The specified ${key_name} is unusual, it should be composed of 16 or 20 decimal digits." 1>&2
			exit 123
		fi
		if [ -n "$(echo -n ${mac} | sed -e 's/^\([0-9A-F]\{2\}:\)\{5\}[0-9A-F]\{2\}$//')" ]; then
			# LAM-EXCLUDE 1
			echo "The specified ${mac_name} address is invalid, only hexadecimal digits (with A to F in uppercase) and double colons are permitted (hh:hh:hh:hh:hh:hh)." 1>&2
			exit 124
		fi
	fi
fi
# LAM-EXCLUDE-MIMICRY-END
# get list of files to incorporate into the chroot environment
chroot_files() { cat <<-EOT
# list of files/actions for chroot jail preparation
#
# type  params              action
# D     <dir>               create directory <dir>
# E     <dst>               create empty file (with "echo -n" for correct character device handling at TFFS) <dst>
# M     <type> <src> <dst>  mount file system of type <type> from <src> at <dst>
# B     <src> <dst>         bind mount from <src> to <dst>
  D var/flash
  E $cfg
  M proc none proc
  B bin $1/bin
  B lib $1/lib
EOT
}
#
# prepare the chroot environment for the decoder utility
#
prepare_chroot()
{
	rel_path() { [ -z $1 ] && return 1; [ ${1:0:1} == / ] && echo ${1:1} || echo $1; }
	dir_path() { [ -z $1 ] && return 1; [ ${1%/*} == $1 ] && echo -n || echo ${1%/*}; }
	c=$1
	while read line; do
		set -- $line
		case $1 in
			D)
				p=$c/$(rel_path $2); mkdir -p $p
				;;
			E)
				d=$c/$(rel_path $2); mkdir -p $(dir_path $d); echo -n >$d
				;;
			M)
				t=$2; s=$3; d=$c/$(rel_path $4); mkdir -p $d; mount -t $t $s $d
				;;
			B)
				s=/$(rel_path $2); d=/$(rel_path $3); mkdir -p $d; mount -o bind $s $d
				;;
			*)
				;;
		esac
	done
}
#
# setup a trap for possible condition, not all will work as expected 
#
trap 'cleanup' EXIT HUP
#
# We expect our input via stdin and save it for repeated use to a temporary file.
#
if [ -t 0 -a $igntty -eq 0 ]; then
	# LAM-EXCLUDE 1
	echo -e "It looks like you've missed redirecting any input data to that script.\nFor intentional reading of stdin from a terminal, specify 'tty' as first argument." 1>&2
	exit 122
fi
[ -L /proc/self/fd/3 ] && fd=3 || fd=0
cat /proc/self/fd/$fd >$tmpdir/$in
#
# an empty input file isn't an error, but we can avoid all that stress
#
[ ! -s $tmpdir/$in ] && exit 0
#
# extract all encrypted data from input and put each item on a single line in 'passwords'
#
sed -n -e 's/.*\(\$\$\$\$[A-Z1-6]*\).*/\1/p' <$tmpdir/$in >$tmpdir/$enc
#
# We'll use sed to write the converted file to stdout later, so we have to make sure, 
# there's a command file (even if it's empty) for the final sed call.
#
touch $tmpdir/$sc
#
# prepare the chroot environment now, if there's something to decode
#
if [ x$(sed -e "/^[ \t]*$/d" $tmpdir/$enc | sed -n -e '$=') != x ]; then
	chroot_files $tmpdir | prepare_chroot $tmpdir
	#
	# that's the part which will be called within the chroot environment, we'll write it to a file there
	# some substitutions will take place here, remember to escape runtime variable references
	#
	cat >$tmpdir/$ms <<-ENDOFWORKER
		while read in; do
			# prepare config file
			echo -n -e "$client {\n\t$username = \"\$in\";\n}" >$cfg
			# let it decode
			out="\$($decode -p username)"
			# if the decoded text is empty, no escaping is needed
			if [ \${#out} -gt 0 ]; then
				# backslash is escape, we've to double each occurence
				out="\${out//\\\\\\\\/\\\\\\\\\\\\\\\\}"
				# ampersand means matched expression but we want the pure character
				out="\${out//&/\\\\&}"
				# pipe symbol is our delimiter, escape it too
				out="\${out//|/\\\\|}"
				# and last but not least the original AVM implementation had escaped 
				# any embedded double quotes, so we'll do that too
				out="\${out//\"/\\\\\\\\\"}"
			fi
			# output the s-command for later execution
			echo "s|\${in}|\${out}|g" >>/$sc
		done </$enc
ENDOFWORKER
	# LAM-EXCLUDE 2
	# not really needed, but let's remove comments from worker, if it's not the lean version, you may want to comment out that step
	sed -e '/^[ \t]*#.*/d' -i $tmpdir/$ms
	# LAM-EXCLUDE-MIMICRY-BEGIN
	if [ ${#key} -gt 0 -a ${#mac} -gt 0 ]; then
		sed -e "/^${key_name}/s/^\([^\t]*\t\).*/\1${key}/" -e "/^${mac_name}/s/^\([^\t]*\t\).*/\1${mac}/" <$tmpdir$urenv >$tmpdir/$ue
		mount -o bind $tmpdir/$ue $tmpdir$urenv
	fi
	# LAM-EXCLUDE-MIMICRY-END
	#
	# call ourself within the jail
	#
	chroot $tmpdir $shl /$ms
fi
#
# let sed change the input and do the output to stdout with one step
#
sed -f $tmpdir/$sc <$tmpdir/$in
#
# remember that 'cleanup' will take place here via a trap condition
#
exit 0
