blob: 2470fa3118aaa94bf32d27d7f48ba89243e2e6c2 (
plain)
1 #!/usr/bin/env bash
2 # IPNotify notifies system owner when the system's external IP changes
3 # Copyright (C) 2018 Aaron Ball <nullspoon@oper.io>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
19 # Write file to tmpfs location. This way the server reports its location on
20 # boot as the filesystem will be recreated at that time.
21 STATUSFILE="${STATUSFILE:-/tmp/$(basename ${0})-status}"
22
23 # SMTP connection string in smtp[s]://host:port format. This is used as the
24 # mail relay to send the report email.
25 SMTP="${SMTP:-}"
26
27 # Address the email should come from (eg: noreply@somehost.foo)
28 FROM="${FROM:-}"
29
30 # URI to query for the current external IP.
31 # NOTE: This URI should only contain the ip address. Any additional text (such
32 # as query timestamps) will trigger an email every time the text changes,
33 # not just when the returned IP changes.
34 QUERYURI="${QUERYURI}"
35
36 # Admin email address.
37 # Can be format user@web.com or "First Last <user@web.com>". Also supports
38 # comma and semicolon delimited emails for multiple admins.
39 ADMIN="${ADMIN:-}"
40
41
42 titlize() {
43 local text="${@}"
44
45 printf -- "%s\n" "${text}"
46 printf -- '-%.0s' $(seq 1 ${#text})
47 printf -- '\n'
48 }
49
50
51 prereq_verify() {
52 # Required binaries:
53 # curl
54 # mailx
55 # traceroute
56
57 # Validate GNU traceroute installation
58 if [ -z "$(type -p traceroute)" ]; then
59 printf "ERROR: GNU traceroute is required but could not be found.\n" >&2
60 return 1
61 fi
62 if [ -z "$(traceroute -V | grep 'GNU inetutils')" ]; then
63 printf "ERROR: GNU traceroute is required but another version is installed.\n" >&2
64 return 1
65 fi
66
67 if [ -z "$(type -p curl)" ]; then
68 printf "ERROR: Curl required but could not be found.\n" >&2
69 return 1
70 fi
71
72 if [ -z "$(type -p mailx)" ]; then
73 printf "ERROR: mailx is required but could not be found.\n" >&2
74 return 1
75 fi
76
77 return 0
78 }
79
80
81 notify_user() {
82 local admin="${1}"
83 local newip="${2}"
84 local smtp="${3}"
85 local from="${4}"
86
87 local email_domain=$(echo ${admin} | cut -d '@' -f 2)
88
89 # Construct email body
90 printf "To: ${admin}
91 Subject: [report] Server $(hostname) has changed IPs
92
93
94 This server, $(hostname), has changed IPS.
95
96 $(titlize New IP)
97
98 ${newip}
99
100
101 $(titlize "ICMP Traceroute to ${email_domain}")
102 $(timeout 12 traceroute --type=icmp -m 20 -w 2 ${email_domain} 2>&1 | sed 's/^/ /')
103
104
105 $(titlize "Resolv.conf")
106 $(cat /etc/resolv.conf | sed 's/^/ /')
107
108
109 " | timeout 14 mailx -S "smtp=${smtp}" -r "${from}" -t "${admin}"
110
111 return $?
112 }
113
114
115 validate_vars() {
116 if [ -z "${QUERY}" ]; then
117 printf "QUERY endpoint unset. Cannot determine external IP.\n" >&2
118 return 1
119 fi
120
121 if [ -z "${SMTP}" ]; then
122 printf "SMTP relay server unset (eg: SMTP=smtp://hostname:587)\n" >&2
123 return 1
124 fi
125
126 if [ -z "${FROM}" ]; then
127 printf "FROM address unset.\n" >&2
128 return 1
129 fi
130
131 if [ -z "${ADMIN}" ]; then
132 printf "ADMIN unset. No one to notify (eg: ADMIN=admin@website.com).\n" >&2
133 return 1
134 fi
135 }
136
137
138 main() {
139 local config="${1:-}" # Config file containing global variables
140 local curip # Placeholder for current ip address (as returned by
141 # the query destination)
142 local cacheip # Cached ip address (if cache file exists)
143
144
145 if [ -z "${config}" ]; then
146 printf "Config file required\n" >&2
147 return 1
148 fi
149
150 # Source the config file to load its variables
151 source "${config}"
152
153 # Validate all required programs are present (eg: mailx, traceroute, ping, etc)
154 prereq_verify
155 [ $? -gt 0 ] && return 1
156
157 # Validate all required variables are set (eg: FROM, ADMIN, etc)
158 validate_vars
159 [ $? -gt 0 ] && return 1
160
161
162 # Get the current real ip
163 curip="$(curl --max-time 7 --location --insecure --silent ${QUERY})"
164
165 if [ -z ${curip:-} ]; then
166 printf "Current ip unknown. Cannot continue\n"
167 return 2
168 fi
169
170 # Set ip to state file if none is found
171 if [ ! -f "${STATUSFILE}" ]; then
172 printf "IP status file not found. Creating\n"
173 printf "${curip}" > "${STATUSFILE}"
174 notify_user "${ADMIN}" "${curip}" "${SMTP}" "${FROM}"
175 return 0
176 fi
177
178 cacheip="$(cat ${STATUSFILE})"
179
180 # Cached ip and current ip don't match - email admin
181 if [ "${cacheip:-}" != "${curip:-}" ]; then
182 printf "Current ip does not match last recorded IP. Notifying admin.\n"
183 notify_user "${ADMIN}" "${curip}" "${SMTP}" "${FROM}"
184
185 if [ $? -eq 0 ]; then
186 printf "${curip}" > "${STATUSFILE}"
187 return 0
188 fi
189 printf "Could not send notification email.\n"
190 printf "Something is probably still wrong. Skipping write of IP\n"
191 return 1
192 fi
193
194 printf "Nothing to see here\n"
195 }
196
197 main ${@}
|