summarylogtreecommitdiffstats
path: root/hostsblock-common.sh
blob: db634211537d11090dd441b560433a7ec34a1a1b (plain)
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
#!/bin/bash
# SUBROUTINES

## Present messages at varying verbosity levels:
### Level 0: Only fatal errors
### Level 1: Level 0 + non-fatal errors
### Level 2: Level 1 + updates to cache files
### Level 3: Level 2 + narration of each major phase
### Level 4: Level 3 + step-by-step details of all processes
### Level 5: Level 4 + stdout/stderr from sub-processes like curl, zip, 7za, etc.

_notify() {
    case $1 in
        0) _level="[FATAL]" ;;
        1) _level="[WARN]" ;;
        2) _level="[NOTE]" ;;
        3) _level="[INFO]" ;;
        4) _level="[DETAIL]" ;;
        5) _level="[DEBUG]" ;;
    esac
    if [ $verbosity -ge $1 ]; then
        echo -e "${_level} $2"
    else
        true
    fi
}

## Report counts of addresses from a given hosts-like file
_count_hosts() {
    grep -ah -- "^[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" "$@" | cut -d" " -f1 | sort -u | while read _addr; do
        _number=$(grep -c -- "^$_addr" "$@")
        _notify 3 "$@: $_number urls redirected to $_addr."
    done
}

## Backup and/or recycle existing hostsfile
_backup_old() {
    if [ $recycle_old == 1 ] || [ "$recycle_old" == "1" ] || [ "$recycle_old" == "yes" ] || [ "$recycle_old" == "true" ]; then
        _notify 3 "Recycling old $hostsfile into new version..."
        sort -u "$hostsfile" | sed "s|$| ! /etc/hosts.block.old|g" > "$tmpdir"/hostsblock/hosts.block.d/hosts.block.old && \
          _notify 3 "Recycled old $hostsfile into new version." || \
          _notify 1 "FAILED to recycle old $hostsfile into new version."
    else
        _notify 3 "Old $hostsfile will not be recycled into new version."
    fi
    if [ $backup_old == 0 ] || [ "$backup_old" == "0" ] && [ "$backup_old" == "no" ] && [ "$backup_old" == "false" ]; then
        _notify 4 "Old $hostfile will not be backed up."
    else
        _notify 4 "Backing up old version of $hostsfile..."
        ls "$hostsfile".old* &>/dev/null && rm "$hostsfile".old*
        cp $_v -f -- "$hostsfile" "$hostsfile".old && \
        if [ $backup_old == 1 ] || [ "$backup_old" == "1" ] || [ "$backup_old" == "yes" ] || [ "$backup_old" == "true" ]; then
            _notify 3 "Backed up old $hostsfile."
        else
            eval $backup_old $_v -- "$hostsfile".old && \
            _notify 3 "Backed up and compressed old $hostsfile with $backup_old." || _notify 1 "FAILED to compress $hostsfile with $backup_old."
        fi || \
        _notify 1 "FAILED to backup $hostsfile."
    fi
}

## Extract entries from cachefiles
_extract_entries() {
    _notify 4 "Extracting entries from $_cachefile..."
    [ -d "$_cachefile_dir" ] || mkdir $_v -- "$_cachefile_dir" && \
      _notify 4 "Created directory $_cachefile_dir." || _notify 1 "FAILED to create directory $_cachefile_dir." 
    cd "$_cachefile_dir"
    case "$_decompresser" in
        none)
            _compress_exit=0
            _notify 4 "No need to decompress $_basename_cachefile."
            cp $_v -- "$_cachefile" "$_cachefile_dir"/ && _notify 4 "Moved $_basename_cachefile to $_cachefile_dir." || \
              _notify 1 "FAILED to move $_basename_cachefile to $_cachefile_dir."
        ;;
        unzip)
            unzip -B -o -j $_v_unzip -- "$_cachefile" && _compress_exit=0 || _compress_exit=1
            [ $_compress_exit == 0 ] && _notify 3 "Unzipped $_basename_cachefile." || _notify 1 "FAILED to unzip $_basename_cachefile."
        ;;
        7z*)
            if [ $verbosity -le 4 ]; then
                eval $_7zip_available e "$_cachefile" &>/dev/null && _compress_exit=0 || _compress_exit=1
            else
                eval $_7zip_available e "$_cachefile" && _compress_exit=0 || _compress_exit=1
            fi
            [ $_compress_exit == 0 ] && _notify 3 "Un7zipped $_basename_cachefile." || _notify 1 "FAILED to un7zip $_basename_cachefile."
        ;;
    esac
    if [ $_compress_exit == 0 ]; then
        _target_hostsfile="$tmpdir/hostsblock/hosts.block.d/$_basename_cachefile.hosts"
        _notify 4 "Extracting obvious entries from $_basename_cachefile..."
        _cachefile_url=$(head -n1 "$cachedir"/"$_basename_cachefile".url)
        if grep -rah -- "^[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" ./* | sed -e 's/[[:space:]][[:space:]]*/ /g' -e \
          "s/\#.*//g" -e "s/[[:space:]]$//g" -e "s/$_notredirect/$redirecturl/g" | sort -u | grep -vf "$whitelist" | \
           sed "s|$| \! $_cachefile_url|g" > "$_target_hostsfile"; then
            _notify 4 "Extracted obvious entries from $_basename_cachefile."
            if [ $verbosity -ge 4 ]; then
                _count_hosts "$_target_hostsfile"
            fi
        else
            _notify 1 "FAILED to extract any obvious entries from $_basename_cachefile."
        fi
        _notify 4 "Extracting less-obvious entries from $_basename_cachefile"
        if grep -rahv "^[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" ./* | grep -v "^\." | grep -v "\.$" | grep -v "\*" |\
          grep -v "\"" | grep -v "\$" | grep "[a-z]" | grep "\." | sed "s/^/$redirecturl /g" | sed -e 's/[[:space:]][[:space:]]*/ /g' \
          -e "s/\#.*//g" -e "s/[[:space:]]$//g" | sort -u | grep -vf "$whitelist" | sed "s|$| \! $_cachefile_url|g" |\
           >> "$_target_hostsfile"; then
            _notify 4 "Extracted less-obvious entries from $_basename_cachefile."
            if [ $verbosity -ge 4 ]; then
                _count_hosts "$_target_hostsfile"
            fi
        else
            _notify 1 "FAILED to extract any less-obvious entries from $_basename_cachefile." 
        fi
        _notify 4 "Deleting $_cachefile_dir..."
        cd "$tmpdir"/hostsblock && \
        rm $_v -r -- "$_cachefile_dir" && _notify 4 "Deleted $_cachefile_dir." || _notify 1 "FAILED to delete $_cachefile_dir."
    fi
}

# OVERRIDE VERBOSITY SETTING IF SPECIFIED ON THE COMMAND LINE
_verbosity_check() {
    if [ $_verbosity_override ]; then
        _notify 4 "Overriding verbosity from $verbosity to $_verbosity_override given from the command line."
        verbosity=$_verbosity_override
    fi
}

# SET VERBOSITY VARIABLES FOR SUB-PROCESSES
_set_subprocess_verbosity() {
    if [ $verbosity -le 2 ]; then
        _v=""
        _v_curl="-s"
        _v_unzip="-qq"
    elif [ $verbosity -le 4 ]; then
        _v=""
        _v_curl="-s"
        _v_unzip="-q"
    else
       _v="-v"
       _v_curl="-v"
       _v_unzip="-v"
    fi
}

# PRELIMINARY CHECK FOR CORRECT PRIVALEDGES AND DEPENDENCIES
_check_root() {
    if [ $(whoami) != "root" ]; then
        _notify 0 "INSUFFICIENT PERMISSIONS. RUN AS ROOT OR VIA SUDO. EXITING."
        exit 3
    else
        _notify 4 "Running as root, which is what we want."
    fi
}

# MAKE SURE NECESSARY DEPENDENCIES ARE PRESENT
_check_depends() {
    for _depends in $@; do
        if which "$_depends" &>/dev/null; then
            _notify 4 "$_depends found."
        else
            _notify 0 "MISSING REQUIRED DEPENDENCY $_depends. PLEASE INSTALL. EXITING."
            exit 5
        fi
    done
}

# CHECK FOR OPTIONAL DECOMPRESSION DEPENDENCIES
_check_unzip() {
    if which unzip &>/dev/null; then
        _notify 4 "unzip found. Will use it to extract zip archives."
        _unzip_available=1
    else
        _notify 1 "Dearchiver for zip NOT FOUND. Optional functions which use this format will be skipped."
        _unzip_available=0
    fi
}

_check_7z() {
    if which 7za &>/dev/null; then
        _notify 4 "7za found. Will use it to handle 7z archives."
        _7zip_available="7za"
    elif which 7z &>/dev/null; then
        _notify 4 "7z found. Will use it to handle 7z archives."
        _7zip_available="7z"
    else
        _notify 1 "Dearchiver for 7za NOT FOUND. Optional functions which use this format will be skipped."
        _7zip_available=0
    fi
}

# SOURCE CONFIG FILE
_source_configfile() {
    if [ $_configfile ]; then
        if [ -f "$_configfile" ]; then
            . "$_configfile"
            _notify 4 "Using configuration file $_configfile."
        elif [ -f /etc/hostsblock/hostsblock.conf ]; then
            . /etc/hostsblock/hostsblock.conf
            _notify 1 "Configuration file $_configfile NOT FOUND, using /etc/hostsblock/hostsblock.conf."
        else
            _notify 1 "Both configuration files $_configfile and /etc/hostsblock/hostsblock.conf NOT FOUND, using defaults."
        fi
    elif [ -f /etc/hostsblock/hostsblock.conf ]; then
        . /etc/hostsblock/hostsblock.conf
        _notify 4 "Using configuration file /etc/hostsblock/hostsblock.conf."
    else
        _notify 1 "Configuration file /etc/hostsblock/hostsblock.conf NOT FOUND, using defaults."
    fi
}

# REWRITE A GIVEN FILE SANS REGEX STATEMENT
_strip_entries() {
    case "$2" in
        *.gz)
            which pigz &>/dev/null && \
              pigz -dc "$2" | grep -v "$1" | pigz -c - > "$2".tmp || \
              gzip -dc "$2" | grep -v "$1" | gzip -c - > "$2".tmp
        ;;
        *)
            grep -v "$1" "$2" > "$2".tmp && \
                mv $_v -f -- "$2".tmp "$2"
        ;;
    esac
}


# CHECK TO SEE IF GIVEN URL IS BLOCKED OR UNBLOCKED AND OFFER TO CHANGE THIS.
_check_url(){
    _url_escaped=$(echo "$@" | sed "s/\./\\\./g")
    case "$annotate" in
        *.gz)
            which pigz &>/dev/null && \
              _matches=$(pigz -dc "$annotate" | grep " $_url_escaped ") || \
              _matches=$(gzip -dc "$annotate" | grep " $_url_escaped ")
        ;;
        *)
            _matches=$(grep " $_url_escaped " "$annotate")
        ;;
    esac
    _block_matches=$(echo "$_matches" | grep -- "^$redirecturl" | sed "s/.* \!\(.*\)$/\1/g" | tr '\n' ',' | sed "s/,$//g")
    _redirect_matches=$(echo "$_matches" | grep -v "^$redirecturl" | \
      sed "s/^\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\) .* \! \(.*\)$/to \1 by blocklist \2/g" | tr '\n' ',' | sed "s/,$//g")
    if [ $(echo "$_block_matches" | wc -w) -gt 0 ] || [ $(echo "$_redirect_matches" | wc -w) -gt 0 ]; then
        [ $(echo "$_block_matches" | wc -w) -gt 0 ] && echo -e "\n'$@' \e[1;31mBLOCKED \e[0mby blocklist(s)${_block_matches}"
        [ $(echo "$_redirect_matches" | wc -w) -gt 0 ] && echo -e "\n'$@' \e[1;33mREDIRECTED \e[0m$_redirect_matches" 
        echo -e "\t1) Unblock/unredirect just $@\n\t2) Unblock/unredirect all sites containing url $@\n\t3) Keep blocked/redirected"
        read -p "1-3 (default: 3): " b
        if [[ $b == 1 || "$b" == "1" ]]; then
            echo "Unblocking just $@"
            echo " $@" >> "$whitelist"
            _strip_entries " $@ \!" "$annotate"
            _strip_entries " $@$" "$blacklist"
            _strip_entries " $@$" "$hostsfile"
            _changed=1
        elif [[ $b == 2 || "$b" == "2" ]]; then
            echo "Unblocking all sites containing url $@"
            echo "$@" >> "$whitelist"
            _strip_entries "$@" "$annotate"
            _strip_entries "$@" "$blacklist"
            _strip_entries "$@" "$hostsfile"
            _changed=1
        fi
    else
        echo -e "\n'$@' \e[0;32mNOT BLOCKED/REDIRECTED\e[0m\n\t1) Block $@\n\t2) Block $@ and delete all whitelist url entries containing $@\n\t3) Keep unblocked (default)"
        read -p "1-3 (default: 3): " c
        if [[ $c == 1 || "$c" == "1" ]]; then
            echo "Blocking $@"
            echo "$@" >> "$blacklist"
            case "$annotate" in
                *.gz)
                    if which pigz &>/dev/null; then
                        pigz -dc "$annotate" > "$annotate".tmp
                        echo "$redirecturl $@ \! $blacklist" >> "$annotate".tmp
                        sort -u "$annotate".tmp | pigz -c - > "$annotate"
                    else
                        gzip -dc "$annotate" > "$annotate".tmp
                        echo "$redirecturl $@ \! $blacklist" >> "$annotate".tmp
                        sort -u "$annotate".tmp | gzip -c - > "$annotate"
                    fi
                    rm -f "$_v" -- "$annotate".tmp
                ;;
                *)
                    echo "$redirecturl $@ \! $blacklist" >> "$annotate"
                ;;
            esac &
            _strip_entries "^$@$" "$whitelist" &
            echo "$redirecturl $@" >> "$hostsfile" &
            _changed=1
            wait
        elif [[ $c == 2 || "$c" == "2" ]]; then
            echo "Blocking $@ and deleting all whitelist url entries containing $@"
            echo "$@" >> "$blacklist" &
            case "$annotate" in
                *.gz)
                    if which pigz &>/dev/null; then
                        pigz -dc "$annotate" > "$annotate".tmp
                        echo "$redirecturl $@ \! $blacklist" >> "$annotate".tmp
                        sort -u "$annotate".tmp | pigz -c - > "$annotate"
                    else
                        gzip -dc "$annotate" > "$annotate".tmp
                        echo "$redirecturl $@ \! $blacklist" >> "$annotate".tmp
                        sort -u "$annotate".tmp | gzip -c - > "$annotate"
                    fi
                    rm -f "$_v" -- "$annotate".tmp
                ;;
                *)
                    echo "$redirecturl $@ \! $blacklist" >> "$annotate"
                ;;
            esac &
            _strip_entries "$@" "$whitelist" &
            echo "$redirecturl $@" >> "$hostsfile" &
            _changed=1
        fi
    fi
}

# SET DEFAULT SETTINGS
export tmpdir="/dev/shm"
export hostsfile="/etc/hosts"
export redirecturl="127.0.0.1"
export dnscacher="auto"
postprocess() {
     /bin/true
}
export blocklists=("http://support.it-mate.co.uk/downloads/HOSTS.txt")
export blacklist="/etc/hostsblock/black.list"
export whitelist="/etc/hostsblock/white.list"
export hostshead="0"
export cachedir="/var/cache/hostsblock"
export redirects="0"
export connect_timeout=60
export retry=0
if which pigz &>/dev/null; then
    export backup_old="pigz"
elif which gzip &>/dev/null; then
    export backup_old="gzip"
else
    export backup_old=0
fi
export recycle_old=1
export verbosity=1
export annotate=/var/lib/hostsblock.db.gz