#!/usr/bin/env bash # IPNotify notifies system owner when the system's external IP changes # Copyright (C) 2018 Aaron Ball # # 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 3 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 for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Write file to tmpfs location. This way the server reports its location on # boot as the filesystem will be recreated at that time. STATUSFILE="${STATUSFILE:-/tmp/$(basename ${0})-status}" # SMTP connection string in smtp[s]://host:port format. This is used as the # mail relay to send the report email. SMTP="${SMTP:-}" # Address the email should come from (eg: noreply@somehost.foo) FROM="${FROM:-}" # URI to query for the current external IP. # NOTE: This URI should only contain the ip address. Any additional text (such # as query timestamps) will trigger an email every time the text changes, # not just when the returned IP changes. QUERYURI="${QUERYURI}" # Admin email address. # Can be format user@web.com or "First Last ". Also supports # comma and semicolon delimited emails for multiple admins. ADMIN="${ADMIN:-}" titlize() { local text="${@}" printf -- "%s\n" "${text}" printf -- '-%.0s' $(seq 1 ${#text}) printf -- '\n' } prereq_verify() { # Required binaries: # curl # mailx # traceroute # Validate GNU traceroute installation if [ -z "$(type -p traceroute)" ]; then printf "ERROR: GNU traceroute is required but could not be found.\n" >&2 return 1 fi if [ -z "$(traceroute -V | grep 'GNU inetutils')" ]; then printf "ERROR: GNU traceroute is required but another version is installed.\n" >&2 return 1 fi if [ -z "$(type -p curl)" ]; then printf "ERROR: Curl required but could not be found.\n" >&2 return 1 fi if [ -z "$(type -p mailx)" ]; then printf "ERROR: mailx is required but could not be found.\n" >&2 return 1 fi return 0 } notify_user() { local admin="${1}" local newip="${2}" local smtp="${3}" local from="${4}" local email_domain=$(echo ${admin} | cut -d '@' -f 2) # Construct email body printf "To: ${admin} Subject: [report] Server $(hostname) has changed IPs This server, $(hostname), has changed IPS. $(titlize New IP) ${newip} $(titlize "ICMP Traceroute to ${email_domain}") $(timeout 12 traceroute --type=icmp -m 20 -w 2 ${email_domain} 2>&1 | sed 's/^/ /') $(titlize "Resolv.conf") $(cat /etc/resolv.conf | sed 's/^/ /') " | timeout 14 mailx -S "smtp=${smtp}" -r "${from}" -t "${admin}" return $? } validate_vars() { if [ -z "${QUERY}" ]; then printf "QUERY endpoint unset. Cannot determine external IP.\n" >&2 return 1 fi if [ -z "${SMTP}" ]; then printf "SMTP relay server unset (eg: SMTP=smtp://hostname:587)\n" >&2 return 1 fi if [ -z "${FROM}" ]; then printf "FROM address unset.\n" >&2 return 1 fi if [ -z "${ADMIN}" ]; then printf "ADMIN unset. No one to notify (eg: ADMIN=admin@website.com).\n" >&2 return 1 fi } main() { local config="${1:-}" # Config file containing global variables local curip # Placeholder for current ip address (as returned by # the query destination) local cacheip # Cached ip address (if cache file exists) if [ -z "${config}" ]; then printf "Config file required\n" >&2 return 1 fi # Source the config file to load its variables source "${config}" # Validate all required programs are present (eg: mailx, traceroute, ping, etc) prereq_verify [ $? -gt 0 ] && return 1 # Validate all required variables are set (eg: FROM, ADMIN, etc) validate_vars [ $? -gt 0 ] && return 1 # Get the current real ip curip="$(curl --max-time 7 --location --insecure --silent ${QUERY})" if [ -z ${curip:-} ]; then printf "Current ip unknown. Cannot continue\n" return 2 fi # Set ip to state file if none is found if [ ! -f "${STATUSFILE}" ]; then printf "IP status file not found. Creating\n" printf "${curip}" > "${STATUSFILE}" notify_user "${ADMIN}" "${curip}" "${SMTP}" "${FROM}" return 0 fi cacheip="$(cat ${STATUSFILE})" # Cached ip and current ip don't match - email admin if [ "${cacheip:-}" != "${curip:-}" ]; then printf "Current ip does not match last recorded IP. Notifying admin.\n" notify_user "${ADMIN}" "${curip}" "${SMTP}" "${FROM}" if [ $? -eq 0 ]; then printf "${curip}" > "${STATUSFILE}" return 0 fi printf "Could not send notification email.\n" printf "Something is probably still wrong. Skipping write of IP\n" return 1 fi printf "Nothing to see here\n" } main ${@}