-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgeoblocker-bash-run
263 lines (187 loc) · 8.74 KB
/
geoblocker-bash-run
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
#!/bin/bash -l
# geoblocker-bash-run
# Serves as a proxy to call the -fetch, -apply and -backup scripts with arguments required for each action.
# Intended for easy triggering from the -manage script and from cron jobs.
# all actions:
# If successful, calls the backup script to create backup of the current iptables state and current ipset.
# If an error is enountered, classifies it as a temporary or a permanent error.
# Permanent errors mean that something is fundamentally broken.
# Temporary errors are transient (for example a download error).
# For permanent errors, calls the -backup script to restore last known-good ipsets and iptables state.
#### Initial setup
export LC_ALL=C
printf '%s\n' "$PATH" | grep '/usr/local/bin' &>/dev/null || export PATH="$PATH:/usr/local/bin"
me=$(basename "$0")
# check for root
[[ "$EUID" -ne 0 ]] && {
err="Error: $me needs to be run as root."
echo "$err" >&2
[[ ! "$nolog" ]] && logger "$err"
exit 1
}
suite_name="geoblocker-bash"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC2015
[[ -n "$script_dir" ]] && cd "$script_dir" || { err="$me: Error: Couldn't cd into '$script_dir'."; echo "$err" >&2; \
[[ ! "$nolog" ]] && logger "$err"; exit 1; }
# shellcheck source=geoblocker-bash-common
source "$script_dir/${suite_name}-common" || { err="$me: Error: Can't source ${suite_name}-common."; echo "$err" >&2; \
[[ ! "$nolog" ]] && logger "$err"; exit 1; }
# **NOTE** that some functions and variables are sourced from the *common script
# sanitize arguments
sanitize_args "$@"
# replace arguments with sanitized ones
set -- "${arguments[@]}"
#### USAGE
usage() {
cat <<EOF
$me
Serves as a proxy to call the -fetch, -apply and -backup scripts with arguments required for each action.
Usage: $me <action> [-l <"list_id [list_id] ... [list_id]">] [-d] [-h]
Actions:
add|remove : Add or remove ip lists to/from geoblocking rules.
update : Fetch ip lists and reactivate them via the *apply script.
apply : Apply previously downloaded lists (skip fetching)
Options:
-l <"list_ids"> : List id's in the format <ccode>_<family>. if passing multiple list id's, use double quotes.
-o : No backup: don't create backup of current firewall state before the action.
-d : Debug
-h : This help
EOF
}
#### PARSE ARGUMENTS
# 1st argument should be the requested action
action_run="$1"
shift 1
# check for valid action and translate *run action to *apply action
# *apply does the same thing whether we want to update, apply(refresh) or to add a new ip list, which is why this translation is needed
case "$action_run" in
add) action_apply="add" ;;
remove) action_apply="remove" ;;
update) action_apply="add" ;;
apply) action_apply="add" ;;
"") usage; die "Specify action in the 1st argument!" ;;
*) usage; err1="Error: Unsupported action: '$action_run'."; err2="Specify action in the 1st argument!"; die "$err1" "$err2" ;;
esac
# process the rest of the arguments
while getopts ":c:odh" opt; do
case $opt in
c) arg_lists=$OPTARG;;
o) nobackup="true";;
d) debugmode_args="true";;
h) usage; exit 0;;
\?) usage; die "Error: Unknown option: '$OPTARG'." ;;
esac
done
shift $((OPTIND -1))
[[ -n "$*" ]] && {
usage
err1 "Error in arguments. First unrecognized argument: '$1'."
err2="Note: If specifying multiple list ids, put them in double quotation marks."
die "$err1" "$err2"
}
echo
# get debugmode variable from either the args or environment variable, depending on what's set
debugmode="${debugmode_args:-$debugmode}"
# set env var to match the result
export debugmode
# Print script enter message for debug
debugentermsg
#### VARIABLES
# Read current list id's from the the config file, unless the variable is already set by a parent script
config_lists="${config_lists:-$(getconfig "Lists")}" || die "Error: Couldn't read value for Lists from the config file."
export config_lists
nobackup="${nobackup:-$(getconfig "NoBackup")}" || die "Error: Couldn't read value for NoBackup from the config file."
dl_source="$(getconfig "Source")" || die "Error: Couldn't read value for Source from the config file."
# if no list id's were passed via arguments, and action is update or apply, get the lists from the config file
# otherwise use the codes from arguments
if [[ ( -z "$arg_lists" ) && ( "$action_run" =~ ^(update|apply)$ ) ]]; then
lists="$config_lists"
else
lists="$arg_lists"
fi
# trim single-quotes if any
lists="${lists//\'}"
# trim extra whitespaces
lists="$(trim_spaces "$lists")"
lists_cnt=$(echo "$lists" | wc -w)
# get list_type from the config file, unless the list_type variable is already set by a parent script
list_type="${list_type:-$(getconfig "ListType")}" || die "Error: Couldn't read value for ListType from the config file."
export list_type
#convert to lower case
action_run="${action_run,,}"
# get datadir from the config file, unless the variable is already set by a parent script
datadir="${datadir:-$(getconfig "Datadir")}" || die "Error: Couldn't read value for Datadir from the config file."
knowngood_file="$datadir/iptables_knowngood.bak"
iplist_dir="$datadir/ip_lists"
status_file="$iplist_dir/status"
failed_lists_cnt=0
#### CHECKS
missing_deps="$(check_deps iptables-save iptables-restore ipset "$script_dir/${suite_name}-fetch" "$script_dir/${suite_name}-apply" \
"$script_dir/${suite_name}-backup")" || die "Error: missing dependencies: $missing_deps."
# check that the config file exists
[[ ! -f "$conf_file" ]] && die "Error: config file '$conf_file' doesn't exist! Run the installation script again."
[[ -z "$knowngood_file" ]] && die "Error: Known-good file path can not be empty!"
[[ -z "$iplist_dir" ]] && die "Error: iplist file path can not be empty!"
[[ -z "$lists" ]] && { err="Error: no list id's were specified!"; usage; die "$err"; }
[[ -z "$list_type" ]] && die "\$list_type variable should not be empty! Something is wrong!"
#### MAIN
### Fetch ip lists
if [[ "$action_run" =~ ^(update|add)$ ]]; then
# mark all lists as failed in the status file before launching *fetch. if *fetch completes successfully, it will reset this
setstatus "$status_file" "failed_lists=$lists" || die "Error: Failed to write to status file '$status_file'."
call_script "${suite_name}-fetch" -l "$lists" -p "$iplist_dir" -s "$status_file" -u "$dl_source"
# read *fetch results from the status file
fetched_lists="$(getstatus "$status_file" "FetchedLists")" || { die "Error: Couldn't read value for 'Fetched_Lists' from status file '$status_file'."; }
failed_lists="$(getstatus "$status_file" "FailedLists")" || { die "Error: Couldn't read value for 'FailedLists' from status file '$status_file'."; }
[[ -n "$failed_lists" ]] && echolog -err "Failed to fetch and validate subnet lists for lists '$failed_lists'."
failed_lists_cnt=$(echo "$failed_lists" | wc -w)
[[ "$failed_lists_cnt" -ge "$lists_cnt" ]] && die 254 "All fetch attempts failed."
else
fetched_lists="$lists"
fi
### Apply ip lists
if [[ -z "$fetched_lists" ]]; then
echolog "Firewall reconfiguration isn't required."
else
if [[ -z "$nobackup" ]]; then
rv=0
# check config coherency vs active ipsets and firewall rules before creating backup
# skip if in manualmode (launched by -manage) because -manage checks coherency anyway
[[ ! "$manualmode" ]] && { check_lists_coherency; rv=$?; }
if [[ $rv -eq 0 ]]; then
# call the backup script to create a known-good backup of ipsets and iptables state
call_script "${suite_name}-backup" create-backup
else
echolog -err "Warning: actual $list_type firewall config differs from the config file!"
echolog -err "Please run the *manage script to restore config coherency!"
echolog -err "If it's a recurring issue, please consider filing a bug report!"
[[ -n "$unexpected_lists" ]] && echolog -err "Unexpected ip lists in the firewall: '$unexpected_lists'"
[[ -n "$missing_lists" ]] && echolog -err "Missing ip lists in the firewall: '$missing_lists'"
echo
fi
else
debugprint "Skipping backup of current firewall state."
fi
call_script "${suite_name}-apply" "$action_apply" -l "$fetched_lists"; rv=$?
# [[ "$testmode" ]] && rv=1
if [[ $rv -eq 254 ]]; then
debugprint "NOTE: *apply exited with error code 254."
echo
echolog -err "Error: *apply exited with code '254'. Failed to execute action '$action_apply'."
elif [[ $rv -gt 0 && $rv -ne 254 ]]; then
debugprint "NOTE: *apply exited with error code '$rv'."
echo
die "$rv"
else
echolog "Successfully executed action '$action_run' for lists '$fetched_lists'."
fi
fi
if [[ "$failed_lists_cnt" -ne 0 ]]; then
debugprint "failed_lists_cnt: $failed_lists_cnt"
rv=254
else
rv=0
fi
debugprint "Exiting with status '$rv'"
exit "$rv"