#!/bin/bash
#
# Example configuration
#
# commands.cfg:
#
# define command {
#     command_name check_fritz_uptime
#     command_line $USER1$/check_fritz -h $HOSTADDRESS$ -f linkuptime
# }
#
# FRITZ!Box monitoring largely based on the works of
# http://terranas.qcloudnas.com/wordpress/nagios-fritzbox-uber-upnp-uberwachen/
#

RC_OK=0
RC_WARN=1
RC_CRIT=2
RC_UNKNOWN=3

CURL=/usr/bin/curl

usage()
{
    echo "usage: check_fritz -d -h hostname -f <function> [-w <warn>] [-c <crit>] [-i <checkIP>]"
    echo "    -d: enable debug output"
    echo "    -w: warn limit, depends on function"
    echo "    -c: critical limit, depends on function"
    echo "    -i: IP address which should be matched in externalip function"
    echo "functions:"
    echo "    linkuptime = connection time in seconds"
    echo "    connection = connection status"
    echo "    upstream   = maximum upstream on current connection"
    echo "    downstream = maximum downstream on current connection"
    echo "    externalip = get external ip address"
    exit ${RC_UNKNOWN}
}

require_number()
{
    VAR=$1
    MSG=$2

    if [[ ! "${VAR}" =~ ^[0-9]+$ ]] ; then
        echo "ERROR - ${MSG} (${VAR})"
        exit ${RC_UNKNOWN}
    fi
}

require_ip_address()
{
    VAR=$1
    MSG=$2

    if [[ ! "${VAR}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] ; then
        echo "ERROR - ${MSG} (${VAR})"
        exit ${RC_UNKNOWN}
    fi
}

find_xml_value()
{
    XML=$1
    VAL=$2

    echo "${XML}" | grep "${VAL}" | sed "s/<${VAL}>\([^<]*\)<\/${VAL}>/\1/"
}

check_greater()
{
    VAL=$1
    WARN=$2
    CRIT=$3
    MSG=$4

    if [ ${VAL} -gt ${WARN} ] || [ ${WARN} -eq 0 ]; then
        echo "OK - ${MSG}"
        exit ${RC_OK}
    elif [ ${VAL} -gt ${CRIT} ] || [ ${CRIT} -eq 0 ]; then
        echo "WARNING - ${MSG}"
        exit ${RC_WARN}
    else
        echo "CRITICAL - ${MSG}"
        exit ${RC_CRIT}
    fi
}

HOSTNAME=fritz.box
PORT=49000
CHECK=linkuptime
DEBUG=0
WARN=0
CRIT=0
IPCHECK=0

while getopts h:f:w:c:i:d OPTNAME; do
    case "${OPTNAME}" in
    h)
        HOSTNAME="${OPTARG}"
        ;;
    f)
        CHECK="${OPTARG}"
        ;;
    d)
        DEBUG=1
        ;;
    w)
        WARN="${OPTARG}"
        ;;
    c)
        CRIT="${OPTARG}"
        ;;
    i)
        IPCHECK="${OPTARG}"
        ;;
    *)
        usage
        ;;
    esac
done

case ${CHECK} in
    linkuptime|connection)
        VERB=GetStatusInfo
        URL=WANIPConn1
        NS=WANIPConnection
        ;;
    downstream|upstream)
        VERB=GetCommonLinkProperties
        URL=WANCommonIFC1
        NS=WANCommonInterfaceConfig
        ;;
    externalip)
        VERB=GetExternalIPAddress
        URL=WANIPConn1
        NS=WANIPConnection
        ;;
    *)
        echo "ERROR - Unknown service check ${CHECK}"
        exit ${RC_UNKNOWN}
        ;;
esac

STATUS=`curl "http://${HOSTNAME}:${PORT}/igdupnp/control/${URL}" \
    -H "Content-Type: text/xml; charset="utf-8"" \
    -H "SoapAction:urn:schemas-upnp-org:service:${NS}:1#${VERB}" \
    -d "<?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:${VERB} xmlns:u="urn:schemas-upnp-org:service:${NS}:1" /> </s:Body> </s:Envelope>" \
    -s`

if [ "$?" -ne "0" ]; then
    echo "ERROR - Could not retrieve status from FRITZ!Box"
    exit ${RC_CRIT}
fi

if [ ${DEBUG} -eq 1 ]; then
    echo "DEBUG - Status:"
    echo "${STATUS}"
fi

case ${CHECK} in
linkuptime)
    UPTIME=$(find_xml_value "${STATUS}" NewUptime)
    require_number "${UPTIME}" "Could not parse uptime"

    DAYS=$((${UPTIME}/86400))
    HOURS=$(((${UPTIME}-(${DAYS}*86400))/3600))
    MINUTES=$(((${UPTIME}-(${DAYS}*86400)-(${HOURS}*3600))/60))
    SECONDS=$((${UPTIME}-(${DAYS}*86400)-(${HOURS}*3600)-(${MINUTES}*60)))

    RESULT="Link uptime ${UPTIME} seconds (${DAYS}d ${HOURS}h ${MINUTES}m ${SECONDS}s)"

    check_greater ${UPTIME} 1 0 "${RESULT}"
    ;;
upstream)
    UPSTREAM=$(find_xml_value "${STATUS}" NewLayer1UpstreamMaxBitRate)
    require_number "${UPSTREAM}" "Could not parse upstream"

    MBIT=$((${UPSTREAM}/1000000))

    RESULT="Upstream ${UPSTREAM} bits/s (${MBIT} Mbit/s)"

    check_greater ${UPSTREAM} ${WARN} ${CRIT} "${RESULT}"
    ;;
downstream)
    DOWNSTREAM=$(find_xml_value "${STATUS}" NewLayer1DownstreamMaxBitRate)
    require_number "${DOWNSTREAM}" "Could not parse downstream"

    MBIT=$((${DOWNSTREAM}/1000000))

    RESULT="Downstream ${DOWNSTREAM} bits/s (${MBIT} Mbit/s)"

    check_greater ${DOWNSTREAM} ${WARN} ${CRIT} "${RESULT}"
    ;;
connection)
    STATE=$(find_xml_value "${STATUS}" NewConnectionStatus)
    case ${STATE} in
    Connected)
        echo "OK - Connected"
        exit ${RC_OK}
        ;;
    Connecting | Disconnected)
        echo "WARNING - Connection lost"
        exit ${RC_WARN}
        ;;
    *)
        echo "ERROR - Unknown connection state ${STATE}"
        exit ${RC_UNKNOWN}
        ;;
    esac
    ;;
externalip)
    EXTERNALIP=$(find_xml_value "${STATUS}" NewExternalIPAddress)
    require_ip_address "${EXTERNALIP}" "Could not parse external ip address"

    if [ ${IPCHECK} == "0" ]; then
        echo "OK - ${EXTERNALIP} (but no check IP is set (-i))"
        exit ${RC_OK}
    elif [ "${EXTERNALIP}" == "${IPCHECK}" ]; then
        echo "OK - ${EXTERNALIP} (external IP matches requirement)"
        exit ${RC_OK}
    else
        echo "CRITICAL - ${EXTERNALIP} (external IP doesn't match requirement ${IPCHECK})"
        exit ${RC_CRIT}
    fi
    ;;
*)
    echo "ERROR - Unknown service check ${CHECK}"
    exit ${RC_UNKNOWN}
esac
