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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
|
#!/bin/sh
# /usr/lib/ddns/dynamic_dns_functions.sh
#
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
# Original written by Eric Paul Bishop, January 2008
# (Loosely) based on the script on the one posted by exobyte in the forums here:
# http://forum.openwrt.org/viewtopic.php?id=14040
# extended and partial rewritten
#.2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
#
# function timeout
# copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
# @author Anthony Thyssen 6 April 2011
#
# variables in small chars are read from /etc/config/ddns
# variables in big chars are defined inside these scripts as global vars
# variables in big chars beginning with "__" are local defined inside functions only
# set -vx #script debugger
. /lib/functions.sh
. /lib/functions/network.sh
# GLOBAL VARIABLES #
if [ -f "/usr/share/ddns/version" ]; then
VERSION="$(cat "/usr/share/ddns/version")"
else
VERSION="unknown"
fi
SECTION_ID="" # hold config's section name
VERBOSE=0 # default mode is log to console, but easily changed with parameter
DRY_RUN=0 # run without actually doing (sending) any changes
MYPROG=$(basename $0) # my program call name
LOGFILE="" # logfile - all files are set in dynamic_dns_updater.sh
PIDFILE="" # pid file
UPDFILE="" # store UPTIME of last update
DATFILE="" # save stdout data of WGet and other external programs called
ERRFILE="" # save stderr output of WGet and other external programs called
IPFILE="" # store registered IP for read by LuCI status
TLDFILE=/usr/share/public_suffix_list.dat.gz # TLD file used by split_FQDN
CHECK_SECONDS=0 # calculated seconds out of given
FORCE_SECONDS=0 # interval and unit
RETRY_SECONDS=0 # in configuration
LAST_TIME=0 # holds the uptime of last successful update
CURR_TIME=0 # holds the current uptime
NEXT_TIME=0 # calculated time for next FORCED update
EPOCH_TIME=0 # seconds since 1.1.1970 00:00:00
CURRENT_IP="" # holds the current IP read from the box
REGISTERED_IP="" # holds the IP read from DNS
URL_USER="" # url encoded $username from config file
URL_PASS="" # url encoded $password from config file
URL_PENC="" # url encoded $param_enc from config file
UPD_ANSWER="" # Answer given by service on success
ERR_LAST=0 # used to save $? return code of program and function calls
RETRY_COUNT=0 # error counter on different current and registered IPs
PID_SLEEP=0 # ProcessID of current background "sleep"
# regular expression to detect IPv4 / IPv6
# IPv4 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x
IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
# IPv6 ( ( 0-9a-f 1-4char ":") min 1x) ( ( 0-9a-f 1-4char )optional) ( (":" 0-9a-f 1-4char ) min 1x)
IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
# characters that are dangerous to pass to a shell command line
SHELL_ESCAPE="[\"\'\`\$\!();><{}?|\[\]\*\\\\]"
# dns character set. "-" must be the last character
DNS_CHARSET="[@a-zA-Z0-9._-]"
# domains can have * for wildcard. "-" must be the last character
DNS_CHARSET_DOMAIN="[@a-zA-Z0-9._*-]"
# detect if called by ddns-lucihelper.sh script, disable retrys (empty variable == false)
LUCI_HELPER=$(printf %s "$MYPROG" | grep -i "luci")
# Name Server Lookup Programs
BIND_HOST=$(command -v host)
KNOT_HOST=$(command -v khost)
DRILL=$(command -v drill)
HOSTIP=$(command -v hostip)
NSLOOKUP=$(command -v nslookup)
# Transfer Programs
WGET=$(command -v wget)
$WGET -V 2>/dev/null | grep -F -q +https && WGET_SSL=$WGET
CURL=$(command -v curl)
# CURL_SSL not empty then SSL support available
CURL_SSL=$($CURL -V 2>/dev/null | grep -F "https")
# CURL_PROXY not empty then Proxy support available
CURL_PROXY=$(find /lib /usr/lib -name libcurl.so* -exec strings {} 2>/dev/null \; | grep -im1 "all_proxy")
UCLIENT_FETCH=$(command -v uclient-fetch)
# Global configuration settings
# allow NON-public IP's
upd_privateip=$(uci -q get ddns.global.upd_privateip) || upd_privateip=0
# directory to store run information to.
ddns_rundir=$(uci -q get ddns.global.ddns_rundir) || ddns_rundir="/var/run/ddns"
[ -d $ddns_rundir ] || mkdir -p -m755 $ddns_rundir
# directory to store log files
ddns_logdir=$(uci -q get ddns.global.ddns_logdir) || ddns_logdir="/var/log/ddns"
[ -d $ddns_logdir ] || mkdir -p -m755 $ddns_logdir
# number of lines to before rotate logfile
ddns_loglines=$(uci -q get ddns.global.ddns_loglines) || ddns_loglines=250
ddns_loglines=$((ddns_loglines + 1)) # correct sed handling
# format to show date information in log and luci-app-ddns default ISO 8601 format
ddns_dateformat=$(uci -q get ddns.global.ddns_dateformat) || ddns_dateformat="%F %R"
DATE_PROG="date +'$ddns_dateformat'"
# USE_CURL if GNU Wget and cURL installed normally Wget is used by do_transfer()
# to change this use global option use_curl '1'
USE_CURL=$(uci -q get ddns.global.use_curl) || USE_CURL=0 # read config
[ -n "$CURL" ] || USE_CURL=0 # check for cURL
# loads all options for a given package and section
# also, sets all_option_variables to a list of the variable names
# $1 = ddns, $2 = SECTION_ID
load_all_config_options()
{
local __PKGNAME="$1"
local __SECTIONID="$2"
local __VAR
local __ALL_OPTION_VARIABLES=""
# this callback loads all the variables in the __SECTIONID section when we do
# config_load. We need to redefine the option_cb for different sections
# so that the active one isn't still active after we're done with it. For reference
# the $1 variable is the name of the option and $2 is the name of the section
config_cb()
{
if [ ."$2" = ."$__SECTIONID" ]; then
option_cb()
{
__ALL_OPTION_VARIABLES="$__ALL_OPTION_VARIABLES $1"
}
else
option_cb() { return 0; }
fi
}
config_load "$__PKGNAME"
# Given SECTION_ID not found so no data, so return 1
[ -z "$__ALL_OPTION_VARIABLES" ] && return 1
for __VAR in $__ALL_OPTION_VARIABLES
do
config_get "$__VAR" "$__SECTIONID" "$__VAR"
done
return 0
}
# read's all service sections from ddns config
# $1 = Name of variable to store
load_all_service_sections() {
local __DATA=""
config_cb()
{
# only look for section type "service", ignore everything else
[ "$1" = "service" ] && __DATA="$__DATA $2"
}
config_load "ddns"
eval "$1=\"$__DATA\""
return
}
# starts updater script for all given sections or only for the one given
# $1 = interface (Optional: when given only scripts are started
# configured for that interface)
# used by /etc/hotplug.d/iface/95-ddns on IFUP
# and by /etc/init.d/ddns start
start_daemon_for_all_ddns_sections()
{
local __EVENTIF="$1"
local __SECTIONS=""
local __SECTIONID=""
local __IFACE=""
load_all_service_sections __SECTIONS
for __SECTIONID in $__SECTIONS; do
config_get __IFACE "$__SECTIONID" interface "wan"
[ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue
if [ $VERBOSE -eq 0 ]; then # start in background
/usr/lib/ddns/dynamic_dns_updater.sh -v 0 -S "$__SECTIONID" -- start &
else
/usr/lib/ddns/dynamic_dns_updater.sh -v "$VERBOSE" -S "$__SECTIONID" -- start
fi
done
}
# stop sections process incl. childs (sleeps)
# $1 = section
stop_section_processes() {
local __PID=0
local __PIDFILE="$ddns_rundir/$1.pid"
[ $# -ne 1 ] && write_log 12 "Error calling 'stop_section_processes()' - wrong number of parameters"
[ -e "$__PIDFILE" ] && {
__PID=$(cat $__PIDFILE)
ps | grep "^[\t ]*$__PID" >/dev/null 2>&1 && kill $__PID || __PID=0 # terminate it
}
[ $__PID -eq 0 ] # report if process was running
}
# stop updater script for all defines sections or only for one given
# $1 = interface (optional)
# used by /etc/hotplug.d/iface/95-ddns on 'ifdown'
# and by /etc/init.d/ddns stop
# needed because we also need to kill "sleep" child processes
stop_daemon_for_all_ddns_sections() {
local __EVENTIF="$1"
local __SECTIONS=""
local __SECTIONID=""
local __IFACE=""
load_all_service_sections __SECTIONS
for __SECTIONID in $__SECTIONS; do
config_get __IFACE "$__SECTIONID" interface "wan"
[ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue
stop_section_processes "$__SECTIONID"
done
}
# reports to console, logfile, syslog
# $1 loglevel 7 == Debug to 0 == EMERG
# value +10 will exit the scripts
# $2..n text to report
write_log() {
local __LEVEL __EXIT __CMD __MSG __MSE
local __TIME=$(date +%H%M%S)
[ $1 -ge 10 ] && {
__LEVEL=$(($1-10))
__EXIT=1
} || {
__LEVEL=$1
__EXIT=0
}
shift # remove loglevel
[ $__EXIT -eq 0 ] && __MSG="$*" || __MSG="$* - TERMINATE"
case $__LEVEL in # create log message and command depending on loglevel
0) __CMD="logger -p user.emerg -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME EMERG : $__MSG" ;;
1) __CMD="logger -p user.alert -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME ALERT : $__MSG" ;;
2) __CMD="logger -p user.crit -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME CRIT : $__MSG" ;;
3) __CMD="logger -p user.err -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME ERROR : $__MSG" ;;
4) __CMD="logger -p user.warn -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME WARN : $__MSG" ;;
5) __CMD="logger -p user.notice -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME note : $__MSG" ;;
6) __CMD="logger -p user.info -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME info : $__MSG" ;;
7) __MSG=" $__TIME : $__MSG";;
*) return;;
esac
# verbose echo
[ $VERBOSE -gt 0 -o $__EXIT -gt 0 ] && echo -e "$__MSG"
# write to logfile
if [ ${use_logfile:-1} -eq 1 -o $VERBOSE -gt 1 ]; then
if [ -n "$password" ]; then
# url encode __MSG, password already done
urlencode __MSE "$__MSG"
# replace encoded password inside encoded message
# and url decode (newline was encoded as %00)
__MSG=$( echo -e "$__MSE" \
| sed -e "s/$URL_PASS/***PW***/g" \
| sed -e "s/+/ /g; s/%00/\n/g; s/%/\\\\x/g" | xargs -0 printf "%b" )
fi
printf "%s\n" "$__MSG" >> $LOGFILE
# VERBOSE > 1 then NO loop so NO truncate log to $ddns_loglines lines
[ $VERBOSE -gt 1 ] || sed -i -e :a -e '$q;N;'$ddns_loglines',$D;ba' $LOGFILE
fi
[ -n "$LUCI_HELPER" ] && return # nothing else todo when running LuCI helper script
[ $__LEVEL -eq 7 ] && return # no syslog for debug messages
__CMD=$(echo -e "$__CMD" | tr -d '\n' | tr '\t' ' ') # remove \n \t chars
[ $__EXIT -eq 1 ] && {
eval '$__CMD' # force syslog before exit
exit 1
}
[ $use_syslog -eq 0 ] && return
[ $((use_syslog + __LEVEL)) -le 7 ] && eval '$__CMD'
return
}
# replace all special chars to their %hex value
# used for USERNAME and PASSWORD in update_url
# unchanged: "-"(minus) "_"(underscore) "."(dot) "~"(tilde)
# to verify: "'"(single quote) '"'(double quote) # because shell delimiter
# "$"(Dollar) # because used as variable output
# tested with the following string stored via Luci Application as password / username
# A B!"#AA$1BB%&'()*+,-./:;<=>?@[\]^_`{|}~ without problems at Dollar or quotes
urlencode() {
# $1 Name of Variable to store encoded string to
# $2 string to encode
local __ENC
[ $# -ne 2 ] && write_log 12 "Error calling 'urlencode()' - wrong number of parameters"
__ENC="$(awk -v str="$2" 'BEGIN{ORS="";for(i=32;i<=127;i++)lookup[sprintf("%c",i)]=i
for(k=1;k<=length(str);++k){enc=substr(str,k,1);if(enc!~"[-_.~a-zA-Z0-9]")enc=sprintf("%%%02x", lookup[enc]);print enc}}')"
eval "$1=\"$__ENC\"" # transfer back to variable
return 0
}
# extract url or script for given DDNS Provider from
# $1 Name of the provider
# $2 Provider directory
# $3 Name of Variable to store url to
# $4 Name of Variable to store script to
# $5 Name of Variable to store service answer to
get_service_data() {
local provider="$1"
shift
local dir="$1"
shift
. /usr/share/libubox/jshn.sh
local name data url answer script
[ $# -ne 3 ] && write_log 12 "Error calling 'get_service_data()' - wrong number of parameters"
[ -f "${dir}/${provider}.json" ] || {
eval "$1=\"\""
eval "$2=\"\""
eval "$3=\"\""
return 1
}
json_load_file "${dir}/${provider}.json"
json_get_var name "name"
if [ "$use_ipv6" -eq "1" ]; then
json_select "ipv6"
else
json_select "ipv4"
fi
json_get_var data "url"
json_get_var answer "answer"
json_select ".."
json_cleanup
# check if URL or SCRIPT is given
url=$(echo "$data" | grep "^http")
[ -z "$url" ] && script="/usr/lib/ddns/${data}"
eval "$1=\"$url\""
eval "$2=\"$script\""
eval "$3=\"$answer\""
return 0
}
# Calculate seconds from interval and unit
# $1 Name of Variable to store result in
# $2 Number and
# $3 Unit of time interval
get_seconds() {
[ $# -ne 3 ] && write_log 12 "Error calling 'get_seconds()' - wrong number of parameters"
case "$3" in
"days" ) eval "$1=$(( $2 * 86400 ))";;
"hours" ) eval "$1=$(( $2 * 3600 ))";;
"minutes" ) eval "$1=$(( $2 * 60 ))";;
* ) eval "$1=$2";;
esac
return 0
}
timeout() {
#.copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
# only did the following changes
# - commented out "#!/bin/bash" and usage section
# - replace exit by return for usage as function
# - some reformatting
#
# timeout [-SIG] time [--] command args...
#
# Run the given command until completion, but kill it if it runs too long.
# Specifically designed to exit immediately (no sleep interval) and clean up
# nicely without messages or leaving any extra processes when finished.
#
# Example use
# timeout 5 countdown
#
# Based on notes in my "Shell Script Hints", section "Command Timeout"
# http://www.ict.griffith.edu.au/~anthony/info/shell/script.hints
#
# This script uses a lot of tricks to terminate both the background command,
# the timeout script, and even the sleep process. It also includes trap
# commands to prevent sub-shells reporting expected "Termination Errors".
#
# It took years of occasional trials, errors and testing to get a pure bash
# timeout command working as well as this does.
#
#.Anthony Thyssen 6 April 2011
#
# PROGNAME=$(type $0 | awk '{print $3}') # search for executable on path
# PROGDIR=$(dirname $PROGNAME) # extract directory of program
# PROGNAME=$(basename $PROGNAME) # base name of program
# output the script comments as docs
# Usage() {
# echo >&2 "$PROGNAME:" "$@"
# sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' "$PROGDIR/$PROGNAME"
# exit 10;
# }
SIG=-TERM
while [ $# -gt 0 ]; do
case "$1" in
--)
# forced end of user options
shift;
break ;;
# -\?|--help|--doc*)
# Usage ;;
[0-9]*)
TIMEOUT="$1" ;;
-*)
SIG="$1" ;;
*)
# unforced end of user options
break ;;
esac
shift # next option
done
# run main command in backgrounds and get its pid
"$@" &
command_pid=$!
# timeout sub-process abort countdown after ABORT seconds! also backgrounded
sleep_pid=0
(
# cleanup sleep process
trap 'kill -TERM $sleep_pid; return 1' 1 2 3 15
# sleep timeout period in background
sleep $TIMEOUT &
sleep_pid=$!
wait $sleep_pid
# Abort the command
kill $SIG $command_pid >/dev/null 2>&1
return 1
) &
timeout_pid=$!
# Wait for main command to finished or be timed out
wait $command_pid
status=$?
# Clean up timeout sub-shell - if it is still running!
kill $timeout_pid 2>/dev/null
wait $timeout_pid 2>/dev/null
# Uncomment to check if a LONG sleep still running (no sleep should be)
# sleep 1
# echo "-----------"
# /bin/ps j # uncomment to show if abort "sleep" is still sleeping
return $status
}
# sanitize a variable
# $1 variable name
# $2 allowed shell pattern
# $3 disallowed shell pattern
sanitize_variable() {
local __VAR=$1
eval __VALUE=\$$__VAR
local __ALLOWED=$2
local __REJECT=$3
# removing all allowed should give empty string
if [ -n "$__ALLOWED" ]; then
[ -z "${__VALUE//$__ALLOWED}" ] || write_log 12 "sanitize on $__VAR found characters outside allowed subset"
fi
# removing rejected pattern should give the same string as the input
if [ -n "$__REJECT" ]; then
[ "$__VALUE" = "${__VALUE//$__REJECT}" ] || write_log 12 "sanitize on $__VAR found rejected characters"
fi
}
# verify given host and port is connectable
# $1 Host/IP to verify
# $2 Port to verify
verify_host_port() {
local __HOST=$1
local __PORT=$2
local __NC=$(command -v nc)
local __NCEXT=$($(command -v nc) --help 2>&1 | grep "\-w" 2>/dev/null) # busybox nc compiled with extensions
local __IP __IPV4 __IPV6 __RUNPROG __PROG __ERR
# return codes
# 1 system specific error
# 2 nslookup/host error
# 3 nc (netcat) error
# 4 unmatched IP version
[ $# -ne 2 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters"
# check if ip or FQDN was given
__IPV4=$(echo $__HOST | grep -m 1 -o "$IPV4_REGEX$") # do not detect ip in 0.0.0.0.example.com
__IPV6=$(echo $__HOST | grep -m 1 -o "$IPV6_REGEX")
# if FQDN given get IP address
[ -z "$__IPV4" -a -z "$__IPV6" ] && {
if [ -n "$BIND_HOST" ]; then # use BIND host if installed
__PROG="BIND host"
__RUNPROG="$BIND_HOST $__HOST >$DATFILE 2>$ERRFILE"
elif [ -n "$KNOT_HOST" ]; then # use Knot host if installed
__PROG="Knot host"
__RUNPROG="$KNOT_HOST $__HOST >$DATFILE 2>$ERRFILE"
elif [ -n "$DRILL" ]; then # use drill if installed
__PROG="drill"
__RUNPROG="$DRILL -V0 $__HOST A >$DATFILE 2>$ERRFILE" # IPv4
__RUNPROG="$__RUNPROG; $DRILL -V0 $__HOST AAAA >>$DATFILE 2>>$ERRFILE" # IPv6
elif [ -n "$HOSTIP" ]; then # use hostip if installed
__PROG="hostip"
__RUNPROG="$HOSTIP $__HOST >$DATFILE 2>$ERRFILE" # IPv4
__RUNPROG="$__RUNPROG; $HOSTIP -6 $__HOST >>$DATFILE 2>>$ERRFILE" # IPv6
else # use BusyBox nslookup
__PROG="BusyBox nslookup"
__RUNPROG="$NSLOOKUP $__HOST >$DATFILE 2>$ERRFILE"
fi
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG
__ERR=$?
# command error
[ $__ERR -gt 0 ] && {
write_log 3 "DNS Resolver Error - $__PROG Error '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
return 2
}
# extract IP address
if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then # use BIND host or Knot host if installed
__IPV4="$(awk -F "address " '/has address/ {print $2; exit}' "$DATFILE")"
__IPV6="$(awk -F "address " '/has IPv6/ {print $2; exit}' "$DATFILE")"
elif [ -n "$DRILL" ]; then # use drill if installed
__IPV4="$(awk '/^'"$__HOST"'/ {print $5}' "$DATFILE" | grep -m 1 -o "$IPV4_REGEX")"
__IPV6="$(awk '/^'"$__HOST"'/ {print $5}' "$DATFILE" | grep -m 1 -o "$IPV6_REGEX")"
elif [ -n "$HOSTIP" ]; then # use hostip if installed
__IPV4="$(grep -m 1 -o "$IPV4_REGEX" "$DATFILE")"
__IPV6="$(grep -m 1 -o "$IPV6_REGEX" "$DATFILE")"
else # use BusyBox nslookup
__IPV4="$(sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV4_REGEX\).*$/\\1/p }" "$DATFILE")"
__IPV6="$(sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV6_REGEX\).*$/\\1/p }" "$DATFILE")"
fi
}
# check IP version if forced
if [ $force_ipversion -ne 0 ]; then
__ERR=0
[ $use_ipv6 -eq 0 -a -z "$__IPV4" ] && __ERR=4
[ $use_ipv6 -eq 1 -a -z "$__IPV6" ] && __ERR=6
[ $__ERR -gt 0 ] && {
[ -n "$LUCI_HELPER" ] && return 4
write_log 14 "Verify host Error '4' - Forced IP Version IPv$__ERR don't match"
}
fi
# verify nc command
# busybox nc compiled without -l option "NO OPT l!" -> critical error
$__NC --help 2>&1 | grep -i "NO OPT l!" >/dev/null 2>&1 && \
write_log 12 "Busybox nc (netcat) compiled without '-l' option, error 'NO OPT l!'"
# busybox nc compiled with extensions
$__NC --help 2>&1 | grep "\-w" >/dev/null 2>&1 && __NCEXT="TRUE"
# connectivity test
# run busybox nc to HOST PORT
# busybox might be compiled with "FEATURE_PREFER_IPV4_ADDRESS=n"
# then nc will try to connect via IPv6 if there is any IPv6 available on any host interface
# not worrying, if there is an IPv6 wan address
# so if not "force_ipversion" to use_ipv6 then connect test via ipv4, if available
[ $force_ipversion -ne 0 -a $use_ipv6 -ne 0 -o -z "$__IPV4" ] && __IP=$__IPV6 || __IP=$__IPV4
if [ -n "$__NCEXT" ]; then # BusyBox nc compiled with extensions (timeout support)
__RUNPROG="$__NC -w 1 $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG
__ERR=$?
[ $__ERR -eq 0 ] && return 0
write_log 3 "Connect error - BusyBox nc (netcat) Error '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
return 3
else # nc compiled without extensions (no timeout support)
__RUNPROG="timeout 2 -- $__NC $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG
__ERR=$?
[ $__ERR -eq 0 ] && return 0
write_log 3 "Connect error - BusyBox nc (netcat) timeout Error '$__ERR'"
return 3
fi
}
# verify given DNS server if connectable
# $1 DNS server to verify
verify_dns() {
local __ERR=255 # last error buffer
local __CNT=0 # error counter
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_dns()' - wrong number of parameters"
write_log 7 "Verify DNS server '$1'"
while [ $__ERR -ne 0 ]; do
# DNS uses port 53
verify_host_port "$1" "53"
__ERR=$?
if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script
return $__ERR
elif [ $__ERR -ne 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry
write_log 4 "Verify DNS server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error"
return $__ERR
elif [ $__ERR -ne 0 ]; then
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_max_count leave here
[ $retry_max_count -gt 0 -a $__CNT -gt $retry_max_count ] && \
write_log 14 "Verify DNS server '$1' failed after $retry_max_count retries"
write_log 4 "Verify DNS server '$1' failed - retry $__CNT/$retry_max_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
fi
done
return 0
}
# analyze and verify given proxy string
# $1 Proxy-String to verify
verify_proxy() {
# complete entry user:password@host:port
# inside user and password NO '@' of ":" allowed
# host and port only host:port
# host only host ERROR unsupported
# IPv4 address instead of host 123.234.234.123
# IPv6 address instead of host [xxxx:....:xxxx] in square bracket
local __TMP __HOST __PORT
local __ERR=255 # last error buffer
local __CNT=0 # error counter
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_proxy()' - wrong number of parameters"
write_log 7 "Verify Proxy server 'http://$1'"
# try to split user:password "@" host:port
__TMP=$(echo $1 | awk -F "@" '{print $2}')
# no "@" found - only host:port is given
[ -z "$__TMP" ] && __TMP="$1"
# now lets check for IPv6 address
__HOST=$(echo $__TMP | grep -m 1 -o "$IPV6_REGEX")
# IPv6 host address found read port
if [ -n "$__HOST" ]; then
# IPv6 split at "]:"
__PORT=$(echo $__TMP | awk -F "]:" '{print $2}')
else
__HOST=$(echo $__TMP | awk -F ":" '{print $1}')
__PORT=$(echo $__TMP | awk -F ":" '{print $2}')
fi
# No Port detected - EXITING
[ -z "$__PORT" ] && {
[ -n "$LUCI_HELPER" ] && return 5
write_log 14 "Invalid Proxy server Error '5' - proxy port missing"
}
while [ $__ERR -gt 0 ]; do
verify_host_port "$__HOST" "$__PORT"
__ERR=$?
if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script
return $__ERR
elif [ $__ERR -gt 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry
write_log 4 "Verify Proxy server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error"
return $__ERR
elif [ $__ERR -gt 0 ]; then
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_max_count leave here
[ $retry_max_count -gt 0 -a $__CNT -gt $retry_max_count ] && \
write_log 14 "Verify Proxy server '$1' failed after $retry_max_count retries"
write_log 4 "Verify Proxy server '$1' failed - retry $__CNT/$retry_max_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
fi
done
return 0
}
do_transfer() {
# $1 # URL to use
local __URL="$1"
local __ERR=0
local __CNT=0 # error counter
local __PROG __RUNPROG
[ $# -ne 1 ] && write_log 12 "Error in 'do_transfer()' - wrong number of parameters"
# Use ip_network as default for bind_network if not separately specified
[ -z "$bind_network" ] && [ "$ip_source" = "network" ] && [ "$ip_network" ] && bind_network="$ip_network"
# lets prefer GNU Wget because it does all for us - IPv4/IPv6/HTTPS/PROXY/force IP version
if [ -n "$WGET_SSL" ] && [ $USE_CURL -eq 0 ]; then # except global option use_curl is set to "1"
__PROG="$WGET --hsts-file=/tmp/.wget-hsts -nv -t 1 -O $DATFILE -o $ERRFILE" # non_verbose no_retry outfile errfile
# force network/ip to use for communication
if [ -n "$bind_network" ]; then
local __BINDIP
# set correct program to detect IP
[ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" || __RUNPROG="network_get_ipaddr6"
eval "$__RUNPROG __BINDIP $bind_network" || \
write_log 13 "Can not detect current IP using '$__RUNPROG $bind_network' - Error: '$?'"
write_log 7 "Force communication via IP '$__BINDIP'"
__PROG="$__PROG --bind-address=$__BINDIP"
fi
# force ip version to use
if [ $force_ipversion -eq 1 ]; then
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6
fi
# set certificate parameters
if [ $use_https -eq 1 ]; then
if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert
__PROG="$__PROG --no-check-certificate"
elif [ -f "$cacert" ]; then
__PROG="$__PROG --ca-certificate=${cacert}"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --ca-directory=${cacert}"
elif [ -n "$cacert" ]; then # it's not a file and not a directory but given
write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication"
fi
fi
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
[ -z "$proxy" ] && __PROG="$__PROG --no-proxy"
# user agent string if provided
if [ -n "$user_agent" ]; then
# replace single and double quotes
user_agent=$(echo $user_agent | sed "s/'/ /g" | sed 's/"/ /g')
__PROG="$__PROG --user-agent='$user_agent'"
fi
__RUNPROG="$__PROG '$__URL'" # build final command
__PROG="GNU Wget" # reuse for error logging
# 2nd choice is cURL IPv4/IPv6/HTTPS
# libcurl might be compiled without Proxy or HTTPS Support
elif [ -n "$CURL" ]; then
__PROG="$CURL -RsS -o $DATFILE --stderr $ERRFILE"
# check HTTPS support
[ -z "$CURL_SSL" -a $use_https -eq 1 ] && \
write_log 13 "cURL: libcurl compiled without https support"
# force network/interface-device to use for communication
if [ -n "$bind_network" ]; then
local __DEVICE
network_get_device __DEVICE $bind_network || \
write_log 13 "Can not detect local device using 'network_get_device $bind_network' - Error: '$?'"
write_log 7 "Force communication via device '$__DEVICE'"
__PROG="$__PROG --interface $__DEVICE"
fi
# force ip version to use
if [ $force_ipversion -eq 1 ]; then
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6
fi
# set certificate parameters
if [ $use_https -eq 1 ]; then
if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert
__PROG="$__PROG --insecure" # but not empty better to use "IGNORE"
elif [ -f "$cacert" ]; then
__PROG="$__PROG --cacert $cacert"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --capath $cacert"
elif [ -n "$cacert" ]; then # it's not a file and not a directory but given
write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication"
fi
fi
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
# or check if libcurl compiled with proxy support
if [ -z "$proxy" ]; then
__PROG="$__PROG --noproxy '*'"
elif [ -z "$CURL_PROXY" ]; then
# if libcurl has no proxy support and proxy should be used then force ERROR
write_log 13 "cURL: libcurl compiled without Proxy support"
fi
__RUNPROG="$__PROG '$__URL'" # build final command
__PROG="cURL" # reuse for error logging
# uclient-fetch possibly with ssl support if /lib/libustream-ssl.so installed
elif [ -n "$UCLIENT_FETCH" ]; then
# UCLIENT_FETCH_SSL not empty then SSL support available
UCLIENT_FETCH_SSL=$(find /lib /usr/lib -name libustream-ssl.so* 2>/dev/null)
__PROG="$UCLIENT_FETCH -q -O $DATFILE"
# force network/ip not supported
[ -n "$__BINDIP" ] && \
write_log 14 "uclient-fetch: FORCE binding to specific address not supported"
# force ip version to use
if [ $force_ipversion -eq 1 ]; then
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6
fi
# https possibly not supported
[ $use_https -eq 1 -a -z "$UCLIENT_FETCH_SSL" ] && \
write_log 14 "uclient-fetch: no HTTPS support! Additional install one of ustream-ssl packages"
# proxy support
[ -z "$proxy" ] && __PROG="$__PROG -Y off" || __PROG="$__PROG -Y on"
# https & certificates
if [ $use_https -eq 1 ]; then
if [ "$cacert" = "IGNORE" ]; then
__PROG="$__PROG --no-check-certificate"
elif [ -f "$cacert" ]; then
__PROG="$__PROG --ca-certificate=$cacert"
elif [ -n "$cacert" ]; then # it's not a file; nothing else supported
write_log 14 "No valid certificate file '$cacert' for HTTPS communication"
fi
fi
__RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command
__PROG="uclient-fetch" # reuse for error logging
# Busybox Wget or any other wget in search $PATH (did not support neither IPv6 nor HTTPS)
elif [ -n "$WGET" ]; then
__PROG="$WGET -q -O $DATFILE"
# force network/ip not supported
[ -n "$__BINDIP" ] && \
write_log 14 "BusyBox Wget: FORCE binding to specific address not supported"
# force ip version not supported
[ $force_ipversion -eq 1 ] && \
write_log 14 "BusyBox Wget: Force connecting to IPv4 or IPv6 addresses not supported"
# https not supported
[ $use_https -eq 1 ] && \
write_log 14 "BusyBox Wget: no HTTPS support"
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
[ -z "$proxy" ] && __PROG="$__PROG -Y off"
__RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command
__PROG="Busybox Wget" # reuse for error logging
else
write_log 13 "Neither 'Wget' nor 'cURL' nor 'uclient-fetch' installed or executable"
fi
while : ; do
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG # DO transfer
__ERR=$? # save error code
[ $__ERR -eq 0 ] && return 0 # no error leave
[ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script
write_log 3 "$__PROG Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Transfer failed - Verbose Mode: $VERBOSE - NO retry on error"
return 1
}
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_max_count leave here
[ $retry_max_count -gt 0 -a $__CNT -gt $retry_max_count ] && \
write_log 14 "Transfer failed after $retry_max_count retries"
write_log 4 "Transfer failed - retry $__CNT/$retry_max_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'do_transfer()' - program coding error"
}
send_update() {
# $1 # IP to set at DDNS service provider
local __IP
[ $# -ne 1 ] && write_log 12 "Error calling 'send_update()' - wrong number of parameters"
if [ $upd_privateip -eq 0 ]; then
# verify given IP / no private IPv4's / no IPv6 addr starting with fxxx of with ":"
[ $use_ipv6 -eq 0 ] && __IP=$(echo $1 | grep -v -E "(^0|^10\.|^100\.6[4-9]\.|^100\.[7-9][0-9]\.|^100\.1[0-1][0-9]\.|^100\.12[0-7]\.|^127|^169\.254|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168)")
[ $use_ipv6 -eq 1 ] && __IP=$(echo $1 | grep "^[0-9a-eA-E]")
else
__IP=$(echo $1 | grep -m 1 -o "$IPV4_REGEX") # valid IPv4 or
[ -z "$__IP" ] && __IP=$(echo $1 | grep -m 1 -o "$IPV6_REGEX") # IPv6
fi
[ -z "$__IP" ] && {
write_log 3 "No or private or invalid IP '$1' given! Please check your configuration"
return 127
}
if [ -n "$update_script" ]; then
write_log 7 "parsing script '$update_script'"
. $update_script
else
local __URL __ERR
# do replaces in URL
__URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$URL_USER#g" -e "s#\[PASSWORD\]#$URL_PASS#g" \
-e "s#\[PARAMENC\]#$URL_PENC#g" -e "s#\[PARAMOPT\]#$param_opt#g" \
-e "s#\[DOMAIN\]#$domain#g" -e "s#\[IP\]#$__IP#g")
[ $use_https -ne 0 ] && __URL=$(echo $__URL | sed -e 's#^http:#https:#')
do_transfer "$__URL" || return 1
write_log 7 "DDNS Provider answered:${N}$(cat $DATFILE)"
[ -z "$UPD_ANSWER" ] && return 0 # not set then ignore
grep -i -E "$UPD_ANSWER" $DATFILE >/dev/null 2>&1
return $? # "0" if found
fi
}
get_current_ip () {
# $1 Name of Variable to store current IP
local __CNT=0 # error counter
local __RUNPROG __DATA __URL __ERR
[ $# -ne 1 ] && write_log 12 "Error calling 'get_current_ip()' - wrong number of parameters"
write_log 7 "Detect current IP on '$ip_source'"
while : ; do
if [ -n "$ip_network" -a "$ip_source" = "network" ]; then
# set correct program
network_flush_cache # force re-read data from ubus
[ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \
|| __RUNPROG="network_get_ipaddr6"
eval "$__RUNPROG __DATA $ip_network" || \
write_log 13 "Can not detect current IP using $__RUNPROG '$ip_network' - Error: '$?'"
[ -n "$__DATA" ] && write_log 7 "Current IP '$__DATA' detected on network '$ip_network'"
elif [ -n "$ip_interface" -a "$ip_source" = "interface" ]; then
local __DATA4=""; local __DATA6=""
if [ -n "$(command -v ip)" ]; then # ip program installed
write_log 7 "#> ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE"
ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE
__ERR=$?
if [ $__ERR -eq 0 ]; then
# DATFILE (sample)
# 10: l2tp-inet: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1456 qdisc fq_codel state UNKNOWN qlen 3\ link/ppp
# 10: l2tp-inet inet 95.30.176.51 peer 95.30.176.1/32 scope global l2tp-inet\ valid_lft forever preferred_lft forever
# 5: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP qlen 1000\ link/ether 08:00:27:d0:10:32 brd ff:ff:ff:ff:ff:ff
# 5: eth1 inet 172.27.10.128/24 brd 172.27.10.255 scope global eth1\ valid_lft forever preferred_lft forever
# 5: eth1 inet 172.55.55.155/24 brd 172.27.10.255 scope global eth1\ valid_lft 12345sec preferred_lft 12345sec
# 5: eth1 inet6 2002:b0c7:f326::806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec
# 5: eth1 inet6 fd43:5368:6f6d:6500:806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec
# 5: eth1 inet6 fd43:5368:6f6d:6500:a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec
# 5: eth1 inet6 2002:b0c7:f326::a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec
# remove remove remove replace replace
# link inet6 fxxx sec forever=>-1 / => ' ' to separate subnet from ip
sed "/link/d; /inet6 f/d; s/sec//g; s/forever/-1/g; s/\// /g" $DATFILE | \
awk '{ print $3" "$4" "$NF }' > $ERRFILE # temp reuse ERRFILE
# we only need inet? IP prefered time
local __TIME4=0; local __TIME6=0
local __TYP __ADR __TIME
while read __TYP __ADR __TIME; do
__TIME=${__TIME:-0} # supress shell errors on last (empty) line of DATFILE
# IPversion no "-1" record stored - now "-1" record or new time > oldtime
[ "$__TYP" = "inet6" -a $__TIME6 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME6 \) ] && {
__DATA6="$__ADR"
__TIME6="$__TIME"
}
[ "$__TYP" = "inet" -a $__TIME4 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME4 \) ] && {
__DATA4="$__ADR"
__TIME4="$__TIME"
}
done < $ERRFILE
else
write_log 3 "ip Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
else # use deprecated ifconfig
write_log 7 "#> ifconfig $ip_interface >$DATFILE 2>$ERRFILE"
ifconfig $ip_interface >$DATFILE 2>$ERRFILE
__ERR=$?
if [ $__ERR -eq 0 ]; then
__DATA4=$(awk '
/inet addr:/ { # Filter IPv4
# inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
$1=""; # remove inet
$3=""; # remove Bcast: ...
$4=""; # remove Mask: ...
FS=":"; # separator ":"
$0=$0; # reread to activate separator
$1=""; # remove addr
FS=" "; # set back separator to default " "
$0=$0; # reread to activate separator (remove whitespaces)
print $1; # print IPv4 addr
}' $DATFILE
)
__DATA6=$(awk '
/inet6/ && /: [0-9a-eA-E]/ { # Filter IPv6 exclude fxxx
# inet6 addr: 2001:db8::xxxx:xxxx/32 Scope:Global
FS="/"; # separator "/"
$0=$0; # reread to activate separator
$2=""; # remove everything behind "/"
FS=" "; # set back separator to default " "
$0=$0; # reread to activate separator
print $3; # print IPv6 addr
}' $DATFILE
)
else
write_log 3 "ifconfig Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
fi
[ $use_ipv6 -eq 0 ] && __DATA="$__DATA4" || __DATA="$__DATA6"
[ -n "$__DATA" ] && write_log 7 "Current IP '$__DATA' detected on interface '$ip_interface'"
elif [ -n "$ip_script" -a "$ip_source" = "script" ]; then
write_log 7 "#> $ip_script >$DATFILE 2>$ERRFILE"
eval $ip_script >$DATFILE 2>$ERRFILE
__ERR=$?
if [ $__ERR -eq 0 ]; then
__DATA=$(cat $DATFILE)
[ -n "$__DATA" ] && write_log 7 "Current IP '$__DATA' detected via script '$ip_script'"
else
write_log 3 "$ip_script Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
elif [ -n "$ip_url" -a "$ip_source" = "web" ]; then
do_transfer "$ip_url"
# use correct regular expression
[ $use_ipv6 -eq 0 ] \
&& __DATA=$(grep -m 1 -o "$IPV4_REGEX" $DATFILE) \
|| __DATA=$(grep -m 1 -o "$IPV6_REGEX" $DATFILE)
[ -n "$__DATA" ] && write_log 7 "Current IP '$__DATA' detected on web at '$ip_url'"
else
write_log 12 "Error in 'get_current_ip()' - unhandled ip_source '$ip_source'"
fi
# valid data found return here
[ -n "$__DATA" ] && {
eval "$1=\"$__DATA\""
return 0
}
[ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script
write_log 7 "Data detected:"
write_log 7 "$(cat $DATFILE)"
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Get current IP via '$ip_source' failed - Verbose Mode: $VERBOSE - NO retry on error"
return 1
}
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_max_count leave here
[ $retry_max_count -gt 0 -a $__CNT -gt $retry_max_count ] && \
write_log 14 "Get current IP via '$ip_source' failed after $retry_max_count retries"
write_log 4 "Get current IP via '$ip_source' failed - retry $__CNT/$retry_max_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'get_current_ip()' - program coding error"
}
get_registered_ip() {
# $1 Name of Variable to store public IP (REGISTERED_IP)
# $2 (optional) if set, do not retry on error
local __CNT=0 # error counter
local __ERR=255
local __REGEX __PROG __RUNPROG __DATA __IP
# return codes
# 1 no IP detected
[ $# -lt 1 -o $# -gt 2 ] && write_log 12 "Error calling 'get_registered_ip()' - wrong number of parameters"
[ $is_glue -eq 1 -a -z "$BIND_HOST" ] && write_log 14 "Lookup of glue records is only supported using BIND host"
write_log 7 "Detect registered/public IP"
# set correct regular expression
[ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX"
if [ -n "$BIND_HOST" ]; then
__PROG="$BIND_HOST"
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA"
if [ $force_ipversion -eq 1 ]; then # force IP version
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"
fi
[ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP
[ $is_glue -eq 1 ] && __PROG="$__PROG -v" # use verbose output to get additional section
__RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="BIND host"
elif [ -n "$KNOT_HOST" ]; then
__PROG="$KNOT_HOST"
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA"
if [ $force_ipversion -eq 1 ]; then # force IP version
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"
fi
[ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP
__RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="Knot host"
elif [ -n "$DRILL" ]; then
__PROG="$DRILL -V0" # drill options name @server type
if [ $force_ipversion -eq 1 ]; then # force IP version
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"
fi
[ $force_dnstcp -eq 1 ] && __PROG="$__PROG -t" || __PROG="$__PROG -u" # force TCP
__PROG="$__PROG $lookup_host"
[ -n "$dns_server" ] && __PROG="$__PROG @$dns_server"
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG A" || __PROG="$__PROG AAAA"
__RUNPROG="$__PROG >$DATFILE 2>$ERRFILE"
__PROG="drill"
elif [ -n "$HOSTIP" ]; then # hostip package installed
__PROG="$HOSTIP"
[ $force_dnstcp -ne 0 ] && \
write_log 14 "hostip - no support for 'DNS over TCP'"
# is IP given as dns_server ?
__IP=$(echo $dns_server | grep -m 1 -o "$IPV4_REGEX")
[ -z "$__IP" ] && __IP=$(echo $dns_server | grep -m 1 -o "$IPV6_REGEX")
# we got NO ip for dns_server, so build command
[ -z "$__IP" -a -n "$dns_server" ] && {
__IP="\`$HOSTIP"
[ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] && __IP="$__IP -6"
__IP="$__IP $dns_server | grep -m 1 -o"
[ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] \
&& __IP="$__IP '$IPV6_REGEX'" \
|| __IP="$__IP '$IPV4_REGEX'"
__IP="$__IP \`"
}
[ $use_ipv6 -eq 1 ] && __PROG="$__PROG -6"
[ -n "$dns_server" ] && __PROG="$__PROG -r $__IP"
__RUNPROG="$__PROG $lookup_host >$DATFILE 2>$ERRFILE"
__PROG="hostip"
elif [ -n "$NSLOOKUP" ]; then # last use BusyBox nslookup
NSLOOKUP_MUSL=$($(command -v nslookup) localhost 2>&1 | grep -F "(null)") # not empty busybox compiled with musl
[ $force_dnstcp -ne 0 ] && \
write_log 14 "Busybox nslookup - no support for 'DNS over TCP'"
[ -n "$NSLOOKUP_MUSL" -a -n "$dns_server" ] && \
write_log 14 "Busybox compiled with musl - nslookup don't support the use of DNS Server"
[ $force_ipversion -ne 0 ] && \
write_log 5 "Busybox nslookup - no support to 'force IP Version' (ignored)"
__RUNPROG="$NSLOOKUP $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="BusyBox nslookup"
else # there must be an error
write_log 12 "Error in 'get_registered_ip()' - no supported Name Server lookup software accessible"
fi
while : ; do
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG
__ERR=$?
if [ $__ERR -ne 0 ]; then
write_log 3 "$__PROG error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
else
if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then
if [ $is_glue -eq 1 ]; then
__DATA=$(cat $DATFILE | grep "^$lookup_host" | grep -om1 "$__REGEX" )
else
__DATA=$(cat $DATFILE | awk -F "address " '/has/ {print $2; exit}' )
fi
elif [ -n "$DRILL" ]; then
__DATA=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5; exit}' )
elif [ -n "$HOSTIP" ]; then
__DATA=$(cat $DATFILE | grep -om1 "$__REGEX")
elif [ -n "$NSLOOKUP" ]; then
__DATA=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($__REGEX\).*$/\\1/p }" )
fi
[ -n "$__DATA" ] && {
write_log 7 "Registered IP '$__DATA' detected"
[ -z "$IPFILE" ] || echo "$__DATA" > $IPFILE
eval "$1=\"$__DATA\"" # valid data found
return 0 # leave here
}
write_log 4 "NO valid IP found"
__ERR=127
fi
[ -z "$IPFILE" ] || echo "" > $IPFILE
[ -n "$LUCI_HELPER" ] && return $__ERR # no retry if called by LuCI helper script
[ -n "$2" ] && return $__ERR # $2 is given -> no retry
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Get registered/public IP for '$lookup_host' failed - Verbose Mode: $VERBOSE - NO retry on error"
return $__ERR
}
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_max_count leave here
[ $retry_max_count -gt 0 -a $__CNT -gt $retry_max_count ] && \
write_log 14 "Get registered/public IP for '$lookup_host' failed after $retry_max_count retries"
write_log 4 "Get registered/public IP for '$lookup_host' failed - retry $__CNT/$retry_max_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'get_registered_ip()' - program coding error"
}
get_uptime() {
# $1 Variable to store result in
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters"
local __UPTIME=$(cat /proc/uptime)
eval "$1=\"${__UPTIME%%.*}\""
}
trap_handler() {
# $1 trap signal
# $2 optional (exit status)
local __PIDS __PID
local __ERR=${2:-0}
local __OLD_IFS=$IFS
local __NEWLINE_IFS='
' # __NEWLINE_IFS
[ $PID_SLEEP -ne 0 ] && kill -$1 $PID_SLEEP 2>/dev/null # kill pending sleep if exist
case $1 in
0) if [ $__ERR -eq 0 ]; then
write_log 5 "PID '$$' exit normal at $(eval $DATE_PROG)${N}"
else
write_log 4 "PID '$$' exit WITH ERROR '$__ERR' at $(eval $DATE_PROG)${N}"
fi ;;
1) write_log 6 "PID '$$' received 'SIGHUP' at $(eval $DATE_PROG)"
# reload config via starting the script again
/usr/lib/ddns/dynamic_dns_updater.sh -v "0" -S "$__SECTIONID" -- start || true
exit 0 ;; # and leave this one
2) write_log 5 "PID '$$' terminated by 'SIGINT' at $(eval $DATE_PROG)${N}";;
3) write_log 5 "PID '$$' terminated by 'SIGQUIT' at $(eval $DATE_PROG)${N}";;
15) write_log 5 "PID '$$' terminated by 'SIGTERM' at $(eval $DATE_PROG)${N}";;
*) write_log 13 "Unhandled signal '$1' in 'trap_handler()'";;
esac
__PIDS=$(pgrep -P $$) # get my childs (pgrep prints with "newline")
IFS=$__NEWLINE_IFS
for __PID in $__PIDS; do
kill -$1 $__PID # terminate it
done
IFS=$__OLD_IFS
# remove out and err file
[ -f $DATFILE ] && rm -f $DATFILE
[ -f $ERRFILE ] && rm -f $ERRFILE
# exit with correct handling:
# remove trap handling settings and send kill to myself
trap - 0 1 2 3 15
[ $1 -gt 0 ] && kill -$1 $$
}
split_FQDN() {
# $1 FQDN to split
# $2 name of variable to store TLD
# $3 name of variable to store (reg)Domain
# $4 name of variable to store Host/Subdomain
[ $# -ne 4 ] && write_log 12 "Error calling 'split_FQDN()' - wrong number of parameters"
[ -z "$1" ] && write_log 12 "Error calling 'split_FQDN()' - missing FQDN to split"
[ -f $TLDFILE ] || write_log 12 "Error calling 'split_FQDN()' - missing file '$TLDFILE'"
local _HOST _FDOM _CTLD _FTLD
local _SET="$@" # save given function parameters
local _PAR=$(echo "$1" | tr [A-Z] [a-z] | tr "." " ") # to lower and replace DOT with SPACE
set -- $_PAR # set new as function parameters
_PAR="" # clear variable for later reuse
while [ -n "$1" ] ; do # as long we have parameters
_PAR="$1 $_PAR" # invert order of parameters
shift
done
set -- $_PAR # use new as function parameters
_PAR="" # clear variable
while [ -n "$1" ] ; do # as long we have parameters
if [ -z "$_CTLD" ]; then # first loop
_CTLD="$1" # CURRENT TLD to look at
shift
else
_CTLD="$1.$_CTLD" # Next TLD to look at
shift
fi
# check if TLD exact match in tld_names.dat, save TLD
zcat $TLDFILE | grep -E "^$_CTLD$" >/dev/null 2>&1 && {
_FTLD="$_CTLD" # save found
_FDOM="$1" # save domain next step might be invalid
continue
}
# check if match any "*" in tld_names.dat,
zcat $TLDFILE | grep -E "^\*.$_CTLD$" >/dev/null 2>&1 && {
[ -z "$1" ] && break # no more data break
# check if next level TLD match excludes "!" in tld_names.dat
if zcat $TLDFILE | grep -E "^!$1.$_CTLD$" >/dev/null 2>&1 ; then
_FTLD="$_CTLD" # Yes
else
_FTLD="$1.$_CTLD"
shift
fi
_FDOM="$1"; shift
}
[ -n "$_FTLD" ] && break # we have something valid, break
done
# the leftover parameters are the HOST/SUBDOMAIN
while [ -n "$1" ]; do
_HOST="$1 $_HOST" # remember we need to invert
shift
done
_HOST=$(echo $_HOST | tr " " ".") # insert DOT
set -- $_SET # set back parameters from function call
[ -n "$_FTLD" ] && {
eval "$2=$_FTLD" # set TLD
eval "$3=$_FDOM" # set registrable domain
eval "$4=$_HOST" # set HOST/SUBDOMAIN
return 0
}
eval "$2=''" # clear TLD
eval "$3=''" # clear registrable domain
eval "$4=''" # clear HOST/SUBDOMAIN
return 1
}
expand_ipv6() {
# Original written for bash by
#.Author: Florian Streibelt <florian@f-streibelt.de>
# Date: 08.04.2012
# License: Public Domain, but please be fair and
# attribute the original author(s) and provide
# a link to the original source for corrections:
#. https://github.com/mutax/IPv6-Address-checks
# $1 IPv6 to expand
# $2 name of variable to store expanded IPv6
[ $# -ne 2 ] && write_log 12 "Error calling 'expand_ipv6()' - wrong number of parameters"
INPUT="$(echo "$1" | tr 'A-F' 'a-f')"
[ "$INPUT" = "::" ] && INPUT="::0" # special case ::
O=""
while [ "$O" != "$INPUT" ]; do
O="$INPUT"
# fill all words with zeroes
INPUT=$( echo "$INPUT" | sed -e 's|:\([0-9a-f]\{3\}\):|:0\1:|g' \
-e 's|:\([0-9a-f]\{3\}\)$|:0\1|g' \
-e 's|^\([0-9a-f]\{3\}\):|0\1:|g' \
-e 's|:\([0-9a-f]\{2\}\):|:00\1:|g' \
-e 's|:\([0-9a-f]\{2\}\)$|:00\1|g' \
-e 's|^\([0-9a-f]\{2\}\):|00\1:|g' \
-e 's|:\([0-9a-f]\):|:000\1:|g' \
-e 's|:\([0-9a-f]\)$|:000\1|g' \
-e 's|^\([0-9a-f]\):|000\1:|g' )
done
# now expand the ::
ZEROES=""
echo "$INPUT" | grep -qs "::"
if [ "$?" -eq 0 ]; then
GRPS="$( echo "$INPUT" | sed 's|[0-9a-f]||g' | wc -m )"
GRPS=$(( GRPS-1 )) # remove carriage return
MISSING=$(( 8-GRPS ))
while [ $MISSING -gt 0 ]; do
ZEROES="$ZEROES:0000"
MISSING=$(( MISSING-1 ))
done
# be careful where to place the :
INPUT=$( echo "$INPUT" | sed -e 's|\(.\)::\(.\)|\1'$ZEROES':\2|g' \
-e 's|\(.\)::$|\1'$ZEROES':0000|g' \
-e 's|^::\(.\)|'$ZEROES':0000:\1|g;s|^:||g' )
fi
# an expanded address has 39 chars + CR
if [ $(echo $INPUT | wc -m) != 40 ]; then
write_log 4 "Error in 'expand_ipv6()' - invalid IPv6 found: '$1' expanded: '$INPUT'"
eval "$2='invalid'"
return 1
fi
# echo the fully expanded version of the address
eval "$2=$INPUT"
return 0
}
|