#!/bin/bash
# Last-Updated: 2026-07-19 13:49 EDT
# Created: 2026-07 (WITH-policy EventScript package)
# Authors: Paul Aidukas KN2R, Logan Crook K8LRC
#
# ============================================================================
# SETUP TEMPLATE - WITH conference POLICY (normal / production)
# Copy this file to your TLB server as tlbevent-conference.sh
# (see SETUP-INSTRUCTIONS.txt Step 6, choice A). Then:
#   chmod +x /path/to/tlbevent-conference.sh
#   EventScript = /path/to/tlbevent-conference.sh   (in tlb.conf)
#
# This is the EventScript WITH rapid-reconnect / multi-conference bans.
# For NO policy (testers), use tlbevent.template -> tlbevent.sh instead.
#
# Verify these paths for YOUR system (edit defaults below if different):
#   TLBCMD       - path to tlbcmd (default /usr/local/bin/tlbcmd)
#   TLBCHAT      - path to tlbchat for policy notify (optional)
#   TLB_LOGFILE  - TLB messages log (default /home/tlb/log/messages)
#   STATE_DIR    - event script state (default /var/lib/thelinkbox/tlbevent_state)
#   WHITELIST_FILE - optional multi-conference whitelist (default /home/whitelist.txt)
#
# IRLP bump: on .bump for stn#### nodes, sends disconnect to port 15425 via nc
# so IRLP nodes drop cleanly instead of lingering Kicked/Inactive in the table.
# ============================================================================
#
# Event script invoked by TheLinkBox events to link connections and enforce
# local policy around rapid reconnects, multi-conferencing and duplicate-text
# storms.
#

TLBCMD="${TLBCMD:-/usr/local/bin/tlbcmd}"
TLBCHAT="${TLBCHAT:-/usr/local/bin/tlbchat}"
TLB_LOGFILE="${TLB_LOGFILE:-/home/tlb/log/messages}"
ACTION_LOG="${EVENT_LOGFILE:-${TLB_LOGFILE}}"

STATE_DIR="${TLB_STATE_DIR:-/var/lib/thelinkbox/tlbevent_state}"
PID_FILE="${STATE_DIR}/monitor.pid"
OFFSET_FILE="${STATE_DIR}/log.offset"
LOCK_DIR="${STATE_DIR}/monitor.lock"
WHITELIST_FILE="${WHITELIST_FILE:-/home/whitelist.txt}"

RECONNECT_WINDOW="${RECONNECT_WINDOW:-300}"
RECONNECT_DISCONNECT_THRESHOLD="${RECONNECT_DISCONNECT_THRESHOLD:-5}"
RECONNECT_THRESHOLD="${RECONNECT_THRESHOLD:-6}"
MULTI_STRIKE_THRESHOLD="${MULTI_STRIKE_THRESHOLD:-2}"

DUP_WINDOW="${TLB_DUP_WINDOW:-60}"
DUP_THRESHOLD="${TLB_DUP_THRESHOLD:-6}"
MONITOR_INTERVAL="${TLB_MONITOR_INTERVAL:-5}"
POLICY_COOLDOWN="${TLB_POLICY_COOLDOWN:-15}"
INFO_CHECK_INTERVAL="${TLB_INFO_CHECK_INTERVAL:-60}"
INFO_CONF_THRESHOLD="${TLB_INFO_CONF_THRESHOLD:-2}"
SHOWIP_CACHE_INTERVAL="${TLB_SHOWIP_INTERVAL:-60}"
INFO_FILES_DIR="${TLB_INFOFILES_DIR:-/home}"

SHOWIP_CACHE_TS=0
SHOWIP_CACHE_DATA=""
SHOWIP_PROTO_RESULT=""

ensure_state_dir() {
    if [[ -d "${STATE_DIR}" ]]; then
        if [[ ! -O "${STATE_DIR}" ]]; then
            log "State directory ${STATE_DIR} is not owned by the event script user"
            return 1
        fi
    else
        if ! mkdir -p "${STATE_DIR}"; then
            log "Unable to create state directory ${STATE_DIR}"
            return 1
        fi
    fi
    chmod 700 "${STATE_DIR}" >/dev/null 2>&1 || true
    return 0
}

log() {
    local message="TLB: $*"
    echo "${0##*/}:${message}" >&2
    printf '%s %s\n' "$(date '+%b %d %Y %T %z')" "${message}" >> "${ACTION_LOG}"
}

run_tlbcmd() {
    "${TLBCMD}" -s "$@"
}

sanitize_call() {
    printf '%s' "$1" | tr -c 'A-Za-z0-9_.-' '_'
}

state_file() {
    printf '%s/%s.state\n' "${STATE_DIR}" "$(sanitize_call "$1")"
}

reset_state_vars() {
    LAST_CONNECT=0
    LAST_DISCONNECT=0
    LAST_ACTION_TS=0
    LAST_INFO_CHECK=0
    WHITELIST_LOGGED=0
    RAPID_RECONNECTS=0
    MULTICONF_STRIKES=0
    DUP_COUNT=0
    DUP_FIRST_TS=0
}

load_state() {
    local call="$1"
    local file key value

    reset_state_vars
    file="$(state_file "${call}")"
    ensure_state_dir || return 0

    if [[ -f "${file}" && ! -L "${file}" && -r "${file}" ]]; then
        while IFS='=' read -r key value; do
            case "${key}" in
                LAST_CONNECT|LAST_DISCONNECT|LAST_ACTION_TS|LAST_INFO_CHECK|WHITELIST_LOGGED|RAPID_RECONNECTS|MULTICONF_STRIKES|DUP_COUNT|DUP_FIRST_TS)
                    if [[ "${value}" =~ ^[0-9]+$ ]]; then
                        printf -v "${key}" '%s' "${value}"
                    fi
                    ;;
            esac
        done < "${file}"
    fi
}

save_state() {
    local call="$1"
    local file

    file="$(state_file "${call}")"
    ensure_state_dir || return 0

    if [[ -e "${file}" && ( -L "${file}" || ! -f "${file}" ) ]]; then
        log "Refusing to write non-regular state file ${file}"
        return 1
    fi

    (
        umask 077
        cat > "${file}" <<EOF
LAST_CONNECT=${LAST_CONNECT:-0}
LAST_DISCONNECT=${LAST_DISCONNECT:-0}
LAST_ACTION_TS=${LAST_ACTION_TS:-0}
LAST_INFO_CHECK=${LAST_INFO_CHECK:-0}
WHITELIST_LOGGED=${WHITELIST_LOGGED:-0}
RAPID_RECONNECTS=${RAPID_RECONNECTS:-0}
MULTICONF_STRIKES=${MULTICONF_STRIKES:-0}
DUP_COUNT=${DUP_COUNT:-0}
DUP_FIRST_TS=${DUP_FIRST_TS:-0}
EOF
    )
}

is_whitelisted() {
    local call="$1"

    [[ -z "${call}" ]] && return 1

    if [[ -r "${WHITELIST_FILE}" && ! -d "${WHITELIST_FILE}" ]]; then
        if grep -Fxq "${call}" "${WHITELIST_FILE}"; then
            return 0
        fi
    fi

    return 1
}

log_whitelist_once() {
    local call="$1"

    load_state "${call}"
    if (( WHITELIST_LOGGED == 0 )); then
        log "Allowing multi-conferencing for whitelisted ${call}"
        WHITELIST_LOGGED=1
        save_state "${call}"
    fi
}

notify_user() {
    local call="$1"
    shift
    local message="$*"
    local chat_text

    [[ -z "${call}" || -z "${message}" ]] && return 0

    if [[ ! -x "${TLBCHAT}" ]]; then
        log "Chat helper ${TLBCHAT} is not available to notify ${call}"
        return 1
    fi

    chat_text="SYSOP>${call}: ${message}"
    log "Notifying ${call} via chat: ${message}"
    "${TLBCHAT}" -s "message ${chat_text}" >/dev/null 2>&1 || true
    return 0
}

get_user_flags() {
    local thiscall="$1"
    local users line

    users="$(run_tlbcmd "..users" 2>/dev/null)"
    local IFS=$'\n'
    for line in ${users}; do
        [[ -z "${line}" ]] && continue
        unset IFS
        set -- ${line}
        IFS=$'\n'
        if [[ "$1" == *[0-9]. && "$2" == "${thiscall}" ]]; then
            printf '%s\n' "$3"
            return 0
        fi
    done
    return 1
}

refresh_showip_cache() {
    local now

    now="$(date +%s)"
    if (( SHOWIP_CACHE_TS == 0 || (now - SHOWIP_CACHE_TS) >= SHOWIP_CACHE_INTERVAL )); then
        SHOWIP_CACHE_DATA="$(run_tlbcmd "showip" 2>/dev/null | tr -d '\r')"
        SHOWIP_CACHE_TS="${now}"
    fi
}

get_user_proto() {
    local call="$1"

    refresh_showip_cache
    SHOWIP_PROTO_RESULT="$(printf '%s\n' "${SHOWIP_CACHE_DATA}" | awk -F '\t' -v target="${call}" '$1 == target { print $3; exit }')"
}

should_check_info() {
    local call="$1"
    local now="$2"

    load_state "${call}"
    if (( LAST_INFO_CHECK > 0 && (now - LAST_INFO_CHECK) < INFO_CHECK_INTERVAL )); then
        return 1
    fi
    LAST_INFO_CHECK="${now}"
    save_state "${call}"
    return 0
}

is_info_candidate() {
    local call="$1"
    local flags="$2"
    local proto="$3"

    [[ "${proto}" != "E" && "${proto}" != "echolink" ]] && return 1
    [[ -z "${call}" ]] && return 1

    if [[ "${call}" == \** || "${call}" == *-R || "${call}" == *-L ]]; then
        return 0
    fi
    if [[ "${flags}" == *R* || "${flags}" == *B* || "${flags}" == *C* ]]; then
        return 0
    fi
    return 1
}

count_info_conf_entries() {
    local call="$1"
    local info_file line count=0

    info_file="${INFO_FILES_DIR}/$(sanitize_call "${call}").info"
    [[ -r "${info_file}" && ! -d "${info_file}" ]] || return 1

    while IFS= read -r line; do
        if [[ "${line}" =~ ^\*[A-Za-z0-9_-]+\*[[:space:]].*[[:space:]]CONF([[:space:]]|$) ]]; then
            count=$((count + 1))
        fi
    done < "${info_file}"

    printf '%s\n' "${count}"
    return 0
}

disconnect_user() {
    local call="$1"
    local reason="$2"
    local message="due to policy enforcement"

    [[ -n "${reason}" ]] && message="${reason}"
    log "Disconnecting ${call} ${message}"
    run_tlbcmd "..disconnect ${call}" >/dev/null 2>&1 || true
}

ban_user() {
    local call="$1"
    local reason="$2"
    local message="for repeated policy violations"

    [[ -n "${reason}" ]] && message="${reason}"
    log "Banning ${call} ${message}"
    run_tlbcmd "..ban add ${call}" >/dev/null 2>&1 || true
    run_tlbcmd "..disconnect ${call}" >/dev/null 2>&1 || true
}

linkit() {
    local thiscall="$1"
    local users isconf line

    users="$(run_tlbcmd "..users" 2>/dev/null)"
    isconf="$(run_tlbcmd "..set ConfEnable" 2>/dev/null | tail -1 | cut -d' ' -f3)"

    if [[ "${isconf}" =~ ^[0-9]+$ ]] && (( isconf == 1 )); then
        log "multi-conferencing is not permitted on the system"
        log "Linking is not supported with ConfEnable enabled."
        log "Setting ConfEnable = 0"
        run_tlbcmd "..set ConfEnable = 0" >/dev/null 2>&1 || true
    fi

    IFS=$'\n'
    for line in ${users}; do
        unset IFS
        set -- ${line}
        if [[ "$1" == *[0-9]. ]]; then
            if [[ "$3" == *K* || "$3" == *x* ]]; then
                log "$2 is already kicked or inactive, skipping link: $*"
                continue
            fi
            if [[ "$2" == "${thiscall}" ]]; then
                log "${thiscall} skip same node: $*"
                continue
            fi
            if [[ "$3" == *R* ]]; then
                if [[ "${MONITOR:-0}" -eq 1 ]]; then
                    log "unlink ${thiscall} $2 in monitor mode: $*"
                    run_tlbcmd "..unlink ${thiscall} $2" >/dev/null 2>&1 || true
                else
                    log "link -m ${thiscall} $2: $*"
                    run_tlbcmd "..link -m ${thiscall} $2" >/dev/null 2>&1 || true
                fi
            else
                log "link ${thiscall} $2: $*"
                run_tlbcmd "..link ${thiscall} $2" >/dev/null 2>&1 || true
            fi
        fi
        IFS=$'\n'
    done
    unset IFS
}

should_skip_policy() {
    local call="$1"
    local now="$2"

    load_state "${call}"
    if (( LAST_ACTION_TS > 0 && (now - LAST_ACTION_TS) < POLICY_COOLDOWN )); then
        return 0
    fi
    return 1
}

apply_multiconf_policy() {
    local call="$1"
    local reason="$2"
    local proto="$3"
    local now notice

    now="$(date +%s)"

    if is_whitelisted "${call}"; then
        log_whitelist_once "${call}"
        return 0
    fi

    if should_skip_policy "${call}" "${now}"; then
        return 0
    fi

    load_state "${call}"
    MULTICONF_STRIKES=$((MULTICONF_STRIKES + 1))
    LAST_ACTION_TS="${now}"
    DUP_COUNT=0
    DUP_FIRST_TS=0

    if [[ "${proto}" == "echolink" || "${proto}" == "E" ]]; then
        notice="Multi-conferencing is not permitted on this system. Please disable multi-conferencing to remain connected."
        notify_user "${call}" "${notice}"
    fi

    if (( MULTICONF_STRIKES >= MULTI_STRIKE_THRESHOLD )); then
        ban_user "${call}" "${reason}"
        RAPID_RECONNECTS=0
        MULTICONF_STRIKES=0
    else
        disconnect_user "${call}" "${reason}"
    fi

    save_state "${call}"
}

record_duplicate() {
    local call="$1"
    local now

    now="$(date +%s)"
    load_state "${call}"

    if (( DUP_FIRST_TS == 0 || (now - DUP_FIRST_TS) > DUP_WINDOW )); then
        DUP_COUNT=1
        DUP_FIRST_TS="${now}"
    else
        DUP_COUNT=$((DUP_COUNT + 1))
    fi

    save_state "${call}"

    if (( DUP_COUNT >= DUP_THRESHOLD )); then
        get_user_proto "${call}"
        log "Duplicate threshold reached for ${call}"
        apply_multiconf_policy "${call}" "for duplicate text / multi-conference traffic" "${SHOWIP_PROTO_RESULT}"
    fi
}

process_log_line() {
    local line="$1"
    local call

    if [[ "${line}" =~ Dup[[:space:]]from[[:space:]]([^,[:space:]]+), ]]; then
        call="${BASH_REMATCH[1]}"
        record_duplicate "${call}"
    fi
}

scan_users_for_multiconf() {
    local users line call flags now info_conf_count

    users="$(run_tlbcmd "..users -q" 2>/dev/null)"
    [[ -z "${users}" ]] && return 0
    now="$(date +%s)"

    IFS=$'\n'
    for line in ${users}; do
        unset IFS
        set -- ${line}
        if [[ "$1" == *[0-9]. ]]; then
            call="$2"
            flags="$3"
            if [[ -n "${call}" && ( "${flags}" == *C* || "${flags}" == *B* ) && "${flags}" != *P* ]]; then
                get_user_proto "${call}"
                apply_multiconf_policy "${call}" "for enabling multi-conferencing" "${SHOWIP_PROTO_RESULT}"
            elif is_info_candidate "${call}" "${flags}" "E"; then
                if should_check_info "${call}" "${now}"; then
                    info_conf_count="$(count_info_conf_entries "${call}" || true)"
                    if [[ "${info_conf_count}" =~ ^[0-9]+$ ]] && (( info_conf_count >= INFO_CONF_THRESHOLD )); then
                        log "${call} info file shows ${info_conf_count} conference entries"
                        apply_multiconf_policy "${call}" "for relaying conference entries in info file" "E"
                    fi
                fi
            fi
        fi
        IFS=$'\n'
    done
    unset IFS
}

detect_log_file() {
    local candidate
    local candidates=(
        "${TLB_LOGFILE}"
        "/var/log/messages"
        "/var/log/syslog"
    )

    for candidate in "${candidates[@]}"; do
        if [[ -n "${candidate}" && -f "${candidate}" ]]; then
            printf '%s\n' "${candidate}"
            return 0
        fi
    done

    return 1
}

monitor_loop() {
    local logfile offset size line

    logfile="$(detect_log_file || true)"
    if [[ -z "${logfile}" ]]; then
        log "monitor could not find a log file; set TLB_LOGFILE to override"
    else
        log "monitor watching ${logfile}"
    fi

    if [[ ! -f "${OFFSET_FILE}" && -n "${logfile}" && -f "${logfile}" ]]; then
        wc -c < "${logfile}" | tr -d ' ' > "${OFFSET_FILE}"
    fi

    while true; do
        if [[ -z "${logfile}" || ! -f "${logfile}" ]]; then
            logfile="$(detect_log_file || true)"
            if [[ -z "${logfile}" ]]; then
                sleep "${MONITOR_INTERVAL}"
                continue
            fi
            log "monitor switched to ${logfile}"
            wc -c < "${logfile}" | tr -d ' ' > "${OFFSET_FILE}"
        fi

        offset=0
        if [[ -f "${OFFSET_FILE}" ]]; then
            offset="$(tr -d ' \r\n' < "${OFFSET_FILE}")"
        fi
        size="$(wc -c < "${logfile}" | tr -d ' ')"

        if (( size < offset )); then
            offset=0
        fi

        if (( size > offset )); then
            while IFS= read -r line; do
                process_log_line "${line}"
            done < <(tail -c +"$((offset + 1))" "${logfile}")
            printf '%s\n' "${size}" > "${OFFSET_FILE}"
        fi

        scan_users_for_multiconf
        sleep "${MONITOR_INTERVAL}"
    done
}

ensure_monitor_running() {
    local pid

    ensure_state_dir || return 0

    if [[ -f "${PID_FILE}" ]]; then
        pid="$(tr -d ' \r\n' < "${PID_FILE}")"
        if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then
            return 0
        fi
        rm -f "${PID_FILE}"
    fi

    if mkdir "${LOCK_DIR}" 2>/dev/null; then
        (
            export TLBCMD TLBCHAT TLB_LOGFILE ACTION_LOG STATE_DIR PID_FILE OFFSET_FILE LOCK_DIR WHITELIST_FILE
            export RECONNECT_WINDOW RECONNECT_DISCONNECT_THRESHOLD RECONNECT_THRESHOLD MULTI_STRIKE_THRESHOLD
            export DUP_WINDOW DUP_THRESHOLD MONITOR_INTERVAL POLICY_COOLDOWN INFO_CHECK_INTERVAL INFO_CONF_THRESHOLD SHOWIP_CACHE_INTERVAL
            nohup "$0" --monitor >/dev/null 2>&1 &
            echo "$!" > "${PID_FILE}"
        )
        rmdir "${LOCK_DIR}" 2>/dev/null || true
        log "started duplicate monitor pid $(cat "${PID_FILE}" 2>/dev/null)"
    fi
}

stop_monitor() {
    local pid

    if [[ -f "${PID_FILE}" ]]; then
        pid="$(tr -d ' \r\n' < "${PID_FILE}")"
        if [[ -n "${pid}" ]]; then
            kill "${pid}" 2>/dev/null || true
        fi
        rm -f "${PID_FILE}"
    fi
}

handle_connected() {
    local type="$1"
    local call="$2"
    local user_count="$3"
    local now flags is_multiconf=0 action="" warn_threshold is_whitelisted_call=0 notice

    now="$(date +%s)"
    load_state "${call}"

    if (( LAST_DISCONNECT > 0 )) && (( now - LAST_DISCONNECT <= RECONNECT_WINDOW )); then
        RAPID_RECONNECTS=$((RAPID_RECONNECTS + 1))
    else
        RAPID_RECONNECTS=0
    fi

    LAST_CONNECT="${now}"

    flags="$(get_user_flags "${call}" || true)"
    if [[ -n "${flags}" && ( "${flags}" == *C* || "${flags}" == *B* ) ]]; then
        is_multiconf=1
    fi

    warn_threshold=$((RECONNECT_DISCONNECT_THRESHOLD - 1))
    if [[ "${type}" == "echolink" ]] && (( warn_threshold >= 0 )) && (( RAPID_RECONNECTS == warn_threshold )); then
        notice="Rapid reconnects detected. Please wait at least ${RECONNECT_WINDOW} seconds between connections to avoid moderation."
        notify_user "${call}" "${notice}"
    fi

    if is_whitelisted "${call}"; then
        is_whitelisted_call=1
    fi

    if (( is_multiconf == 1 )) && (( is_whitelisted_call == 1 )); then
        log_whitelist_once "${call}"
    fi

    if [[ "${type}" == "echolink" ]] && (( is_multiconf == 1 )) && (( is_whitelisted_call == 0 )); then
        notice="Multi-conferencing is not permitted on this system. Please disable multi-conferencing to remain connected."
        notify_user "${call}" "${notice}"
        MULTICONF_STRIKES=$((MULTICONF_STRIKES + 1))
        LAST_ACTION_TS="${now}"
        if (( MULTICONF_STRIKES >= MULTI_STRIKE_THRESHOLD )); then
            ban_user "${call}" "for repeated multi-conferencing"
            RAPID_RECONNECTS=0
            MULTICONF_STRIKES=0
            action="ban"
        else
            disconnect_user "${call}" "for enabling multi-conferencing"
            action="disconnect"
        fi
    else
        if (( is_multiconf == 0 )) || (( is_whitelisted_call == 1 )); then
            MULTICONF_STRIKES=0
        fi
    fi

    if [[ -z "${action}" ]] && (( RAPID_RECONNECTS > 0 )); then
        if (( RAPID_RECONNECTS >= RECONNECT_THRESHOLD )); then
            ban_user "${call}" "for repeated rapid reconnects"
            RAPID_RECONNECTS=0
            MULTICONF_STRIKES=0
            LAST_ACTION_TS="${now}"
            action="ban"
        elif (( RAPID_RECONNECTS >= RECONNECT_DISCONNECT_THRESHOLD )); then
            disconnect_user "${call}" "due to rapid reconnects"
            LAST_ACTION_TS="${now}"
            action="disconnect"
        fi
    fi

    save_state "${call}"

    if [[ "${action}" != "ban" && "${action}" != "disconnect" ]]; then
        linkit "${call}"
    fi
    log "connected ${type} ${call} ${user_count}"
}

handle_disconnected() {
    local reason="$1"
    local call="$2"
    local user_count="$3"
    local now

    now="$(date +%s)"
    load_state "${call}"

    if (( LAST_CONNECT > 0 )) && (( now - LAST_CONNECT >= RECONNECT_WINDOW )); then
        RAPID_RECONNECTS=0
    fi

    LAST_DISCONNECT="${now}"
    WHITELIST_LOGGED=0
    save_state "${call}"
    log "disconnected ${reason} ${call} ${user_count}"
}

handle_command_event() {
    local event="$1"
    local talking="" station="" sf_bind_ip="" nodeip="" statusmsg="" cmdargs=""

    if [[ "${event}" != "command" ]]; then
        return 0
    fi

    if [[ "${3}" == *idisc* || "${3}" == *ikick* || "${3}" == *iban* ]]; then
        if [[ "${4}" == "." ]]; then
            talking="$(${TLBCMD} -s port 2>/dev/null | egrep 'Rx$' | tail -1 | cut -c3- | cut -d' ' -f1)"
        fi

        if [[ ${4:0:3} == stn || ${5:0:3} == stn || ${talking:0:3} == stn ]]; then
            station="${4}"
            [[ -n "${5:-}" ]] && station="${5}"
            [[ -n "${talking}" ]] && station="${talking}"
            sf_bind_ip="$(${TLBCMD} -s "set SFBind2IP" 2>/dev/null | grep '^SFBind2IP' | cut -d' ' -f3 | tr -d '"')"
            [[ -n "${sf_bind_ip}" ]] && sf_bind_ip="-s ${sf_bind_ip}"
            nodeip="$(${TLBCMD} -s "showip" 2>/dev/null | egrep "^${station}[[:space:]]" | tail -1 | cut -f2)"
            [[ -z "${nodeip}" ]] && nodeip="${station}.ip.irlp.net"
            log "sending disconnect to ${station} ${nodeip}"
            statusmsg="$(echo disconnect | nc ${sf_bind_ip} -w2 -q2 "${nodeip}" 15425 2>/dev/null)"
            [[ -n "${statusmsg}" ]] && log "${statusmsg}"
        fi

        cmdargs="${4:-}"
        [[ -n "${5:-}" ]] && cmdargs="${cmdargs} ${5}"
        [[ -n "${6:-}" ]] && cmdargs="${cmdargs} ${6}"
        statusmsg="$(${TLBCMD} -s "${3:1} ${cmdargs}" 2>/dev/null)"
        [[ -n "${statusmsg}" ]] && log "${statusmsg}"
        return 0
    fi

    if [[ "${3}" == "bump" ]]; then
        if [[ ${4:0:3} == stn ]]; then
            sf_bind_ip="$(${TLBCMD} -s "set SFBind2IP" 2>/dev/null | grep '^SFBind2IP' | cut -d' ' -f3 | tr -d '"')"
            [[ -n "${sf_bind_ip}" ]] && sf_bind_ip="-s${sf_bind_ip}"
            nodeip="$(${TLBCMD} -s "showip" 2>/dev/null | egrep "^${4}[[:space:]]" | tail -1 | cut -f2)"
            [[ -z "${nodeip}" ]] && nodeip="${4}.ip.irlp.net"
            log "sending disconnect to ${4} ${nodeip}"
            statusmsg="$(echo disconnect | nc ${sf_bind_ip} -w2 -q2 "${nodeip}" 15425 2>/dev/null)"
            [[ -n "${statusmsg}" ]] && log "${statusmsg}"
        fi
        statusmsg="$(${TLBCMD} -s "disconnect ${4}" 2>/dev/null)"
        [[ -n "${statusmsg}" ]] && log "${statusmsg}"
    fi
}

if [[ "${1:-}" == "--monitor" ]]; then
    trap 'rm -f "${PID_FILE}"; exit 0' INT TERM
    ensure_state_dir || exit 1
    echo "$$" > "${PID_FILE}"
    monitor_loop
    exit 0
fi

ensure_state_dir || exit 1

log "${1:-} ${2:-} ${3:-} ${4:-}"

case "${1:-}" in
    starting)
        ensure_monitor_running
        ;;
    connected)
        ensure_monitor_running
        handle_connected "${2:-}" "${3:-}" "${4:-}"
        ;;
    disconnected)
        handle_disconnected "${2:-}" "${3:-}" "${4:-}"
        ;;
    shutdown)
        stop_monitor
        ;;
    command)
        handle_command_event "$@"
        ;;
    *)
        :
        ;;
esac

exit 0
