#!/bin/sh

# needs: curl, yq (xq)
# usage: fb-getip.sh [ip address]
#
# Endpoints & Services:
#   http://fritz.box:49000/tr64desc.xml
# AVM TR064 Documentation:
#   https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_TR-064_first_steps.pdf
#   https://www.broadband-forum.org/technical/download/TR-064.pdf

FBHOST="fritz.box"
[ ! -z "$1" ] && FBHOST="$1"

# Variable parts, which can be looked up in the TR-064 documentation
SERVICE=WANIPConnection
ACTION=GetExternalIPAddress

# Endpoints supported by the fritzbox, can be looked up at the
# "Endpoint & Services" URL.
ENDPOINT=WANIPConn1

# SOAP protocol payload (https://en.wikipedia.org/wiki/SOAP)
# This is the language the fritzbox is "speaking". So we create this
# payload with the appropriage Service, Action variables.
PAYLOAD=\
'<?xml version="1.0" encoding="utf-8" ?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <u:'$ACTION' xmlns:u="urn:schemas-upnp-org:service:'$SERVICE':1" />
  </s:Body>
</s:Envelope>'

# We use curl to send the payload with the appropriate soap+upnp header
# to the fritzbox and receive the SOAP response.
#
# Header explanation:
#
# - Header: Content-Type - describes that we send format text/xml with utf8 encoding.
#   See HTTP Specification:
#       https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type
#       (Chapter 8.3)
#
# - Header: SoapAction... - it's redundant with the payload information.
#   See SoapAction specification:
#       https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528
#       (Chapter 6.1.1)
#
# -  SoapAction:<parameter>... - This <parameter> is "urn:schemas-upnp-org:<service>
#    Each <service> has it's own specifcation.
#    WANIPConnection Service Specification:
#       https://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v1-Service.pdf
#       (Chapter 2.1)

RESPONSE="$(curl -s "http://$FBHOST:49000/igdupnp/control/$ENDPOINT" \
            -H "Content-Type: text/xml; charset=utf-8" \
            -H "SoapAction:urn:schemas-upnp-org:service:$SERVICE:1#$ACTION" \
            -d "$PAYLOAD")"

# The SOAP response is ugly XML (just like the requst), so we use "xq",
# which converts XML to JSON and allows us to operate on it with "jq"
# syntax.
#   yq/xq documentation: https://kislyuk.github.io/yq
#   jq documentation: https://jqlang.github.io/jq/manual
#   Tutorial: https://www.ashbyhq.com/blog/engineering/jq-and-yq
#
# (once understood, these tools are _very_ powerful! - worth to learn)

# This "case" is only there so we can do a different kind parsing for
# other endpoints. Like when the ACTION variable is changed the parsing
# needs to be adapted.
case "$ACTION" in
     GetExternalIPAddress) printf '%s' "$RESPONSE" \
        | xq -r '."s:Envelope"."s:Body"."u:GetExternalIPAddressResponse".NewExternalIPAddress'
     ;;
     *) printf '%s' "$RESPONSE"
     ;;
esac
