#!/bin/bash
MYPWD="$(dirname $(realpath $0))"

help() {
echo "$0: Ask avm's JUIS update service for latest firmware infos like download url"
cat << 'EOX'

Parameters:
	-a	[Prio4]	Actions, shows the URL if there is a newer version, prepended with the HWR.
	-i	[Prio3]	Computer, shows the URL if there is a newer version.
	-s	[Prio2]	Fritzbox, shows if a new version is available. Response is cached in: "/tmp/.juis_check"
	-d	[Prio1]	RAW-output of the response (or no -a, -i and -s).

	--nofb		Do not try to read values from the local Fritzbox in juis_boxinfo.xml or older jason_boxinfo.xml files, but use random if vaiable is not set.

	--dect		Dect-Update: Searches for a firmware for an dect device.
	--bpjm		BPjM-Update: Searches for the encrypted BPjM-Modul file. To format the bpjm file and remove hashes for empty strings:
			             cat $RAWFILE | hexdump -s 64 -v -e '16/1 "%02x" " " 16/1 "%02x" " " 1/1 "%02x\n"' | sed 's/d41d8cd98f00b204e9800998ecf8427e 00$//'
			              - 1st row is the hash of protocol + domain (without subdomain), eg "http://example.com".
			              - 2nd row is the hash of the path + file, eg "/directory/file.extension" or empty.
			              To read the CRC32 checksum in the header: head -c4 $RAWFILE | xxd -p
			              To calculate the CRC32 checksum of the body: crc32 <( tail -c +$((1 + 4)) $RAWFILE )
	--info		InfoMessage: Searches for a very important info message by avm.
	--down		Downgrade:   Searches for latest release firmware, like "Zurück zum offiziellen FRITZ!OS". "Buildtype" should not be "1".
	Without --dect, --bpjm, --info and --down, a firmware for a Fritzbox is searched.

	--plain		Forces to use http. AVM uses this only.
	--https		Forces to use https. Curl is mandatory for https.
	Without --plain and --https, the default is https if curl is installed. But on fritzbox only if "FREETZ_ADD_JUIS_CHECK__SSL" is set.

Variables:
	On Fritzbox no variables are needed and missing will be read from the device.
	On Computer the hardware-recision "HW" is mandatory and for very old (HWR<150) devices you have to set (at least the "Major" part of) "Version".
	With Dect: The hardware number "DHW" is mandatory additional, the software version "DSW" and type "DTP" are optional.

	Name		Default if not set      Comment
	----------------------------------------------------------------------------------------------------------------------------------------------------------
	HW		[mandatory]		The HWR code of the device, eg "226" for Fritzbox 7590
	Version		[HW].00.00-00000	The "HW" as "Major" does not work with HW<150
	Name		[random]		There is no space in the name but a zero-width space: "printf '\342\200\212'". Or copy & paste examples below.
	OEM		avm			Examples: "avm", "avme", "kdg", "1und1", "lgi", "otwo"
	Lang		de			Examples: "de", "en", "es", "it", "fr", "pl", "nl"
	Annex		B (Kabel for docsis)	Extender use "Ohne", docsis devices "Kabel".
	Country		049			Examples: "049", "99", "041", "0420", "0421", "043", "044", "045", "046", "047", "048", "061", "064", "066", "0972"
	Buildtype	1 (1000 for --down)	Common values: "1"=retail, "1001"=labor, "1000"=inhaus
	Serial		[random]		Example: "123456789ABC"
	Nonce		[random]		Example: "123456789aBcDeEfGhIjkL=="
	Flag		[shuffle]		If you do not want to set a flag use "empty", for docsis devices "cable_retail" is added.
	UserAgent	[shuffle]		Values: "Box", "TestClient" or "BoxInternetCheck"
	Provider	[shuffle]		Use "empty" if you want no value
	ProviderName	[empty]			This is not added if not set
	ManualRequest	[shuffle]		Values: "true" or "false"
	UpdateConfig	[shuffle]		Values: "1" or "3"
	DHW (dect)	[mandatory]		The MHW code of the device, eg "06.08" for Dect 302
	DSW (dect)	00.00			The firmware version of the dect device to check, "00.00" works always.
	DTP (dect)	1			Values: "1"=Dect, "2"=PLC, "3"=LTE

Examples:
	env - tools/juis_check --dect Version=154.07.25-80000 Name=FRITZ\!Box 7590           HW=226  DHW=06.03                      -i -s -d
	env - tools/juis_check --bpjm Version=154.07.25-80000 Name=FRITZ\!Box 7590           HW=226  OEM=avm Lang=de Country=049    -i -s -d
	env - tools/juis_check --info Version=84.06.87-92934  Name=FRITZ\!Box Fon WLAN 7390  HW=156                                 -i -s -d
	env - tools/juis_check --down                                                        HW=267                                 -i -s -d
	env - tools/juis_check        Version=154.07.39-90000 Name=FRITZ\!Box 7590           HW=226  Buildtype=1001    Annex=B      -i -s -d
	env - tools/juis_check        Version=252.07.20       Name=FRITZ\!Box 6660 Cable     HW=252  Flag=cable_retail Annex=Kabel  -i -s -d
	env - tools/juis_check        Version=29.04.87                                       HW=94                                  -i -s -d
	env - tools/juis_check        Version=67                                             HW=137                                 -i -s -d
	env - tools/juis_check                                                               HW=156                                 -i -s -d
	env - tools/juis_check                                                               HW=259                                 -i -s -d
	for x in $(seq 150 300); do                            env - tools/juis_check        HW=$x                                  -a; done
	for x in $(seq 200 300); do                            env - tools/juis_check        HW=$x   Buildtype=1001                 -a; done
	for x in $(seq  10 109); do [ ${#x} != 3 ] && x="0$x"; env - tools/juis_check --dect HW=259  DHW=${x::-1}.0${x:2}           -a; done
	Or just: tools/juis m  # f

EOX
exit
}

js() { [ -e "$1" ] && sed -rn "s/^<.:$2>(.*)<.*/\1/p" "$1" ; }

main() {
	local x soap avmca url host sdir docsis flags Envelope body post cache info payload day ser
	local line='################################################################'
	local type='text/xml; charset="utf-8"'
	local zwsp="$(printf '\342\200\212')"
	#soap='SOAPAction: "http://jason.avm.de/updatecheck/BoxFirmwareUpdateCheck"'


	[ -z "$Nonce" ] && Nonce="$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | base64)"

	[ -z "$UserAgent" ] && UserAgent="$(echo 'Box TestClient BoxInternetCheck' | sed 's/ /\n/g' | shuf | head -n1)"

	[ -z "$ManualRequest" ] && ManualRequest="$(echo 'true false' | sed 's/ /\n/g' | shuf | head -n1)"

	[ -z "$UpdateConfig" ] && UpdateConfig="$(echo '1 3' | sed 's/ /\n/g' | shuf | head -n1)"

	[ -z "$Provider" ] && Provider="$(echo 'null oma_ipclient oma_lan vodafone2_vdsl' | sed 's/ /\n/g' | shuf | head -n1 | grep -v '^null$')"
	[ "$Provider" == "empty" ] && Provider=''

	[ -z "$Serial" ] && while [ "${#Serial}" != "12" ]; do Serial="$(hexdump -n6 -e '/1 "%02X"' /dev/urandom | sed 's/[^0-9A-F]//g')"; done

	if [ "$BIX" != "n" ]; then
		[ -e '/var/juis_boxinfo.xml'  ] && info='/var/juis_boxinfo.xml'
		[ -e '/var/jason_boxinfo.xml' ] && info='/var/jason_boxinfo.xml'
	fi
	[ -z "$Name"      ] &&      Name="$(js $info 'Name')"
	[ -z "$HW"        ] &&        HW="$(js $info 'HW')"
	case "$HW" in 175|176|182|187|213|220|231|233|252|267) docsis='y' ;; esac
	[ -z "$OEM"       ] &&       OEM="$(js $info 'OEM')"
	[ -z "$OEM"       ] &&       OEM="avm"
	[ -z "$Lang"      ] &&      Lang="$(js $info 'Lang')"
	[ -z "$Lang"      ] &&      Lang="de"
	[ -z "$Annex"     ] &&     Annex="$(js $info 'Annex')"
	if [ -z "$Annex"  ]; then
		[ -n "$docsis" ] && Annex="Kabel" || Annex="B"
	fi
	[ -z "$Country"   ] &&   Country="$(js $info 'Country')"
	[ -z "$Country"   ] &&   Country="049"
	[ -z "$Flag"      ] &&      Flag="$(js $info 'Flag')"
	[ -z "$Buildtype" ] && Buildtype="$(js $info 'Buildtype')"
	if [ -z "$Buildtype" ]; then
		[ "$CHK" == "BoxFirmwareDowngradeCheck" ] && Buildtype="1000" || Buildtype="1"
	fi

	if [ -z "$Name" ]; then
		while [ "${#Name}" != "4" ]; do Name="$Name$(hexdump -n1 -e '/1 "%02X"' /dev/urandom | sed -rn 's/.*([0-9]).*/\1/p')"; done
		Name="FRITZ\!Box$zwsp$Name"
	fi

	if [ -z "$Version" ]; then
		Version="$(js $info 'Version')"
		if [ -n "$Version" ]; then
			Buildnumber="$(js $info 'Buildnumber')"
			[ -z "$Buildnumber" ] && Buildnumber="$(js $info 'Revision')"
			[ -n "$Buildnumber" ] && Version="$Version-$Buildnumber"
		fi
		if [ -z "$Version" ]; then
			if [ "$HW" -ge 150 2>/dev/null ]; then
				if [ "$HW" -lt 248 2>/dev/null ]; then
					Version="$(( $HW - 72 ))"
				else
					Version="$HW"
				fi
			fi
		fi
	fi

	Buildnumber="${Version#*-}"
	x="${Version%-*}"
	Major="${x%%.*}"
	x="${x#*.}"
	Minor="${x%.*}"
	Minor="${Minor#0}"
	Patch="${x#*.}"
	[ "$Buildnumber" == "$Version" ] && Buildnumber="00000"
	[ "$Minor" == "$x" ] && Minor="00"
	[ "$Patch" == "$x" ] && Patch="00"

	[ -z "$HW"      ] && echo "You have to provide 'HW'." 1>&2 && exit 1
	[ -z "$Version" ] && echo "You have to provide 'HW' and 'Version'." 1>&2 && exit 1

	if [ "$SSL" == 'y' ]; then
		avmca="$MYPWD/avm-rootca.pem"
		[ -e "/etc/avm_root_ca.pem"     ] && avmca="/etc/avm_root_ca.pem"
		[ -e "/etc/jasonii_root_ca.pem" ] && avmca="/etc/jasonii_root_ca.pem"
		host="jws.avm.de"      # https-cert matches hostname
	else
		host="$HW.jws.avm.de"  # no wirldcard certificate
	fi

	#old: /Jason/UpdateCheck
	sdir='/Jason/UpdateInfoService'
	Envelope='xmlns:e="http://juis.avm.de/updateinfo" xmlns:q="http://juis.avm.de/request">'

	if [ "$CHK" == "BoxMessageCheck" ]; then
		sdir='/Jason/MessageInfoService'
		Envelope='xmlns:e="http://juis.avm.de/messageinfo" xmlns:q="http://juis.avm.de/request" xmlns:s="http://juis.avm.de/response">'
	fi
	
	url="$host$sdir"

	if [ "$CHK" == "BPjMUpdateCheck" ]; then
		while [ "${#day}" != "1" ]; do day="$day$(hexdump -n4 -e '/1 "%02X"' /dev/urandom | sed -rn 's/.*([012]).*/\1/p')"; done
		while [ "${#day}" != "2" ]; do day="$day$(hexdump -n1 -e '/1 "%02X"' /dev/urandom | sed -rn 's/.*([0-9]).*/\1/p')"; done
		payload="<e:BPjMVersion>$(( $(date +%Y%m) - 100 ))$day</e:BPjMVersion>"
	fi

	if [ "$CHK" == "DeviceFirmwareUpdateCheck" ]; then
		[ -z "$DHW" ] && echo "For --dect you have to provide 'DHW'." 1>&2 && exit 1
		[ -z "$DSW" ] && DSW="00.00"
		[ -z "$DTP" ] && DTP="1"
		while [ "${#ser}" != "12" ]; do ser="$ser$(hexdump -n1 -e '/1 "%02X"' /dev/urandom | sed -rn 's/.*([0-9]).*/\1/p')"; done
		payload="
<e:DeviceInfo>
<q:Type>$DTP</q:Type>
<q:MHW>$DHW</q:MHW>
<q:Version>$DSW</q:Version>
<q:Serial>$ser</q:Serial>
<q:Lang>$Lang</q:Lang></e:DeviceInfo>"
	fi

if [ -n "$ProviderName" ]; then
ProviderName="
<q:ProviderName>$ProviderName</q:ProviderName>"
fi

	if [ -z "${Flag// /}" ]; then
		Flag="$(echo 'null mesh_master mesh_master_no_trusted mesh_repeater mesh_repeater_no_trusted' | sed 's/ /\n/g' | shuf | head -n1)"
		Flag="$Flag $(echo 'null medium_lan medium_dsl' | sed 's/ /\n/g' | shuf | head -n1)"
		Flag="$Flag $(echo 'null null crashreport myfritz_letsencrypt botnet_detection avm_acs prov_acs' | sed 's/ /\n/g' | shuf | head -n3)"
		[ -n "$docsis" ] && Flag="$Flag cable_retail"
		[ -z "${Flag// /}" ] && Flag='empty'
	fi
for x in ${Flag//null/}; do flags="<q:Flag>${x/empty/}</q:Flag>
$flags"; done
flags="$(echo "$flags" | grep -v '^$')"

body=$(cat << EOX
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" $Envelope
<soap:Header/>
<soap:Body>
<e:$CHK>
<e:RequestHeader>
<q:Nonce>$Nonce</q:Nonce>
<q:UserAgent>$UserAgent</q:UserAgent>
<q:ManualRequest>$ManualRequest</q:ManualRequest></e:RequestHeader>
<e:BoxInfo>
<q:Name>${Name//$zwsp/ }</q:Name>
<q:HW>$HW</q:HW>
<q:Major>$Major</q:Major>
<q:Minor>$Minor</q:Minor>
<q:Patch>$Patch</q:Patch>
<q:Buildnumber>$Buildnumber</q:Buildnumber>
<q:Buildtype>$Buildtype</q:Buildtype>
<q:Serial>$Serial</q:Serial>
<q:OEM>$OEM</q:OEM>
<q:Lang>$Lang</q:Lang>
<q:Country>$Country</q:Country>
<q:Annex>$Annex</q:Annex>
$flags
<q:UpdateConfig>$UpdateConfig</q:UpdateConfig>
<q:Provider>$Provider</q:Provider>$ProviderName</e:BoxInfo>$payload</e:$CHK></soap:Body></soap:Envelope>
EOX
)

post=$(cat << EOX
POST /${url#*/} HTTP/1.1
Host: ${url%%/*}:80
Content-Length: ${#body}
Content-Type: ${type}
${soap}

${body}
EOX
)

	cache='/tmp/.juis_check'
	[ "$DEV" == 'i' -o "$DEV" == 'a' ] && cache='/dev/null'

	if [ "$SSL" == 'y' ]; then
		resp="$(curl -s -X POST -H "${type}" ${soap:+-H $soap} -d "${body}" "https://${url}" --cacert "${avmca}" \
		  | tee $cache | sed 's,><,>\n<,g' | sed 's,</.*,,g;s/.*>$//g')"
	else
		resp="$(echo -e "${post}" | nc ${url%%/*} 80 2>/dev/null \
		  | tee $cache | sed 's,><,>\n<,g' | sed 's,</.*,,g;s/.*>$//g')"
	fi
	[ -z "$resp" ] && echo 'Did not received an anser.' 1>&2 && exit 1

	case "$DEV" in
		a)
			URL="$(echo "$resp" | sed -n "s/^<ns3:DownloadURL>//p")"
			[ -n "$URL" ] && ( [ "$CHK" == "DeviceFirmwareUpdateCheck" ] && echo "$DHW=$URL" || echo "$HW=$URL" )
			;;
		i)
			URL="$(echo "$resp" | sed -n "s/^<ns3:DownloadURL>//p")"
			[ -n "$URL" ] && echo "URL=$URL"
			;;
		s)
			VER="$(echo "$resp" | sed -n "s/^<ns3:Version>//p")"
			[ -n "$VER" -a "$VER" != "$Version" ] && echo "Found newer version: $VER" 1>&2 || echo "No newer version found." 1>&2
			;;
		*)
			echo -e "Using SSL:=${SSL:-n}\n\nRequest:\n$line"
			[ "$SSL" == 'y' ] && echo -ne "$body" || echo -ne "$post"
			echo -e "\n$line\n\nResponse:\n$line"
			if command -v xmllint &>/dev/null; then
				grep '^<' -v "$cache"
				grep '^<' "$cache" | xmllint --format -
			else
				echo "$resp"
			fi
			echo -e "$line\n"
			rm "$cache"
			;;
	esac
}

DEV=''
SSL=''
BIX=''
CHK='BoxFirmwareUpdateCheck'
args() {
	[ "${#*}" == '0' ] && help
	local DEVA DEVI DEVS DEVD
	for x in $*; do
		l="${x%=*}"
		r="${x#*=}"
		[ "$x" == '-a' ] && DEVA='x'
		[ "$x" == '-i' ] && DEVI='x'
		[ "$x" == '-s' ] && DEVS='x'
		[ "$x" == '-d' ] && DEVD='x'
		[ "$x" == '--nofb' ] && BIX='n'
		[ "$x" == '--down' ] && CHK='BoxFirmwareDowngradeCheck'
		[ "$x" == '--info' ] && CHK='BoxMessageCheck'
		[ "$x" == '--bpjm' ] && CHK='BPjMUpdateCheck'
		[ "$x" == '--dect' ] && CHK='DeviceFirmwareUpdateCheck'
		[ "$x" == '--plain' ] && SSL='n'
		[ "$x" == '--https' ] && SSL='y'
		[ "$x" == "$l" ] && continue
		eval $l="$r"
	done
	[ -n "$DEVA" ] && DEV='a'
	[ -n "$DEVI" ] && DEV='i'
	[ -n "$DEVS" ] && DEV='s'
	[ -n "$DEVD" ] && DEV='d'
	[ "$SSL" == 'y' ] && [ -z "$(command -v curl)" ] && echo 'Curl is mandatory for https.' 1>&2 && exit 1
	if [ -z "$SSL" ]; then
		command -v curl >/dev/null && SSL='y' || SSL='n'
		if [ -e '/etc/.freetz-ng-version' ]; then
			grep -q "^FREETZ_ADD_JUIS_CHECK__SSL='y'" /etc/options.cfg 2>/dev/null || SSL='n'
		fi
	fi
}

args "$@"
main