-
Notifications
You must be signed in to change notification settings - Fork 22
/
cpanel-dynamic-dns
executable file
·411 lines (362 loc) · 12.2 KB
/
cpanel-dynamic-dns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#!/bin/sh
# Copyright 2018 cPanel, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Tested Configurations
# RedHat EL 4,5,6
# CentOS 4,5,6
# OpenWRT (w/openssl installed)
#
# Requires Perl 5.8 or newer with JSON::PP.
# Configuration should be done in the configuration files
# or it can be manually set here
#
# CONTACT_EMAIL is the email address that will be contacted upon failure
#
CONTACT_EMAIL=""
#
# DOMAIN and SUBDOMAIN are the domain that should get its A entry updated
# SUBDOMAIN can be left blank if you wish to update the root domain
#
DOMAIN=""
SUBDOMAIN=""
#
# CPANEL_SERVER is the hostname or ip address to connect to
#
CPANEL_SERVER=""
#
# CPANEL_USER and CPANEL_PASS are the username and password for your
# cPanel Account
#
CPANEL_USER=""
CPANEL_PASS=""
#
# QUIET supresses all information messages (not errors)
# set to 0 or 1
#
QUIET=""
# Program starts here
banner ()
{
if [ "$QUIET" != "1" ]; then
echo "=="
echo "== cPanel Dyanmic DNS Updater $VERSION"
echo "=="
echo "== Updating domain $SUBDOMAIN$DOMAIN"
echo "=="
echo $CFGMESSAGE1
echo $CFGMESSAGE2
echo "=="
fi
}
exit_timeout ()
{
ALARMPID=""
say "The operation timed out while connecting to %s\n" "$LAST_CONNECT_HOST"
notify_failure "Timeout" "Connection Timeout" "Timeout while connecting to $LAST_CONNECT_HOST"
exit
}
setup_timeout ()
{
(sleep $TIMEOUT; kill -ALRM $PARENTPID) &
ALARMPID=$!
trap exit_timeout ALRM
}
setup_vars ()
{
VERSION="2.1"
APINAME=""
PARENTPID=$$
HOMEDIR=`echo ~`
LAST_CONNECT_HOST=""
FAILURE_NOTIFY_INTERVAL="14400"
PERMIT_ROOT_EXECUTION="0"
NOTIFY_FAILURE="1"
TIMEOUT="120"
BASEDIR="cpdyndns"
# Find a suitable path for perl.
PATH="/usr/local/cpanel/3rdparty/bin:$PATH:/usr/bin:/usr/local/bin"
}
setup_config_vars ()
{
if [ "$SUBDOMAIN" = "" ]; then
APINAME="$DOMAIN."
else
APINAME="$SUBDOMAIN"
SUBDOMAIN="$SUBDOMAIN."
fi
LAST_RUN_FILE="$HOMEDIR/.$BASEDIR/$SUBDOMAIN$DOMAIN.lastrun"
LAST_FAIL_FILE="$HOMEDIR/.$BASEDIR/$SUBDOMAIN$DOMAIN.lastfail"
}
load_config ()
{
if [ -e "/etc/$BASEDIR.conf" ]; then
chmod 0600 /etc/$BASEDIR.conf
. /etc/$BASEDIR.conf
CFGMESSAGE1="== /etc/$BASEDIR.conf is being used for configuration"
else
CFGMESSAGE1="== /etc/$BASEDIR.conf does not exist"
fi
if [ -e "$HOMEDIR/etc/$BASEDIR.conf" ]; then
chmod 0600 $HOMEDIR/etc/$BASEDIR.conf
. $HOMEDIR/etc/$BASEDIR.conf
CFGMESSAGE2="== $HOMEDIR/etc/$BASEDIR.conf is being used for configuration"
else
CFGMESSAGE2="== $HOMEDIR/etc/$BASEDIR.conf does not exist"
fi
}
create_dirs ()
{
if [ ! -e "$HOMEDIR/.$BASEDIR" ]; then
mkdir -p "$HOMEDIR/.$BASEDIR"
chmod 0700 "$HOMEDIR/.$BASEDIR"
fi
}
say ()
{
[ "$QUIET" = "1" ] && return
printf "$@"
}
fetch_myaddress ()
{
say "Determining IP Address..."
LAST_CONNECT_HOST="myip.cpanel.net"
MYADDRESS=`printf "GET /v1.0/ HTTP/1.0\r\nHost: myip.cpanel.net\r\nConnection: close\r\n\r\n" | openssl s_client -quiet -connect myip.cpanel.net:443 2>/dev/null | tail -1`
say "%s...Done\n" "$MYADDRESS"
if [ "$MYADDRESS" = "" ]; then
say "Failed to determine IP Address (via https://www.cpanel.net/myip/)\n"
terminate
fi
return
}
load_last_run ()
{
if [ -e "$LAST_RUN_FILE" ]; then
. $LAST_RUN_FILE
fi
}
exit_if_last_address_is_current ()
{
if [ "$LAST_ADDRESS" = "$MYADDRESS" ]; then
say "Last update was for %s, and address has not changed.\n" "$LAST_ADDRESS"
say "If you want to force an update, remove %s\n" "$LAST_RUN_FILE"
terminate
fi
}
generate_auth_string () {
AUTH_STRING=`printf "%s:%s" "$CPANEL_USER" "$CPANEL_PASS" | openssl enc -base64`
}
fetch_zone () {
say "Fetching zone for %s...." "$DOMAIN"
LAST_CONNECT_HOST=$CPANEL_SERVER
REQUEST="GET /json-api/cpanel?cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone&cpanel_jsonapi_apiversion=2&domain=$DOMAIN HTTP/1.0\r\nConnection: close\r\nAuthorization: Basic $AUTH_STRING\r\nUser-Agent: cpanel-dynamic-dns $VERSION\r\n\r\n\r\n"
RECORD=""
LINES=""
INRECORD=0
USETHISRECORD=0
REQUEST_RESULTS=`printf "$REQUEST" | openssl s_client -quiet -connect $CPANEL_SERVER:2083 2>/dev/null`
check_results_for_error "$REQUEST_RESULTS" "$REQUEST"
LINES="$(extract_from_json "$REQUEST_RESULTS" "
do {
join(qq{\n}, map {
qq{\$_->{Line}=\$_->{address}}
} grep {
\$_->{type} eq q{A} && \$_->{name} eq q{$SUBDOMAIN$DOMAIN.}
} @{\$_->{cpanelresult}{data}[0]{record}});
}
")"
say "Done\n"
}
parse_zone () {
say "Looking for duplicate entries..."
FIRSTLINE=""
REVERSELINES=""
DUPECOUNT=0
for LINE in `printf "$LINES"`
do
if [ "$LINE" = "" ]; then
continue
fi
if [ "$FIRSTLINE" = "" ]; then
FIRSTLINE=$LINE
continue
fi
DUPECOUNT=`expr $DUPECOUNT + 1`
REVERSELINES="$LINE\n$REVERSELINES"
done
say "Found %d duplicates\n" "$DUPECOUNT"
for LINE in `printf "$REVERSELINES"`
do
if [ "$LINE" = "" ]; then
continue
fi
LINENUM=`echo $LINE | awk -F= '{print $1}'`
LAST_CONNECT_HOST=$CPANEL_SERVER
REQUEST="GET /json-api/cpanel?cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&cpanel_jsonapi_apiversion=2&domain=$DOMAIN&line=$LINENUM HTTP/1.0\r\nConnection: close\r\nAuthorization: Basic $AUTH_STRING\r\nUser-Agent: cpanel-dynamic-dns $VERSION\r\n\r\n\r\n"
say "Removing Duplicate entry for %s%s. (line %d)\n" "$SUBDOMAIN" "$DOMAIN" "$LINENUM"
RESULT=`printf "$REQUEST" | openssl s_client -quiet -connect $CPANEL_SERVER:2083 2>&1`
check_results_for_error "$RESULT" "$REQUEST"
say "%s\n" "$RESULT"
done
}
update_records () {
if [ "$FIRSTLINE" = "" ]; then
say "Record %s%s. does not exist. Setting %s%s. to %s\n" "$SUBDOMAIN" "$DOMAIN" "$SUBDOMAIN" "$DOMAIN" "$MYADDRESS"
LAST_CONNECT_HOST=$CPANEL_SERVER
REQUEST="GET /json-api/cpanel?cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&cpanel_jsonapi_apiversion=2&domain=$DOMAIN&name=$APINAME&type=A&address=$MYADDRESS&ttl=300 HTTP/1.0\r\nConnection: close\r\nAuthorization: Basic $AUTH_STRING\r\nUser-Agent: cpanel-dynamic-dns $VERSION\r\n\r\n\r\n"
RESULT=`printf "$REQUEST" | openssl s_client -quiet -connect $CPANEL_SERVER:2083 2>&1`
check_results_for_error "$RESULT" "$REQUEST"
else
ADDRESS=`echo $FIRSTLINE | awk -F= '{print $2}'`
LINENUM=`echo $FIRSTLINE | awk -F= '{print $1}'`
if [ "$ADDRESS" = "$MYADDRESS" ]; then
say "Record %s%s. already exists in zone on line %s of the %s zone.\n" "$SUBDOMAIN" "$DOMAIN" "$LINENUM" "$DOMAIN"
say "Not updating as its already set to %s\n" "$ADDRESS"
echo "LAST_ADDRESS=\"$MYADDRESS\"" > $LAST_RUN_FILE
terminate
fi
say "Record %s%s. already exists in zone on line %d with address %s. Updating to %s\n" "$SUBDOMAIN" "$DOMAIN" "$LINENUM" "$ADDRESS" "$MYADDRESS"
LAST_CONNECT_HOST=$CPANEL_SERVER
REQUEST="GET /json-api/cpanel?cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=edit_zone_record&cpanel_jsonapi_apiversion=2&Line=$FIRSTLINE&domain=$DOMAIN&name=$APINAME&type=A&address=$MYADDRESS&ttl=300 HTTP/1.0\r\nConnection: close\r\nAuthorization: Basic $AUTH_STRING\r\nUser-Agent: cpanel-dynamic-dns $VERSION\r\n\r\n\r\n"
RESULT=`printf "$REQUEST" | openssl s_client -quiet -connect $CPANEL_SERVER:2083 2>&1`
check_results_for_error "$RESULT" "$REQUEST"
fi
if [ "`echo $RESULT | grep newserial`" ]; then
say "Record updated ok\n"
echo "LAST_ADDRESS=\"$MYADDRESS\"" > $LAST_RUN_FILE
else
say "Failed to update record\n"
say "%s\n" "$RESULT"
fi
}
extract_from_json ()
{
DATA="$1"
PATTERN="$2"
printf "%s" "$DATA" | \
perl -0777 -MJSON::PP -p \
-e '(undef, $_) = split /\r\n\r\n/, $_, 2;' \
-e '$_ = JSON::PP::decode_json($_);' \
-e "\$_ = $PATTERN;"
}
check_results_for_error ()
{
RESULTS="$1"
REQUEST="$2"
if [ "$(extract_from_json "$RESULTS" '$_->{cpanelresult}{event}{result}')" = "1" ]; then
say "success..."
else
INREASON=0
INSTATUSMSG=0
MSG=""
STATUSMSG=""
MSG="$(extract_from_json "$RESULTS" 'ref $_->{cpanelresult}{data} eq q{ARRAY} ? $_->{cpanelresult}{data}[0]{reason} : $_->{cpanelresult}{data}{reason}')"
STATUSMSG="$(extract_from_json "$RESULTS" 'ref $_->{cpanelresult}{data} eq {ARRAY} ? $_->{cpanelresult}{data}[0]{statusmsg} : $_->{cpanelresult}{data}{statusmsg}')"
if [ "$MSG" = "" ]; then
MSG="Unknown Error"
if [ "$STATUSMSG" = "" ]; then
STATUSMSG="Please make sure you have the zoneedit, or simplezone edit permission on your account."
fi
fi
say "Request failed with error: %s (%s)\n" "$MSG" "$STATUSMSG"
notify_failure "$MSG" "$STATUSMSG" "$REQUEST_RESULTS" "$REQUEST"
terminate
fi
}
notify_failure ()
{
MSG="$1"
STATUSMSG="$2"
REQUEST_RESULTS="$3"
CURRENT_TIME=`date +%s`
LAST_TIME=0
if [ -e "$LAST_FAIL_FILE" ]; then
. $LAST_FAIL_FILE
fi
TIME_DIFF=`expr $CURRENT_TIME - $LAST_TIME`
if [ "$CONTACT_EMAIL" = "" ]; then
echo "No contact email address was set. Cannot send failure notification."
return
fi
if [ $TIME_DIFF -gt $FAILURE_NOTIFY_INTERVAL ]; then
echo "LAST_TIME=$CURRENT_TIME" > $LAST_FAIL_FILE
SUBJECT="Failed to update dynamic DNS for $SUBDOMAIN$DOMAIN. on $CPANEL_SERVER : $MSG ($STATUMSG)"
if [ -e "/bin/mail" ]; then
say "sending email notification of failure.\n"
printf "Status Message: $STATUSMSG\nThe full response was: $REQUEST_RESULTS" | /bin/mail -s "$SUBJECT" $CONTACT_EMAIL
else
say "/bin/mail is not available, cannot send notification of failure.\n"
fi
else
say "skipping notification because a notication was sent %d seconds ago.\n" "$TIME_DIFF"
fi
}
terminate () {
if [ "$ALARMPID" != "" ]; then
kill $ALARMPID
fi
exit
}
check_for_root () {
if [ "$PERMIT_ROOT_EXECUTION" = "1" ]; then
return
fi
if [ "`id -u`" = "0" ]; then
echo "You should not run this script as root if possible"
echo "If you really want to run as root please run"
echo "echo \"PERMIT_ROOT_EXECUTION=1\" >> /etc/$BASEDIR.conf"
echo "and run this script again"
terminate
fi
}
check_config () {
if [ "$CONTACT_EMAIL" = "" ]; then
echo "= Warning: no email address set for notifications"
fi
if [ "$CPANEL_SERVER" = "" ]; then
echo "= Error: CPANEL_SERVER must be set in a configuration file"
exit
fi
if [ "$DOMAIN" = "" ]; then
echo "= Error: DOMAIN must be set in a configuration file"
exit
fi
if [ "$CPANEL_USER" = "" ]; then
echo "= Error: CPANEL_USER must be set in a configuration file"
exit
fi
if [ "$CPANEL_PASS" = "" ]; then
echo "= Error: CPANEL_PASS must be set in a configuration file"
exit
fi
if ! perl -MJSON::PP -e1 >/dev/null 2>&1; then
echo "= Error: A version of perl with JSON::PP must be in the PATH"
exit
fi
}
setup_vars
setup_timeout
load_config
setup_config_vars
banner
check_for_root
check_config
fetch_myaddress
create_dirs
load_last_run
exit_if_last_address_is_current
generate_auth_string
fetch_zone
parse_zone
update_records
terminate